Roll external/skia dbe7770afc61..f6bf5169268f (4 commits)

https://skia.googlesource.com/skia.git/+log/dbe7770afc61..f6bf5169268f

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

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

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

Test: Presubmit checks will test this change.
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
Change-Id: Id4f0c70ab27241a7ad3ae0798af3867ec2f7662a
diff --git a/Android.bp b/Android.bp
index 3fc7028..09ec4e1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1818,6 +1818,7 @@
         "tests/VkWrapTests.cpp",
         "tests/VkYcbcrSamplerTest.cpp",
         "tests/VptrTest.cpp",
+        "tests/WangsFormulaTest.cpp",
         "tests/WindowRectanglesTest.cpp",
         "tests/WritePixelsTest.cpp",
         "tests/Writer32Test.cpp",
diff --git a/METADATA b/METADATA
index ddcdfc0..28f2eaf 100644
--- a/METADATA
+++ b/METADATA
@@ -9,11 +9,11 @@
     type: GIT
     value: "https://skia.googlesource.com/skia"
   }
-  version: "dbe7770afc613fbd478f239d8cdbf280e7f6e5c4"
+  version: "f6bf5169268fd83795b9b6d66b28eb9801676797"
   license_type: RECIPROCAL
   last_upgrade_date {
     year: 2020
     month: 5
-    day: 13
+    day: 14
   }
 }
diff --git a/bench/TessellatePathBench.cpp b/bench/TessellatePathBench.cpp
index cb61e57..dc0daf7 100644
--- a/bench/TessellatePathBench.cpp
+++ b/bench/TessellatePathBench.cpp
@@ -7,9 +7,11 @@
 
 #include "bench/Benchmark.h"
 #include "include/gpu/GrContext.h"
+#include "src/core/SkPathPriv.h"
 #include "src/gpu/GrContextPriv.h"
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/tessellate/GrTessellatePathOp.h"
+#include "src/gpu/tessellate/GrWangsFormula.h"
 #include "tools/ToolUtils.h"
 
 // This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.)
@@ -19,29 +21,74 @@
     SkRandom rand;
     SkPath path;
     for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
-                float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f;
+        float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f;
         path.cubicTo(111.625f*x, 308.188f*x, 764.62f*x, -435.688f*x, 742.63f*x, 85.187f*x);
         path.cubicTo(764.62f*x, -435.688f*x, 111.625f*x, 308.188f*x, 0, 0);
     }
     return path;
 }
 
-static sk_sp<GrContext> make_tessellation_mock_context() {
-    GrMockOptions mockOptions;
-    mockOptions.fDrawInstancedSupport = true;
-    mockOptions.fTessellationSupport = true;
-    mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag;
-    return GrContext::MakeMock(&mockOptions);
-}
+// This is a dummy GrMeshDrawOp::Target implementation that just gives back pointers into
+// pre-allocated CPU buffers, rather than allocating and mapping GPU buffers.
+class BenchmarkTarget : public GrMeshDrawOp::Target {
+public:
+    void resetAllocator() { fAllocator.reset(); }
+    SkArenaAlloc* allocator() override { return &fAllocator; }
+    void putBackVertices(int vertices, size_t vertexStride) override { /* no-op */ }
+
+    void* makeVertexSpace(size_t vertexSize, int vertexCount, sk_sp<const GrBuffer>*,
+                          int* startVertex) override {
+        if (vertexSize * vertexCount > sizeof(fStaticVertexData)) {
+            SK_ABORT(SkStringPrintf(
+                    "FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
+                    vertexSize * vertexCount, SK_ARRAY_COUNT(fStaticVertexData)).c_str());
+        }
+        return fStaticVertexData;
+    }
+
+    GrDrawIndexedIndirectCommand* makeDrawIndexedIndirectSpace(
+            int drawCount, sk_sp<const GrBuffer>* buffer, size_t* offsetInBytes) override {
+        int staticBufferCount = (int)SK_ARRAY_COUNT(fStaticDrawIndexedIndirectData);
+        if (drawCount > staticBufferCount) {
+            SK_ABORT(SkStringPrintf(
+                    "FATAL: wanted %i static drawIndexedIndirect elements; only have %i.\n",
+                    drawCount, staticBufferCount).c_str());
+        }
+        return fStaticDrawIndexedIndirectData;
+    }
+
+#define UNIMPL(...) __VA_ARGS__ override { SK_ABORT("unimplemented."); }
+    UNIMPL(void recordDraw(const GrGeometryProcessor*, const GrSimpleMesh[], int,
+                           const GrSurfaceProxy* const[], GrPrimitiveType))
+    UNIMPL(uint16_t* makeIndexSpace(int, sk_sp<const GrBuffer>*, int*))
+    UNIMPL(void* makeVertexSpaceAtLeast(size_t, int, int, sk_sp<const GrBuffer>*, int*, int*))
+    UNIMPL(uint16_t* makeIndexSpaceAtLeast(int, int, sk_sp<const GrBuffer>*, int*, int*))
+    UNIMPL(GrDrawIndirectCommand* makeDrawIndirectSpace(int, sk_sp<const GrBuffer>*, size_t*))
+    UNIMPL(void putBackIndices(int))
+    UNIMPL(GrRenderTargetProxy* proxy() const)
+    UNIMPL(const GrSurfaceProxyView* writeView() const)
+    UNIMPL(const GrAppliedClip* appliedClip() const)
+    UNIMPL(GrAppliedClip detachAppliedClip())
+    UNIMPL(const GrXferProcessor::DstProxyView& dstProxyView() const)
+    UNIMPL(GrResourceProvider* resourceProvider() const)
+    UNIMPL(GrStrikeCache* strikeCache() const)
+    UNIMPL(GrAtlasManager* atlasManager() const)
+    UNIMPL(SkTArray<GrSurfaceProxy*, true>* sampledProxyArray())
+    UNIMPL(const GrCaps& caps() const)
+    UNIMPL(GrDeferredUploadTarget* deferredUploadTarget())
+#undef UNIMPL
+
+private:
+    SkPoint fStaticVertexData[(kNumCubicsInChalkboard + 2) * 5];
+    GrDrawIndexedIndirectCommand fStaticDrawIndexedIndirectData[32];
+    SkSTArenaAlloc<1024 * 1024> fAllocator;
+};
 
 // This serves as a base class for benchmarking individual methods on GrTessellatePathOp.
 class GrTessellatePathOp::TestingOnly_Benchmark : public Benchmark {
 public:
     TestingOnly_Benchmark(const char* subName, SkPath path, const SkMatrix& m)
-            : fCtx(make_tessellation_mock_context())
-            , fOp(m, path, GrPaint(), GrAAType::kMSAA)
-            , fFlushState(fCtx->priv().getGpu(), fCtx->priv().resourceProvider(), nullptr,
-                          GrBufferAllocPool::CpuBufferCache::Make(6)) {
+            : fOp(m, path, GrPaint(), GrAAType::kMSAA) {
         fName.printf("tessellate_%s", subName);
     }
 
@@ -51,6 +98,7 @@
     class MiddleOutInnerTrianglesBench;
     class OuterCubicsBench;
     class CubicWedgesBench;
+    class WangsFormulaBench;
 
 private:
     void onDraw(int loops, SkCanvas*) final {
@@ -60,15 +108,15 @@
             fOp.fDoFillTriangleBuffer = false;
             fOp.fCubicBuffer.reset();
             fOp.fStencilCubicsShader = nullptr;
-            this->runBench(&fFlushState, &fOp);
+            this->runBench(&fTarget, &fOp);
+            fTarget.resetAllocator();
         }
     }
 
-    virtual void runBench(GrOpFlushState*, GrTessellatePathOp*) = 0;
+    virtual void runBench(GrMeshDrawOp::Target*, GrTessellatePathOp*) = 0;
 
-    sk_sp<GrContext> fCtx;
     GrTessellatePathOp fOp;
-    GrOpFlushState fFlushState;
+    BenchmarkTarget fTarget;
     SkString fName;
 };
 
