Roll external/skia 81b7e3df3..346f82c1c (17 commits)

https://skia.googlesource.com/skia.git/+log/81b7e3df3..346f82c1c

2019-06-04 mtklein@google.com print 1/K floats as fractions
2019-06-04 mtklein@google.com move peepholes where they're relevant
2019-06-04 fmalita@chromium.org [skottie] Initial text range selector support
2019-06-04 mtklein@google.com print SKVM test failures
2019-06-04 mtklein@google.com allow two immediates
2019-06-04 robertphillips@google.com Make color initialization version of createBackendTexture public
2019-06-04 mtklein@google.com add SkVMBuilders.* to DM deps in G3
2019-06-04 michaelludwig@google.com Extract GrQuadList into separate header
2019-06-04 borenet@google.com [infra] Fixes for chrome_release_branch
2019-06-04 mtklein@google.com dump register and instruction count
2019-06-04 mtklein@google.com add extract instruction
2019-06-04 mtklein@google.com add pack instruction
2019-06-04 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial).
2019-06-04 mtklein@google.com add mul_unorm8 instruction
2019-06-04 mtklein@google.com centralize test/bench SkVM builders
2019-06-04 skia-autoroll@skia-public.iam.gserviceaccount.com Roll skia/third_party/skcms ca5b4470be98..386ae89d2b64 (1 commits)
2019-06-04 robertphillips@google.com Switch all internel uses of GrContext::createBackendTexture over to initialized versions

The AutoRoll server is located here: https://autoroll-internal.skia.org/r/android-master-autoroll

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

If the roll is causing failures, please contact the current sheriff, who should
be CC'd on the roll, and stop the roller if necessary.

Test: Presubmit checks will test this change.
Change-Id: I6938f1ea2314d732efc22076e5b4a889dd17650e
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
diff --git a/Android.bp b/Android.bp
index 3b6dc6d..63316cc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1785,6 +1785,7 @@
         "tools/LsanSuppressions.cpp",
         "tools/ProcStats.cpp",
         "tools/Resources.cpp",
+        "tools/SkVMBuilders.cpp",
         "tools/ToolUtils.cpp",
         "tools/UrlDataManager.cpp",
         "tools/debugger/DebugCanvas.cpp",
@@ -2361,6 +2362,7 @@
         "tools/LsanSuppressions.cpp",
         "tools/ProcStats.cpp",
         "tools/Resources.cpp",
+        "tools/SkVMBuilders.cpp",
         "tools/ToolUtils.cpp",
         "tools/UrlDataManager.cpp",
         "tools/debugger/DebugCanvas.cpp",
diff --git a/BUILD.gn b/BUILD.gn
index b5efce4..3fba86e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1665,6 +1665,13 @@
     }
   }
 
+  test_lib("skvm_builders") {
+    sources = [
+      "tools/SkVMBuilders.cpp",
+      "tools/SkVMBuilders.h",
+    ]
+  }
+
   import("gn/tests.gni")
   test_lib("tests") {
     sources = tests_sources + pathops_tests_sources
@@ -1681,6 +1688,7 @@
       ":experimental_svg_model",
       ":flags",
       ":skia",
+      ":skvm_builders",
       ":tool_utils",
       "modules/skottie:tests",
       "modules/sksg:tests",
@@ -1702,6 +1710,7 @@
       ":gm",
       ":gpu_tool_utils",
       ":skia",
+      ":skvm_builders",
       ":tool_utils",
     ]
   }
diff --git a/bench/SkVMBench.cpp b/bench/SkVMBench.cpp
index eae0b70..32708ac 100644
--- a/bench/SkVMBench.cpp
+++ b/bench/SkVMBench.cpp
@@ -8,124 +8,13 @@
 #include "bench/Benchmark.h"
 #include "src/core/SkOpts.h"
 #include "src/core/SkVM.h"