@@ -81,13 +129,13 @@
                                                          kNumCubicsInChalkboard),
                                     SkMatrix::I()) {
     }
-    void runBench(GrOpFlushState* flushState, GrTessellatePathOp* op) override {
+    void runBench(GrMeshDrawOp::Target* target, GrTessellatePathOp* op) override {
         int numBeziers;
-        op->prepareMiddleOutInnerTriangles(flushState, &numBeziers);
+        op->prepareMiddleOutInnerTriangles(target, &numBeziers);
     }
 };
 
-DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::MiddleOutInnerTrianglesBench(););
+DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::MiddleOutInnerTrianglesBench(); );
 
 class GrTessellatePathOp::TestingOnly_Benchmark::OuterCubicsBench
         : public GrTessellatePathOp::TestingOnly_Benchmark {
@@ -95,13 +143,13 @@
     OuterCubicsBench()
             : TestingOnly_Benchmark("prepareOuterCubics", make_cubic_path(), SkMatrix::I()) {
     }
-    void runBench(GrOpFlushState* flushState, GrTessellatePathOp* op) override {
-        op->prepareOuterCubics(flushState, kNumCubicsInChalkboard,
+    void runBench(GrMeshDrawOp::Target* target, GrTessellatePathOp* op) override {
+        op->prepareOuterCubics(target, kNumCubicsInChalkboard,
                                CubicDataAlignment::kVertexBoundary);
     }
 };
 
-DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::OuterCubicsBench(););
+DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::OuterCubicsBench(); );
 
 class GrTessellatePathOp::TestingOnly_Benchmark::CubicWedgesBench
         : public GrTessellatePathOp::TestingOnly_Benchmark {
@@ -109,9 +157,46 @@
     CubicWedgesBench()
             : TestingOnly_Benchmark("prepareCubicWedges", make_cubic_path(), SkMatrix::I()) {
     }
-    void runBench(GrOpFlushState* flushState, GrTessellatePathOp* op) override {
-        op->prepareCubicWedges(flushState);
+    void runBench(GrMeshDrawOp::Target* target, GrTessellatePathOp* op) override {
+        op->prepareCubicWedges(target);
     }
 };
 
 DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::CubicWedgesBench(););
+
+class GrTessellatePathOp::TestingOnly_Benchmark::WangsFormulaBench
+        : public GrTessellatePathOp::TestingOnly_Benchmark {
+public:
+    WangsFormulaBench(const char* suffix, const SkMatrix& matrix)
+            : TestingOnly_Benchmark(SkStringPrintf("wangs_formula_cubic_log2%s", suffix).c_str(),
+                                    make_cubic_path(), SkMatrix::I())
+            , fMatrix(matrix) {
+    }
+    void runBench(GrMeshDrawOp::Target*, GrTessellatePathOp* op) override {
+        int sum = 0;
+        GrVectorXform xform(fMatrix);
+        for (auto [verb, pts, w] : SkPathPriv::Iterate(op->fPath)) {
+            if (verb == SkPathVerb::kCubic) {
+                sum += GrWangsFormula::cubic_log2(4, pts, xform);
+            }
+        }
+        // Don't let the compiler optimize away GrWangsFormula::cubic_log2.
+        if (sum <= 0) {
+            SK_ABORT("sum should be > 0.");
+        }
+    }
+private:
+    SkMatrix fMatrix;
+};
+
+DEF_BENCH(
+    return new GrTessellatePathOp::TestingOnly_Benchmark::WangsFormulaBench("", SkMatrix::I());
+);
+DEF_BENCH(
+    return new GrTessellatePathOp::TestingOnly_Benchmark::WangsFormulaBench(
+            "_scale", SkMatrix::MakeScale(1.1f, 0.9f));
+);
+DEF_BENCH(
+    return new GrTessellatePathOp::TestingOnly_Benchmark::WangsFormulaBench(
+            "_affine", SkMatrix::MakeAll(.9f,0.9f,0,  1.1f,1.1f,0, 0,0,1));
+);
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 2081203..2310000 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -445,6 +445,8 @@
   "$_src/gpu/tessellate/GrTessellatePathOp.h",
   "$_src/gpu/tessellate/GrTessellationPathRenderer.cpp",
   "$_src/gpu/tessellate/GrTessellationPathRenderer.h",
+  "$_src/gpu/tessellate/GrVectorXform.h",
+  "$_src/gpu/tessellate/GrWangsFormula.h",
 
   # text
   "$_src/gpu/text/GrAtlasManager.cpp",
diff --git a/gn/tests.gni b/gn/tests.gni
index 0944a4f..63354fc 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -398,4 +398,5 @@
   "$_tests/PathOpsTigerTest.cpp",
   "$_tests/PathOpsTightBoundsTest.cpp",
   "$_tests/PathOpsTypesTest.cpp",