+#include "tools/SkVMBuilders.h"
 
 namespace {
 
     enum Mode {Opts, RP, F32, I32, I32_SWAR};
     static const char* kMode_name[] = { "Opts", "RP","F32", "I32", "I32_SWAR" };
 
-    struct SrcoverBuilder_F32 : public skvm::Builder {
-        SrcoverBuilder_F32() {
-
-            skvm::Arg src = arg(0),
-                      dst = arg(1);
-
-            auto byte_to_f32 = [&](skvm::I32 byte) {
-                return mul(splat(1/255.0f), to_f32(byte));
-            };
-            auto f32_to_byte = [&](skvm::F32 f32) {
-                return to_i32(mad(f32, splat(255.0f), splat(0.5f)));
-            };
-
-            auto load = [&](skvm::Arg ptr,
-                            skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32* a) {
-                skvm::I32 rgba = load32(ptr);
-                *r = byte_to_f32(bit_and(    rgba     , splat(0xff)));
-                *g = byte_to_f32(bit_and(shr(rgba,  8), splat(0xff)));
-                *b = byte_to_f32(bit_and(shr(rgba, 16), splat(0xff)));
-                *a = byte_to_f32(        shr(rgba, 24)              );
-            };
-
-            skvm::F32 r,g,b,a;
-            load(src, &r,&g,&b,&a);
-
-            skvm::F32 dr,dg,db,da;
-            load(dst, &dr,&dg,&db,&da);
-
-            skvm::F32 invA = sub(splat(1.0f), a);
-            r = mad(dr, invA, r);
-            g = mad(dg, invA, g);
-            b = mad(db, invA, b);
-            a = mad(da, invA, a);
-
-            store32(dst, bit_or(    f32_to_byte(r)     ,
-                         bit_or(shl(f32_to_byte(g),  8),
-                         bit_or(shl(f32_to_byte(b), 16),
-                                shl(f32_to_byte(a), 24)))));
-        }
-    };
-
-    struct SrcoverBuilder_I32 : public skvm::Builder {
-        SrcoverBuilder_I32() {
-            skvm::Arg src = arg(0),
-                      dst = arg(1);
-
-            auto load = [&](skvm::Arg ptr,
-                            skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) {
-                skvm::I32 rgba = load32(ptr);
-                *r = bit_and(    rgba     , splat(0xff));
-                *g = bit_and(shr(rgba,  8), splat(0xff));
-                *b = bit_and(shr(rgba, 16), splat(0xff));
-                *a =         shr(rgba, 24)              ;
-            };
-
-            auto mul_unorm8 = [&](skvm::I32 x, skvm::I32 y) {
-                // (x*y + 127)/255 ~= (x*y+255)/256
-                return shr(add(mul(x, y), splat(0xff)), 8);
-            };
-
-            skvm::I32 r,g,b,a;
-            load(src, &r,&g,&b,&a);
-
-            skvm::I32 dr,dg,db,da;
-            load(dst, &dr,&dg,&db,&da);
-
-            skvm::I32 invA = sub(splat(0xff), a);
-            r = add(r, mul_unorm8(dr, invA));
-            g = add(g, mul_unorm8(dg, invA));
-            b = add(b, mul_unorm8(db, invA));
-            a = add(a, mul_unorm8(da, invA));
-
-            store32(dst, bit_or(    r     ,
-                         bit_or(shl(g,  8),
-                         bit_or(shl(b, 16),
-                                shl(a, 24)))));
-        }
-    };
-
-    struct SrcoverBuilder_I32_SWAR : public skvm::Builder {
-        SrcoverBuilder_I32_SWAR() {
-            skvm::Arg src = arg(0),
-                      dst = arg(1);
-
-            auto load = [&](skvm::Arg ptr,
-                            skvm::I32* rb, skvm::I32* ga) {
-                skvm::I32 rgba = load32(ptr);
-                *rb = bit_and(    rgba,     splat(0x00ff00ff));
-                *ga = bit_and(shr(rgba, 8), splat(0x00ff00ff));
-            };
-
-            auto mul_unorm8 = [&](skvm::I32 x, skvm::I32 y) {
-                // As above, assuming x is two SWAR bytes in lanes 0 and 2, and y is a byte.
-                return bit_and(shr(add(mul(x, y),
-                                       splat(0x00ff00ff)),
-                                   8),
-                               splat(0x00ff00ff));
-            };
-
-            skvm::I32 rb, ga;
-            load(src, &rb, &ga);
-
-            skvm::I32 drb, dga;
-            load(dst, &drb, &dga);
-
-            skvm::I32 invA = sub(splat(0xff), shr(ga, 16));
-            rb = add(rb, mul_unorm8(drb, invA));
-            ga = add(ga, mul_unorm8(dga, invA));
-
-            store32(dst, bit_or(rb, shl(ga, 8)));
-        }
-    };
 }
 
 class SkVMBench : public Benchmark {
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 26373a1..473be2b 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1389,7 +1389,7 @@
             break;
         case SkCommandLineConfigGpu::SurfType::kBackendTexture:
             backendTexture = context->createBackendTexture(
-                    info.width(), info.height(), info.colorType(),
+                    info.width(), info.height(), info.colorType(), SkColors::kTransparent,
                     GrMipMapped::kNo, GrRenderable::kYes);
             surface = SkSurface::MakeFromBackendTexture(context, backendTexture,
                                                         kTopLeft_GrSurfaceOrigin, fSampleCount,
diff --git a/gm/imagefromyuvtextures.cpp b/gm/imagefromyuvtextures.cpp
index 53a828a..9db63e4 100644
--- a/gm/imagefromyuvtextures.cpp
+++ b/gm/imagefromyuvtextures.cpp
@@ -143,7 +143,8 @@
     void createResultTexture(GrContext* context, int width, int height,
                              GrBackendTexture* resultTexture) {
         *resultTexture = context->createBackendTexture(
-                width, height, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kYes);
+                width, height, kRGBA_8888_SkColorType, SkColors::kTransparent,
+                GrMipMapped::kNo, GrRenderable::kYes);
 
         context->resetContext();
     }
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 9d012f0..78224cf 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -345,6 +345,7 @@
   "$_src/gpu/geometry/GrPathUtils.h",
   "$_src/gpu/geometry/GrQuad.cpp",
   "$_src/gpu/geometry/GrQuad.h",
+  "$_src/gpu/geometry/GrQuadList.h",
   "$_src/gpu/geometry/GrRect.h",
   "$_src/gpu/geometry/GrShape.cpp",
   "$_src/gpu/geometry/GrShape.h",
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 850e157..f5914d9 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -346,8 +346,10 @@
     * before deleting the GrContext used to create them. Additionally, clients should only
     * delete these objects on the thread for which that GrContext is active.
     *
-    * Additionally, the client is responsible for ensuring synchronization between different uses
-    * of the backend object.
+    * The client is responsible for ensuring synchronization between different uses
+    * of the backend object (i.e., wrapping it in a surface, rendering to it, deleting the
+    * surface, rewrapping it in a image and drawing the image will require explicit
+    * sychronization on the client's part).
     */
 
     // If possible, create an uninitialized backend texture. The client should ensure that the
@@ -366,6 +368,20 @@
                                           GrMipMapped,
                                           GrRenderable);
 
+    // If possible, create a backend texture initialized to a particular color. The client should
+    // ensure that the returned backend texture is valid.
+    GrBackendTexture createBackendTexture(int width, int height,
+                                          GrBackendFormat, const SkColor4f& color,
+                                          GrMipMapped, GrRenderable);
+
+    // If possible, create a backend texture initialized to a particular color. The client should
+    // ensure that the returned backend texture is valid.
+    // If successful, the created backend texture will be compatible with the provided
+    // SkColorType.
+    GrBackendTexture createBackendTexture(int width, int height,
+                                          SkColorType, const SkColor4f& color,
+                                          GrMipMapped, GrRenderable);
+
     void deleteBackendTexture(GrBackendTexture);
 
 protected:
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 19bae32..23c58a9 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -14,7 +14,7 @@
   "deps": {
     "depot_tools": {
       "branch": "master",
-      "revision": "0f476788122d4b40b0293226e193e534ec66cad6",
+      "revision": "2c48f24c7f88f2f09821db492e8cc9a4361ded58",
       "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
     },
     "recipe_engine": {
diff --git a/modules/skottie/skottie.gni b/modules/skottie/skottie.gni
index 6a1488e..dc9c418 100644
--- a/modules/skottie/skottie.gni
+++ b/modules/skottie/skottie.gni
@@ -28,6 +28,8 @@
   "$_src/SkottieValue.cpp",
   "$_src/SkottieValue.h",
 
+  "$_src/text/RangeSelector.cpp",
+  "$_src/text/RangeSelector.h",
   "$_src/text/SkottieShaper.cpp",
   "$_src/text/SkottieShaper.h",
   "$_src/text/TextAdapter.cpp",
diff --git a/modules/skottie/src/text/RangeSelector.cpp b/modules/skottie/src/text/RangeSelector.cpp
new file mode 100644
index 0000000..d2cd7f0
--- /dev/null
+++ b/modules/skottie/src/text/RangeSelector.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 "modules/skottie/src/text/RangeSelector.h"
+
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottieValue.h"
+
+#include <algorithm>
+#include <cmath>
+
+namespace skottie {
+namespace internal {
+
+namespace  {
+
+// Maps a 1-based JSON enum to one of the values in the array.
+template <typename T, typename TArray>
+T ParseEnum(const TArray& arr, const skjson::Value& jenum,
+            const AnimationBuilder* abuilder, const char* warn_name) {
+
+    const auto idx = ParseDefault<int>(jenum, 1);
+
+    if (idx > 0 && SkToSizeT(idx) <= SK_ARRAY_COUNT(arr)) {
+        return arr[idx - 1];
+    }
+
+    abuilder->log(Logger::Level::kWarning, nullptr,
+                  "Ignoring unknown range selector %s '%d'", warn_name, idx);
+
+    static_assert(SK_ARRAY_COUNT(arr) > 0, "");
+    return arr[0];
+}
+
+template <RangeSelector::Units>
+struct UnitTraits;
+
+template <>
+struct UnitTraits<RangeSelector::Units::kPercentage> {
+    static constexpr auto Defaults() {
+        return std::make_tuple<float, float, float>(0, 100, 0);
+    }
+
+    static auto Resolve(float s, float e, float o, size_t domain_size) {
+        return std::make_tuple(domain_size * (s + o) / 100,
+                               domain_size * (e + o) / 100);
+    }
+};
+
+template <>
+struct UnitTraits<RangeSelector::Units::kIndex> {
+    static constexpr auto Defaults() {
+        // It's OK to default fEnd to FLOAT_MAX, as it gets clamped when resolved.
+        return std::make_tuple<float, float, float>(0, std::numeric_limits<float>::max(), 0);
+    }
+
+    static auto Resolve(float s, float e, float o, size_t domain_size) {
+        return std::make_tuple(s + o, e + o);
+    }
+};
+
+template <RangeSelector::Mode>
+struct ModeTraits;
+
+template <>
+struct ModeTraits<RangeSelector::Mode::kAdd> {
+    static void modulate(TextAnimator::AnimatedPropsModulator* c_begin,
+                         const TextAnimator::AnimatedPropsModulator* c_end, float amount) {
+        if (!amount) return; // 0 -> noop
+
+        for (auto c = c_begin; c < c_end; ++c) {
+            c->coverage = SkTPin<float>(c->coverage + amount, -1, 1);
+        }
+    }
+};
+
+} // namespace
+
+sk_sp<RangeSelector> RangeSelector::Make(const skjson::ObjectValue* jrange,
+                                         const AnimationBuilder* abuilder,
+                                         AnimatorScope *ascope) {
+    if (!jrange) {
+        return nullptr;
+    }
+
+    static constexpr Units gUnitMap[] = {
+        Units::kPercentage,  // 'r': 1
+        Units::kIndex,       // 'r': 2
+    };
+
+    static constexpr Domain gDomainMap[] = {
+        Domain::kChars,      // 'b': 1
+    };
+
+    static constexpr Mode gModeMap[] = {
+        Mode::kAdd,          // 'm': 1
+    };
+
+    auto selector = sk_sp<RangeSelector>(
+            new RangeSelector(ParseEnum<Units> (gUnitMap  , (*jrange)["r"], abuilder, "units" ),
+                              ParseEnum<Domain>(gDomainMap, (*jrange)["b"], abuilder, "domain"),
+                              ParseEnum<Mode>  (gModeMap  , (*jrange)["m"], abuilder, "mode"  )));
+
+    abuilder->bindProperty<ScalarValue>((*jrange)["s"], ascope,
+        [selector](const ScalarValue& s) {
+            selector->fStart = s;
+        });
+    abuilder->bindProperty<ScalarValue>((*jrange)["e"], ascope,
+        [selector](const ScalarValue& e) {
+            selector->fEnd = e;
+        });
+    abuilder->bindProperty<ScalarValue>((*jrange)["o"], ascope,
+        [selector](const ScalarValue& o) {
+            selector->fOffset = o;
+        });
+    abuilder->bindProperty<ScalarValue>((*jrange)["a"], ascope,
+        [selector](const ScalarValue& a) {
+            selector->fAmount = a;
+        });
+
+    return selector;
+}
+
+RangeSelector::RangeSelector(Units u, Domain d, Mode m)
+    : fUnits(u)
+    , fDomain(d)
+    , fMode(m) {
+
+    // Range defaults are unit-specific.
+    switch (fUnits) {
+    case Units::kPercentage:
+        std::tie(fStart, fEnd, fOffset) = UnitTraits<Units::kPercentage>::Defaults();
+        break;
+    case Units::kIndex:
+        std::tie(fStart, fEnd, fOffset) = UnitTraits<Units::kIndex     >::Defaults();
+        break;
+    }
+}
+
+std::tuple<float, float> RangeSelector::resolve(size_t len) const {
+    float f_i0, f_i1;
+
+    SkASSERT(fUnits == Units::kPercentage || fUnits == Units::kIndex);
+    const auto resolver = (fUnits == Units::kPercentage)
+            ? UnitTraits<Units::kPercentage>::Resolve
+            : UnitTraits<Units::kIndex     >::Resolve;
+
+    std::tie(f_i0, f_i1) = resolver(fStart, fEnd, fOffset, len);
+    if (f_i0 > f_i1) {
+        std::swap(f_i0, f_i1);
+    }
+
+    return std::make_tuple(SkTPin<float>(f_i0, 0, len),
+                           SkTPin<float>(f_i1, 0, len));
+}
+
+void RangeSelector::modulateCoverage(TextAnimator::ModulatorBuffer& buf) const {
+    SkASSERT(!buf.empty());
+
+    // Amount is percentage based [-100% .. 100%].
+    const auto amount  = SkTPin<float>(fAmount / 100, -1, 1);
+
+    // First, resolve to a float range in the given domain.
+    SkAssertResult(fDomain == Domain::kChars);
+    const auto f_range = this->resolve(buf.size());
+
+    // Figure out the integral/index coverage range.
+    const auto i0 = std::min<size_t>(std::get<0>(f_range), buf.size() - 1),
+               i1 = std::min<size_t>(std::get<1>(f_range), buf.size() - 1);
+
+    SkAssertResult(fMode == Mode::kAdd);
+    const auto modulate = ModeTraits<Mode::kAdd>::modulate;
+
+    // Apply coverage modulation across different domain segments.
+    modulate(buf.data()         , buf.data() + i0        ,      0); // [0 ..i0) -> zero coverage.
+    modulate(buf.data() + i0 + 1, buf.data() + i1        , amount); // (i0..i1) -> full coverage.
+    modulate(buf.data() + i1 + 1, buf.data() + buf.size(),      0); // (i1.. N] -> zero coverage.
+
+    // For i0 and i1 we have fractional coverage.
+    const auto fract_0 = 1 - (std::get<0>(f_range) - i0),
+               fract_1 =      std::get<1>(f_range) - i1;
+    SkASSERT(fract_0 >= 0 && fract_0 <= 1);
+    SkASSERT(fract_1 >= 0 && fract_1 <= 1);
+
+    if (i0 == i1) {
+        // The range falls within a single index.
+        SkASSERT(fract_0 + fract_1 >= 1);
+        modulate(buf.data() + i0, buf.data() + i0 + 1, (fract_0 + fract_1 - 1) * amount);
+    } else {
+        // Separate indices for i0, i1.
+        modulate(buf.data() + i0, buf.data() + i0 + 1, fract_0 * amount);
+        modulate(buf.data() + i1, buf.data() + i1 + 1, fract_1 * amount);
+    }
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/text/RangeSelector.h b/modules/skottie/src/text/RangeSelector.h
new file mode 100644
index 0000000..d78534b
--- /dev/null
+++ b/modules/skottie/src/text/RangeSelector.h
@@ -0,0 +1,69 @@
+/*
+ * 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 SkottieRangeSelector_DEFINED
+#define SkottieRangeSelector_DEFINED
+
+#include "include/core/SkRefCnt.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/text/TextAnimator.h"
+
+#include <tuple>
+#include <vector>
+
+namespace skottie {
+namespace internal {
+
+class RangeSelector final : public SkNVRefCnt<RangeSelector> {
+public:
+    static sk_sp<RangeSelector> Make(const skjson::ObjectValue*,
+                                     const AnimationBuilder*,
+                                     AnimatorScope* ascope);
+
+    enum class Units : uint8_t {
+        kPercentage,  // values are percentages of domain size
+        kIndex,       // values are direct domain indices
+    };
+
+    enum class Domain : uint8_t {
+        kChars,                      // domain indices map to glyph indices
+        // kCharsExcludingSpaces,    // domain indices map to glyph indices (ignoring spaces)
+        // kWords,                   // domain indices map to word indices
+        // kLines,                   // domain indices map to line indices
+    };
+
+    enum class Mode : uint8_t {
+        kAdd,
+        // kSubtract,
+        // kIntersect,
+        // kMin,
+        // kMax,
+        // kDifference,
+    };
+
+    void modulateCoverage(TextAnimator::ModulatorBuffer&) const;
+
+private:
+    RangeSelector(Units, Domain, Mode);
+
+    // Resolves this selector to a range in the coverage buffer index domain.
+    std::tuple<float, float> resolve(size_t domain_size) const;
+
+    const Units  fUnits;
+    const Domain fDomain;
+    const Mode   fMode;
+
+    float        fStart,
+                 fEnd,
+                 fOffset,
+                 fAmount = 100;
+};
+
+} // namespace internal
+} // namespace skottie
+
+#endif // SkottieRangeSelector_DEFINED
diff --git a/modules/skottie/src/text/TextAdapter.cpp b/modules/skottie/src/text/TextAdapter.cpp
index 899778b..993657b 100644
--- a/modules/skottie/src/text/TextAdapter.cpp
+++ b/modules/skottie/src/text/TextAdapter.cpp
@@ -6,6 +6,9 @@
  */
 
 #include "modules/skottie/src/text/TextAdapter.h"
+
+#include "modules/skottie/src/text/RangeSelector.h"
+#include "modules/skottie/src/text/TextAnimator.h"
 #include "modules/sksg/include/SkSGDraw.h"
 #include "modules/sksg/include/SkSGGroup.h"
 #include "modules/sksg/include/SkSGPaint.h"
@@ -14,6 +17,7 @@
 #include "modules/sksg/include/SkSGTransform.h"
 
 namespace skottie {
+namespace internal {
 
 TextAdapter::TextAdapter(sk_sp<sksg::Group> root, bool hasAnimators)
     : fRoot(std::move(root))
@@ -117,29 +121,52 @@
 #endif
 }
 
-void TextAdapter::applyAnimatedProps(const AnimatedProps& props) {
-    // TODO: share this with TransformAdapter2D?
-    auto t = SkMatrix::MakeTrans(props.position.x(), props.position.y());
-    t.preRotate(props.rotation);
-    t.preScale(props.scale, props.scale);
+void TextAdapter::applyAnimators(const std::vector<sk_sp<TextAnimator>>& animators) {
+    if (fFragments.empty()) {
+        return;
+    }
 
-    const auto fc = SkColorSetA(props.fill_color,
-                                SkScalarRoundToInt(props.opacity*SkColorGetA(props.fill_color))),
-               sc = SkColorSetA(props.stroke_color,
-                                SkScalarRoundToInt(props.opacity*SkColorGetA(props.stroke_color)));
+    const auto& txt_val = this->getText();
 
-    for (const auto& rec : fFragments) {
-        rec.fMatrixNode->setMatrix(SkMatrix::Concat(SkMatrix::MakeTrans(rec.fOrigin.x(),
-                                                                        rec.fOrigin.y()),
-                                                    t));
+    // Seed props from the current text value.
+    TextAnimator::AnimatedProps seed_props;
+    seed_props.fill_color   = txt_val.fFillColor;
+    seed_props.stroke_color = txt_val.fStrokeColor;
 
-        if (rec.fFillColorNode) {
-            rec.fFillColorNode->setColor(fc);
-        }
-        if (rec.fStrokeColorNode) {
-            rec.fStrokeColorNode->setColor(sc);
-        }
+    TextAnimator::ModulatorBuffer buf;
+    buf.resize(fFragments.size(), { seed_props, 0 });
+
+    // Apply all animators to the modulator buffer.
+    for (const auto& animator : animators) {
+        animator->modulateProps(buf);
+    }
+
+    // Finally, push all props to their corresponding fragment.
+    for (size_t i = 0; i < fFragments.size(); ++i) {
+        this->pushPropsToFragment(buf[i].props, fFragments[i]);
     }
 }
 
+void TextAdapter::pushPropsToFragment(const TextAnimator::AnimatedProps& props,
+                                      const FragmentRec& rec) const {
+    // TODO: share this with TransformAdapter2D?
+    auto t = SkMatrix::MakeTrans(rec.fOrigin.x() + props.position.x(),
+                                 rec.fOrigin.y() + props.position.y());
+    t.preRotate(props.rotation);
+    t.preScale(props.scale, props.scale);
+    rec.fMatrixNode->setMatrix(t);
+
+    const auto scale_alpha = [](SkColor c, float o) {
+        return SkColorSetA(c, SkScalarRoundToInt(o * SkColorGetA(c)));
+    };
+
+    if (rec.fFillColorNode) {
+        rec.fFillColorNode->setColor(scale_alpha(props.fill_color, props.opacity));
+    }
+    if (rec.fStrokeColorNode) {
+        rec.fStrokeColorNode->setColor(scale_alpha(props.stroke_color, props.opacity));
+    }
+}
+
+} // namespace internal
 } // namespace skottie
diff --git a/modules/skottie/src/text/TextAdapter.h b/modules/skottie/src/text/TextAdapter.h
index 916392b..fb9da69 100644
--- a/modules/skottie/src/text/TextAdapter.h
+++ b/modules/skottie/src/text/TextAdapter.h
@@ -10,6 +10,7 @@
 
 #include "modules/skottie/src/SkottieAdapter.h"
 #include "modules/skottie/src/text/SkottieShaper.h"
+#include "modules/skottie/src/text/TextAnimator.h"
 #include "modules/skottie/src/text/TextValue.h"
 
 #include <vector>
@@ -19,6 +20,7 @@
 } // namespace sksg
 
 namespace skottie {
+namespace internal {
 
 class TextAdapter final : public SkNVRefCnt<TextAdapter> {
 public:
@@ -29,16 +31,7 @@
 
     const sk_sp<sksg::Group>& root() const { return fRoot; }
 
-    struct AnimatedProps {
-        SkPoint   position = { 0, 0 };
-        SkColor fill_color = SK_ColorTRANSPARENT,
-              stroke_color = SK_ColorTRANSPARENT;
-        float      opacity = 1,
-                     scale = 1,
-                  rotation = 0;
-    };
-
-    void applyAnimatedProps(const AnimatedProps&);
+    void applyAnimators(const std::vector<sk_sp<TextAnimator>>&);
 
 private:
     struct FragmentRec;
@@ -47,12 +40,15 @@
 
     void apply();
 
+    void pushPropsToFragment(const TextAnimator::AnimatedProps&, const FragmentRec&) const;
+
     sk_sp<sksg::Group>       fRoot;
     std::vector<FragmentRec> fFragments;
 
     const bool               fHasAnimators;
 };
 
+} // namespace internal
 } // namespace skottie
 
 #endif // SkottieTextAdapter_DEFINED
diff --git a/modules/skottie/src/text/TextAnimator.cpp b/modules/skottie/src/text/TextAnimator.cpp
index c6c4da6..c9a868a 100644
--- a/modules/skottie/src/text/TextAnimator.cpp
+++ b/modules/skottie/src/text/TextAnimator.cpp
@@ -9,7 +9,8 @@
 
 #include "include/core/SkColor.h"
 #include "include/core/SkPoint.h"
-#include "modules/skottie/src/SkottiePriv.h"
+#include "include/private/SkNx.h"
+#include "modules/skottie/src/text/RangeSelector.h"
 #include "modules/skottie/src/text/TextAdapter.h"
 #include "src/utils/SkJSON.h"
 
@@ -56,95 +57,128 @@
  *   ...
  * }
  */
-class TextAnimator final : public SkNVRefCnt<TextAnimator> {
-public:
-    static sk_sp<TextAnimator> Make(const skjson::ObjectValue* janimator,
-                                    const AnimationBuilder* abuilder,
-                                    AnimatorScope* ascope) {
-        if (!janimator) {
-            return nullptr;
-        }
-
-        if (const skjson::ObjectValue* jselector = (*janimator)["s"]) {
-            abuilder->log(Logger::Level::kWarning, jselector, "Unsupported text range selector.");
-        }
-
-        const skjson::ObjectValue* jprops = (*janimator)["a"];
-
-        return jprops
-            ? sk_sp<TextAnimator>(new TextAnimator(*jprops, abuilder, ascope))
-            : nullptr;
+sk_sp<TextAnimator> TextAnimator::Make(const skjson::ObjectValue* janimator,
+                                       const AnimationBuilder* abuilder,
+                                       AnimatorScope* ascope) {
+    if (!janimator) {
+        return nullptr;
     }
 
-    void modulateProps(TextAdapter::AnimatedProps* dst) const {
-        // Transform props compose.
-        if (fHasPosition) {
-            dst->position += fTextProps.position;
-        }
-        if (fHasScale) {
-            dst->scale *= fTextProps.scale;
-        }
-        if (fHasRotation) {
-            dst->rotation += fTextProps.rotation;
-        }
-
-        // Colors and opacity are overridden.
-        if (fHasFillColor) {
-            dst->fill_color = fTextProps.fill_color;
-        }
-        if (fHasStrokeColor) {
-            dst->stroke_color = fTextProps.stroke_color;
-        }
-        if (fHasOpacity) {
-            dst->opacity = fTextProps.opacity;
+    std::vector<sk_sp<RangeSelector>> selectors;
+    if (const skjson::ObjectValue* jselector = (*janimator)["s"]) {
+        // Single range selector for now.
+        if (auto sel = RangeSelector::Make(jselector, abuilder, ascope)) {
+            selectors.reserve(1);
+            selectors.push_back(std::move(sel));
         }
     }
 
-private:
-    TextAnimator(const skjson::ObjectValue& jprops,
-                 const AnimationBuilder* abuilder,
-                 AnimatorScope* ascope) {
-        // It's *probably* OK to capture a raw pointer to this animator, because the lambda
-        // life time is limited to |ascope|, which is also the container for the TextAnimatorList
-        // owning us. But for peace of mind (and future-proofing) let's grab a ref.
-        auto animator = sk_ref_sp(this);
+    const skjson::ObjectValue* jprops = (*janimator)["a"];
 
-        fHasPosition    = abuilder->bindProperty<VectorValue>(jprops["p"], ascope,
-            [animator](const VectorValue& p) {
-                animator->fTextProps.position = ValueTraits<VectorValue>::As<SkPoint>(p);
-            });
-        fHasScale       = abuilder->bindProperty<ScalarValue>(jprops["s"], ascope,
-            [animator](const ScalarValue& s) {
-                // Scale is 100-based.
-                animator->fTextProps.scale = s * 0.01f;
-            });
-        fHasRotation    = abuilder->bindProperty<ScalarValue>(jprops["r"], ascope,
-            [animator](const ScalarValue& r) {
-                animator->fTextProps.rotation = r;
-            });
-        fHasFillColor   = abuilder->bindProperty<VectorValue>(jprops["fc"], ascope,
-            [animator](const VectorValue& fc) {
-                animator->fTextProps.fill_color = ValueTraits<VectorValue>::As<SkColor>(fc);
-            });
-        fHasStrokeColor = abuilder->bindProperty<VectorValue>(jprops["sc"], ascope,
-            [animator](const VectorValue& sc) {
-                animator->fTextProps.stroke_color = ValueTraits<VectorValue>::As<SkColor>(sc);
-            });
-        fHasOpacity     = abuilder->bindProperty<ScalarValue>(jprops["o"], ascope,
-            [animator](const ScalarValue& o) {
-                // Opacity is 100-based.
-                animator->fTextProps.opacity = SkTPin<float>(o * 0.01f, 0, 1);
-            });
+    return jprops
+        ? sk_sp<TextAnimator>(new TextAnimator(std::move(selectors), *jprops, abuilder, ascope))
+        : nullptr;
+}
+
+void TextAnimator::modulateProps(ModulatorBuffer& buf) const {
+    // Coverage is scoped per animator.
+    for (auto& mod : buf) {
+        mod.coverage = 0;
     }
 
-    TextAdapter::AnimatedProps fTextProps;
-    bool                       fHasPosition    : 1,
-                               fHasScale       : 1,
-                               fHasRotation    : 1,
-                               fHasFillColor   : 1,
-                               fHasStrokeColor : 1,
-                               fHasOpacity     : 1;
-};
+    // Accumulate selector coverage.
+    for (const auto& selector : fSelectors) {
+        selector->modulateCoverage(buf);
+    }
+
+    // Modulate animated props.
+    for (auto& mod : buf) {
+        mod.props = this->modulateProps(mod.props, mod.coverage);
+    }
+}
+
+TextAnimator::AnimatedProps TextAnimator::modulateProps(const AnimatedProps& props,
+                                                        float amount) const {
+    auto modulated_props = props;
+
+    // Transform props compose.
+    if (fHasPosition) {
+        modulated_props.position += fTextProps.position * amount;
+    }
+    if (fHasScale) {
+        modulated_props.scale *= 1 + (fTextProps.scale - 1) * amount;
+    }
+    if (fHasRotation) {
+        modulated_props.rotation += fTextProps.rotation * amount;
+    }
+
+    const auto lerp_color = [](SkColor c0, SkColor c1, float t) {
+        const auto c0_4f = SkNx_cast<float>(Sk4b::Load(&c0)),
+                   c1_4f = SkNx_cast<float>(Sk4b::Load(&c1)),
+                    c_4f = c0_4f + (c1_4f - c0_4f) * t;
+
+        SkColor c;
+        SkNx_cast<uint8_t>(Sk4f_round(c_4f)).store(&c);
+        return c;
+    };
+
+    // Colors and opacity are overridden, and use a clamped amount value.
+    const auto clamped_amount = std::max(amount, 0.0f);
+    if (fHasFillColor) {
+        modulated_props.fill_color = lerp_color(props.fill_color,
+                                                fTextProps.fill_color,
+                                                clamped_amount);
+    }
+    if (fHasStrokeColor) {
+        modulated_props.stroke_color = lerp_color(props.stroke_color,
+                                                  fTextProps.stroke_color,
+                                                  clamped_amount);
+    }
+    if (fHasOpacity) {
+        modulated_props.opacity = 1 + (fTextProps.opacity - 1) * clamped_amount;
+    }
+
+    return modulated_props;
+}
+
+TextAnimator::TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
+                           const skjson::ObjectValue& jprops,
+                           const AnimationBuilder* abuilder,
+                           AnimatorScope* ascope)
+    : fSelectors(std::move(selectors)) {
+
+    // It's *probably* OK to capture a raw pointer to this animator, because the lambda
+    // life time is limited to |ascope|, which is also the container for the TextAnimatorList
+    // owning us. But for peace of mind (and future-proofing) let's grab a ref.
+    auto animator = sk_ref_sp(this);
+
+    fHasPosition    = abuilder->bindProperty<VectorValue>(jprops["p"], ascope,
+        [animator](const VectorValue& p) {
+            animator->fTextProps.position = ValueTraits<VectorValue>::As<SkPoint>(p);
+        });
+    fHasScale       = abuilder->bindProperty<ScalarValue>(jprops["s"], ascope,
+        [animator](const ScalarValue& s) {
+            // Scale is 100-based.
+            animator->fTextProps.scale = s * 0.01f;
+        });
+    fHasRotation    = abuilder->bindProperty<ScalarValue>(jprops["r"], ascope,
+        [animator](const ScalarValue& r) {
+            animator->fTextProps.rotation = r;
+        });
+    fHasFillColor   = abuilder->bindProperty<VectorValue>(jprops["fc"], ascope,
+        [animator](const VectorValue& fc) {
+            animator->fTextProps.fill_color = ValueTraits<VectorValue>::As<SkColor>(fc);
+        });
+    fHasStrokeColor = abuilder->bindProperty<VectorValue>(jprops["sc"], ascope,
+        [animator](const VectorValue& sc) {
+            animator->fTextProps.stroke_color = ValueTraits<VectorValue>::As<SkColor>(sc);
+    });
+    fHasOpacity     = abuilder->bindProperty<ScalarValue>(jprops["o"], ascope,
+        [animator](const ScalarValue& o) {
+            // Opacity is 100-based.
+            animator->fTextProps.opacity = SkTPin<float>(o * 0.01f, 0, 1);
+        });
+}
 
 std::unique_ptr<TextAnimatorList> TextAnimatorList::Make(const skjson::ArrayValue& janimators,
                                                          const AnimationBuilder* abuilder,
@@ -182,22 +216,7 @@
     this->INHERITED::onTick(t);
 
     // Then push the final property values to the text adapter.
-    this->applyAnimators();
-}
-
-void TextAnimatorList::applyAnimators() const {
-    const auto& txt_val = fAdapter->getText();
-
-    // Seed props from the current text value.
-    TextAdapter::AnimatedProps modulated_props;
-    modulated_props.fill_color   = txt_val.fFillColor;
-    modulated_props.stroke_color = txt_val.fStrokeColor;
-
-    for (const auto& animator : fAnimators) {
-        animator->modulateProps(&modulated_props);
-    }
-
-    fAdapter->applyAnimatedProps(modulated_props);
+    fAdapter->applyAnimators(fAnimators);
 }
 
 } // namespace internal
diff --git a/modules/skottie/src/text/TextAnimator.h b/modules/skottie/src/text/TextAnimator.h
index 04a8f21..d48a7f7 100644
--- a/modules/skottie/src/text/TextAnimator.h
+++ b/modules/skottie/src/text/TextAnimator.h
@@ -8,24 +8,62 @@
 #ifndef SkottieTextAnimator_DEFINED
 #define SkottieTextAnimator_DEFINED
 
+#include "include/core/SkRefCnt.h"
 #include "modules/skottie/src/SkottieAdapter.h"
+#include "modules/skottie/src/SkottiePriv.h"
 #include "modules/sksg/include/SkSGScene.h"
 
 #include <memory>
 #include <vector>
 
-namespace skjson {
-class ArrayValue;
-}
-
 namespace skottie {
-
-class TextAdapter;
-
 namespace internal {
 
 class AnimationBuilder;
-class TextAnimator;
+class RangeSelector;
+class TextAdapter;
+
+class TextAnimator final : public SkNVRefCnt<TextAnimator> {
+public:
+    static sk_sp<TextAnimator> Make(const skjson::ObjectValue*,
+                                    const AnimationBuilder*,
+                                    AnimatorScope*);
+
+    struct AnimatedProps {
+        SkPoint   position = { 0, 0 };
+        float      opacity = 1,
+                     scale = 1,
+                  rotation = 0;
+        SkColor fill_color = SK_ColorTRANSPARENT,
+              stroke_color = SK_ColorTRANSPARENT;
+    };
+
+    struct AnimatedPropsModulator {
+        AnimatedProps props;     // accumulates properties across *all* animators
+        float         coverage;  // accumulates range selector coverage for a given animator
+    };
+    using ModulatorBuffer = std::vector<AnimatedPropsModulator>;
+
+    void modulateProps(ModulatorBuffer&) const;
+
+private:
+    TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
+                 const skjson::ObjectValue& jprops,
+                 const AnimationBuilder* abuilder,
+                 AnimatorScope* ascope);
+
+    AnimatedProps modulateProps(const AnimatedProps&, float amount) const;
+
+    const std::vector<sk_sp<RangeSelector>> fSelectors;
+
+    AnimatedProps fTextProps;
+    bool          fHasPosition    : 1,
+                  fHasScale       : 1,
+                  fHasRotation    : 1,
+                  fHasFillColor   : 1,
+                  fHasStrokeColor : 1,
+                  fHasOpacity     : 1;
+};
 
 class TextAnimatorList final : public sksg::GroupAnimator {
 public:
@@ -40,8 +78,6 @@
 private:
     TextAnimatorList(sk_sp<TextAdapter>, sksg::AnimatorList&&, std::vector<sk_sp<TextAnimator>>&&);
 
-    void applyAnimators() const;
-
     const std::vector<sk_sp<TextAnimator>> fAnimators;
     const sk_sp<TextAdapter>               fAdapter;
 
diff --git a/public.bzl b/public.bzl
index 2b52e15..7676751 100644
--- a/public.bzl
+++ b/public.bzl
@@ -474,6 +474,8 @@
         "tools/ResourceFactory.h",
         "tools/Resources.cpp",
         "tools/Resources.h",
+        "tools/SkVMBuilders.cpp",
+        "tools/SkVMBuilders.h",
         "tools/UrlDataManager.cpp",
         "tools/UrlDataManager.h",
         "tools/debugger/*.cpp",
diff --git a/resources/SkVMTest.expected b/resources/SkVMTest.expected
index b40149b..f4c80a8 100644
--- a/resources/SkVMTest.expected
+++ b/resources/SkVMTest.expected
@@ -1,6 +1,7 @@
 A8 over A8
+3 registers, 15 instructions:
 r0 = load8 arg(0)
-r1 = splat 3B808081 (0.0039215689)
+r1 = splat 3B808081 (1/255)
 r0 = to_f32 r0
 r0 = mul_f32 r1 r0
 r2 = load8 arg(1)
@@ -10,14 +11,15 @@
 r1 = sub_f32 r1 r0
 r1 = mad_f32 r2 r1 r0
 r2 = splat 437F0000 (255)
-r0 = splat 3F000000 (0.5)
+r0 = splat 3F000000 (1/2)
 r0 = mad_f32 r1 r2 r0
 r0 = to_i32 r0
 store8 arg(1) r0
 
 A8 over G8
+4 registers, 21 instructions:
 r0 = load8 arg(0)
-r1 = splat 3B808081 (0.0039215689)
+r1 = splat 3B808081 (1/255)
 r0 = to_f32 r0
 r0 = mul_f32 r1 r0
 r2 = load8 arg(1)
@@ -33,58 +35,54 @@
 r3 = mad_f32 r1 r0 r3
 r3 = mad_f32 r1 r2 r3
 r2 = splat 437F0000 (255)
-r1 = splat 3F000000 (0.5)
+r1 = splat 3F000000 (1/2)
 r1 = mad_f32 r3 r2 r1
 r1 = to_i32 r1
 store8 arg(1) r1
 
 A8 over RGBA_8888
+6 registers, 37 instructions:
 r0 = load8 arg(0)
-r1 = splat 3B808081 (0.0039215689)
+r1 = splat 3B808081 (1/255)
 r0 = to_f32 r0
 r0 = mul_f32 r1 r0
 r2 = load32 arg(1)
-r3 = splat FF (3.5733111e-43)
-r4 = bit_and r2 r3
+r3 = extract r2 FF
+r3 = to_f32 r3
+r3 = mul_f32 r1 r3
+r4 = extract r2 FF00
 r4 = to_f32 r4
 r4 = mul_f32 r1 r4
-r5 = shr r2 8 (1.1210388e-44)
-r5 = bit_and r5 r3
+r5 = extract r2 FF0000
 r5 = to_f32 r5
 r5 = mul_f32 r1 r5
-r6 = shr r2 10 (2.2420775e-44)
-r6 = bit_and r6 r3
-r6 = to_f32 r6
-r6 = mul_f32 r1 r6
-r2 = shr r2 18 (3.3631163e-44)
+r2 = shr r2 24
 r2 = to_f32 r2
 r2 = mul_f32 r1 r2
 r1 = splat 3F800000 (1)
 r1 = sub_f32 r1 r0
+r3 = mul_f32 r3 r1
 r4 = mul_f32 r4 r1
 r5 = mul_f32 r5 r1
-r6 = mul_f32 r6 r1
 r1 = mad_f32 r2 r1 r0
 r2 = splat 437F0000 (255)
-r0 = splat 3F000000 (0.5)
+r0 = splat 3F000000 (1/2)
+r3 = mad_f32 r3 r2 r0
+r3 = to_i32 r3
 r4 = mad_f32 r4 r2 r0
 r4 = to_i32 r4
 r5 = mad_f32 r5 r2 r0
 r5 = to_i32 r5
-r5 = shl r5 8 (1.1210388e-44)
-r6 = mad_f32 r6 r2 r0
-r6 = to_i32 r6
-r6 = shl r6 10 (2.2420775e-44)
 r0 = mad_f32 r1 r2 r0
 r0 = to_i32 r0
-r0 = shl r0 18 (3.3631163e-44)
-r5 = bit_or r4 r5
-r5 = bit_or r5 r6
-r5 = bit_or r5 r0
-store32 arg(1) r5
+r4 = pack r3 r4 8
+r0 = pack r5 r0 8
+r0 = pack r4 r0 16
+store32 arg(1) r0
 
 G8 over A8
-r0 = splat 3B808081 (0.0039215689)
+3 registers, 12 instructions:
+r0 = splat 3B808081 (1/255)
 r1 = splat 3F800000 (1)
 r2 = load8 arg(1)
 r2 = to_f32 r2
@@ -92,14 +90,15 @@
 r0 = sub_f32 r1 r1
 r0 = mad_f32 r2 r0 r1
 r2 = splat 437F0000 (255)
-r1 = splat 3F000000 (0.5)
+r1 = splat 3F000000 (1/2)
 r1 = mad_f32 r0 r2 r1
 r1 = to_i32 r1
 store8 arg(1) r1
 
 G8 over G8
+4 registers, 21 instructions:
 r0 = load8 arg(0)
-r1 = splat 3B808081 (0.0039215689)
+r1 = splat 3B808081 (1/255)
 r0 = to_f32 r0
 r0 = mul_f32 r1 r0
 r2 = splat 3F800000 (1)
@@ -115,60 +114,56 @@
 r1 = mad_f32 r2 r0 r1
 r1 = mad_f32 r2 r3 r1
 r3 = splat 437F0000 (255)
-r2 = splat 3F000000 (0.5)
+r2 = splat 3F000000 (1/2)
 r2 = mad_f32 r1 r3 r2
 r2 = to_i32 r2
 store8 arg(1) r2
 
 G8 over RGBA_8888
+7 registers, 37 instructions:
 r0 = load8 arg(0)
-r1 = splat 3B808081 (0.0039215689)
+r1 = splat 3B808081 (1/255)
 r0 = to_f32 r0
 r0 = mul_f32 r1 r0
 r2 = splat 3F800000 (1)
 r3 = load32 arg(1)
-r4 = splat FF (3.5733111e-43)
-r5 = bit_and r3 r4
+r4 = extract r3 FF
+r4 = to_f32 r4
+r4 = mul_f32 r1 r4
+r5 = extract r3 FF00
 r5 = to_f32 r5
 r5 = mul_f32 r1 r5
-r6 = shr r3 8 (1.1210388e-44)
-r6 = bit_and r6 r4
+r6 = extract r3 FF0000
 r6 = to_f32 r6
 r6 = mul_f32 r1 r6
-r7 = shr r3 10 (2.2420775e-44)
-r7 = bit_and r7 r4
-r7 = to_f32 r7
-r7 = mul_f32 r1 r7
-r3 = shr r3 18 (3.3631163e-44)
+r3 = shr r3 24
 r3 = to_f32 r3
 r3 = mul_f32 r1 r3
 r1 = sub_f32 r2 r2
+r4 = mad_f32 r4 r1 r0
 r5 = mad_f32 r5 r1 r0
 r6 = mad_f32 r6 r1 r0
-r7 = mad_f32 r7 r1 r0
 r1 = mad_f32 r3 r1 r2
 r3 = splat 437F0000 (255)
-r2 = splat 3F000000 (0.5)
+r2 = splat 3F000000 (1/2)
+r4 = mad_f32 r4 r3 r2
+r4 = to_i32 r4
 r5 = mad_f32 r5 r3 r2
 r5 = to_i32 r5
 r6 = mad_f32 r6 r3 r2
 r6 = to_i32 r6
-r6 = shl r6 8 (1.1210388e-44)
-r7 = mad_f32 r7 r3 r2
-r7 = to_i32 r7
-r7 = shl r7 10 (2.2420775e-44)
 r2 = mad_f32 r1 r3 r2
 r2 = to_i32 r2
-r2 = shl r2 18 (3.3631163e-44)
-r6 = bit_or r5 r6
-r6 = bit_or r6 r7
-r6 = bit_or r6 r2
-store32 arg(1) r6
+r5 = pack r4 r5 8
+r2 = pack r6 r2 8
+r2 = pack r5 r2 16
+store32 arg(1) r2
 
 RGBA_8888 over A8
+3 registers, 16 instructions:
 r0 = load32 arg(0)
-r1 = splat 3B808081 (0.0039215689)
-r0 = shr r0 18 (3.3631163e-44)
+r1 = splat 3B808081 (1/255)
+r0 = shr r0 24
 r0 = to_f32 r0
 r0 = mul_f32 r1 r0
 r2 = load8 arg(1)
@@ -178,103 +173,144 @@
 r1 = sub_f32 r1 r0
 r1 = mad_f32 r2 r1 r0
 r2 = splat 437F0000 (255)
-r0 = splat 3F000000 (0.5)
+r0 = splat 3F000000 (1/2)
 r0 = mad_f32 r1 r2 r0
 r0 = to_i32 r0
 store8 arg(1) r0
 
 RGBA_8888 over G8
+6 registers, 33 instructions:
 r0 = load32 arg(0)
-r1 = splat FF (3.5733111e-43)
-r2 = bit_and r0 r1
-r3 = splat 3B808081 (0.0039215689)
-r2 = to_f32 r2
-r2 = mul_f32 r3 r2
-r4 = shr r0 8 (1.1210388e-44)
-r4 = bit_and r4 r1
-r4 = to_f32 r4
-r4 = mul_f32 r3 r4
-r5 = shr r0 10 (2.2420775e-44)
-r5 = bit_and r5 r1
-r5 = to_f32 r5
-r5 = mul_f32 r3 r5
-r0 = shr r0 18 (3.3631163e-44)
-r0 = to_f32 r0
-r0 = mul_f32 r3 r0
-r1 = load8 arg(1)
+r1 = extract r0 FF
+r2 = splat 3B808081 (1/255)
 r1 = to_f32 r1
-r1 = mul_f32 r3 r1
-r3 = splat 3F800000 (1)
-r3 = sub_f32 r3 r0
-r2 = mad_f32 r1 r3 r2
-r4 = mad_f32 r1 r3 r4
-r3 = mad_f32 r1 r3 r5
-r1 = splat 3E59B3D0 (0.21259999)
-r5 = splat 3F371759 (0.71520001)
+r1 = mul_f32 r2 r1
+r3 = extract r0 FF00
+r3 = to_f32 r3
+r3 = mul_f32 r2 r3
+r4 = extract r0 FF0000
+r4 = to_f32 r4
+r4 = mul_f32 r2 r4
+r0 = shr r0 24
+r0 = to_f32 r0
+r0 = mul_f32 r2 r0
+r5 = load8 arg(1)
+r5 = to_f32 r5
+r5 = mul_f32 r2 r5
+r2 = splat 3F800000 (1)
+r2 = sub_f32 r2 r0
+r1 = mad_f32 r5 r2 r1
+r3 = mad_f32 r5 r2 r3
+r2 = mad_f32 r5 r2 r4
+r5 = splat 3E59B3D0 (0.21259999)
+r4 = splat 3F371759 (0.71520001)
 r0 = splat 3D93DD98 (0.0722)
-r0 = mul_f32 r3 r0
-r0 = mad_f32 r4 r5 r0
-r0 = mad_f32 r2 r1 r0
-r1 = splat 437F0000 (255)
-r2 = splat 3F000000 (0.5)
-r2 = mad_f32 r0 r1 r2
-r2 = to_i32 r2
-store8 arg(1) r2
+r0 = mul_f32 r2 r0
+r0 = mad_f32 r3 r4 r0
+r0 = mad_f32 r1 r5 r0
+r5 = splat 437F0000 (255)
+r1 = splat 3F000000 (1/2)
+r1 = mad_f32 r0 r5 r1
+r1 = to_i32 r1
+store8 arg(1) r1
 
 RGBA_8888 over RGBA_8888
+9 registers, 47 instructions:
 r0 = load32 arg(0)
-r1 = splat FF (3.5733111e-43)
-r2 = bit_and r0 r1
-r3 = splat 3B808081 (0.0039215689)
-r2 = to_f32 r2
-r2 = mul_f32 r3 r2
-r4 = shr r0 8 (1.1210388e-44)
-r4 = bit_and r4 r1
+r1 = extract r0 FF
+r2 = splat 3B808081 (1/255)
+r1 = to_f32 r1
+r1 = mul_f32 r2 r1
+r3 = extract r0 FF00
+r3 = to_f32 r3
+r3 = mul_f32 r2 r3
+r4 = extract r0 FF0000
 r4 = to_f32 r4
-r4 = mul_f32 r3 r4
-r5 = shr r0 10 (2.2420775e-44)
-r5 = bit_and r5 r1
-r5 = to_f32 r5
-r5 = mul_f32 r3 r5
-r0 = shr r0 18 (3.3631163e-44)
+r4 = mul_f32 r2 r4
+r0 = shr r0 24
 r0 = to_f32 r0
-r0 = mul_f32 r3 r0
-r6 = load32 arg(1)
-r7 = bit_and r6 r1
-r7 = to_f32 r7
-r7 = mul_f32 r3 r7
-r8 = shr r6 8 (1.1210388e-44)
-r8 = bit_and r8 r1
-r8 = to_f32 r8
-r8 = mul_f32 r3 r8
-r9 = shr r6 10 (2.2420775e-44)
-r9 = bit_and r9 r1
-r9 = to_f32 r9
-r9 = mul_f32 r3 r9
-r6 = shr r6 18 (3.3631163e-44)
+r0 = mul_f32 r2 r0
+r5 = load32 arg(1)
+r6 = extract r5 FF
 r6 = to_f32 r6
-r6 = mul_f32 r3 r6
-r3 = splat 3F800000 (1)
-r3 = sub_f32 r3 r0
-r7 = mad_f32 r7 r3 r2
-r8 = mad_f32 r8 r3 r4
-r9 = mad_f32 r9 r3 r5
-r3 = mad_f32 r6 r3 r0
-r6 = splat 437F0000 (255)
-r0 = splat 3F000000 (0.5)
-r7 = mad_f32 r7 r6 r0
+r6 = mul_f32 r2 r6
+r7 = extract r5 FF00
+r7 = to_f32 r7
+r7 = mul_f32 r2 r7
+r8 = extract r5 FF0000
+r8 = to_f32 r8
+r8 = mul_f32 r2 r8
+r5 = shr r5 24
+r5 = to_f32 r5
+r5 = mul_f32 r2 r5
+r2 = splat 3F800000 (1)
+r2 = sub_f32 r2 r0
+r6 = mad_f32 r6 r2 r1
+r7 = mad_f32 r7 r2 r3
+r8 = mad_f32 r8 r2 r4
+r2 = mad_f32 r5 r2 r0
+r5 = splat 437F0000 (255)
+r0 = splat 3F000000 (1/2)
+r6 = mad_f32 r6 r5 r0
+r6 = to_i32 r6
+r7 = mad_f32 r7 r5 r0
 r7 = to_i32 r7
-r8 = mad_f32 r8 r6 r0
+r8 = mad_f32 r8 r5 r0
 r8 = to_i32 r8
-r8 = shl r8 8 (1.1210388e-44)
-r9 = mad_f32 r9 r6 r0
-r9 = to_i32 r9
-r9 = shl r9 10 (2.2420775e-44)
-r0 = mad_f32 r3 r6 r0
+r0 = mad_f32 r2 r5 r0
 r0 = to_i32 r0
-r0 = shl r0 18 (3.3631163e-44)
-r8 = bit_or r7 r8
-r8 = bit_or r8 r9
-r8 = bit_or r8 r0
+r7 = pack r6 r7 8
+r0 = pack r8 r0 8
+r0 = pack r7 r0 16
+store32 arg(1) r0
+
+I32 8888 over 8888
+9 registers, 24 instructions:
+r0 = load32 arg(0)
+r1 = extract r0 FF
+r2 = extract r0 FF00
+r3 = extract r0 FF0000
+r0 = shr r0 24
+r4 = load32 arg(1)
+r5 = extract r4 FF
+r6 = extract r4 FF00
+r7 = extract r4 FF0000
+r4 = shr r4 24
+r8 = splat FF (3.5733111e-43)
+r8 = sub_i32 r8 r0
+r5 = mul_unorm8 r5 r8
+r5 = add_i32 r1 r5
+r6 = mul_unorm8 r6 r8
+r6 = add_i32 r2 r6
+r7 = mul_unorm8 r7 r8
+r7 = add_i32 r3 r7
+r8 = mul_unorm8 r4 r8
+r8 = add_i32 r0 r8
+r6 = pack r5 r6 8
+r8 = pack r7 r8 8
+r8 = pack r6 r8 16
 store32 arg(1) r8
 
+I32 (SWAR) 8888 over 8888
+6 registers, 20 instructions:
+r0 = load32 arg(0)
+r1 = extract r0 FF00FF
+r0 = extract r0 FF00FF00
+r2 = load32 arg(1)
+r3 = extract r2 FF00FF
+r2 = extract r2 FF00FF00
+r4 = splat FF (3.5733111e-43)
+r5 = shr r0 16
+r5 = sub_i32 r4 r5
+r4 = splat FF00FF (2.3418409e-38)
+r3 = mul_i32 r3 r5
+r3 = add_i32 r3 r4
+r3 = extract r3 FF00FF00
+r3 = add_i32 r1 r3
+r5 = mul_i32 r2 r5
+r5 = add_i32 r5 r4
+r5 = extract r5 FF00FF00
+r5 = add_i32 r0 r5
+r5 = pack r3 r5 8
+store32 arg(1) r5
+
diff --git a/resources/skottie/skottie-text-animator-2.json b/resources/skottie/skottie-text-animator-2.json
new file mode 100644
index 0000000..62b6b88
--- /dev/null
+++ b/resources/skottie/skottie-text-animator-2.json
@@ -0,0 +1 @@
+{"v":"5.5.2","fr":60,"ip":0,"op":300,"w":500,"h":500,"nm":"range selectors","ddd":0,"assets":[],"fonts":{"list":[{"fName":"Roboto-Black","fFamily":"Roboto","fStyle":"Black","ascent":79.0488770231605}]},"layers":[{"ddd":0,"ind":1,"ty":5,"nm":"Foo Bar Baz","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[248,125.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"sz":[496,88],"ps":[-248,-110.5],"s":72,"f":"Roboto-Black","t":"Foo Bar Baz","j":2,"tr":-100,"lh":86.4,"ls":0,"fc":[0.973,0,0]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"e":{"a":0,"k":20,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-20]},{"t":299,"s":[100]}],"ix":3},"r":1},"a":{"p":{"a":0,"k":[0,50,0],"ix":2}}}]},"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":5,"nm":"Foo Bar Baz 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[248,125.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"sz":[496,88],"ps":[-248,-110.5],"s":72,"f":"Roboto-Black","t":"Foo Bar Baz","j":2,"tr":-100,"lh":86.4,"ls":0,"fc":[0.973,0,0]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":-100,"ix":4},"b":1,"rn":0,"sh":1,"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[12]},{"t":299,"s":[0]}],"ix":5},"r":2},"a":{"s":{"a":0,"k":[50,50,100],"ix":3}}},{"nm":"Animator 2","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"r":1},"a":{"p":{"a":0,"k":[0,147,0],"ix":2},"fc":{"a":0,"k":[0,1,0,1],"ix":12}}}]},"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":5,"nm":"Foo Bar Baz 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[248,125.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"sz":[496,88],"ps":[-248,-110.5],"s":72,"f":"Roboto-Black","t":"Foo Bar Baz","j":2,"tr":-100,"lh":86.4,"ls":0,"fc":[0.973,1,0]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":60,"ix":4},"b":1,"rn":0,"sh":1,"s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[50]},{"t":299,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[50]},{"t":299,"s":[100]}],"ix":2},"r":1},"a":{"fc":{"a":0,"k":[0,0,1,1],"ix":12}}},{"nm":"Animator 2","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"r":1},"a":{"p":{"a":0,"k":[0,325,0],"ix":2}}},{"nm":"Animator 3","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"r":1},"a":{"o":{"a":0,"k":50,"ix":9}}}]},"ip":0,"op":300,"st":0,"bm":0}],"markers":[],"chars":[{"ch":"F","size":72,"style":"Black","w":54.69,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[50.146,-41.504],[22.51,-41.504],[22.51,-57.861],[52.93,-57.861],[52.93,-71.094],[5.371,-71.094],[5.371,0],[22.51,0],[22.51,-28.32],[50.146,-28.32]],"c":true},"ix":2},"nm":"F","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"F","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Roboto"},{"ch":"o","size":72,"style":"Black","w":56.15,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-4.558,-4.508],[-7.52,0],[-4.574,4.834],[0,8.236],[0,0],[4.557,4.9],[7.91,0],[3.841,-2.213],[2.051,-4.118],[0,-5.273]],"o":[[0.358,7.715],[4.557,4.509],[7.812,0],[4.573,-4.834],[0,0],[0,-8.43],[-4.558,-4.899],[-5.176,0],[-3.841,2.214],[-2.051,4.118],[0,0]],"v":[[2.588,-24.121],[9.961,-5.786],[28.076,0.977],[46.655,-6.274],[53.516,-25.879],[53.516,-26.465],[46.68,-46.46],[27.979,-53.809],[14.453,-50.488],[5.615,-40.991],[2.539,-26.904]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-5.502,0],[0,-9.473],[0,0],[5.859,0],[1.53,2.279],[0,4.883]],"o":[[0.455,-8.17],[6.055,0],[0,0],[-0.13,9.017],[-2.995,0],[-1.53,-2.278],[0,0]],"v":[[19.043,-28.857],[27.979,-41.113],[37.061,-26.904],[37.061,-25.244],[28.076,-11.719],[21.289,-15.137],[18.994,-25.879]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"o","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Roboto"},{"ch":" ","size":72,"style":"Black","w":24.9,"data":{},"fFamily":"Roboto"},{"ch":"B","size":72,"style":"Black","w":64.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-4.411,3.5],[0,6.966],[1.985,2.67],[3.711,0.977],[-1.693,2.605],[0,3.679],[4.768,3.353],[9.147,0],[0,0],[0,0]],"o":[[8.659,-0.065],[4.411,-3.499],[0,-3.841],[-1.986,-2.669],[3.288,-1.237],[1.692,-2.604],[0,-6.38],[-4.769,-3.352],[0,0],[0,0],[0,0]],"v":[[34.229,0],[53.833,-5.347],[60.449,-21.045],[57.471,-30.811],[48.926,-36.279],[56.396,-42.041],[58.936,-51.465],[51.782,-66.064],[30.908,-71.094],[5.371,-71.094],[5.371,0]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,-5.924],[1.66,-1.416],[3.092,0],[0,0],[0,0]],"o":[[5.891,0.033],[0,2.572],[-1.66,1.416],[0,0],[0,0],[0,0]],"v":[[34.521,-30.225],[43.359,-21.289],[40.869,-15.308],[33.74,-13.184],[22.51,-13.184],[22.51,-30.225]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-1.726,-1.322],[0,-3.004],[1.595,-1.338],[3.743,-0.032],[0,0]],"o":[[0,0],[3.841,0],[1.725,1.322],[0,2.612],[-1.595,1.339],[0,0],[0,0]],"v":[[22.51,-57.91],[30.908,-57.91],[39.258,-55.927],[41.846,-49.438],[39.453,-43.512],[31.445,-41.455],[22.51,-41.455]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"B","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Roboto"},{"ch":"a","size":72,"style":"Black","w":53.08,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.065,4.851],[0,0],[3.906,3.288],[6.738,0],[4.346,-3.186],[0,-5.104],[0,0],[-3.906,0],[0,-4.874],[0,0],[0,0],[4.231,-2.93],[0,-5.729],[-3.484,-3.011],[-4.98,0],[-3.027,3.809],[-0.586,-1.074]],"o":[[0,0],[-1.433,-2.571],[0,0],[-0.098,-5.729],[-3.906,-3.288],[-6.738,0],[-4.346,3.186],[0,0],[0,-3.86],[4.199,0],[0,0],[0,0],[-8.073,0],[-4.232,2.93],[0,4.558],[3.483,3.011],[5.436,0],[0.455,2.084],[0,0]],"v":[[50.83,0],[50.83,-0.83],[48.584,-11.963],[48.584,-35.352],[42.578,-48.877],[26.611,-53.809],[9.985,-49.029],[3.467,-36.593],[19.922,-36.593],[25.781,-42.383],[32.08,-35.072],[32.08,-32.275],[27.002,-32.275],[8.545,-27.881],[2.197,-14.893],[7.422,-3.54],[20.117,0.977],[32.812,-4.736],[34.375,0]],"c":true},"ix":2},"nm":"a","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.009,0.928],[0,1.498],[-5.73,0],[0,0],[0,0],[1.35,-0.879],[2.051,0]],"o":[[-1.009,-0.928],[0,-5.143],[0,0],[0,0],[-0.586,1.107],[-1.351,0.879],[-1.562,0]],"v":[[20.215,-12.134],[18.701,-15.771],[27.295,-23.486],[32.08,-23.486],[32.08,-15.039],[29.175,-12.061],[24.072,-10.742]],"c":true},"ix":2},"nm":"a","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"a","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Roboto"},{"ch":"r","size":72,"style":"Black","w":37.89,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.758,0],[2.766,-5.176],[0,0],[0,0],[0,0],[0,0],[0,0],[-5.176,0],[0,0]],"o":[[-1.302,-0.391],[-4.948,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1.465,-3.255],[0,0],[0,0]],"v":[[36.865,-53.223],[32.275,-53.809],[20.703,-46.045],[20.166,-52.832],[4.736,-52.832],[4.736,0],[21.191,0],[21.191,-33.447],[31.152,-38.33],[36.572,-37.939]],"c":true},"ix":2},"nm":"r","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"r","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Roboto"},{"ch":"z","size":72,"style":"Black","w":51.46,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[47.559,-43.945],[47.559,-52.832],[4.248,-52.832],[4.248,-40.137],[26.66,-40.137],[3.223,-9.18],[3.223,0],[48.096,0],[48.096,-12.695],[24.023,-12.695]],"c":true},"ix":2},"nm":"z","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"z","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Roboto"}]}
\ No newline at end of file
diff --git a/src/core/SkVM.cpp b/src/core/SkVM.cpp
index fcf1246..efa7366 100644
--- a/src/core/SkVM.cpp
+++ b/src/core/SkVM.cpp
@@ -9,12 +9,12 @@
 #include "src/core/SkOpts.h"
 #include "src/core/SkVM.h"
 #include <string.h>
+#if defined(SK_BUILD_FOR_WIN)
+    #include <intrin.h>
+#endif
 
 namespace skvm {
 
-    // We reserve the last ID as a sentinel meaning none, n/a, null, nil, etc.
-    static const ID NA = ~0;
-
     Program::Program(std::vector<Instruction> instructions, int regs)
         : fInstructions(std::move(instructions))
         , fRegs(regs)
@@ -94,14 +94,11 @@
                 inst.op,
                 lookup_register(id),
                 lookup_register(inst.x),
-                lookup_register(inst.y),
+               {lookup_register(inst.y)},
                {lookup_register(inst.z)},
             };
-            // If the z argument is the N/A sentinel, copy in the immediate instead.
-            // (No Op uses both 3 arguments and an immediate.)
-            if (inst.z == NA) {
-                pinst.z.imm = inst.imm;
-            }
+            if (inst.y == NA) { pinst.y.imm = inst.immy; }
+            if (inst.z == NA) { pinst.z.imm = inst.immz; }
             program.push_back(pinst);
         }
 
@@ -111,19 +108,8 @@
     // Most instructions produce a value and return it by ID,
     // the value-producing instruction's own index in the program vector.
 
-    ID Builder::push(Op op, ID x=NA, ID y=NA, ID z=NA, int imm=0) {
-        Instruction inst{op, /*life=*/NA, x, y, z, imm};
-
-        // Simple peepholes that come up fairly often.
-
-        auto is_zero = [&](ID id) {
-            return fProgram[id].op  == Op::splat
-                && fProgram[id].imm == 0;
-        };
-
-        // x*y+0 --> x*y
-        if (op == Op::mad_f32 && is_zero(z)) { inst = { Op::mul_f32, NA, x,y,NA, 0 }; }
-
+    ID Builder::push(Op op, ID x, ID y, ID z, int immy, int immz) {
+        Instruction inst{op, /*life=*/NA, x, y, z, immy, immz};
 
         // Basic common subexpression elimination:
         // if we've already seen this exact Instruction, use it instead of creating a new one.
@@ -138,6 +124,11 @@
         return id;
     }
 
+    bool Builder::isZero(ID id) const {
+        return fProgram[id].op   == Op::splat
+            && fProgram[id].immy == 0;
+    }
+
     Arg Builder::arg(int ix) { return {ix}; }
 
     void Builder::store8 (Arg ptr, I32 val) { (void)this->push(Op::store8 , val.id,NA,NA, ptr.ix); }
@@ -154,11 +145,16 @@
         return {this->push(Op::splat, NA,NA,NA, bits)};
     }
 