+  "$_tests/WangsFormulaTest.cpp",
 ]
diff --git a/infra/bots/recipe_modules/flavor/android.py b/infra/bots/recipe_modules/flavor/android.py
index 4cdc7cb..5fcb80a 100644
--- a/infra/bots/recipe_modules/flavor/android.py
+++ b/infra/bots/recipe_modules/flavor/android.py
@@ -510,7 +510,7 @@
   def step(self, name, cmd):
     sh = '%s.sh' % cmd[0]
     self.m.run.writefile(self.m.vars.tmp_dir.join(sh),
-        'set -x; env LD_LIBRARY_PATH=%s %s%s; echo $? >%src' % (
+        'set -x; LD_LIBRARY_PATH=%s %s%s; echo $? >%src' % (
             self.device_dirs.bin_dir,
             self.device_dirs.bin_dir, subprocess.list2cmdline(map(str, cmd)),
             self.device_dirs.bin_dir))
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
index e2b13ae..2659d16 100644
--- 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
@@ -837,13 +837,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS20-GPU-MaliG77-arm64-Release-All-Android_Vulkan.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS20-GPU-MaliG77-arm64-Release-All-Android_Vulkan.json
index 38a2046..bcb522a 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS20-GPU-MaliG77-arm64-Release-All-Android_Vulkan.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS20-GPU-MaliG77-arm64-Release-All-Android_Vulkan.json
@@ -870,13 +870,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
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 fab1c0c..1484bda 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
@@ -853,13 +853,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-NVIDIA_Shield-CPU-TegraX1-arm64-Release-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-NVIDIA_Shield-CPU-TegraX1-arm64-Release-All-Android.json
index aa2c890..4bf53df 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-NVIDIA_Shield-CPU-TegraX1-arm64-Release-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-NVIDIA_Shield-CPU-TegraX1-arm64-Release-All-Android.json
@@ -1055,13 +1055,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
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 1871ebc..2f7ef28 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
@@ -1231,13 +1231,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench_Mskp.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench_Mskp.json
index 4efd691..559be8a 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench_Mskp.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench_Mskp.json
@@ -582,13 +582,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
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 bd86699..ec8d26d 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
@@ -895,13 +895,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
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 ac64e22..1b7e674 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
@@ -853,13 +853,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
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 899cd40..faaa989 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
@@ -1137,13 +1137,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
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 5b81d35..f1bae98 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
@@ -1227,13 +1227,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Pixel3a-GPU-Adreno615-arm64-Debug-All-Android_Vulkan.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Pixel3a-GPU-Adreno615-arm64-Debug-All-Android_Vulkan.json
index 2acfc96..dd40db7 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Pixel3a-GPU-Adreno615-arm64-Debug-All-Android_Vulkan.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Pixel3a-GPU-Adreno615-arm64-Debug-All-Android_Vulkan.json
@@ -895,13 +895,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
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 87a3a9e..33fed19 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
@@ -1306,13 +1306,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
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 80a4ee4..7f7e7a2 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
@@ -1231,13 +1231,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
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 f3fe315..cc01e1c 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
@@ -1280,13 +1280,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
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 70a8667..2ccc832 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
@@ -1281,13 +1281,13 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --some-flag; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.sh@@@"
     ]
   },
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
index 2db7409..29ab746 100644
--- 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
@@ -1088,7 +1088,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --dummy --flags -i /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/nanobench --texttraces /sdcard/revenge_of_the_skiabot/text_blob_traces --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --dummy --flags -i /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/nanobench --texttraces /sdcard/revenge_of_the_skiabot/text_blob_traces --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/nanobench.sh"
     ],
     "env": {
@@ -1098,7 +1098,7 @@
     "infra_step": true,
     "name": "write nanobench.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@nanobench.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --dummy --flags -i /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/nanobench --texttraces /sdcard/revenge_of_the_skiabot/text_blob_traces --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@nanobench.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/nanobench --dummy --flags -i /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/nanobench --texttraces /sdcard/revenge_of_the_skiabot/text_blob_traces --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@nanobench.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
index ec6b27e..60c090f 100644
--- 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
@@ -449,7 +449,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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": {
@@ -459,7 +459,7 @@
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
@@ -636,7 +636,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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": {
@@ -646,7 +646,7 @@
     "infra_step": true,
     "name": "write dm.sh (2)",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
@@ -823,7 +823,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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": {
@@ -833,7 +833,7 @@
     "infra_step": true,
     "name": "write dm.sh (3)",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
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
index 74cdc51..6eb9d68 100644
--- a/infra/bots/recipes/perf_skottietrace.expected/skottietracing_parse_trace_error.json
+++ b/infra/bots/recipes/perf_skottietrace.expected/skottietracing_parse_trace_error.json
@@ -449,7 +449,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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": {
@@ -459,7 +459,7 @@
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
diff --git a/infra/bots/recipes/perf_skottietrace.expected/skottietracing_trybot.json b/infra/bots/recipes/perf_skottietrace.expected/skottietracing_trybot.json
index af00a68..82f2d1d 100644
--- a/infra/bots/recipes/perf_skottietrace.expected/skottietracing_trybot.json
+++ b/infra/bots/recipes/perf_skottietrace.expected/skottietracing_trybot.json
@@ -449,7 +449,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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": {
@@ -459,7 +459,7 @@
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
@@ -636,7 +636,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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": {
@@ -646,7 +646,7 @@
     "infra_step": true,
     "name": "write dm.sh (2)",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
@@ -823,7 +823,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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": {
@@ -833,7 +833,7 @@
     "infra_step": true,
     "name": "write dm.sh (3)",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /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@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
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 82977ed..ed73fdc 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
@@ -1044,7 +1044,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
@@ -1054,7 +1054,7 @@
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android.json
index dd937c5..b75ad6c 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android.json
@@ -1180,7 +1180,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out; echo $? >/data/local/tmp/rc",
+      "set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
@@ -1190,7 +1190,7 @@
     "infra_step": true,
     "name": "write dm.sh",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@dm.sh@set -x; env LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out; echo $? >/data/local/tmp/rc@@@",
+      "@@@STEP_LOG_LINE@dm.sh@set -x; LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/dm --dummy --flags --properties bot skia-bot-123 key1 value1 task 123456 --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 --svgs /sdcard/revenge_of_the_skiabot/svgs --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out; echo $? >/data/local/tmp/rc@@@",
       "@@@STEP_LOG_END@dm.sh@@@"
     ]
   },