-    F32 Builder::add(F32 x, F32 y       ) { return {this->push(Op::add_f32, x.id, y.id      )}; }
-    F32 Builder::sub(F32 x, F32 y       ) { return {this->push(Op::sub_f32, x.id, y.id      )}; }
-    F32 Builder::mul(F32 x, F32 y       ) { return {this->push(Op::mul_f32, x.id, y.id      )}; }
-    F32 Builder::div(F32 x, F32 y       ) { return {this->push(Op::div_f32, x.id, y.id      )}; }
-    F32 Builder::mad(F32 x, F32 y, F32 z) { return {this->push(Op::mad_f32, x.id, y.id, z.id)}; }
+    F32 Builder::add(F32 x, F32 y       ) { return {this->push(Op::add_f32, x.id, y.id)}; }
+    F32 Builder::sub(F32 x, F32 y       ) { return {this->push(Op::sub_f32, x.id, y.id)}; }
+    F32 Builder::mul(F32 x, F32 y       ) { return {this->push(Op::mul_f32, x.id, y.id)}; }
+    F32 Builder::div(F32 x, F32 y       ) { return {this->push(Op::div_f32, x.id, y.id)}; }
+    F32 Builder::mad(F32 x, F32 y, F32 z) {
+        if (this->isZero(z.id)) {
+            return this->mul(x,y);
+        }
+        return {this->push(Op::mad_f32, x.id, y.id, z.id)};
+    }
 
     I32 Builder::add(I32 x, I32 y) { return {this->push(Op::add_i32, x.id, y.id)}; }
     I32 Builder::sub(I32 x, I32 y) { return {this->push(Op::sub_i32, x.id, y.id)}; }
@@ -172,12 +168,35 @@
     I32 Builder::shr(I32 x, int bits) { return {this->push(Op::shr, x.id,NA,NA, bits)}; }
     I32 Builder::sra(I32 x, int bits) { return {this->push(Op::sra, x.id,NA,NA, bits)}; }
 
+    I32 Builder::mul_unorm8(I32 x, I32 y) { return {this->push(Op::mul_unorm8, x.id, y.id)}; }
+
+    I32 Builder::extract(I32 x, int mask) {
+        SkASSERT(mask != 0);
+    #if defined(SK_BUILD_FOR_WIN)
+        unsigned long shift;
+        _BitScanForward(&shift, mask);
+    #else
+        const int shift = __builtin_ctz(mask);
+    #endif
+        if ((unsigned)mask == (~0u << shift)) {
+            return this->shr(x, shift);
+        }
+        return {this->push(Op::extract, x.id,NA,NA, mask, shift)};
+    }
+
+    I32 Builder::pack(I32 x, I32 y, int bits) {
+        return {this->push(Op::pack, x.id,y.id,NA, 0,bits)};
+    }
+
     F32 Builder::to_f32(I32 x) { return {this->push(Op::to_f32, x.id)}; }
     I32 Builder::to_i32(F32 x) { return {this->push(Op::to_i32, x.id)}; }
 
     // ~~~~ Program::dump() and co. ~~~~ //
 
-    struct Reg { ID id; };
+    struct R { ID id; };
+    struct Shift { int bits; };
+    struct Mask  { int bits; };
+    struct Splat { int bits; };
 
     static void write(SkWStream* o, const char* s) {
         o->writeText(s);
@@ -188,17 +207,34 @@
         o->writeDecAsText(a.ix);
         write(o, ")");
     }
-    static void write(SkWStream* o, Reg r) {
+    static void write(SkWStream* o, R r) {
         write(o, "r");
         o->writeDecAsText(r.id);
     }
-
-    static void write(SkWStream* o, int bits) {
+    static void write(SkWStream* o, Shift s) {
+        o->writeDecAsText(s.bits);
+    }
+    static void write(SkWStream* o, Mask m) {
+        o->writeHexAsText(m.bits);
+    }
+    static void write(SkWStream* o, Splat s) {
+        o->writeHexAsText(s.bits);
         float f;
-        memcpy(&f, &bits, 4);
-        o->writeHexAsText(bits);
+        memcpy(&f, &s.bits, 4);
         write(o, " (");
-        o->writeScalarAsText(f);
+
+        // It's friendlier to print floats that represent 1/K as fractions.
+        const int d = (int)(1.0f/f);
+
+        if (f < 1.0f && f == 1.0f/d) {
+            write(o, "1/");
+            o->writeDecAsText(d);
+        } else if (f < 1.0f && f == 1.0f/(d+1)) {
+            write(o, "1/");
+            o->writeDecAsText(d+1);
+        } else {
+            o->writeScalarAsText(f);
+        }
         write(o, ")");
     }
 
@@ -210,41 +246,50 @@
     }
 
     void Program::dump(SkWStream* o) const {
+        o->writeDecAsText(fRegs);
+        o->writeText(" registers, ");
+        o->writeDecAsText(fInstructions.size());
+        o->writeText(" instructions:\n");
         for (const Instruction& inst : fInstructions) {
             Op  op = inst.op;
             ID   d = inst.d,
-                 x = inst.x,
-                 y = inst.y;
-            auto z = inst.z;
+                 x = inst.x;
+            auto y = inst.y,
+                 z = inst.z;
             switch (op) {
-                case Op::store8:  write(o, "store8" , Arg{z.imm}, Reg{x}); break;
-                case Op::store32: write(o, "store32", Arg{z.imm}, Reg{x}); break;
+                case Op::store8:  write(o, "store8" , Arg{y.imm}, R{x}); break;
+                case Op::store32: write(o, "store32", Arg{y.imm}, R{x}); break;
 
-                case Op::load8:  write(o, Reg{d}, "= load8" , Arg{z.imm}); break;
-                case Op::load32: write(o, Reg{d}, "= load32", Arg{z.imm}); break;
+                case Op::load8:  write(o, R{d}, "= load8" , Arg{y.imm}); break;
+                case Op::load32: write(o, R{d}, "= load32", Arg{y.imm}); break;
 
-                case Op::splat:  write(o, Reg{d}, "= splat", z.imm); break;
+                case Op::splat:  write(o, R{d}, "= splat", Splat{y.imm}); break;
 
-                case Op::add_f32: write(o, Reg{d}, "= add_f32", Reg{x}, Reg{y}           ); break;
-                case Op::sub_f32: write(o, Reg{d}, "= sub_f32", Reg{x}, Reg{y}           ); break;
-                case Op::mul_f32: write(o, Reg{d}, "= mul_f32", Reg{x}, Reg{y}           ); break;
-                case Op::div_f32: write(o, Reg{d}, "= div_f32", Reg{x}, Reg{y}           ); break;
-                case Op::mad_f32: write(o, Reg{d}, "= mad_f32", Reg{x}, Reg{y}, Reg{z.id}); break;
+                case Op::add_f32: write(o, R{d}, "= add_f32", R{x}, R{y.id}           ); break;
+                case Op::sub_f32: write(o, R{d}, "= sub_f32", R{x}, R{y.id}           ); break;
+                case Op::mul_f32: write(o, R{d}, "= mul_f32", R{x}, R{y.id}           ); break;
+                case Op::div_f32: write(o, R{d}, "= div_f32", R{x}, R{y.id}           ); break;
+                case Op::mad_f32: write(o, R{d}, "= mad_f32", R{x}, R{y.id}, R{z.id}); break;
 
-                case Op::add_i32: write(o, Reg{d}, "= add_i32", Reg{x}, Reg{y}); break;
-                case Op::sub_i32: write(o, Reg{d}, "= sub_i32", Reg{x}, Reg{y}); break;
-                case Op::mul_i32: write(o, Reg{d}, "= mul_i32", Reg{x}, Reg{y}); break;
+                case Op::add_i32: write(o, R{d}, "= add_i32", R{x}, R{y.id}); break;
+                case Op::sub_i32: write(o, R{d}, "= sub_i32", R{x}, R{y.id}); break;
+                case Op::mul_i32: write(o, R{d}, "= mul_i32", R{x}, R{y.id}); break;
 
-                case Op::bit_and: write(o, Reg{d}, "= bit_and", Reg{x}, Reg{y}); break;
-                case Op::bit_or : write(o, Reg{d}, "= bit_or" , Reg{x}, Reg{y}); break;
-                case Op::bit_xor: write(o, Reg{d}, "= bit_xor", Reg{x}, Reg{y}); break;
+                case Op::bit_and: write(o, R{d}, "= bit_and", R{x}, R{y.id}); break;
+                case Op::bit_or : write(o, R{d}, "= bit_or" , R{x}, R{y.id}); break;
+                case Op::bit_xor: write(o, R{d}, "= bit_xor", R{x}, R{y.id}); break;
 
-                case Op::shl: write(o, Reg{d}, "= shl", Reg{x}, z.imm); break;
-                case Op::shr: write(o, Reg{d}, "= shr", Reg{x}, z.imm); break;
-                case Op::sra: write(o, Reg{d}, "= sra", Reg{x}, z.imm); break;
+                case Op::shl: write(o, R{d}, "= shl", R{x}, Shift{y.imm}); break;
+                case Op::shr: write(o, R{d}, "= shr", R{x}, Shift{y.imm}); break;
+                case Op::sra: write(o, R{d}, "= sra", R{x}, Shift{y.imm}); break;
 
-                case Op::to_f32: write(o, Reg{d}, "= to_f32", Reg{x}); break;
-                case Op::to_i32: write(o, Reg{d}, "= to_i32", Reg{x}); break;
+                case Op::mul_unorm8: write(o, R{d}, "= mul_unorm8", R{x}, R{y.id}); break;
+
+                case Op::extract: write(o, R{d}, "= extract", R{x}, Mask{y.imm}); break;
+                case Op::pack: write(o, R{d}, "= pack", R{x}, R{y.id}, Shift{z.imm}); break;
+
+                case Op::to_f32: write(o, R{d}, "= to_f32", R{x}); break;
+                case Op::to_i32: write(o, R{d}, "= to_i32", R{x}); break;
             }
             write(o, "\n");
         }
diff --git a/src/core/SkVM.h b/src/core/SkVM.h
index 0069140..a392049 100644
--- a/src/core/SkVM.h
+++ b/src/core/SkVM.h
@@ -23,6 +23,9 @@
         add_i32, sub_i32, mul_i32,
         bit_and, bit_or, bit_xor,
         shl, shr, sra,
+        mul_unorm8,
+        extract,
+        pack,
         to_f32, to_i32,
     };
 
@@ -30,10 +33,10 @@
 
     class Program {
     public:
-        struct Instruction {   // d = op(x,y, z.id/z.imm)
+        struct Instruction {   // d = op(x, y.id/y.imm, z.id/z.imm)
             Op op;
-            ID d,x,y;
-            union { ID id; int imm; } z;
+            ID d,x;
+            union { ID id; int imm; } y,z;
         };
 
         Program(std::vector<Instruction>, int regs);
@@ -92,15 +95,36 @@
         I32 shr(I32 x, int bits);
         I32 sra(I32 x, int bits);
 
+        I32 mul_unorm8(I32 x, I32 y);   // (x*y+255)/256, approximating (x*y+127)/255.
+
+        // (x & mask) >> k, where k is the lowest set bit of mask. E.g.
+        //    extract(x, 0xff)   == (x & 0xff)
+        //    extract(x, 0xff00) == (x & 0xff00) >> 8
+        //
+        //    extract(x, 0x00ff00ff) == (x & 0x00ff00ff)
+        //    extract(x, 0xff00ff00) == (x & 0xff00ff00) >> 8
+        //
+        //    extract(x, 0x003ff) == (x & 0x003ff)
+        //    extract(x, 0xffc00) == (x & 0xffc00) >> 10
+        I32 extract(I32 x, int mask);
+
+        // Interlace bits from x and y as if x | (y << bits),
+        // assuming no bits from x and (y << bits) collide with each other, (x & (y << bits)) == 0.
+        // (This allows implementation with SSE punpckl?? or NEON vzip.?? instructions.)
+        I32 pack(I32 x, I32 y, int bits);
+
         F32 to_f32(I32 x);
         I32 to_i32(F32 x);
 
     private:
+        // We reserve the last ID as a sentinel meaning none, n/a, null, nil, etc.
+        static const ID NA = ~0;
+
         struct Instruction {
-            Op  op;      // v* = op(x,y,z,imm), where * == index of this Instruction.
-            ID  life;    // ID of last instruction using this instruction's result.
-            ID  x,y,z;   // Enough arguments for mad().
-            int imm;     // Immediate bit pattern, shift count, or argument index.
+            Op  op;         // v* = op(x,y,z,imm), where * == index of this Instruction.
+            ID  life;       // ID of last instruction using this instruction's result.
+            ID  x,y,z;      // Enough arguments for mad().
+            int immy,immz;  // Immediate bit patterns, shift counts, argument indexes.
 
             bool operator==(const Instruction& o) const {
                 return op   == o.op
@@ -108,7 +132,8 @@
                     && x    == o.x
                     && y    == o.y
                     && z    == o.z
-                    && imm  == o.imm;
+                    && immy == o.immy
+                    && immz == o.immz;
             }
         };
 
@@ -123,14 +148,16 @@
                      ^ Hash(inst.x)
                      ^ Hash(inst.y)
                      ^ Hash(inst.z)
-                     ^ Hash(inst.imm);
+                     ^ Hash(inst.immy)
+                     ^ Hash(inst.immz);
             }
         };
 
+        ID push(Op, ID x, ID y=NA, ID z=NA, int immy=0, int immz=0);
+        bool isZero(ID) const;
+
         std::unordered_map<Instruction, ID, InstructionHash> fIndex;
         std::vector<Instruction>                             fProgram;
-
-        ID push(Op, ID, ID, ID, int);
     };
 
     // TODO: comparison operations, if_then_else
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index d6bc93c..054456f 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -392,6 +392,49 @@
     return this->createBackendTexture(width, height, format, mipMapped, renderable);
 }
 
+GrBackendTexture GrContext::createBackendTexture(int width, int height,
+                                                 GrBackendFormat backendFormat,
+                                                 const SkColor4f& color,
+                                                 GrMipMapped mipMapped,
+                                                 GrRenderable renderable) {
+    if (!this->asDirectContext()) {
+        return GrBackendTexture();
+    }
+
+    if (this->abandoned()) {
+        return GrBackendTexture();
+    }
+
+    if (!backendFormat.isValid()) {
+        return GrBackendTexture();
+    }
+
+    return fGpu->createBackendTexture(width, height, backendFormat,
+                                      mipMapped, renderable,
+                                      nullptr, 0, color);
+}
+
+GrBackendTexture GrContext::createBackendTexture(int width, int height,
+                                                 SkColorType colorType,
+                                                 const SkColor4f& color,
+                                                 GrMipMapped mipMapped,
+                                                 GrRenderable renderable) {
+    if (!this->asDirectContext()) {
+        return GrBackendTexture();
+    }
+
+    if (this->abandoned()) {
+        return GrBackendTexture();
+    }
+
+    GrBackendFormat format = this->caps()->getBackendFormatFromColorType(colorType);
+    if (!format.isValid()) {
+        return GrBackendTexture();
+    }
+
+    return this->createBackendTexture(width, height, format, color, mipMapped, renderable);
+}
+
 void GrContext::deleteBackendTexture(GrBackendTexture backendTex) {
     if (this->abandoned() || !backendTex.isValid()) {
         return;
diff --git a/src/gpu/GrContextPriv.cpp b/src/gpu/GrContextPriv.cpp
index 2ab6822..6a482d1 100644
--- a/src/gpu/GrContextPriv.cpp
+++ b/src/gpu/GrContextPriv.cpp
@@ -760,48 +760,3 @@
     fContext->drawingManager()->testingOnly_removeOnFlushCallbackObject(cb);
 }
 #endif
-
-//////////////////////////////////////////////////////////////////////////////
-GrBackendTexture GrContextPriv::createBackendTexture(int width, int height,
-                                                     GrBackendFormat backendFormat,
-                                                     const SkColor4f& color,
-                                                     GrMipMapped mipMapped,
-                                                     GrRenderable renderable) {
-    if (!fContext->asDirectContext()) {
-        return GrBackendTexture();
-    }
-
-    if (this->abandoned()) {
-        return GrBackendTexture();
-    }
-
-    if (!backendFormat.isValid()) {
-        return GrBackendTexture();
-    }
-
-    GrGpu* gpu = fContext->fGpu.get();
-
-    return gpu->createBackendTexture(width, height, backendFormat,
-                                     mipMapped, renderable, nullptr, 0, color);
-}
-
-GrBackendTexture GrContextPriv::createBackendTexture(int width, int height,
-                                                     SkColorType colorType,
-                                                     const SkColor4f& color,
-                                                     GrMipMapped mipMapped,
-                                                     GrRenderable renderable) {
-    if (!fContext->asDirectContext()) {
-        return GrBackendTexture();
-    }
-
-    if (this->abandoned()) {
-        return GrBackendTexture();
-    }
-
-    GrBackendFormat format = fContext->caps()->getBackendFormatFromColorType(colorType);
-    if (!format.isValid()) {
-        return GrBackendTexture();
-    }
-
-    return this->createBackendTexture(width, height, format, color, mipMapped, renderable);
-}
diff --git a/src/gpu/GrContextPriv.h b/src/gpu/GrContextPriv.h
index 5dbb038..86af1f3 100644
--- a/src/gpu/GrContextPriv.h
+++ b/src/gpu/GrContextPriv.h
@@ -292,20 +292,6 @@
     void testingOnly_flushAndRemoveOnFlushCallbackObject(GrOnFlushCallbackObject*);
 #endif
 
-    // If possible, create a backend texture initialized to a particular color. The client should
-    // ensure that the returned backend texture is valid.
-    GrBackendTexture createBackendTexture(int width, int height,
-                                          GrBackendFormat, const SkColor4f& color,
-                                          GrMipMapped, GrRenderable);
-
-    // If possible, create a backend texture initialized to a particular color. The client should
-    // ensure that the returned backend texture is valid.
-    // If successful, the created backend texture will be compatible with the provided
-    // SkColorType.
-    GrBackendTexture createBackendTexture(int width, int height,
-                                          SkColorType, const SkColor4f& color,
-                                          GrMipMapped, GrRenderable);
-
 private:
     explicit GrContextPriv(GrContext* context) : fContext(context) {}
     GrContextPriv(const GrContextPriv&); // unimpl
diff --git a/src/gpu/geometry/GrQuad.h b/src/gpu/geometry/GrQuad.h
index cb2f15d..f7d31ce 100644
--- a/src/gpu/geometry/GrQuad.h
+++ b/src/gpu/geometry/GrQuad.h
@@ -11,7 +11,6 @@
 #include "include/core/SkMatrix.h"
 #include "include/core/SkPoint.h"
 #include "include/core/SkPoint3.h"
-#include "include/private/SkTArray.h"
 #include "include/private/SkVx.h"
 
 enum class GrAAType : unsigned;
@@ -182,182 +181,4 @@
     GrQuadType fType;
 };
 