diff --git a/src/gpu/GrTriangulator.cpp b/src/gpu/GrTriangulator.cpp
index a969732..b2b2af3 100644
--- a/src/gpu/GrTriangulator.cpp
+++ b/src/gpu/GrTriangulator.cpp
@@ -822,8 +822,10 @@
     SkScalar toleranceSqd = tolerance * tolerance;
     bool innerPolygons = (Mode::kSimpleInnerPolygons == mode);
 
+    SkPoint pts[4];
     int localCurveCount = 0;
     VertexList* contour = contours;
+    SkPath::Iter iter(path, false);
     if (path.isInverseFillType()) {
         SkPoint quad[4];
         clipBounds.toQuad(quad);
@@ -833,32 +835,34 @@
         contour++;
     }
     SkAutoConicToQuads converter;
-    for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
+    SkPath::Verb verb;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
         switch (verb) {
-            case SkPathVerb::kConic: {
+            case SkPath::kConic_Verb: {
                 ++localCurveCount;
                 if (innerPolygons) {
                     append_point_to_contour(pts[2], contour, alloc);
                     break;
                 }
-                const SkPoint* quadPts = converter.computeQuads(pts, *w, toleranceSqd);
+                SkScalar weight = iter.conicWeight();
+                const SkPoint* quadPts = converter.computeQuads(pts, weight, toleranceSqd);
                 for (int i = 0; i < converter.countQuads(); ++i) {
                     append_quadratic_to_contour(quadPts, toleranceSqd, contour, alloc);
                     quadPts += 2;
                 }
                 break;
             }
-            case SkPathVerb::kMove:
+            case SkPath::kMove_Verb:
                 if (contour->fHead) {
                     contour++;
                 }
                 append_point_to_contour(pts[0], contour, alloc);
                 break;
-            case SkPathVerb::kLine: {
+            case SkPath::kLine_Verb: {
                 append_point_to_contour(pts[1], contour, alloc);
                 break;
             }
-            case SkPathVerb::kQuad: {
+            case SkPath::kQuad_Verb: {
                 ++localCurveCount;
                 if (innerPolygons) {
                     append_point_to_contour(pts[2], contour, alloc);
@@ -867,7 +871,7 @@
                 append_quadratic_to_contour(pts, toleranceSqd, contour, alloc);
                 break;
             }
-            case SkPathVerb::kCubic: {
+            case SkPath::kCubic_Verb: {
                 ++localCurveCount;
                 if (innerPolygons) {
                     append_point_to_contour(pts[3], contour, alloc);
@@ -878,7 +882,8 @@
                                       pointsLeft, alloc);
                 break;
             }
-            case SkPathVerb::kClose:
+            case SkPath::kClose_Verb:
+            case SkPath::kDone_Verb:
                 break;
         }
     }
@@ -2385,18 +2390,21 @@
     int contourCnt = 1;
     bool hasPoints = false;
 
+    SkPath::Iter iter(path, false);
+    SkPath::Verb verb;
+    SkPoint pts[4];
     bool first = true;
-    for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
         switch (verb) {
-            case SkPathVerb::kMove:
+            case SkPath::kMove_Verb:
                 if (!first) {
                     ++contourCnt;
                 }
                 // fallthru.
-            case SkPathVerb::kLine:
-            case SkPathVerb::kConic:
-            case SkPathVerb::kQuad:
-            case SkPathVerb::kCubic:
+            case SkPath::kLine_Verb:
+            case SkPath::kConic_Verb:
+            case SkPath::kQuad_Verb:
+            case SkPath::kCubic_Verb:
                 hasPoints = true;
                 // fallthru to break.
             default:
diff --git a/src/gpu/tessellate/GrTessellatePathOp.cpp b/src/gpu/tessellate/GrTessellatePathOp.cpp
index 28d4eab..7e0950a 100644
--- a/src/gpu/tessellate/GrTessellatePathOp.cpp
+++ b/src/gpu/tessellate/GrTessellatePathOp.cpp
@@ -76,7 +76,7 @@
     this->prepareCubicWedges(state);
 }
 
-bool GrTessellatePathOp::prepareNonOverlappingInnerTriangles(GrOpFlushState* flushState,
+bool GrTessellatePathOp::prepareNonOverlappingInnerTriangles(GrMeshDrawOp::Target* target,
                                                              int* numCountedCurves) {
     SkASSERT(!fTriangleBuffer);
     SkASSERT(!fDoStencilTriangleBuffer);
@@ -84,7 +84,7 @@
 
     using GrTriangulator::Mode;
 
-    GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fTriangleBuffer, &fBaseTriangleVertex);
+    GrEagerDynamicVertexAllocator vertexAlloc(target, &fTriangleBuffer, &fBaseTriangleVertex);
     fTriangleVertexCount = GrTriangulator::PathToTriangles(fPath, 0, SkRect::MakeEmpty(),
                                                            &vertexAlloc, Mode::kSimpleInnerPolygons,
                                                            numCountedCurves);
@@ -94,7 +94,7 @@
         return false;
     }
     if (((Flags::kStencilOnly | Flags::kWireframe) & fFlags) || GrAAType::kCoverage == fAAType ||
-        (flushState->appliedClip() && flushState->appliedClip()->hasStencilClip())) {
+        (target->appliedClip() && target->appliedClip()->hasStencilClip())) {
         // If we have certain flags, mixed samples, or a stencil clip then we unfortunately
         // can't fill the inner polygon directly. Indicate that these triangles need to be
         // stencilled.
@@ -106,7 +106,7 @@
     return true;
 }
 
-void GrTessellatePathOp::prepareMiddleOutInnerTriangles(GrOpFlushState* flushState,
+void GrTessellatePathOp::prepareMiddleOutInnerTriangles(GrMeshDrawOp::Target* target,
                                                         int* numCountedCurves) {
     SkASSERT(!fTriangleBuffer);
     SkASSERT(!fDoStencilTriangleBuffer);
@@ -116,7 +116,7 @@
     // Each triangle has 3 vertices.
     int maxVertices = (fPath.countVerbs() - 1) * 3;
 
-    GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fTriangleBuffer, &fBaseTriangleVertex);
+    GrEagerDynamicVertexAllocator vertexAlloc(target, &fTriangleBuffer, &fBaseTriangleVertex);
     auto* vertexData = vertexAlloc.lock<SkPoint>(maxVertices);
     if (!vertexData) {
         return;
@@ -176,7 +176,7 @@
     out[3] = pts[2];
 }
 
-void GrTessellatePathOp::prepareOuterCubics(GrOpFlushState* flushState, int numCountedCurves,
+void GrTessellatePathOp::prepareOuterCubics(GrMeshDrawOp::Target* target, int numCountedCurves,
                                             CubicDataAlignment alignment) {
     SkASSERT(!fCubicBuffer);
     SkASSERT(!fStencilCubicsShader);
@@ -190,7 +190,7 @@
     int instanceOrVertexCount = (instanceAligned) ? numCountedCurves : numCountedCurves * 4;
     int baseInstanceOrVertex;
 
-    auto* vertexData = static_cast<SkPoint*>(flushState->makeVertexSpace(
+    auto* vertexData = static_cast<SkPoint*>(target->makeVertexSpace(
             instanceOrVertexStride, instanceOrVertexCount, &fCubicBuffer, &baseInstanceOrVertex));
     if (!vertexData) {
         return;
@@ -216,10 +216,10 @@
     }
     SkASSERT(fCubicVertexCount == numCountedCurves * 4);
 
-    fStencilCubicsShader = flushState->allocator()->make<GrStencilCubicShader>(fViewMatrix);
+    fStencilCubicsShader = target->allocator()->make<GrStencilCubicShader>(fViewMatrix);
 }
 
-void GrTessellatePathOp::prepareCubicWedges(GrOpFlushState* flushState) {
+void GrTessellatePathOp::prepareCubicWedges(GrMeshDrawOp::Target* target) {
     SkASSERT(!fCubicBuffer);
     SkASSERT(!fStencilCubicsShader);
 
@@ -227,7 +227,7 @@
     // Each wedge has 5 vertices.
     int maxVertices = (fPath.countVerbs() + 1) * 5;
 
-    GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fCubicBuffer, &fBaseCubicVertex);
+    GrEagerDynamicVertexAllocator vertexAlloc(target, &fCubicBuffer, &fBaseCubicVertex);
     auto* vertexData = vertexAlloc.lock<SkPoint>(maxVertices);
     if (!vertexData) {
         return;
@@ -274,7 +274,7 @@
     vertexAlloc.unlock(fCubicVertexCount);
 
     if (fCubicVertexCount) {
-        fStencilCubicsShader = flushState->allocator()->make<GrStencilWedgeShader>(fViewMatrix);
+        fStencilCubicsShader = target->allocator()->make<GrStencilWedgeShader>(fViewMatrix);
     }
 }
 
diff --git a/src/gpu/tessellate/GrTessellatePathOp.h b/src/gpu/tessellate/GrTessellatePathOp.h
index 1c0b473..8ee6d21 100644
--- a/src/gpu/tessellate/GrTessellatePathOp.h
+++ b/src/gpu/tessellate/GrTessellatePathOp.h
@@ -8,7 +8,7 @@
 #ifndef GrTessellatePathOp_DEFINED
 #define GrTessellatePathOp_DEFINED
 
-#include "src/gpu/ops/GrDrawOp.h"
+#include "src/gpu/ops/GrMeshDrawOp.h"
 
 class GrAppliedHardClip;
 class GrStencilPathShader;
@@ -64,7 +64,7 @@
     // Returns false if the inner triangles do not form a simple polygon (e.g., self intersection,
     // double winding). Non-simple polygons would need to split edges in order to avoid overlap,
     // and this is not an option as it would introduce T-junctions with the outer cubics.
-    bool prepareNonOverlappingInnerTriangles(GrOpFlushState*, int* numCountedCurves);
+    bool prepareNonOverlappingInnerTriangles(GrMeshDrawOp::Target*, int* numCountedCurves);
 
     // Produces a "Red Book" style triangulation of the SkPath's inner polygon(s). The inner
     // polygons connect the endpoints of each verb. (i.e., they are the path that would result from
@@ -74,7 +74,7 @@
     // This method emits the inner triangles with a "middle-out" topology. Middle-out can reduce
     // the load on the rasterizer by a great deal as compared to a linear triangle strip or fan.
     // See GrMiddleOutPolygonTriangulator.
-    void prepareMiddleOutInnerTriangles(GrOpFlushState*, int* numCountedCurves);
+    void prepareMiddleOutInnerTriangles(GrMeshDrawOp::Target*, int* numCountedCurves);
 
     enum class CubicDataAlignment : bool {
         kVertexBoundary,
@@ -84,7 +84,7 @@
     // Writes an array of "outer" cubics from each bezier in the SkPath, converting any quadratics
     // to cubics. An outer cubic is an independent, 4-point closed contour consisting of a single
     // cubic curve. Stencilled together with the inner triangles, these define the complete path.
-    void prepareOuterCubics(GrOpFlushState* flushState, int numCountedCurves, CubicDataAlignment);
+    void prepareOuterCubics(GrMeshDrawOp::Target*, int numCountedCurves, CubicDataAlignment);
 
     // Writes an array of cubic "wedges" from the SkPath, converting any lines or quadratics to
     // cubics. A wedge is an independent, 5-point closed contour consisting of 4 cubic control
@@ -92,7 +92,7 @@
     // stencilled, these wedges alone define the complete path.
     //
     // TODO: Eventually we want to use rational cubic wedges in order to support conics.
-    void prepareCubicWedges(GrOpFlushState*);
+    void prepareCubicWedges(GrMeshDrawOp::Target*);
 
     void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
     void drawStencilPass(GrOpFlushState*);
diff --git a/src/gpu/tessellate/GrVectorXform.h b/src/gpu/tessellate/GrVectorXform.h
new file mode 100644
index 0000000..61b7412
--- /dev/null
+++ b/src/gpu/tessellate/GrVectorXform.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrVectorXform_DEFINED
+#define GrVectorXform_DEFINED
+
+#include "include/core/SkMatrix.h"
+#include "include/private/SkNx.h"
+
+// We enclose this class in the anonymous namespace so it can have Sk2f/Sk4f members.
+namespace {  // NOLINT(google-build-namespaces)
+
+// Represents the upper-left 2x2 matrix of an affine transform for applying to vectors:
+//
+//     VectorXform(p1 - p0) == M * float3(p1, 1) - M * float3(p0, 1)
+//
+class GrVectorXform {
+public:
+    explicit GrVectorXform() : fType(Type::kIdentity) {}
+    explicit GrVectorXform(const SkMatrix& m) {
+        SkASSERT(!m.hasPerspective());
+        if (m.getType() & SkMatrix::kAffine_Mask) {
+            fType = Type::kAffine;
+            fScaleXSkewY = {m.getScaleX(), m.getSkewY()};
+            fSkewXScaleY = {m.getSkewX(), m.getScaleY()};
+            fScaleXYXY = {m.getScaleX(), m.getScaleY(), m.getScaleX(), m.getScaleY()};
+            fSkewXYXY = {m.getSkewX(), m.getSkewY(), m.getSkewX(), m.getSkewY()};
+        } else if (m.getType() & SkMatrix::kScale_Mask) {
+            fType = Type::kScale;
+            fScaleXY = {m.getScaleX(), m.getScaleY()};
+            fScaleXYXY = {m.getScaleX(), m.getScaleY(), m.getScaleX(), m.getScaleY()};
+        } else {
+            SkASSERT(!(m.getType() & ~SkMatrix::kTranslate_Mask));
+            fType = Type::kIdentity;
+        }
+    }
+    Sk2f operator()(const Sk2f& vector) const {
+        switch (fType) {
+            case Type::kIdentity:
+                return vector;
+            case Type::kScale:
+                return fScaleXY * vector;
+            case Type::kAffine:
+                return fScaleXSkewY * vector[0] + fSkewXScaleY * vector[1];
+        }
+        SkUNREACHABLE;
+    }
+    Sk4f operator()(const Sk4f& vectors) const {
+        switch (fType) {
+            case Type::kIdentity:
+                return vectors;
+            case Type::kScale:
+                return vectors * fScaleXYXY;
+            case Type::kAffine:
+                return fScaleXYXY * vectors + fSkewXYXY * SkNx_shuffle<1,0,3,2>(vectors);
+        }
+        SkUNREACHABLE;
+    }
+private:
+    enum class Type { kIdentity, kScale, kAffine } fType;
+    union { Sk2f fScaleXY, fScaleXSkewY; };
+    Sk2f fSkewXScaleY;
+    Sk4f fScaleXYXY;
+    Sk4f fSkewXYXY;
+};
+
+}  // namespace
+
+#endif
diff --git a/src/gpu/tessellate/GrWangsFormula.h b/src/gpu/tessellate/GrWangsFormula.h
new file mode 100644
index 0000000..1fa79c6
--- /dev/null
+++ b/src/gpu/tessellate/GrWangsFormula.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrWangsFormula_DEFINED
+#define GrWangsFormula_DEFINED
+
+#include "include/core/SkPoint.h"
+#include "include/private/SkNx.h"
+#include "src/gpu/tessellate/GrVectorXform.h"
+
+// Wang's formulas for cubics and quadratics (1985) give us the minimum number of evenly spaced (in
+// the parametric sense) line segments that a curve must be chopped into in order to guarantee all
+// lines stay within a distance of "1/intolerance" pixels from the true curve.
+namespace GrWangsFormula {
+
+SK_ALWAYS_INLINE static float length(const Sk2f& n) {
+    Sk2f nn = n*n;
+    return std::sqrt(nn[0] + nn[1]);
+}
+
+// Returns the minimum number of evenly spaced (in the parametric sense) line segments that the
+// quadratic must be chopped into in order to guarantee all lines stay within a distance of
+// "1/intolerance" pixels from the true curve.
+SK_ALWAYS_INLINE static float quadratic(float intolerance, const SkPoint pts[]) {
+    Sk2f p0 = Sk2f::Load(pts);
+    Sk2f p1 = Sk2f::Load(pts + 1);
+    Sk2f p2 = Sk2f::Load(pts + 2);
+    float k = intolerance * .25f;
+    return SkScalarSqrt(k * length(p0 - p1*2 + p2));
+}
+
+// Returns the minimum number of evenly spaced (in the parametric sense) line segments that the
+// cubic must be chopped into in order to guarantee all lines stay within a distance of
+// "1/intolerance" pixels from the true curve.
+SK_ALWAYS_INLINE static float cubic(float intolerance, const SkPoint pts[]) {
+    Sk2f p0 = Sk2f::Load(pts);
+    Sk2f p1 = Sk2f::Load(pts + 1);
+    Sk2f p2 = Sk2f::Load(pts + 2);
+    Sk2f p3 = Sk2f::Load(pts + 3);
+    float k = intolerance * .75f;
+    return SkScalarSqrt(k * length(Sk2f::Max((p0 - p1*2 + p2).abs(),
+                                             (p1 - p2*2 + p3).abs())));
+}
+
+// Returns the log2 of the provided value, were that value to be rounded up to the next power of 2.
+// Returns 0 if value <= 0:
+// Never returns a negative number, even if value is NaN.
+//
+//     nextlog2((-inf..1]) -> 0
+//     nextlog2((1..2]) -> 1
+//     nextlog2((2..4]) -> 2
+//     nextlog2((4..8]) -> 3
+//     ...
+SK_ALWAYS_INLINE static int nextlog2(float value) {
+    uint32_t bits;
+    memcpy(&bits, &value, 4);
+    bits += (1u << 23) - 1u;  // Increment the exponent for non-powers-of-2.
+    int exp = ((int32_t)bits >> 23) - 127;
+    return exp & ~(exp >> 31);  // Return 0 for negative or denormalized floats, and exponents < 0.
+}
+
+// Returns the minimum log2 number of evenly spaced (in the parametric sense) line segments that the
+// transformed quadratic must be chopped into in order to guarantee all lines stay within a distance
+// of "1/intolerance" pixels from the true curve.
+SK_ALWAYS_INLINE static int quadratic_log2(float intolerance, const SkPoint pts[],
+                                           const GrVectorXform& vectorXform = GrVectorXform()) {
+    Sk2f p0 = Sk2f::Load(pts);
+    Sk2f p1 = Sk2f::Load(pts + 1);
+    Sk2f p2 = Sk2f::Load(pts + 2);
+    Sk2f v = p0 + p1*-2 + p2;
+    v = vectorXform(v);
+    Sk2f vv = v*v;
+    float k = intolerance * .25f;
+    float f = k*k * (vv[0] + vv[1]);
+    return (nextlog2(f) + 3) >> 2;  // ceil(log2(sqrt(sqrt(f))))
+}
+
+// Returns the minimum log2 number of evenly spaced (in the parametric sense) line segments that the
+// transformed cubic must be chopped into in order to guarantee all lines stay within a distance of
+// "1/intolerance" pixels from the true curve.
+SK_ALWAYS_INLINE static int cubic_log2(float intolerance, const SkPoint pts[],
+                                       const GrVectorXform& vectorXform = GrVectorXform()) {
+    Sk4f p01 = Sk4f::Load(pts);
+    Sk4f p12 = Sk4f::Load(pts + 1);
+    Sk4f p23 = Sk4f::Load(pts + 2);
+    Sk4f v = p01 + p12*-2 + p23;
+    v = vectorXform(v);
+    Sk4f vv = v*v;
+    vv = Sk4f::Max(vv, SkNx_shuffle<2,3,0,1>(vv));
+    float k = intolerance * .75f;
+    float f = k*k * (vv[0] + vv[1]);
+    return (nextlog2(f) + 3) >> 2;  // ceil(log2(sqrt(sqrt(f))))
+}
+
+}  // namespace
+
+#endif
diff --git a/tests/WangsFormulaTest.cpp b/tests/WangsFormulaTest.cpp
new file mode 100644
index 0000000..a8e1c77
--- /dev/null
+++ b/tests/WangsFormulaTest.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/utils/SkRandom.h"
+#include "src/core/SkGeometry.h"
+#include "src/gpu/tessellate/GrWangsFormula.h"
+#include "tests/Test.h"
+
+constexpr static int kIntolerance = 4;  // 1/4 pixel max error.
+
+const SkPoint kSerp[4] = {
+        {285.625f, 499.687f}, {411.625f, 808.188f}, {1064.62f, 135.688f}, {1042.63f, 585.187f}};
+
+const SkPoint kLoop[4] = {
+        {635.625f, 614.687f}, {171.625f, 236.188f}, {1064.62f, 135.688f}, {516.625f, 570.187f}};
+
+const SkPoint kQuad[4] = {
+        {460.625f, 557.187f}, {707.121f, 209.688f}, {779.628f, 577.687f}};
+
+DEF_TEST(WangsFormula_nextlog2, r) {
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(-std::numeric_limits<float>::infinity()) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(-std::numeric_limits<float>::max()) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(-1000.0f) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(-0.1f) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(-std::numeric_limits<float>::min()) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(-std::numeric_limits<float>::denorm_min()) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(0.0f) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(std::numeric_limits<float>::denorm_min()) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(std::numeric_limits<float>::min()) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(0.1f) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(1.0f) == 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(1.1f) == 1);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(2.0f) == 1);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(2.1f) == 2);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(3.0f) == 2);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(3.1f) == 2);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(4.0f) == 2);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(4.1f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(5.0f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(5.1f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(6.0f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(6.1f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(7.0f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(7.1f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(8.0f) == 3);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(8.1f) == 4);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(9.0f) == 4);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(9.1f) == 4);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(std::numeric_limits<float>::max()) == 128);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(std::numeric_limits<float>::infinity()) > 0);
+    REPORTER_ASSERT(r, GrWangsFormula::nextlog2(std::numeric_limits<float>::quiet_NaN()) >= 0);
+
+    for (int i = 0; i < 100; ++i) {
+        float pow2 = std::ldexp(1, i);
+        float epsilon = std::ldexp(SK_ScalarNearlyZero, i);
+        REPORTER_ASSERT(r, GrWangsFormula::nextlog2(pow2) == i);
+        REPORTER_ASSERT(r, GrWangsFormula::nextlog2(pow2 + epsilon) == i + 1);
+        REPORTER_ASSERT(r, GrWangsFormula::nextlog2(pow2 - epsilon) == i);
+    }
+}
+
+void for_random_matrices(SkRandom* rand, std::function<void(const SkMatrix&)> f) {
+    SkMatrix m;
+    m.setIdentity();
+    f(m);
+
+    for (int i = -10; i <= 30; ++i) {
+        for (int j = -10; j <= 30; ++j) {
+            m.setScaleX(std::ldexp(1 + rand->nextF(), i));
+            m.setSkewX(0);
+            m.setSkewY(0);
+            m.setScaleY(std::ldexp(1 + rand->nextF(), j));
+            f(m);
+
+            m.setScaleX(std::ldexp(1 + rand->nextF(), i));
+            m.setSkewX(std::ldexp(1 + rand->nextF(), (j + i) / 2));
+            m.setSkewY(std::ldexp(1 + rand->nextF(), (j + i) / 2));
+            m.setScaleY(std::ldexp(1 + rand->nextF(), j));
+            f(m);
+        }
+    }
+}
+
+void for_random_beziers(int numPoints, SkRandom* rand, std::function<void(const SkPoint[])> f) {
+    SkASSERT(numPoints <= 4);
+    SkPoint pts[4];
+    for (int i = -10; i <= 30; ++i) {
+        for (int j = 0; j < numPoints; ++j) {
+            pts[j].set(std::ldexp(1 + rand->nextF(), i), std::ldexp(1 + rand->nextF(), i));
+        }
+        f(pts);
+    }
+}
+
+// Ensure the optimized "*_log2" versions return the same value as ceil(std::log2(f)).
+DEF_TEST(WangsFormula_log2, r) {
+    // Constructs a cubic such that the 'length' term in wang's formula == term.
+    //
+    //     f = sqrt(k * length(max(abs(p0 - p1*2 + p2),
+    //                             abs(p1 - p2*2 + p3))));
+    auto setupCubicLengthTerm = [](int seed, SkPoint pts[], float term) {
+        memset(pts, 0, sizeof(SkPoint) * 4);
+
+        SkPoint term2d = (seed & 1) ?
+                SkPoint::Make(term, 0) : SkPoint::Make(.5f, std::sqrt(3)/2) * term;
+        seed >>= 1;
+
+        if (seed & 1) {
+            term2d.fX = -term2d.fX;
+        }
+        seed >>= 1;
+
+        if (seed & 1) {
+            std::swap(term2d.fX, term2d.fY);
+        }
+        seed >>= 1;
+
+        switch (seed % 4) {
+            case 0:
+                pts[0] = term2d;
+                pts[3] = term2d * .75f;
+                return;
+            case 1:
+                pts[1] = term2d * -.5f;
+                return;
+            case 2:
+                pts[1] = term2d * -.5f;
+                return;
+            case 3:
+                pts[3] = term2d;
+                pts[0] = term2d * .75f;
+                return;
+        }
+    };
+
+    // Constructs a quadratic such that the 'length' term in wang's formula == term.
+    //
+    //     f = sqrt(k * length(p0 - p1*2 + p2));
+    auto setupQuadraticLengthTerm = [](int seed, SkPoint pts[], float term) {
+        memset(pts, 0, sizeof(SkPoint) * 3);
+
+        SkPoint term2d = (seed & 1) ?
+                SkPoint::Make(term, 0) : SkPoint::Make(.5f, std::sqrt(3)/2) * term;
+        seed >>= 1;
+
+        if (seed & 1) {
+            term2d.fX = -term2d.fX;
+        }
+        seed >>= 1;
+
+        if (seed & 1) {
+            std::swap(term2d.fX, term2d.fY);
+        }
+        seed >>= 1;
+
+        switch (seed % 3) {
+            case 0:
+                pts[0] = term2d;
+                return;
+            case 1:
+                pts[1] = term2d * -.5f;
+                return;
+            case 2:
+                pts[2] = term2d;
+                return;
+        }
+    };
+
+    for (int level = 0; level < 30; ++level) {
+        float epsilon = std::ldexp(SK_ScalarNearlyZero, level * 2);
+        SkPoint pts[4];
+
+        {
+            // Test cubic boundaries.
+            //     f = sqrt(k * length(max(abs(p0 - p1*2 + p2),
+            //                             abs(p1 - p2*2 + p3))));
+            constexpr static float k = (3 * 2) / (8 * (1.f/kIntolerance));
+            float x = std::ldexp(1, level * 2) / k;
+            setupCubicLengthTerm(level << 1, pts, x - epsilon);
+            REPORTER_ASSERT(r,
+                    std::ceil(std::log2(GrWangsFormula::cubic(kIntolerance, pts))) == level);
+            REPORTER_ASSERT(r, GrWangsFormula::cubic_log2(kIntolerance, pts) == level);
+            setupCubicLengthTerm(level << 1, pts, x + epsilon);
+            REPORTER_ASSERT(r,
+                    std::ceil(std::log2(GrWangsFormula::cubic(kIntolerance, pts))) == level + 1);
+            REPORTER_ASSERT(r, GrWangsFormula::cubic_log2(kIntolerance, pts) == level + 1);
+        }
+
+        {
+            // Test quadratic boundaries.
+            //     f = std::sqrt(k * Length(p0 - p1*2 + p2));
+            constexpr static float k = 2 / (8 * (1.f/kIntolerance));
+            float x = std::ldexp(1, level * 2) / k;
+            setupQuadraticLengthTerm(level << 1, pts, x - epsilon);
+            REPORTER_ASSERT(r,
+                    std::ceil(std::log2(GrWangsFormula::quadratic(kIntolerance, pts))) == level);
+            REPORTER_ASSERT(r, GrWangsFormula::quadratic_log2(kIntolerance, pts) == level);
+            setupQuadraticLengthTerm(level << 1, pts, x + epsilon);
+            REPORTER_ASSERT(r,
+                    std::ceil(std::log2(GrWangsFormula::quadratic(kIntolerance, pts))) == level+1);
+            REPORTER_ASSERT(r, GrWangsFormula::quadratic_log2(kIntolerance, pts) == level + 1);
+        }
+    }
+
+    auto check_cubic_log2 = [&](const SkPoint* pts) {
+        float f = std::max(1.f, GrWangsFormula::cubic(kIntolerance, pts));
+        int f_log2 = GrWangsFormula::cubic_log2(kIntolerance, pts);
+        REPORTER_ASSERT(r, SkScalarCeilToInt(std::log2(f)) == f_log2);
+    };
+
+    auto check_quadratic_log2 = [&](const SkPoint* pts) {
+        float f = std::max(1.f, GrWangsFormula::quadratic(kIntolerance, pts));
+        int f_log2 = GrWangsFormula::quadratic_log2(kIntolerance, pts);
+        REPORTER_ASSERT(r, SkScalarCeilToInt(std::log2(f)) == f_log2);
+    };
+
+    SkRandom rand;
+
+    for_random_matrices(&rand, [&](const SkMatrix& m) {
+        SkPoint pts[4];
+        m.mapPoints(pts, kSerp, 4);
+        check_cubic_log2(pts);
+
+        m.mapPoints(pts, kLoop, 4);
+        check_cubic_log2(pts);
+
+        m.mapPoints(pts, kQuad, 3);
+        check_quadratic_log2(pts);
+    });
+
+    for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
+        check_cubic_log2(pts);
+    });
+
+    for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
+        check_quadratic_log2(pts);
+    });
+}
+
+// Ensure using transformations gives the same result as pre-transforming all points.
+DEF_TEST(WangsFormula_vectorXforms, r) {
+    auto check_cubic_log2_with_transform = [&](const SkPoint* pts, const SkMatrix& m){
+        SkPoint ptsXformed[4];
+        m.mapPoints(ptsXformed, pts, 4);
+        int expected = GrWangsFormula::cubic_log2(kIntolerance, ptsXformed);
+        int actual = GrWangsFormula::cubic_log2(kIntolerance, pts, GrVectorXform(m));
+        REPORTER_ASSERT(r, actual == expected);
+    };
+
+    auto check_quadratic_log2_with_transform = [&](const SkPoint* pts, const SkMatrix& m) {
+        SkPoint ptsXformed[3];
+        m.mapPoints(ptsXformed, pts, 3);
+        int expected = GrWangsFormula::quadratic_log2(kIntolerance, ptsXformed);
+        int actual = GrWangsFormula::quadratic_log2(kIntolerance, pts, GrVectorXform(m));
+        REPORTER_ASSERT(r, actual == expected);
+    };
+
+    SkRandom rand;
+
+    for_random_matrices(&rand, [&](const SkMatrix& m) {
+        check_cubic_log2_with_transform(kSerp, m);
+        check_cubic_log2_with_transform(kLoop, m);
+        check_quadratic_log2_with_transform(kQuad, m);
+
+        for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
+            check_cubic_log2_with_transform(pts, m);
+        });
+
+        for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
+            check_quadratic_log2_with_transform(pts, m);
+        });
+    });
+
+}