-// Underlying data used by GrQuadListBase. It is defined outside of GrQuadListBase due to compiler
-// issues related to specializing member types.
-template<typename T>
-struct QuadData {
-    float fX[4];
-    float fY[4];
-    T fMetadata;
-};
-
-template<>
-struct QuadData<void> {
-    float fX[4];
-    float fY[4];
-};
-
-// A dynamic list of (possibly) perspective quads that tracks the most general quad type of all
-// added quads. It avoids storing the 3rd component if the quad type never becomes perspective.
-// Use GrQuadList subclass when only storing quads. Use GrTQuadList subclass when storing quads
-// and per-quad templated metadata (such as color or domain).
-template<typename T>
-class GrQuadListBase {
-public:
-
-    int count() const { return fXYs.count(); }
-
-    GrQuadType quadType() const { return fType; }
-
-    void reserve(int count, bool needsPerspective) {
-        fXYs.reserve(count);
-        if (needsPerspective || fType == GrQuadType::kPerspective) {
-            fWs.reserve(4 * count);
-        }
-    }
-
-    GrPerspQuad operator[] (int i) const {
-        SkASSERT(i < this->count());
-        SkASSERT(i >= 0);
-
-        const QuadData<T>& item = fXYs[i];
-        if (fType == GrQuadType::kPerspective) {
-            // Read the explicit ws
-            return GrPerspQuad(item.fX, item.fY, fWs.begin() + 4 * i, fType);
-        } else {
-            // Ws are implicitly 1s.
-            static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f};
-            return GrPerspQuad(item.fX, item.fY, kNoPerspectiveWs, fType);
-        }
-    }
-
-    // Subclasses expose push_back(const GrQuad|GrPerspQuad&, GrQuadType, [const T&]), where
-    // the metadata argument is only present in GrTQuadList's push_back definition.
-
-protected:
-    GrQuadListBase() : fType(GrQuadType::kRect) {}
-
-    void concatImpl(const GrQuadListBase<T>& that) {
-        this->upgradeType(that.fType);
-        fXYs.push_back_n(that.fXYs.count(), that.fXYs.begin());
-        if (fType == GrQuadType::kPerspective) {
-            if (that.fType == GrQuadType::kPerspective) {
-                // Copy the other's ws into the end of this list's data
-                fWs.push_back_n(that.fWs.count(), that.fWs.begin());
-            } else {
-                // This list stores ws but the appended list had implicit 1s, so add explicit 1s to
-                // fill out the total list
-                fWs.push_back_n(4 * that.count(), 1.f);
-            }
-        }
-    }
-
-    // Returns the added item data so that its metadata can be initialized if T is not void
-    QuadData<T>& pushBackImpl(const GrQuad& quad) {
-        this->upgradeType(quad.quadType());
-        QuadData<T>& item = fXYs.push_back();
-        memcpy(item.fX, quad.fX, 4 * sizeof(float));
-        memcpy(item.fY, quad.fY, 4 * sizeof(float));
-        if (fType == GrQuadType::kPerspective) {
-            fWs.push_back_n(4, 1.f);
-        }
-        return item;
-    }
-
-    QuadData<T>& pushBackImpl(const GrPerspQuad& quad) {
-        this->upgradeType(quad.quadType());
-        QuadData<T>& item = fXYs.push_back();
-        memcpy(item.fX, quad.fX, 4 * sizeof(float));
-        memcpy(item.fY, quad.fY, 4 * sizeof(float));
-        if (fType == GrQuadType::kPerspective) {
-            fWs.push_back_n(4, quad.fW);
-        }
-        return item;
-    }
-
-    const QuadData<T>& item(int i) const {
-        return fXYs[i];
-    }
-
-    QuadData<T>& item(int i) {
-        return fXYs[i];
-    }
-
-private:
-    void upgradeType(GrQuadType type) {
-        // Possibly upgrade the overall type tracked by the list
-        if (type > fType) {
-            fType = type;
-            if (type == GrQuadType::kPerspective) {
-                // All existing quads were 2D, so the ws array just needs to be filled with 1s
-                fWs.push_back_n(4 * this->count(), 1.f);
-            }
-        }
-    }
-
-    // Interleaves xs, ys, and per-quad metadata so that all data for a single quad is together
-    // (barring ws, which can be dropped entirely if the quad type allows it).
-    SkSTArray<1, QuadData<T>, true> fXYs;
-    // The w channel is kept separate so that it can remain empty when only dealing with 2D quads.
-    SkTArray<float, true> fWs;
-
-    GrQuadType fType;
-};
-
-// This list only stores the quad data itself.
-class GrQuadList : public GrQuadListBase<void> {
-public:
-    GrQuadList() : INHERITED() {}
-
-    void concat(const GrQuadList& that) {
-        this->concatImpl(that);
-    }
-
-    void push_back(const GrQuad& quad) {
-        this->pushBackImpl(quad);
-    }
-
-    void push_back(const GrPerspQuad& quad) {
-        this->pushBackImpl(quad);
-    }
-
-private:
-    typedef GrQuadListBase<void> INHERITED;
-};
-
-// This variant of the list allows simple metadata to be stored per quad as well, such as color
-// or texture domain.
-template<typename T>
-class GrTQuadList : public GrQuadListBase<T> {
-public:
-    GrTQuadList() : INHERITED() {}
-
-    void concat(const GrTQuadList<T>& that) {
-        this->concatImpl(that);
-    }
-
-    // Adding to the list requires metadata
-    void push_back(const GrQuad& quad, T&& metadata) {
-        QuadData<T>& item = this->pushBackImpl(quad);
-        item.fMetadata = std::move(metadata);
-    }
-
-    void push_back(const GrPerspQuad& quad, T&& metadata) {
-        QuadData<T>& item = this->pushBackImpl(quad);
-        item.fMetadata = std::move(metadata);
-    }
-
-    // And provide access to the metadata per quad
-    const T& metadata(int i) const {
-        return this->item(i).fMetadata;
-    }
-
-    T& metadata(int i) {
-        return this->item(i).fMetadata;
-    }
-
-private:
-    typedef GrQuadListBase<T> INHERITED;
-};
-
 #endif
diff --git a/src/gpu/geometry/GrQuadList.h b/src/gpu/geometry/GrQuadList.h
new file mode 100644
index 0000000..a802f9e
--- /dev/null
+++ b/src/gpu/geometry/GrQuadList.h
@@ -0,0 +1,192 @@
+/*
+ * 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 GrQuadList_DEFINED
+#define GrQuadList_DEFINED
+
+#include "include/private/SkTArray.h"
+#include "src/gpu/geometry/GrQuad.h"
+
+// Underlying data used by GrQuadListBase. It is defined outside of GrQuadListBase due to compiler
+// issues related to specializing member types.
+template<typename T>
+struct QuadData {
+    float fX[4];
+    float fY[4];
+    T fMetadata;
+};
+
+template<>
+struct QuadData<void> {
+    float fX[4];
+    float fY[4];
+};
+
+// A dynamic list of (possibly) perspective quads that tracks the most general quad type of all
+// added quads. It avoids storing the 3rd component if the quad type never becomes perspective.
+// Use GrQuadList subclass when only storing quads. Use GrTQuadList subclass when storing quads
+// and per-quad templated metadata (such as color or domain).
+template<typename T>
+class GrQuadListBase {
+public:
+
+    int count() const { return fXYs.count(); }
+
+    GrQuadType quadType() const { return fType; }
+
+    void reserve(int count, bool needsPerspective) {
+        fXYs.reserve(count);
+        if (needsPerspective || fType == GrQuadType::kPerspective) {
+            fWs.reserve(4 * count);
+        }
+    }
+
+    GrPerspQuad operator[] (int i) const {
+        SkASSERT(i < this->count());
+        SkASSERT(i >= 0);
+
+        const QuadData<T>& item = fXYs[i];
+        if (fType == GrQuadType::kPerspective) {
+            // Read the explicit ws
+            return GrPerspQuad(item.fX, item.fY, fWs.begin() + 4 * i, fType);
+        } else {
+            // Ws are implicitly 1s.
+            static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f};
+            return GrPerspQuad(item.fX, item.fY, kNoPerspectiveWs, fType);
+        }
+    }
+
+    // Subclasses expose push_back(const GrQuad|GrPerspQuad&, GrQuadType, [const T&]), where
+    // the metadata argument is only present in GrTQuadList's push_back definition.
+
+protected:
+    GrQuadListBase() : fType(GrQuadType::kRect) {}
+
+    void concatImpl(const GrQuadListBase<T>& that) {
+        this->upgradeType(that.fType);
+        fXYs.push_back_n(that.fXYs.count(), that.fXYs.begin());
+        if (fType == GrQuadType::kPerspective) {
+            if (that.fType == GrQuadType::kPerspective) {
+                // Copy the other's ws into the end of this list's data
+                fWs.push_back_n(that.fWs.count(), that.fWs.begin());
+            } else {
+                // This list stores ws but the appended list had implicit 1s, so add explicit 1s to
+                // fill out the total list
+                fWs.push_back_n(4 * that.count(), 1.f);
+            }
+        }
+    }
+
+    // Returns the added item data so that its metadata can be initialized if T is not void
+    QuadData<T>& pushBackImpl(const GrQuad& quad) {
+        this->upgradeType(quad.quadType());
+        QuadData<T>& item = fXYs.push_back();
+        memcpy(item.fX, quad.fX, 4 * sizeof(float));
+        memcpy(item.fY, quad.fY, 4 * sizeof(float));
+        if (fType == GrQuadType::kPerspective) {
+            fWs.push_back_n(4, 1.f);
+        }
+        return item;
+    }
+
+    QuadData<T>& pushBackImpl(const GrPerspQuad& quad) {
+        this->upgradeType(quad.quadType());
+        QuadData<T>& item = fXYs.push_back();
+        memcpy(item.fX, quad.fX, 4 * sizeof(float));
+        memcpy(item.fY, quad.fY, 4 * sizeof(float));
+        if (fType == GrQuadType::kPerspective) {
+            fWs.push_back_n(4, quad.fW);
+        }
+        return item;
+    }
+
+    const QuadData<T>& item(int i) const {
+        return fXYs[i];
+    }
+
+    QuadData<T>& item(int i) {
+        return fXYs[i];
+    }
+
+private:
+    void upgradeType(GrQuadType type) {
+        // Possibly upgrade the overall type tracked by the list
+        if (type > fType) {
+            fType = type;
+            if (type == GrQuadType::kPerspective) {
+                // All existing quads were 2D, so the ws array just needs to be filled with 1s
+                fWs.push_back_n(4 * this->count(), 1.f);
+            }
+        }
+    }
+
+    // Interleaves xs, ys, and per-quad metadata so that all data for a single quad is together
+    // (barring ws, which can be dropped entirely if the quad type allows it).
+    SkSTArray<1, QuadData<T>, true> fXYs;
+    // The w channel is kept separate so that it can remain empty when only dealing with 2D quads.
+    SkTArray<float, true> fWs;
+
+    GrQuadType fType;
+};
+
+// This list only stores the quad data itself.
+class GrQuadList : public GrQuadListBase<void> {
+public:
+    GrQuadList() : INHERITED() {}
+
+    void concat(const GrQuadList& that) {
+        this->concatImpl(that);
+    }
+
+    void push_back(const GrQuad& quad) {
+        this->pushBackImpl(quad);
+    }
+
+    void push_back(const GrPerspQuad& quad) {
+        this->pushBackImpl(quad);
+    }
+
+private:
+    typedef GrQuadListBase<void> INHERITED;
+};
+
+// This variant of the list allows simple metadata to be stored per quad as well, such as color
+// or texture domain.
+template<typename T>
+class GrTQuadList : public GrQuadListBase<T> {
+public:
+    GrTQuadList() : INHERITED() {}
+
+    void concat(const GrTQuadList<T>& that) {
+        this->concatImpl(that);
+    }
+
+    // Adding to the list requires metadata
+    void push_back(const GrQuad& quad, T&& metadata) {
+        QuadData<T>& item = this->pushBackImpl(quad);
+        item.fMetadata = std::move(metadata);
+    }
+
+    void push_back(const GrPerspQuad& quad, T&& metadata) {
+        QuadData<T>& item = this->pushBackImpl(quad);
+        item.fMetadata = std::move(metadata);
+    }
+
+    // And provide access to the metadata per quad
+    const T& metadata(int i) const {
+        return this->item(i).fMetadata;
+    }
+
+    T& metadata(int i) {
+        return this->item(i).fMetadata;
+    }
+
+private:
+    typedef GrQuadListBase<T> INHERITED;
+};
+
+#endif
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index 8e8ae3f..18765d9 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -14,6 +14,7 @@
 #include "src/gpu/GrPaint.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/geometry/GrQuad.h"
+#include "src/gpu/geometry/GrQuadList.h"
 #include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h"
 #include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
 #include "src/gpu/glsl/GrGLSLVarying.h"
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 70382860..342d7ba 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -30,6 +30,7 @@
 #include "src/gpu/GrTexturePriv.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/geometry/GrQuad.h"
+#include "src/gpu/geometry/GrQuadList.h"
 #include "src/gpu/glsl/GrGLSLVarying.h"
 #include "src/gpu/ops/GrMeshDrawOp.h"
 #include "src/gpu/ops/GrQuadPerEdgeAA.h"
diff --git a/src/opts/SkVM_opts.h b/src/opts/SkVM_opts.h
index b692ba4..c824184 100644
--- a/src/opts/SkVM_opts.h
+++ b/src/opts/SkVM_opts.h
@@ -73,11 +73,11 @@
             for (int i = 0; i < ninsts; i++) {
                 skvm::Program::Instruction inst = insts[i];
 
-                // d = op(x, y, z.id/z.imm)
+                // d = op(x, y.id/z.imm, z.id/z.imm)
                 ID   d = inst.d,
-                     x = inst.x,
-                     y = inst.y;
-                auto z = inst.z;
+                     x = inst.x;
+                auto y = inst.y,
+                     z = inst.z;
 
                 // Ops that interact with memory need to know whether we're stride=1 or stride=K,
                 // but all non-memory ops can run the same code no matter the stride.
@@ -85,42 +85,48 @@
 
                 #define STRIDE_1(op) case 2*(int)op
                 #define STRIDE_K(op) case 2*(int)op + 1
-                    STRIDE_1(Op::store8 ): memcpy(arg(z.imm), &r(x).i32, 1); break;
-                    STRIDE_1(Op::store32): memcpy(arg(z.imm), &r(x).i32, 4); break;
+                    STRIDE_1(Op::store8 ): memcpy(arg(y.imm), &r(x).i32, 1); break;
+                    STRIDE_1(Op::store32): memcpy(arg(y.imm), &r(x).i32, 4); break;
 
-                    STRIDE_K(Op::store8 ): skvx::cast<uint8_t>(r(x).i32).store(arg(z.imm)); break;
-                    STRIDE_K(Op::store32):                    (r(x).i32).store(arg(z.imm)); break;
+                    STRIDE_K(Op::store8 ): skvx::cast<uint8_t>(r(x).i32).store(arg(y.imm)); break;
+                    STRIDE_K(Op::store32):                    (r(x).i32).store(arg(y.imm)); break;
 
-                    STRIDE_1(Op::load8 ): r(d).i32 = 0; memcpy(&r(d).i32, arg(z.imm), 1); break;
-                    STRIDE_1(Op::load32): r(d).i32 = 0; memcpy(&r(d).i32, arg(z.imm), 4); break;
+                    STRIDE_1(Op::load8 ): r(d).i32 = 0; memcpy(&r(d).i32, arg(y.imm), 1); break;
+                    STRIDE_1(Op::load32): r(d).i32 = 0; memcpy(&r(d).i32, arg(y.imm), 4); break;
 
-                    STRIDE_K(Op::load8 ): r(d).i32 = skvx::cast<int>(U8 ::Load(arg(z.imm))); break;
-                    STRIDE_K(Op::load32): r(d).i32 =                 I32::Load(arg(z.imm)) ; break;
+                    STRIDE_K(Op::load8 ): r(d).i32 = skvx::cast<int>(U8 ::Load(arg(y.imm))); break;
+                    STRIDE_K(Op::load32): r(d).i32 =                 I32::Load(arg(y.imm)) ; break;
                 #undef STRIDE_1
                 #undef STRIDE_K
 
                     // Ops that don't interact with memory should never care about the stride.
                 #define CASE(op) case 2*(int)op: /*fallthrough*/ case 2*(int)op+1
-                    CASE(Op::splat): r(d).i32 = z.imm; break;
+                    CASE(Op::splat): r(d).i32 = y.imm; break;
 
-                    CASE(Op::add_f32): r(d).f32 = r(x).f32 + r(y).f32; break;
-                    CASE(Op::sub_f32): r(d).f32 = r(x).f32 - r(y).f32; break;
-                    CASE(Op::mul_f32): r(d).f32 = r(x).f32 * r(y).f32; break;
-                    CASE(Op::div_f32): r(d).f32 = r(x).f32 / r(y).f32; break;
+                    CASE(Op::add_f32): r(d).f32 = r(x).f32 + r(y.id).f32; break;
+                    CASE(Op::sub_f32): r(d).f32 = r(x).f32 - r(y.id).f32; break;
+                    CASE(Op::mul_f32): r(d).f32 = r(x).f32 * r(y.id).f32; break;
+                    CASE(Op::div_f32): r(d).f32 = r(x).f32 / r(y.id).f32; break;
 
-                    CASE(Op::mad_f32): r(d).f32 = r(x).f32 * r(y).f32 + r(z.id).f32; break;
+                    CASE(Op::mad_f32): r(d).f32 = r(x).f32 * r(y.id).f32 + r(z.id).f32; break;
 
-                    CASE(Op::add_i32): r(d).i32 = r(x).i32 + r(y).i32; break;
-                    CASE(Op::sub_i32): r(d).i32 = r(x).i32 - r(y).i32; break;
-                    CASE(Op::mul_i32): r(d).i32 = r(x).i32 * r(y).i32; break;
+                    CASE(Op::add_i32): r(d).i32 = r(x).i32 + r(y.id).i32; break;
+                    CASE(Op::sub_i32): r(d).i32 = r(x).i32 - r(y.id).i32; break;
+                    CASE(Op::mul_i32): r(d).i32 = r(x).i32 * r(y.id).i32; break;
 
-                    CASE(Op::bit_and): r(d).i32 = r(x).i32 & r(y).i32; break;
-                    CASE(Op::bit_or ): r(d).i32 = r(x).i32 | r(y).i32; break;
-                    CASE(Op::bit_xor): r(d).i32 = r(x).i32 ^ r(y).i32; break;
+                    CASE(Op::bit_and): r(d).i32 = r(x).i32 & r(y.id).i32; break;
+                    CASE(Op::bit_or ): r(d).i32 = r(x).i32 | r(y.id).i32; break;
+                    CASE(Op::bit_xor): r(d).i32 = r(x).i32 ^ r(y.id).i32; break;
 
-                    CASE(Op::shl): r(d).i32 =                 r(x).i32 << z.imm ; break;
-                    CASE(Op::sra): r(d).i32 =                 r(x).i32 >> z.imm ; break;
-                    CASE(Op::shr): r(d).i32 = skvx::cast<int>(r(x).u32 >> z.imm); break;
+                    CASE(Op::shl): r(d).i32 = r(x).i32 << y.imm; break;
+                    CASE(Op::sra): r(d).i32 = r(x).i32 >> y.imm; break;
+                    CASE(Op::shr): r(d).u32 = r(x).u32 >> y.imm; break;
+
+                    CASE(Op::mul_unorm8): r(d).i32 = (r(x).i32 * r(y.id).i32 + 255) / 256; break;
+
+                    CASE(Op::extract): r(d).u32 = (r(x).u32 & y.imm) >> z.imm; break;
+
+                    CASE(Op::pack): r(d).i32 = r(x).i32 | (r(y.id).i32 << z.imm); break;
 
                     CASE(Op::to_f32): r(d).f32 = skvx::cast<float>(r(x).i32); break;
                     CASE(Op::to_i32): r(d).i32 = skvx::cast<int>  (r(x).f32); break;
diff --git a/tests/BackendAllocationTest.cpp b/tests/BackendAllocationTest.cpp
index 701eae4..e76d210 100644
--- a/tests/BackendAllocationTest.cpp
+++ b/tests/BackendAllocationTest.cpp
@@ -365,8 +365,8 @@
                                                           const SkColor4f& color,
                                                           GrMipMapped mipMapped,
                                                           GrRenderable renderable) {
-                        return context->priv().createBackendTexture(32, 32, colorType, color,
-                                                                    mipMapped, renderable);
+                        return context->createBackendTexture(32, 32, colorType, color,
+                                                             mipMapped, renderable);
                     };
 
                     test_color_init(context, reporter, createWithColorMtd,
@@ -527,8 +527,8 @@
                                                        const SkColor4f& color,
                                                        GrMipMapped mipMapped,
                                                        GrRenderable renderable) {
-                        return context->priv().createBackendTexture(32, 32, format, color,
-                                                                    mipMapped, renderable);
+                        return context->createBackendTexture(32, 32, format, color,
+                                                             mipMapped, renderable);
                     };
 
                     test_color_init(context, reporter, createWithColorMtd,
@@ -637,8 +637,8 @@
                                                        const SkColor4f& color,
                                                        GrMipMapped mipMapped,
                                                        GrRenderable renderable) {
-                        return context->priv().createBackendTexture(32, 32, format, color,
-                                                                    mipMapped, renderable);
+                        return context->createBackendTexture(32, 32, format, color,
+                                                             mipMapped, renderable);
                     };
 
                     test_color_init(context, reporter, createWithColorMtd,
diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp
index 687f9e2..f128f6c 100644
--- a/tests/DeferredDisplayListTest.cpp
+++ b/tests/DeferredDisplayListTest.cpp
@@ -186,6 +186,7 @@
         }
 
         *backend = context->createBackendTexture(fWidth, fHeight, fColorType,
+                                                 SkColors::kTransparent,
                                                  mipmapped, GrRenderable::kYes);
         if (!backend->isValid() || !gpu->isTestingOnlyBackendTexture(*backend)) {
             return nullptr;
@@ -578,7 +579,8 @@
     GrContext* context = ctxInfo.grContext();
 
     GrBackendTexture backendTex = context->createBackendTexture(
-            kSize, kSize, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+            kSize, kSize, kRGBA_8888_SkColorType,
+            SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
     if (!backendTex.isValid()) {
         return;
     }
diff --git a/tests/EGLImageTest.cpp b/tests/EGLImageTest.cpp
index 808dd10..96483a5 100644
--- a/tests/EGLImageTest.cpp
+++ b/tests/EGLImageTest.cpp
@@ -88,6 +88,7 @@
     static const int kSize = 100;
     backendTexture1 =
         context1->createBackendTexture(kSize, kSize, kRGBA_8888_SkColorType,
+                                       SkColors::kTransparent,
                                        GrMipMapped::kNo, GrRenderable::kNo);
 
     if (!backendTexture1.isValid() || !gpu1->isTestingOnlyBackendTexture(backendTexture1)) {
diff --git a/tests/GrMipMappedTest.cpp b/tests/GrMipMappedTest.cpp
index 15718c7..ef0ef66 100644
--- a/tests/GrMipMappedTest.cpp
+++ b/tests/GrMipMappedTest.cpp
@@ -41,7 +41,8 @@
             // so we don't send any. However, we pretend there is data for the checks below which is
             // fine since we are never actually using these textures for any work on the gpu.
             GrBackendTexture backendTex = context->createBackendTexture(
-                    kSize, kSize, kRGBA_8888_SkColorType, mipMapped, renderable);
+                    kSize, kSize, kRGBA_8888_SkColorType,
+                    SkColors::kTransparent, mipMapped, renderable);
 
             sk_sp<GrTextureProxy> proxy;
             sk_sp<SkImage> image;
@@ -106,7 +107,8 @@
     for (auto mipMapped : {GrMipMapped::kNo, GrMipMapped::kYes}) {
         for (auto willUseMips : {false, true}) {
             GrBackendTexture backendTex = context->createBackendTexture(
-                    kSize, kSize, kRGBA_8888_SkColorType, mipMapped, GrRenderable::kNo);
+                    kSize, kSize, kRGBA_8888_SkColorType,
+                    SkColors::kTransparent, mipMapped, GrRenderable::kNo);
 
             sk_sp<SkImage> image = SkImage::MakeFromTexture(context, backendTex,
                                                             kTopLeft_GrSurfaceOrigin,
@@ -248,7 +250,8 @@
             GrMipMapped mipMapped = willUseMips ? GrMipMapped::kYes : GrMipMapped::kNo;
             sk_sp<SkSurface> surface;
             GrBackendTexture backendTex = context->createBackendTexture(
-                    kSize, kSize, kRGBA_8888_SkColorType, mipMapped, GrRenderable::kYes);
+                    kSize, kSize, kRGBA_8888_SkColorType,
+                    SkColors::kTransparent, mipMapped, GrRenderable::kYes);
             if (isWrapped) {
                 surface = SkSurface::MakeFromBackendTexture(context,
                                                             backendTex,
diff --git a/tests/GrPorterDuffTest.cpp b/tests/GrPorterDuffTest.cpp
index 2d124a8..79b36e6 100644
--- a/tests/GrPorterDuffTest.cpp
+++ b/tests/GrPorterDuffTest.cpp
@@ -997,7 +997,7 @@
     }
 
     GrBackendTexture backendTex =
-        ctx->createBackendTexture(100, 100, kRGBA_8888_SkColorType,
+        ctx->createBackendTexture(100, 100, kRGBA_8888_SkColorType, SkColors::kTransparent,
                                   GrMipMapped::kNo, GrRenderable::kNo);
 
     GrXferProcessor::DstProxy fakeDstProxy;
diff --git a/tests/GrQuadListTest.cpp b/tests/GrQuadListTest.cpp
index b8d0acb..89e7607 100644
--- a/tests/GrQuadListTest.cpp
+++ b/tests/GrQuadListTest.cpp
@@ -7,7 +7,7 @@
 
 #include "tests/Test.h"
 
-#include "src/gpu/geometry/GrQuad.h"
+#include "src/gpu/geometry/GrQuadList.h"
 
 #define ASSERT(cond) REPORTER_ASSERT(r, cond)
 #define ASSERTF(cond, ...) REPORTER_ASSERT(r, cond, __VA_ARGS__)
diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp
index 45867fe..178d6d6 100644
--- a/tests/GrSurfaceTest.cpp
+++ b/tests/GrSurfaceTest.cpp
@@ -53,7 +53,8 @@
     REPORTER_ASSERT(reporter, static_cast<GrSurface*>(tex1.get()) == tex1->asTexture());
 
     GrBackendTexture backendTex = context->createBackendTexture(
-        256, 256, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+        256, 256, kRGBA_8888_SkColorType,
+        SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
 
     sk_sp<GrSurface> texRT2 = resourceProvider->wrapRenderableBackendTexture(
             backendTex, 1, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo);
@@ -331,7 +332,8 @@
         if (context->priv().caps()->mipMapSupport()) {
             delete_backend_texture(context, backendTex);
             backendTex = context->createBackendTexture(
-                    kSize, kSize, kRGBA_8888_SkColorType, GrMipMapped::kYes, GrRenderable::kYes);
+                    kSize, kSize, kRGBA_8888_SkColorType,
+                    SkColors::kTransparent, GrMipMapped::kYes, GrRenderable::kYes);
             proxy = proxyProvider->wrapBackendTexture(backendTex, kTopLeft_GrSurfaceOrigin,
                                                       kBorrow_GrWrapOwnership, GrWrapCacheable::kNo,
                                                       ioType);
@@ -347,7 +349,7 @@
 
 static sk_sp<GrTexture> make_wrapped_texture(GrContext* context, GrRenderable renderable) {
     auto backendTexture = context->createBackendTexture(
-            10, 10, kRGBA_8888_SkColorType, GrMipMapped::kNo, renderable);
+            10, 10, kRGBA_8888_SkColorType, SkColors::kTransparent, GrMipMapped::kNo, renderable);
     sk_sp<GrTexture> texture;
     if (GrRenderable::kYes == renderable) {
         texture = context->priv().resourceProvider()->wrapRenderableBackendTexture(
diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp
index 6a4df50..c8fbacf 100644
--- a/tests/ImageTest.cpp
+++ b/tests/ImageTest.cpp
@@ -491,7 +491,8 @@
         bool can = context->colorTypeSupportedAsImage(colorType);
 
         GrBackendTexture backendTex = context->createBackendTexture(
-                kSize, kSize, colorType, GrMipMapped::kNo, GrRenderable::kNo);
+                kSize, kSize, colorType, SkColors::kTransparent,
+                GrMipMapped::kNo, GrRenderable::kNo);
 
         auto img = SkImage::MakeFromTexture(context, backendTex, kTopLeft_GrSurfaceOrigin,
                                             colorType, kOpaque_SkAlphaType, nullptr);
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
index b5a60b3..da92b6b 100644
--- a/tests/LazyProxyTest.cpp
+++ b/tests/LazyProxyTest.cpp
@@ -472,7 +472,8 @@
         desc.fConfig = kRGBA_8888_GrPixelConfig;
 
         GrBackendTexture backendTex = ctx->createBackendTexture(
-                kSize, kSize, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+                kSize, kSize, kRGBA_8888_SkColorType, SkColors::kTransparent,
+                GrMipMapped::kNo, GrRenderable::kNo);
 
         sk_sp<GrTextureProxy> lazyProxy = proxyProvider->createLazyProxy(
                 [instantiatePtr, releasePtr,
diff --git a/tests/PromiseImageTest.cpp b/tests/PromiseImageTest.cpp
index 6a60970..38c513e 100644
--- a/tests/PromiseImageTest.cpp
+++ b/tests/PromiseImageTest.cpp
@@ -168,7 +168,8 @@
     GrGpu* gpu = ctx->priv().getGpu();
 
     GrBackendTexture backendTex = ctx->createBackendTexture(
-            kWidth, kHeight, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kYes);
+            kWidth, kHeight, kRGBA_8888_SkColorType,
+            SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kYes);
     REPORTER_ASSERT(reporter, backendTex.isValid());
 
     GrBackendFormat backendFormat = backendTex.getBackendFormat();
@@ -243,11 +244,13 @@
     GrGpu* gpu = ctx->priv().getGpu();
 
     GrBackendTexture backendTex1 = ctx->createBackendTexture(
-            kWidth, kHeight, kGray_8_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+            kWidth, kHeight, kGray_8_SkColorType,
+            SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
     REPORTER_ASSERT(reporter, backendTex1.isValid());
 
     GrBackendTexture backendTex2 = ctx->createBackendTexture(
-            kWidth, kHeight, kAlpha_8_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+            kWidth, kHeight, kAlpha_8_SkColorType,
+            SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
     REPORTER_ASSERT(reporter, backendTex2.isValid());
     if (backendTex1.getBackendFormat() != backendTex2.getBackendFormat()) {
         ctx->deleteBackendTexture(backendTex1);
@@ -353,7 +356,8 @@
             }
 
             GrBackendTexture backendTex = ctx->createBackendTexture(
-                    kWidth, kHeight, kAlpha_8_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+                    kWidth, kHeight, kAlpha_8_SkColorType,
+                    SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
             REPORTER_ASSERT(reporter, backendTex.isValid());
 
             SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType,
@@ -391,7 +395,8 @@
     GrContext* ctx = ctxInfo.grContext();
 
     GrBackendTexture backendTex = ctx->createBackendTexture(
-            kWidth, kHeight, kAlpha_8_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+            kWidth, kHeight, kAlpha_8_SkColorType,
+            SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
     REPORTER_ASSERT(reporter, backendTex.isValid());
 
     SkImageInfo info =
@@ -454,7 +459,8 @@
 
     // Do all this just to get a valid backend format for the image.
     GrBackendTexture backendTex = ctx->createBackendTexture(
-            kWidth, kHeight, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kYes);
+            kWidth, kHeight, kRGBA_8888_SkColorType,
+            SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kYes);
     REPORTER_ASSERT(reporter, backendTex.isValid());
     GrBackendFormat backendFormat = backendTex.getBackendFormat();
     REPORTER_ASSERT(reporter, backendFormat.isValid());
diff --git a/tests/ProxyTest.cpp b/tests/ProxyTest.cpp
index ffd2106..8b5e130 100644
--- a/tests/ProxyTest.cpp
+++ b/tests/ProxyTest.cpp
@@ -277,6 +277,7 @@
                     GrBackendTexture backendTex =
                             context->createBackendTexture(kWidthHeight, kWidthHeight,
                                                           colorType,
+                                                          SkColors::kTransparent,
                                                           GrMipMapped::kNo,
                                                           GrRenderable::kYes);
                     sk_sp<GrSurfaceProxy> sProxy = proxyProvider->wrapBackendTextureAsRenderTarget(
@@ -302,6 +303,7 @@
                     GrBackendTexture backendTex =
                             context->createBackendTexture(kWidthHeight, kWidthHeight,
                                                           colorType,
+                                                          SkColors::kTransparent,
                                                           GrMipMapped::kNo,
                                                           GrRenderable::kYes);
 
@@ -330,6 +332,7 @@
                     GrBackendTexture backendTex =
                             context->createBackendTexture(kWidthHeight, kWidthHeight,
                                                           colorType,
+                                                          SkColors::kTransparent,
                                                           GrMipMapped::kNo,
                                                           GrRenderable::kNo);
 
diff --git a/tests/ResourceAllocatorTest.cpp b/tests/ResourceAllocatorTest.cpp
index f0cba29..c23c85e 100644
--- a/tests/ResourceAllocatorTest.cpp
+++ b/tests/ResourceAllocatorTest.cpp
@@ -63,6 +63,7 @@
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
 
     *backendTex = context->createBackendTexture(p.fSize, p.fSize, p.fColorType,
+                                                SkColors::kTransparent,
                                                 GrMipMapped::kNo, GrRenderable::kNo);
     if (!backendTex->isValid()) {
         return nullptr;
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 17b21bd..caed56b 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -210,8 +210,10 @@
     static const int kH = 100;
 
     backendTextures[0] = context->createBackendTexture(kW, kH, kRGBA_8888_SkColorType,
+                                                       SkColors::kTransparent,
                                                        GrMipMapped::kNo, GrRenderable::kNo);
     backendTextures[1] = context->createBackendTexture(kW, kH, kRGBA_8888_SkColorType,
+                                                       SkColors::kTransparent,
                                                        GrMipMapped::kNo, GrRenderable::kNo);
     REPORTER_ASSERT(reporter, backendTextures[0].isValid());
     REPORTER_ASSERT(reporter, backendTextures[1].isValid());
diff --git a/tests/SkVMTest.cpp b/tests/SkVMTest.cpp
index 7d8c0b0..fdb62ca 100644
--- a/tests/SkVMTest.cpp
+++ b/tests/SkVMTest.cpp
@@ -10,117 +10,27 @@
 #include "src/core/SkVM.h"
 #include "tests/Test.h"
 #include "tools/Resources.h"
+#include "tools/SkVMBuilders.h"
 
-enum Fmt { A8, G8, RGBA_8888 };
+using Fmt = SrcoverBuilder_F32::Fmt;
 const char* fmt_name(Fmt fmt) {
     switch (fmt) {
-        case A8:        return "A8";
-        case G8:        return "G8";
-        case RGBA_8888: return "RGBA_8888";
+        case Fmt::A8:        return "A8";
+        case Fmt::G8:        return "G8";
+        case Fmt::RGBA_8888: return "RGBA_8888";
     }
     return "";
 }
 
-// Here's a cute little trick that avoids the need to explicitly thread
-// and skvm::Builder* through and make a lot of builder->foo() calls.
-// Instead the builder becomes this, with this-> omitted for clarity.
-//
-// Think of this as
-//    static void srcover(skvm::Builder*, Fmt srcFmt, Fmt dstFmt) { ... }
-//
-// Some parts of this builder code are written less fluently than possible,
-// to avoid any ambiguity of function argument evaluation order.  This lets
-// our golden tests work portably.  In general there's no reason to fear
-// nesting calls to Builder routines.
-
-struct SrcoverBuilder : public skvm::Builder {
-    SrcoverBuilder(Fmt srcFmt, Fmt dstFmt) {
-        skvm::Arg src = arg(0),
-                  dst = arg(1);
-
-        auto byte_to_f32 = [&](skvm::I32 byte) {
-            skvm::F32 _1_255 = splat(1/255.0f);
-            return mul(_1_255, to_f32(byte));
-        };
-
-        auto load = [&](skvm::Arg ptr, Fmt fmt,
-                        skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32* a) {
-            switch (fmt) {
-                case A8: {
-                    *r = *g = *b = splat(0.0f);
-                    *a = byte_to_f32(load8(ptr));
-                } break;
-
-                case G8: {
-                    *r = *g = *b = byte_to_f32(load8(ptr));
-                    *a = splat(1.0f);
-                } break;
-
-                case RGBA_8888: {
-                    skvm::I32 rgba = load32(ptr),
-                              _255 = splat(255);
-                    *r = byte_to_f32(bit_and(    rgba     , _255));
-                    *g = byte_to_f32(bit_and(shr(rgba,  8), _255));
-                    *b = byte_to_f32(bit_and(shr(rgba, 16), _255));
-                    *a = byte_to_f32(        shr(rgba, 24)       );
-                } break;
-            }
-        };
-
-        skvm::F32 r,g,b,a;
-        load(src, srcFmt, &r,&g,&b,&a);
-
-        skvm::F32 dr,dg,db,da;
-        load(dst, dstFmt, &dr,&dg,&db,&da);
-
-        skvm::F32 invA = sub(splat(1.0f), a);
-        r = mad(dr, invA, r);
-        g = mad(dg, invA, g);
-        b = mad(db, invA, b);
-        a = mad(da, invA, a);
-
-        auto f32_to_byte = [&](skvm::F32 f32) {
-            skvm::F32 _255 = splat(255.0f),
-                      _0_5 = splat(0.5f);
-            return to_i32(mad(f32, _255, _0_5));
-        };
-        switch (dstFmt) {
-            case A8: {
-                store8(dst, f32_to_byte(a));
-            } break;
-
-            case G8: {
-                skvm::F32 _2126 = splat(0.2126f),
-                          _7152 = splat(0.7152f),
-                          _0722 = splat(0.0722f);
-                store8(dst, f32_to_byte(mad(r, _2126,
-                                        mad(g, _7152,
-                                        mul(b, _0722)))));
-            } break;
-
-            case RGBA_8888: {
-                skvm::I32 R =     f32_to_byte(r)     ,
-                          G = shl(f32_to_byte(g),  8),
-                          B = shl(f32_to_byte(b), 16),
-                          A = shl(f32_to_byte(a), 24);
-
-                R = bit_or(R,G);
-                R = bit_or(R,B);
-                R = bit_or(R,A);
-
-                store32(dst, R);
-            } break;
-        }
-    }
-};
-
 DEF_TEST(SkVM, r) {
     SkDynamicMemoryWStream buf;
+
+    // Write all combinations of SrcoverBuilder_F32
     for (int s = 0; s < 3; s++)
     for (int d = 0; d < 3; d++) {
         auto srcFmt = (Fmt)s,
              dstFmt = (Fmt)d;
-        skvm::Program program = SrcoverBuilder{srcFmt, dstFmt}.done();
+        skvm::Program program = SrcoverBuilder_F32{srcFmt, dstFmt}.done();
 
         buf.writeText(fmt_name(srcFmt));
         buf.writeText(" over ");
@@ -130,22 +40,43 @@
         buf.writeText("\n");
     }
 
+    // Write the I32 Srcovers also.
+    {
+        skvm::Program program = SrcoverBuilder_I32{}.done();
+        buf.writeText("I32 8888 over 8888\n");
+        program.dump(&buf);
+        buf.writeText("\n");
+    }
+    {
+        skvm::Program program = SrcoverBuilder_I32_SWAR{}.done();
+        buf.writeText("I32 (SWAR) 8888 over 8888\n");
+        program.dump(&buf);
+        buf.writeText("\n");
+    }
+
     sk_sp<SkData> blob = buf.detachAsData();
     {
 
         sk_sp<SkData> expected = GetResourceAsData("SkVMTest.expected");
-        REPORTER_ASSERT(r, expected
-                        && blob->size() == expected->size()
-                        && 0 == memcmp(blob->data(), expected->data(), blob->size()));
+        REPORTER_ASSERT(r, expected, "Couldn't load SkVMTest.expected.");
+        if (expected) {
+            if (blob->size() != expected->size()
+                    || 0 != memcmp(blob->data(), expected->data(), blob->size())) {
 
-        SkFILEWStream out(GetResourcePath("SkVMTest.expected").c_str());
-        if (out.isValid()) {
-            out.write(blob->data(), blob->size());
+                ERRORF(r, "SkVMTest expected\n%.*s\nbut got\n%.*s\n",
+                       expected->size(), expected->data(),
+                       blob->size(), blob->data());
+            }
+
+            SkFILEWStream out(GetResourcePath("SkVMTest.expected").c_str());
+            if (out.isValid()) {
+                out.write(blob->data(), blob->size());
+            }
         }
     }
 
     {
-        skvm::Program program = SrcoverBuilder{RGBA_8888, RGBA_8888}.done();
+        skvm::Program program = SrcoverBuilder_F32{Fmt::RGBA_8888, Fmt::RGBA_8888}.done();
 
         uint32_t src = 0xbb007733,
                  dst = 0xffaaccee;
@@ -164,7 +95,45 @@
     }
 
     {
-        skvm::Program program = SrcoverBuilder{RGBA_8888, G8}.done();
+        skvm::Program program = SrcoverBuilder_I32{}.done();
+
+        uint32_t src = 0xbb007733,
+                 dst = 0xffaaccee;
+        SkPMColor want = SkPMSrcOver(src, dst);  // 0xff2dad73
+
+        program.eval(1, &src, &dst);
+
+        // dst is probably 0xff2dad72.
+        for (int i = 0; i < 4; i++) {
+            uint8_t d = dst  & 0xff,
+                    w = want & 0xff;
+            REPORTER_ASSERT(r, abs(d-w) < 2);
+            dst  >>= 8;
+            want >>= 8;
+        }
+    }
+
+    {
+        skvm::Program program = SrcoverBuilder_I32_SWAR{}.done();
+
+        uint32_t src = 0xbb007733,
+                 dst = 0xffaaccee;
+        SkPMColor want = SkPMSrcOver(src, dst);  // 0xff2dad73
+
+        program.eval(1, &src, &dst);
+
+        // dst is probably 0xff2dad72.
+        for (int i = 0; i < 4; i++) {
+            uint8_t d = dst  & 0xff,
+                    w = want & 0xff;
+            REPORTER_ASSERT(r, abs(d-w) < 2);
+            dst  >>= 8;
+            want >>= 8;
+        }
+    }
+
+    {
+        skvm::Program program = SrcoverBuilder_F32{Fmt::RGBA_8888, Fmt::G8}.done();
 
         uint32_t src = 0xbb007733;
         uint8_t dst = 0x42;
@@ -179,7 +148,7 @@
     }
 
     {
-        skvm::Program program = SrcoverBuilder{A8, A8}.done();
+        skvm::Program program = SrcoverBuilder_F32{Fmt::A8, Fmt::A8}.done();
 
         uint8_t src[256],
                 dst[256];
diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp
index 6fb6df4..fa9fe79 100644
--- a/tests/SurfaceTest.cpp
+++ b/tests/SurfaceTest.cpp
@@ -103,7 +103,8 @@
                         colorType, can, SkToBool(surf));
 
         GrBackendTexture backendTex = context->createBackendTexture(
-                kSize, kSize, colorType, GrMipMapped::kNo, GrRenderable::kYes);
+                kSize, kSize, colorType,
+                SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kYes);
         surf = SkSurface::MakeFromBackendTexture(context, backendTex,
                                                  kTopLeft_GrSurfaceOrigin, 0, colorType, nullptr,
                                                  nullptr);
@@ -128,6 +129,7 @@
                         colorType, can, SkToBool(surf));
 
         backendTex = context->createBackendTexture(kSize, kSize, colorType,
+                                                   SkColors::kTransparent,
                                                    GrMipMapped::kNo, GrRenderable::kYes);
         surf = SkSurface::MakeFromBackendTexture(context, backendTex,
                                                  kTopLeft_GrSurfaceOrigin, kSampleCnt, colorType,
@@ -197,7 +199,8 @@
             continue;
         }
         GrBackendTexture backendTex = context->createBackendTexture(
-                kSize, kSize, colorType, GrMipMapped::kNo, GrRenderable::kYes);
+                kSize, kSize, colorType, SkColors::kTransparent,
+                GrMipMapped::kNo, GrRenderable::kYes);
         if (!backendTex.isValid()) {
             continue;
         }
diff --git a/tests/TextureBindingsResetTest.cpp b/tests/TextureBindingsResetTest.cpp
index 3daa712..67c0994 100644
--- a/tests/TextureBindingsResetTest.cpp
+++ b/tests/TextureBindingsResetTest.cpp
@@ -102,7 +102,8 @@
 
     if (supportExternal) {
         GrBackendTexture texture2D = context->createBackendTexture(
-                10, 10, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+                10, 10, kRGBA_8888_SkColorType,
+                SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
         GrGLTextureInfo info2D;
         REPORTER_ASSERT(reporter, texture2D.getGLTextureInfo(&info2D));
         GrEGLImage eglImage = ctxInfo.glContext()->texture2DToEGLImage(info2D.fID);
diff --git a/tests/VkBackendSurfaceTest.cpp b/tests/VkBackendSurfaceTest.cpp
index adc40d9..ced9f88 100644
--- a/tests/VkBackendSurfaceTest.cpp
+++ b/tests/VkBackendSurfaceTest.cpp
@@ -34,6 +34,7 @@
 
     GrBackendTexture backendTex = context->createBackendTexture(1, 1,
                                                                 kRGBA_8888_SkColorType,
+                                                                SkColors::kTransparent,
                                                                 GrMipMapped::kNo,
                                                                 GrRenderable::kNo);
     REPORTER_ASSERT(reporter, backendTex.isValid());
@@ -140,6 +141,7 @@
     for (bool useExternal : {false, true}) {
         GrBackendTexture backendTex = context->createBackendTexture(1, 1,
                                                                     kRGBA_8888_SkColorType,
+                                                                    SkColors::kTransparent,
                                                                     GrMipMapped::kNo,
                                                                     GrRenderable::kNo);
         sk_sp<SkImage> image;
@@ -223,7 +225,8 @@
                 continue;
             }
             GrBackendTexture backendTex = context->createBackendTexture(
-                    4, 4, kRGBA_8888_SkColorType, GrMipMapped::kNo,
+                    4, 4, kRGBA_8888_SkColorType,
+                    SkColors::kTransparent, GrMipMapped::kNo,
                     useSurface ? GrRenderable::kYes : GrRenderable::kNo);
 
             // Make a backend texture with an external queue family and general layout.
@@ -339,7 +342,8 @@
     }
 
     GrBackendTexture backendTex = context->createBackendTexture(
-            1, 1, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo);
+            1, 1, kRGBA_8888_SkColorType,
+            SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo);
     sk_sp<SkImage> image;
     // Make a backend texture with an external queue family and general layout.
     GrVkImageInfo vkInfo;
diff --git a/tests/VkWrapTests.cpp b/tests/VkWrapTests.cpp
index 2dcf5de..081cfe4 100644
--- a/tests/VkWrapTests.cpp
+++ b/tests/VkWrapTests.cpp
@@ -38,6 +38,7 @@
 
     GrBackendTexture origBackendTex = context->createBackendTexture(kW, kH,
                                                                     kColorType,
+                                                                    SkColors::kTransparent,
                                                                     GrMipMapped::kNo,
                                                                     GrRenderable::kNo);
     GrVkImageInfo imageInfo;
@@ -92,6 +93,7 @@
 
     GrBackendTexture origBackendTex = context->createBackendTexture(kW, kH,
                                                                     kColorType,
+                                                                    SkColors::kTransparent,
                                                                     GrMipMapped::kNo,
                                                                     GrRenderable::kYes);
 
@@ -135,6 +137,7 @@
 
     GrBackendTexture origBackendTex = context->createBackendTexture(kW, kH,
                                                                     kColorType,
+                                                                    SkColors::kTransparent,
                                                                     GrMipMapped::kNo,
                                                                     GrRenderable::kYes);
     GrVkImageInfo imageInfo;
diff --git a/tests/WritePixelsTest.cpp b/tests/WritePixelsTest.cpp
index 12d5f1b..0badd6b 100644
--- a/tests/WritePixelsTest.cpp
+++ b/tests/WritePixelsTest.cpp
@@ -456,7 +456,8 @@
 
     for (auto& origin : { kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin }) {
         GrBackendTexture backendTex = context->createBackendTexture(
-                DEV_W, DEV_H, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kYes);
+                DEV_W, DEV_H, kRGBA_8888_SkColorType,
+                SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kYes);
         if (!backendTex.isValid()) {
             continue;
         }
diff --git a/third_party/skcms/version.sha1 b/third_party/skcms/version.sha1
index ebc0b05..36a939f 100755
--- a/third_party/skcms/version.sha1
+++ b/third_party/skcms/version.sha1
@@ -1 +1 @@
-ca5b4470be98480d38290c27ccd45330e020d6cd
\ No newline at end of file
+386ae89d2b6441ca431a1fb98843ae044fc12ba5
\ No newline at end of file
diff --git a/tools/SkVMBuilders.cpp b/tools/SkVMBuilders.cpp
new file mode 100644
index 0000000..5db79e3
--- /dev/null
+++ b/tools/SkVMBuilders.cpp
@@ -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 "tools/SkVMBuilders.h"
+
+// Some parts of this builder code are written less fluently than possible,
+// to avoid any ambiguity of function argument evaluation order.  This lets
+// our golden tests work portably.  In general there's no reason to fear
+// nesting calls to Builder routines.
+
+SrcoverBuilder_F32::SrcoverBuilder_F32(Fmt srcFmt, Fmt dstFmt) {
+    skvm::Arg src = arg(0),
+              dst = arg(1);
+
+    auto byte_to_f32 = [&](skvm::I32 byte) {
+        skvm::F32 _1_255 = splat(1/255.0f);
+        return mul(_1_255, to_f32(byte));
+    };
+
+    auto load = [&](skvm::Arg ptr, Fmt fmt,
+                    skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32* a) {
+        switch (fmt) {
+            case Fmt::A8: {
+                *r = *g = *b = splat(0.0f);
+                *a = byte_to_f32(load8(ptr));
+            } break;
+
+            case Fmt::G8: {
+                *r = *g = *b = byte_to_f32(load8(ptr));
+                *a = splat(1.0f);
+            } break;
+
+            case Fmt::RGBA_8888: {
+                skvm::I32 rgba = load32(ptr);
+                *r = byte_to_f32(extract(rgba, 0xff));
+                *g = byte_to_f32(extract(rgba, 0xff00));
+                *b = byte_to_f32(extract(rgba, 0xff0000));
+                *a = byte_to_f32(extract(rgba, 0xff000000));
+            } break;
+        }
+    };
+
+    skvm::F32 r,g,b,a;
+    load(src, srcFmt, &r,&g,&b,&a);
+
+    skvm::F32 dr,dg,db,da;
+    load(dst, dstFmt, &dr,&dg,&db,&da);
+
+    skvm::F32 invA = sub(splat(1.0f), a);
+    r = mad(dr, invA, r);
+    g = mad(dg, invA, g);
+    b = mad(db, invA, b);
+    a = mad(da, invA, a);
+
+    auto f32_to_byte = [&](skvm::F32 f32) {
+        skvm::F32 _255 = splat(255.0f),
+                  _0_5 = splat(0.5f);
+        return to_i32(mad(f32, _255, _0_5));
+    };
+    switch (dstFmt) {
+        case Fmt::A8: {
+            store8(dst, f32_to_byte(a));
+        } break;
+
+        case Fmt::G8: {
+            skvm::F32 _2126 = splat(0.2126f),
+                      _7152 = splat(0.7152f),
+                      _0722 = splat(0.0722f);
+            store8(dst, f32_to_byte(mad(r, _2126,
+                                    mad(g, _7152,
+                                    mul(b, _0722)))));
+        } break;
+
+        case Fmt::RGBA_8888: {
+            skvm::I32 R = f32_to_byte(r),
+                      G = f32_to_byte(g),
+                      B = f32_to_byte(b),
+                      A = f32_to_byte(a);
+
+            R = pack(R, G, 8);
+            B = pack(B, A, 8);
+            R = pack(R, B, 16);
+
+            store32(dst, R);
+        } break;
+    }
+}
+
+SrcoverBuilder_I32::SrcoverBuilder_I32() {
+    skvm::Arg src = arg(0),
+              dst = arg(1);
+
+    auto load = [&](skvm::Arg ptr,
+                    skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) {
+        skvm::I32 rgba = load32(ptr);
+        *r = extract(rgba, 0xff);
+        *g = extract(rgba, 0xff00);
+        *b = extract(rgba, 0xff0000);
+        *a = extract(rgba, 0xff000000);
+    };
+
+    skvm::I32 r,g,b,a;
+    load(src, &r,&g,&b,&a);
+
+    skvm::I32 dr,dg,db,da;
+    load(dst, &dr,&dg,&db,&da);
+
+    skvm::I32 invA = sub(splat(0xff), a);
+    r = add(r, mul_unorm8(dr, invA));
+    g = add(g, mul_unorm8(dg, invA));
+    b = add(b, mul_unorm8(db, invA));
+    a = add(a, mul_unorm8(da, invA));
+
+    r = pack(r, g, 8);
+    b = pack(b, a, 8);
+    r = pack(r, b, 16);
+    store32(dst, r);
+}
+
+SrcoverBuilder_I32_SWAR::SrcoverBuilder_I32_SWAR() {
+    skvm::Arg src = arg(0),
+              dst = arg(1);
+
+    auto load = [&](skvm::Arg ptr,
+                    skvm::I32* rb, skvm::I32* ga) {
+        skvm::I32 rgba = load32(ptr);
+        *rb = extract(rgba, 0x00ff00ff);
+        *ga = extract(rgba, 0xff00ff00);
+    };
+
+    auto mul_unorm8_SWAR = [&](skvm::I32 x, skvm::I32 y) {
+        // As above, assuming x is two SWAR bytes in lanes 0 and 2, and y is a byte.
+        skvm::I32 _255 = splat(0x00ff00ff);
+        return extract(add(mul(x, y), _255),
+                       0xff00ff00);
+    };
+
+    skvm::I32 rb, ga;
+    load(src, &rb, &ga);
+
+    skvm::I32 drb, dga;
+    load(dst, &drb, &dga);
+
+    skvm::I32 _255 = splat(0xff),
+              invA = sub(_255, shr(ga, 16));
+    rb = add(rb, mul_unorm8_SWAR(drb, invA));
+    ga = add(ga, mul_unorm8_SWAR(dga, invA));
+
+    store32(dst, pack(rb, ga, 8));
+}
diff --git a/tools/SkVMBuilders.h b/tools/SkVMBuilders.h
new file mode 100644
index 0000000..9d57837
--- /dev/null
+++ b/tools/SkVMBuilders.h
@@ -0,0 +1,29 @@
+/*
+ * 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 SkVMBuilders_DEFINED
+#define SkVMBuilders_DEFINED
+
+#include "src/core/SkVM.h"
+
+// SkVM builders used by both SkVMBench.cpp and SkVMTest.cpp.
+
+struct SrcoverBuilder_F32 : public skvm::Builder {
+    enum class Fmt { A8, G8, RGBA_8888 };
+    SrcoverBuilder_F32(Fmt srcFmt = Fmt::RGBA_8888,
+                       Fmt dstFmt = Fmt::RGBA_8888);
+};
+
+struct SrcoverBuilder_I32 : public skvm::Builder {
+    SrcoverBuilder_I32();  // 8888 over 8888
+};
+
+struct SrcoverBuilder_I32_SWAR : public skvm::Builder {
+    SrcoverBuilder_I32_SWAR();  // 8888 over 8888
+};
+
+#endif//SkVMBuilders_DEFINED
diff --git a/tools/chrome_release_branch.py b/tools/chrome_release_branch.py
index d5e52ed..d9085aa 100644
--- a/tools/chrome_release_branch.py
+++ b/tools/chrome_release_branch.py
@@ -77,15 +77,15 @@
     sys.exit(1)
   go.get(go.INFRA_GO+'/go/supported_branches/cmd/new-branch')
   subprocess.check_call(['new-branch',
-                         '--branch', new_branch,
-                         '--delete', old_branch,
+                         '--branch', new_branch[len(REFS_HEADS_PREFIX):],
+                         '--delete', old_branch[len(REFS_HEADS_PREFIX):],
                          '--owner', owner,
                          '--exclude-trybots=chromium.*',
                          '--exclude-trybots=.*Android_Framework.*'])
 
 
 def main():
-  if len(sys.argv) != 2:
+  if len(sys.argv) != 2 or '--help' in sys.argv or '-h' in sys.argv:
     print >> sys.stderr, 'Usage: %s <commit hash for branch>' % sys.argv[0]
     sys.exit(1)
   go.check()
diff --git a/tools/gpu/ProxyUtils.cpp b/tools/gpu/ProxyUtils.cpp
index d3cc1f1..9483088 100644
--- a/tools/gpu/ProxyUtils.cpp
+++ b/tools/gpu/ProxyUtils.cpp
@@ -35,7 +35,7 @@
     if (kBottomLeft_GrSurfaceOrigin == origin) {
         // We (soon will) only support using kBottomLeft with wrapped textures.
         auto backendTex = context->createBackendTexture(
-                width, height, format, GrMipMapped::kNo, renderable);
+                width, height, format, SkColors::kTransparent, GrMipMapped::kNo, renderable);
         if (!backendTex.isValid()) {
             return nullptr;
         }