Reland "[graphite] Use hardware image tiling when not subsetting"

This is a reland of commit bf557aeaaef8e0fda504103a46c9bfb261e9b460.

The breaking pixel test in Chrome has been disabled until we can roll
this change in and rebaseline the expected image.

Original change's description:
> [graphite] Use hardware image tiling when not subsetting
>
> Bug: b/238754663
> Bug: b/300467187
> Change-Id: I33248dc453e593ca8c321b521245192cb2a97ffd
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/765318
> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
> Reviewed-by: Robert Phillips <robertphillips@google.com>
> Commit-Queue: James Godfrey-Kittle <jamesgk@google.com>

Bug: b/238754663
Bug: b/300467187
Change-Id: Ib73bbc1afd7438e553aa657da34e858842de5148
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/768077
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: James Godfrey-Kittle <jamesgk@google.com>
diff --git a/include/core/SkSize.h b/include/core/SkSize.h
index 867f4ee..5a2fff9 100644
--- a/include/core/SkSize.h
+++ b/include/core/SkSize.h
@@ -52,13 +52,13 @@
     SkScalar fWidth;
     SkScalar fHeight;
 
-    static SkSize Make(SkScalar w, SkScalar h) { return {w, h}; }
+    static constexpr SkSize Make(SkScalar w, SkScalar h) { return {w, h}; }
 
-    static SkSize Make(const SkISize& src) {
+    static constexpr SkSize Make(const SkISize& src) {
         return {SkIntToScalar(src.width()), SkIntToScalar(src.height())};
     }
 
-    static SkSize MakeEmpty() { return {0, 0}; }
+    static constexpr SkSize MakeEmpty() { return {0, 0}; }
 
     void set(SkScalar w, SkScalar h) { *this = SkSize{w, h}; }
 
diff --git a/src/gpu/graphite/BuiltInCodeSnippetID.h b/src/gpu/graphite/BuiltInCodeSnippetID.h
index 3ccba4c..93d99fd 100644
--- a/src/gpu/graphite/BuiltInCodeSnippetID.h
+++ b/src/gpu/graphite/BuiltInCodeSnippetID.h
@@ -41,6 +41,7 @@
     kLocalMatrixShader,
     kImageShader,
     kCubicImageShader,
+    kHWImageShader,
     kYUVImageShader,
     kCoordClampShader,
     kDitherShader,
diff --git a/src/gpu/graphite/FactoryFunctions.cpp b/src/gpu/graphite/FactoryFunctions.cpp
index 9600043..56de206 100644
--- a/src/gpu/graphite/FactoryFunctions.cpp
+++ b/src/gpu/graphite/FactoryFunctions.cpp
@@ -270,8 +270,8 @@
     PrecompileImageShader() {}
 
 private:
-    // cubic and non-cubic sampling
-    inline static constexpr int kNumIntrinsicCombinations = 2;
+    // hardware-tiled, shader-tiled and cubic sampling
+    inline static constexpr int kNumIntrinsicCombinations = 3;
 
     int numIntrinsicCombinations() const override { return kNumIntrinsicCombinations; }
 
@@ -284,11 +284,19 @@
         static constexpr SkSamplingOptions kDefaultCubicSampling(SkCubicResampler::Mitchell());
         static constexpr SkSamplingOptions kDefaultSampling;
 
-        ImageShaderBlock::ImageData imgData(desiredCombination > 0 ? kDefaultCubicSampling
-                                                                   : kDefaultSampling,
+        // ImageShaderBlock will use hardware tiling when the subset covers the entire image, so we
+        // create subset + image size combinations where subset == imgSize (for a shader that uses
+        // hardware tiling) and subset < imgSize (for a shader that does shader-based tiling).
+        static constexpr SkRect kSubset = SkRect::MakeWH(1.0f, 1.0f);
+        static constexpr SkISize kHwTileableSize = SkISize::Make(1, 1);
+        static constexpr SkISize kNonHwTileableSize = SkISize::Make(2, 2);
+
+        ImageShaderBlock::ImageData imgData(desiredCombination == 2 ? kDefaultCubicSampling
+                                                                    : kDefaultSampling,
                                             SkTileMode::kClamp, SkTileMode::kClamp,
-                                            SkISize::MakeEmpty(), SkRect::MakeEmpty(),
-                                            ReadSwizzle::kRGBA);
+                                            desiredCombination == 1 ? kHwTileableSize
+                                                                    : kNonHwTileableSize,
+                                            kSubset, ReadSwizzle::kRGBA);
 
         ImageShaderBlock::AddBlock(keyContext, builder, gatherer, imgData);
     }
diff --git a/src/gpu/graphite/KeyHelpers.cpp b/src/gpu/graphite/KeyHelpers.cpp
index 0752460..2569e9f 100644
--- a/src/gpu/graphite/KeyHelpers.cpp
+++ b/src/gpu/graphite/KeyHelpers.cpp
@@ -496,6 +496,18 @@
     add_color_space_uniforms(imgData.fSteps, gatherer);
 }
 
+void add_hw_image_uniform_data(const ShaderCodeDictionary* dict,
+                               const ImageShaderBlock::ImageData& imgData,
+                               PipelineDataGatherer* gatherer) {
+    SkASSERT(!imgData.fSampling.useCubic);
+    VALIDATE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kHWImageShader)
+
+    gatherer->write(SkSize::Make(imgData.fImgSize));
+    gatherer->write(SkTo<int>(imgData.fReadSwizzle));
+
+    add_color_space_uniforms(imgData.fSteps, gatherer);
+}
+
 } // anonymous namespace
 
 ImageShaderBlock::ImageData::ImageData(const SkSamplingOptions& sampling,
@@ -512,6 +524,14 @@
     SkASSERT(fSteps.flags.mask() == 0);   // By default, the colorspace should have no effect
 }
 
+static bool can_do_tiling_in_hw(const Caps* caps, const ImageShaderBlock::ImageData& imgData) {
+    if (!caps->clampToBorderSupport() && (imgData.fTileModes[0] == SkTileMode::kDecal ||
+                                          imgData.fTileModes[1] == SkTileMode::kDecal)) {
+        return false;
+    }
+    return imgData.fSubset.contains(SkRect::Make(imgData.fImgSize));
+}
+
 void ImageShaderBlock::AddBlock(const KeyContext& keyContext,
                                 PaintParamsKeyBuilder* builder,
                                 PipelineDataGatherer* gatherer,
@@ -522,9 +542,18 @@
         return;
     }
 
-    gatherer->add(imgData.fSampling, imgData.fTileModes, imgData.fTextureProxy);
+    const Caps* caps = keyContext.caps();
+    const bool doTilingInHw = !imgData.fSampling.useCubic && can_do_tiling_in_hw(caps, imgData);
 
-    if (imgData.fSampling.useCubic) {
+    static constexpr SkTileMode kDefaultTileModes[2] = {SkTileMode::kClamp, SkTileMode::kClamp};
+    gatherer->add(imgData.fSampling,
+                  doTilingInHw ? imgData.fTileModes : kDefaultTileModes,
+                  imgData.fTextureProxy);
+
+    if (doTilingInHw) {
+        add_hw_image_uniform_data(keyContext.dict(), imgData, gatherer);
+        builder->addBlock(BuiltInCodeSnippetID::kHWImageShader);
+    } else if (imgData.fSampling.useCubic) {
         add_cubic_image_uniform_data(keyContext.dict(), imgData, gatherer);
         builder->addBlock(BuiltInCodeSnippetID::kCubicImageShader);
     } else {
diff --git a/src/gpu/graphite/ShaderCodeDictionary.cpp b/src/gpu/graphite/ShaderCodeDictionary.cpp
index cf395a5..a7d70a1 100644
--- a/src/gpu/graphite/ShaderCodeDictionary.cpp
+++ b/src/gpu/graphite/ShaderCodeDictionary.cpp
@@ -865,6 +865,17 @@
         { "csXformCoeffs",         SkSLType::kHalf4x4 },
 };
 
+static constexpr Uniform kHWImageShaderUniforms[] = {
+        { "imgSize",               SkSLType::kFloat2 },
+        { "readSwizzle",           SkSLType::kInt },
+        // The next 5 uniforms are for the color space transformation
+        { "csXformFlags",          SkSLType::kInt },
+        { "csXformSrcKind",        SkSLType::kInt },
+        { "csXformGamutTransform", SkSLType::kHalf3x3 },
+        { "csXformDstKind",        SkSLType::kInt },
+        { "csXformCoeffs",         SkSLType::kHalf4x4 },
+};
+
 static constexpr TextureAndSampler kISTexturesAndSamplers[] = {
         {"sampler"},
 };
@@ -894,6 +905,7 @@
 
 static constexpr char kImageShaderName[] = "sk_image_shader";
 static constexpr char kCubicImageShaderName[] = "sk_cubic_image_shader";
+static constexpr char kHWImageShaderName[] = "sk_hw_image_shader";
 
 //--------------------------------------------------------------------------------------------------
 
@@ -1604,6 +1616,16 @@
             GenerateDefaultPreamble,
             kNoChildren
     };
+    fBuiltInCodeSnippets[(int) BuiltInCodeSnippetID::kHWImageShader] = {
+            "HardwareImageShader",
+            SkSpan(kHWImageShaderUniforms),
+            SnippetRequirementFlags::kLocalCoords,
+            SkSpan(kISTexturesAndSamplers),
+            kHWImageShaderName,
+            GenerateDefaultExpression,
+            GenerateDefaultPreamble,
+            kNoChildren
+    };
     fBuiltInCodeSnippets[(int) BuiltInCodeSnippetID::kYUVImageShader] = {
             "YUVImageShader",
             SkSpan(kYUVImageShaderUniforms),
diff --git a/src/sksl/generated/sksl_graphite_frag.minified.sksl b/src/sksl/generated/sksl_graphite_frag.minified.sksl
index 1799448..056858c 100644
--- a/src/sksl/generated/sksl_graphite_frag.minified.sksl
+++ b/src/sksl/generated/sksl_graphite_frag.minified.sksl
@@ -19,49 +19,52 @@
 "(b&16)){g.xyz*=g.w;}return half4(g);}$pure float $m(int a,float b,float c,float"
 " d){switch(a){case 0:return clamp(b,c,d);case 1:{float e=d-c;return mod(b-c"
 ",e)+c;}case 2:{float e=d-c;float g=2.*e;float h=mod(b-c,g);return mix(h,g-h"
-",step(e,h))+c;}default:return b;}}$pure half4 $n(float2 a,float2 b,float4 c"
-",int d,int e,int f,int g,sampler2D h){if(d==3&&f==0){float i=floor(a.x)+.5;"
-"if(i<c.x||i>c.z){return half4(0.);}}if(e==3&&f==0){float i=floor(a.y)+.5;if"
-"(i<c.y||i>c.w){return half4(0.);}}a.x=$m(d,a.x,c.x,c.z);a.y=$m(e,a.y,c.y,c."
-"w);float4 i;if(f==0){i=float4(floor(c.xy)+.50001,ceil(c.zw)-.50001);}else{i"
-"=float4(c.xy+.50001,c.zw-.50001);}float2 j=clamp(a,i.xy,i.zw);half4 k=sample"
-"(h,j/b);k=$k(g,k);if(f==1){half2 l=half2(a-j);half2 m=abs(l);bool n=d==1;bool"
-" o=e==1;if(n||o){float p;float q;half4 r;half4 t;if(n){p=l.x>0.?i.x:i.z;r=sample"
-"(h,float2(p,j.y)/b);r=$k(g,r);}if(o){q=l.y>0.?i.y:i.w;t=sample(h,float2(j.x"
-",q)/b);t=$k(g,t);}if(n&&o){half4 u=sample(h,float2(p,q)/b);u=$k(g,u);k=mix("
-"mix(k,r,m.x),mix(t,u,m.x),m.y);}else if(n){k=mix(k,r,m.x);}else if(o){k=mix"
-"(k,t,m.y);}}if(d==3){k*=max(1.-m.x,0.);}if(e==3){k*=max(1.-m.y,0.);}}return"
-" k;}$pure half4 $o(float2 a,float2 b,float4 c,int d,int e,half4x4 g,int h,sampler2D"
-" i){float2 j=fract(a-.5);a-=1.5;a=floor(a)+.5;half4 k=g*half4(1.,half(j.x),"
-"half(j.x*j.x),half((j.x*j.x)*j.x));half4 l=g*half4(1.,half(j.y),half(j.y*j."
-"y),half((j.y*j.y)*j.y));half4 m=half4(0.);for(int n=0;n<4;++n){half4 o=half4"
-"(0.);for(int p=0;p<4;++p){o+=k[p]*$n(a+float2(float(p),float(n)),b,c,d,e,0,"
-"h,i);}m+=l[n]*o;}m.w=saturate(m.w);m.xyz=clamp(m.xyz,half3(0.),m.www);return"
-" m;}$pure half4 sk_image_shader(float2 a,float2 b,float4 c,int d,int e,int f"
-",int g,int h,int i,half3x3 j,int k,half4x4 l,sampler2D m){half4 n=$n(a,b,c,"
-"d,e,f,g,m);return sk_color_space_transform(n,h,i,j,k,l);}$pure half4 sk_cubic_image_shader"
-"(float2 a,float2 b,float4 c,int d,int e,half4x4 f,int g,int h,int i,half3x3"
-" j,int k,half4x4 l,sampler2D m){half4 n=$o(a,b,c,d,e,f,g,m);return sk_color_space_transform"
-"(n,h,i,j,k,l);}$pure half4 sk_yuv_image_shader(float2 a,float2 b,float4 c,int"
+",step(e,h))+c;}default:return b;}}$pure half4 $n(float2 a,float2 b,int c,sampler2D"
+" d){half4 e=sample(d,a/b);return $k(c,e);}$pure half4 $o(float2 a,float2 b,"
+"float4 c,int d,int e,int f,int g,sampler2D h){if(d==3&&f==0){float i=floor("
+"a.x)+.5;if(i<c.x||i>c.z){return half4(0.);}}if(e==3&&f==0){float i=floor(a."
+"y)+.5;if(i<c.y||i>c.w){return half4(0.);}}a.x=$m(d,a.x,c.x,c.z);a.y=$m(e,a."
+"y,c.y,c.w);float4 i;if(f==0){i=float4(floor(c.xy)+.50001,ceil(c.zw)-.50001)"
+";}else{i=float4(c.xy+.50001,c.zw-.50001);}float2 j=clamp(a,i.xy,i.zw);half4"
+" k=$n(j,b,g,h);if(f==1){half2 l=half2(a-j);half2 m=abs(l);bool n=d==1;bool o"
+"=e==1;if(n||o){float p;float q;half4 r;half4 t;if(n){p=l.x>0.?i.x:i.z;r=$n("
+"float2(p,j.y),b,g,h);}if(o){q=l.y>0.?i.y:i.w;t=$n(float2(j.x,q),b,g,h);}if("
+"n&&o){half4 u=$n(float2(p,q),b,g,h);k=mix(mix(k,r,m.x),mix(t,u,m.x),m.y);}else"
+" if(n){k=mix(k,r,m.x);}else if(o){k=mix(k,t,m.y);}}if(d==3){k*=max(1.-m.x,0."
+");}if(e==3){k*=max(1.-m.y,0.);}}return k;}$pure half4 $p(float2 a,float2 b,"
+"float4 c,int d,int e,half4x4 g,int h,sampler2D i){float2 j=fract(a-.5);a-=1.5"
+";a=floor(a)+.5;half4 k=g*half4(1.,half(j.x),half(j.x*j.x),half((j.x*j.x)*j."
+"x));half4 l=g*half4(1.,half(j.y),half(j.y*j.y),half((j.y*j.y)*j.y));half4 m"
+"=half4(0.);for(int n=0;n<4;++n){half4 o=half4(0.);for(int p=0;p<4;++p){o+=k"
+"[p]*$o(a+float2(float(p),float(n)),b,c,d,e,0,h,i);}m+=l[n]*o;}m.w=saturate("
+"m.w);m.xyz=clamp(m.xyz,half3(0.),m.www);return m;}$pure half4 sk_image_shader"
+"(float2 a,float2 b,float4 c,int d,int e,int f,int g,int h,int i,half3x3 j,int"
+" k,half4x4 l,sampler2D m){half4 n=$o(a,b,c,d,e,f,g,m);return sk_color_space_transform"
+"(n,h,i,j,k,l);}$pure half4 sk_cubic_image_shader(float2 a,float2 b,float4 c"
+",int d,int e,half4x4 f,int g,int h,int i,half3x3 j,int k,half4x4 l,sampler2D"
+" m){half4 n=$p(a,b,c,d,e,f,g,m);return sk_color_space_transform(n,h,i,j,k,l"
+");}$pure half4 sk_hw_image_shader(float2 a,float2 b,int c,int d,int e,half3x3"
+" f,int g,half4x4 h,sampler2D i){half4 j=$n(a,b,c,i);return sk_color_space_transform"
+"(j,d,e,f,g,h);}$pure half4 sk_yuv_image_shader(float2 a,float2 b,float4 c,int"
 " d,int e,int f,int g,half4x4 h,half4 i,half4 j,half4 k,half4 l,half3x3 m,float3"
 " n,int o,int p,half3x3 q,int r,half4x4 s,sampler2D t,sampler2D u,sampler2D v"
-",sampler2D w){half4 x=g!=0?$o(a,b,c,d,e,h,0,t):$n(a,b,c,d,e,f,0,t);half4 y="
-"g!=0?$o(a,b,c,d,e,h,0,u):$n(a,b,c,d,e,f,0,u);half4 z=g!=0?$o(a,b,c,d,e,h,0,"
-"t):$n(a,b,c,d,e,f,0,v);float A=float(dot(i,x));float B=float(dot(j,y));float"
+",sampler2D w){half4 x=g!=0?$p(a,b,c,d,e,h,0,t):$o(a,b,c,d,e,f,0,t);half4 y="
+"g!=0?$p(a,b,c,d,e,h,0,u):$o(a,b,c,d,e,f,0,u);half4 z=g!=0?$p(a,b,c,d,e,h,0,"
+"t):$o(a,b,c,d,e,f,0,v);float A=float(dot(i,x));float B=float(dot(j,y));float"
 " C=float(dot(k,z));half3 D=half3(half(A),half(B),half(C));half4 E;E.xyz=saturate"
-"(m*D+half3(n));if(l==half4(0.)){E.w=1.;}else{half4 F=g!=0?$o(a,b,c,d,e,h,0,"
-"w):$n(a,b,c,d,e,f,0,w);E.w=dot(l,F);E.xyz*=E.w;}return sk_color_space_transform"
+"(m*D+half3(n));if(l==half4(0.)){E.w=1.;}else{half4 F=g!=0?$p(a,b,c,d,e,h,0,"
+"w):$o(a,b,c,d,e,f,0,w);E.w=dot(l,F);E.xyz*=E.w;}return sk_color_space_transform"
 "(E,o,p,q,r,s);}$pure half4 sk_dither_shader(half4 a,float2 b,half c,sampler2D"
 " d){half f=sample(d,b*.125).x-.5;return half4(clamp(a.xyz+f*c,0.,a.w),a.w);"
-"}$pure float2 $p(int a,float2 b){switch(a){case 0:b.x=saturate(b.x);break;case"
+"}$pure float2 $q(int a,float2 b){switch(a){case 0:b.x=saturate(b.x);break;case"
 " 1:b.x=fract(b.x);break;case 2:{float c=b.x-1.;b.x=(c-2.*floor(c*.5))-1.;if"
 "(sk_Caps.mustDoOpBetweenFloorAndAbs){b.x=clamp(b.x,-1.,1.);}b.x=abs(b.x);break"
 ";}case 3:if(b.x<0.||b.x>1.){return float2(0.,-1.);}break;}return b;}$pure half4"
-" $q(float4[4]a,float4 b,float2 c){if(c.y<0.){return half4(0.);}else if(c.x<="
+" $r(float4[4]a,float4 b,float2 c){if(c.y<0.){return half4(0.);}else if(c.x<="
 "b.x){return half4(a[0]);}else if(c.x<b.y){return half4(mix(a[0],a[1],(c.x-b"
 ".x)/(b.y-b.x)));}else if(c.x<b.z){return half4(mix(a[1],a[2],(c.x-b.y)/(b.z"
 "-b.y)));}else if(c.x<b.w){return half4(mix(a[2],a[3],(c.x-b.z)/(b.w-b.z)));"
-"}else{return half4(a[3]);}}$pure half4 $r(float4[8]a,float4[2]b,float2 c){if"
+"}else{return half4(a[3]);}}$pure half4 $s(float4[8]a,float4[2]b,float2 c){if"
 "(c.y<0.){return half4(0.);}else if(c.x<b[1].x){if(c.x<b[0].z){if(c.x<=b[0]."
 "x){return half4(a[0]);}else if(c.x<b[0].y){return half4(mix(a[0],a[1],(c.x-"
 "b[0].x)/(b[0].y-b[0].x)));}else{return half4(mix(a[1],a[2],(c.x-b[0].y)/(b["
@@ -70,7 +73,7 @@
 "[0].w)));}}}else{if(c.x<b[1].z){if(c.x<b[1].y){return half4(mix(a[4],a[5],("
 "c.x-b[1].x)/(b[1].y-b[1].x)));}else{return half4(mix(a[5],a[6],(c.x-b[1].y)"
 "/(b[1].z-b[1].y)));}}else{if(c.x<b[1].w){return half4(mix(a[6],a[7],(c.x-b["
-"1].z)/(b[1].w-b[1].z)));}else{return half4(a[7]);}}}}half4 $s(sampler2D a,int"
+"1].z)/(b[1].w-b[1].z)));}else{return half4(a[7]);}}}}half4 $t(sampler2D a,int"
 " b,float2 c){if(c.y<0.){return half4(0.);}else if(c.x==0.){return sampleLod"
 "(a,float2(0.,.25),0.);}else if(c.x==1.){return sampleLod(a,float2(1.,.25),0."
 ");}else{int f=0;int g=b;for(int h=1;h<b;h<<=1){int i=(f+g)/2;float j=(float"
@@ -80,21 +83,21 @@
 " k=sampleLod(a,float2(i,.25),0.);float2 l=float2(sampleLod(a,float2(h,.75),"
 "0.).xy);float m=ldexp(l.x,int(l.y));l=float2(sampleLod(a,float2(i,.75),0.)."
 "xy);float n=ldexp(l.x,int(l.y));return half4(mix(float4(j),float4(k),(c.x-m"
-")/(n-m)));}}$pure float2 $t(float2 a,float2 b,float2 c){c-=a;float2 d=b-a;float"
-" e=dot(c,d)/dot(d,d);return float2(e,1.);}$pure float2 $u(float2 a,float b,"
-"float2 c){float d=distance(c,a)/b;return float2(d,1.);}$pure float2 $v(float2"
+")/(n-m)));}}$pure float2 $u(float2 a,float2 b,float2 c){c-=a;float2 d=b-a;float"
+" e=dot(c,d)/dot(d,d);return float2(e,1.);}$pure float2 $v(float2 a,float b,"
+"float2 c){float d=distance(c,a)/b;return float2(d,1.);}$pure float2 $w(float2"
 " a,float b,float c,float2 d){d-=a;float e=sk_Caps.atan2ImplementedAsAtanYOverX"
 "?2.*atan(-d.y,length(d)-d.x):atan(-d.y,-d.x);float f=((e*.159154937+.5)+b)*"
-"c;return float2(f,1.);}$pure float3x3 $w(float2 a,float2 b){return float3x3"
+"c;return float2(f,1.);}$pure float3x3 $x(float2 a,float2 b){return float3x3"
 "(0.,-1.,0.,1.,0.,0.,0.,0.,1.)*inverse(float3x3(b.y-a.y,a.x-b.x,0.,b.x-a.x,b"
-".y-a.y,0.,a.x,a.y,1.));}$pure float2 $x(float2 a,float2 b,float c,float d,float2"
+".y-a.y,0.,a.x,a.y,1.));}$pure float2 $y(float2 a,float2 b,float c,float d,float2"
 " e){const float f=.000244140625;float g=distance(a,b);float h=d-c;bool i=g<"
 "f;bool j=abs(h)<f;if(i){if(j){return float2(0.,-1.);}float k=1./h;float l=sign"
 "(h);float m=c/h;float2 n=(e-a)*k;float o=length(n)*l-m;return float2(o,1.);"
-"}else if(j){float3x3 k=$w(a,b);float l=c/g;float m=l*l;float2 n=(k*float3(e"
+"}else if(j){float3x3 k=$x(a,b);float l=c/g;float m=l*l;float2 n=(k*float3(e"
 ",1.)).xy;float o=m-n.y*n.y;if(o<0.){return float2(0.,-1.);}o=n.x+sqrt(o);return"
 " float2(o,1.);}else{float k=c/(c-d);bool l=abs(k-1.)<f;if(l){float2 m=a;a=b"
-";b=m;k=0.;}float2 m=mix(a,b,k);float3x3 n=$w(m,b);float o=abs(1.-k);float p"
+";b=m;k=0.;}float2 m=mix(a,b,k);float3x3 n=$x(m,b);float o=abs(1.-k);float p"
 "=o;float q=abs(d-c)/g;bool r=abs(q-1.)<f;if(r){o*=.5;p*=.5;}else{o*=q/(q*q-"
 "1.);p/=sqrt(abs(q*q-1.));}n=float3x3(o,0.,0.,0.,p,0.,0.,0.,1.)*n;float2 s=("
 "n*float3(e,1.)).xy;float u=1./q;float v=sign(1.-k);bool w=!r&&q>1.;float x="
@@ -102,36 +105,36 @@
 "x-s.y*s.y;if(y>=0.){if(l||v<0.){x=-sqrt(y)-s.x*u;}else{x=sqrt(y)-s.x*u;}}}if"
 "(!w&&x<0.){return float2(0.,-1.);}float y=k+v*x;if(l){y=1.-y;}return float2"
 "(y,1.);}}$pure half4 sk_linear_grad_4_shader(float2 a,float4[4]b,float4 c,float2"
-" d,float2 e,int f,int g,int h){float2 i=$t(d,e,a);i=$p(f,i);half4 j=$q(b,c,"
+" d,float2 e,int f,int g,int h){float2 i=$u(d,e,a);i=$q(f,i);half4 j=$r(b,c,"
 "i);return $interpolated_to_rgb_unpremul(j,g,h);}$pure half4 sk_linear_grad_8_shader"
 "(float2 a,float4[8]b,float4[2]c,float2 d,float2 e,int f,int g,int h){float2"
-" i=$t(d,e,a);i=$p(f,i);half4 j=$r(b,c,i);return $interpolated_to_rgb_unpremul"
+" i=$u(d,e,a);i=$q(f,i);half4 j=$s(b,c,i);return $interpolated_to_rgb_unpremul"
 "(j,g,h);}$pure half4 sk_linear_grad_tex_shader(float2 a,float2 b,float2 c,int"
-" d,int e,int f,int g,sampler2D h){float2 i=$t(b,c,a);i=$p(e,i);half4 j=$s(h"
+" d,int e,int f,int g,sampler2D h){float2 i=$u(b,c,a);i=$q(e,i);half4 j=$t(h"
 ",d,i);return $interpolated_to_rgb_unpremul(j,f,g);}$pure half4 sk_radial_grad_4_shader"
 "(float2 a,float4[4]b,float4 c,float2 d,float e,int f,int g,int h){float2 i="
-"$u(d,e,a);i=$p(f,i);half4 j=$q(b,c,i);return $interpolated_to_rgb_unpremul("
+"$v(d,e,a);i=$q(f,i);half4 j=$r(b,c,i);return $interpolated_to_rgb_unpremul("
 "j,g,h);}$pure half4 sk_radial_grad_8_shader(float2 a,float4[8]b,float4[2]c,"
-"float2 d,float e,int f,int g,int h){float2 i=$u(d,e,a);i=$p(f,i);half4 j=$r"
+"float2 d,float e,int f,int g,int h){float2 i=$v(d,e,a);i=$q(f,i);half4 j=$s"
 "(b,c,i);return $interpolated_to_rgb_unpremul(j,g,h);}$pure half4 sk_radial_grad_tex_shader"
-"(float2 a,float2 b,float c,int d,int e,int f,int g,sampler2D h){float2 i=$u"
-"(b,c,a);i=$p(e,i);half4 j=$s(h,d,i);return $interpolated_to_rgb_unpremul(j,"
+"(float2 a,float2 b,float c,int d,int e,int f,int g,sampler2D h){float2 i=$v"
+"(b,c,a);i=$q(e,i);half4 j=$t(h,d,i);return $interpolated_to_rgb_unpremul(j,"
 "f,g);}$pure half4 sk_sweep_grad_4_shader(float2 a,float4[4]b,float4 c,float2"
-" d,float e,float f,int g,int h,int i){float2 j=$v(d,e,f,a);j=$p(g,j);half4 k"
-"=$q(b,c,j);return $interpolated_to_rgb_unpremul(k,h,i);}$pure half4 sk_sweep_grad_8_shader"
+" d,float e,float f,int g,int h,int i){float2 j=$w(d,e,f,a);j=$q(g,j);half4 k"
+"=$r(b,c,j);return $interpolated_to_rgb_unpremul(k,h,i);}$pure half4 sk_sweep_grad_8_shader"
 "(float2 a,float4[8]b,float4[2]c,float2 d,float e,float f,int g,int h,int i)"
-"{float2 j=$v(d,e,f,a);j=$p(g,j);half4 k=$r(b,c,j);return $interpolated_to_rgb_unpremul"
+"{float2 j=$w(d,e,f,a);j=$q(g,j);half4 k=$s(b,c,j);return $interpolated_to_rgb_unpremul"
 "(k,h,i);}$pure half4 sk_sweep_grad_tex_shader(float2 a,float2 b,float c,float"
-" d,int e,int f,int g,int h,sampler2D i){float2 j=$v(b,c,d,a);j=$p(f,j);half4"
-" k=$s(i,e,j);return $interpolated_to_rgb_unpremul(k,g,h);}$pure half4 sk_conical_grad_4_shader"
+" d,int e,int f,int g,int h,sampler2D i){float2 j=$w(b,c,d,a);j=$q(f,j);half4"
+" k=$t(i,e,j);return $interpolated_to_rgb_unpremul(k,g,h);}$pure half4 sk_conical_grad_4_shader"
 "(float2 a,float4[4]b,float4 c,float2 d,float2 e,float f,float g,int h,int i"
-",int j){float2 k=$x(d,e,f,g,a);k=$p(h,k);half4 l=$q(b,c,k);return $interpolated_to_rgb_unpremul"
+",int j){float2 k=$y(d,e,f,g,a);k=$q(h,k);half4 l=$r(b,c,k);return $interpolated_to_rgb_unpremul"
 "(l,i,j);}$pure half4 sk_conical_grad_8_shader(float2 a,float4[8]b,float4[2]"
-"c,float2 d,float2 e,float f,float g,int h,int i,int j){float2 k=$x(d,e,f,g,"
-"a);k=$p(h,k);half4 l=$r(b,c,k);return $interpolated_to_rgb_unpremul(l,i,j);"
+"c,float2 d,float2 e,float f,float g,int h,int i,int j){float2 k=$y(d,e,f,g,"
+"a);k=$q(h,k);half4 l=$s(b,c,k);return $interpolated_to_rgb_unpremul(l,i,j);"
 "}$pure half4 sk_conical_grad_tex_shader(float2 a,float2 b,float2 c,float d,"
-"float e,int f,int g,int h,int i,sampler2D j){float2 k=$x(b,c,d,e,a);k=$p(g,"
-"k);half4 l=$s(j,f,k);return $interpolated_to_rgb_unpremul(l,h,i);}$pure half4"
+"float e,int f,int g,int h,int i,sampler2D j){float2 k=$y(b,c,d,e,a);k=$q(g,"
+"k);half4 l=$t(j,f,k);return $interpolated_to_rgb_unpremul(l,h,i);}$pure half4"
 " sk_matrix_colorfilter(half4 a,float4x4 b,float4 c,int d){if(bool(d)){a=$rgb_to_hsl"
 "(a.xyz,a.w);}else{a=unpremul(a);}half4 e=half4(b*float4(a)+c);if(bool(d)){e"
 "=$hsl_to_rgb(e.xyz,e.w);}else{e=saturate(e);e.xyz*=e.w;}return e;}$pure half4"
@@ -186,25 +189,25 @@
 "(d),dFdy(d));half f=dot(e,e);e=f>=.0001?e*inversesqrt(f):half2(.7071);half2"
 " g=half2(dFdx(c));half2 h=half2(dFdy(c));half2 i=half2(e.x*g.x+e.y*h.x,e.x*"
 "g.y+e.y*h.y);half j=.65*length(i);return half4(smoothstep(-j,j,d));}$pure float"
-" $y(float2 a,float2x2 b){float2 c=a*b;return inversesqrt(dot(c,c));}$pure float2"
-" $z(float2 a,float2 b,float c,float2x2 d){float2 e=1./(b*b+c*c);float2 g=e*"
-"a;float h=$y(g,d);float i=(.5*h)*(dot(a,g)-1.);float j=((b.x*c)*e.x)*h;return"
-" float2(j-i,j+i);}void $A(inout float2 a,float2x2 b,float2 c,float2 d,float2"
+" $z(float2 a,float2x2 b){float2 c=a*b;return inversesqrt(dot(c,c));}$pure float2"
+" $A(float2 a,float2 b,float c,float2x2 d){float2 e=1./(b*b+c*c);float2 g=e*"
+"a;float h=$z(g,d);float i=(.5*h)*(dot(a,g)-1.);float j=((b.x*c)*e.x)*h;return"
+" float2(j-i,j+i);}void $B(inout float2 a,float2x2 b,float2 c,float2 d,float2"
 " e,float2 f){float2 g=f-d;if(g.x>0.&&g.y>0.){if(f.x>0.&&f.y>0.||c.x>0.&&c.y"
-"<0.){float2 h=$z(g*e,f,c.x,b);if(f.x-c.x<=0.){h.y=1.;}else{h.y*=-1.;}a=min("
-"a,h);}else if(c.y==0.){float h=((c.x-g.x)-g.y)*$y(e,b);a.x=min(a.x,h);}}}void"
-" $B(inout float2 a,float2x2 b,float2 c,float4 e,float4 f,float4 g){$A(a,b,c"
-",e.xy,float2(-1.),float2(f.x,g.x));$A(a,b,c,e.zy,float2(1.,-1.),float2(f.y,"
-"g.y));$A(a,b,c,e.zw,float2(1.),float2(f.z,g.z));$A(a,b,c,e.xw,float2(-1.,1."
+"<0.){float2 h=$A(g*e,f,c.x,b);if(f.x-c.x<=0.){h.y=1.;}else{h.y*=-1.;}a=min("
+"a,h);}else if(c.y==0.){float h=((c.x-g.x)-g.y)*$z(e,b);a.x=min(a.x,h);}}}void"
+" $C(inout float2 a,float2x2 b,float2 c,float4 e,float4 f,float4 g){$B(a,b,c"
+",e.xy,float2(-1.),float2(f.x,g.x));$B(a,b,c,e.zy,float2(1.,-1.),float2(f.y,"
+"g.y));$B(a,b,c,e.zw,float2(1.),float2(f.z,g.z));$B(a,b,c,e.xw,float2(-1.,1."
 "),float2(f.w,g.w));}$pure half4 analytic_rrect_coverage_fn(float4 a,float4 b"
 ",float4 c,float4 d,float4 e,float2 f,float2 g){if(g.x>0.){return half4(1.);"
 "}else if(g.y>1.){float2 h=min(c.xy,c.zw);float i=min(h.x,h.y)*a.w;float j=("
 "g.y-1.)*a.w;float k=coverage_bias(j);return half4(half(saturate(j*(i+k))));"
-"}else{float2x2 h=float2x2(b)*(1./a.w);float2 i=float2($y(float2(1.,0.),h),$y"
+"}else{float2x2 h=float2x2(b)*(1./a.w);float2 i=float2($z(float2(1.,0.),h),$z"
 "(float2(0.,1.),h));float2 j=i*(f.x+min(c.xy,c.zw));float2 k=float2(min(j.x,"
 "j.y),-1.);float l;float m;if(g.x>-.95){float2 n=i*((c.xy+c.zw)+2.*f.xx);l=min"
 "(min(n.x,n.y),1.);m=coverage_bias(l);}else{float2 n=(2.*f.x)*i;float2 o=n-j"
 ";k.y=-max(o.x,o.y);if(f.x>0.){float p=min(n.x,n.y);if(o.y>=-.5&&n.y>p){p=n."
 "y;}if(o.x>=-.5&&n.x>p){p=n.x;}l=min(p,1.);m=coverage_bias(l);}else{l=(m=1.)"
-";}}$B(k,h,f,c,d,e);float n=min(g.y,0.)*a.w;float o=l*(min(k.x+n,-k.y)+m);return"
+";}}$C(k,h,f,c,d,e);float n=min(g.y,0.)*a.w;float o=l*(min(k.x+n,-k.y)+m);return"
 " half4(half(saturate(o)));}}";
diff --git a/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl b/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
index 8b65133..5dca0e0 100644
--- a/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
+++ b/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
@@ -34,7 +34,9 @@
 " 0:return clamp(f,low,high);case 1:{float length=high-low;return mod(f-low,"
 "length)+low;}case 2:{float length=high-low;float length2=2.*length;float tmp"
 "=mod(f-low,length2);return mix(tmp,length2-tmp,step(length,tmp))+low;}default"
-":return f;}}$pure half4 $sample_image(float2 pos,float2 imgSize,float4 subset"
+":return f;}}$pure half4 $sample_image(float2 pos,float2 imgSize,int readSwizzle"
+",sampler2D s){half4 color=sample(s,pos/imgSize);return $apply_swizzle(readSwizzle"
+",color);}$pure half4 $sample_image_subset(float2 pos,float2 imgSize,float4 subset"
 ",int tileModeX,int tileModeY,int filterMode,int readSwizzle,sampler2D s){if"
 "(tileModeX==$kTileModeDecal&&filterMode==$kFilterModeNearest){float snappedX"
 "=floor(pos.x)+.5;if(snappedX<subset.x||snappedX>subset.z){return half4(0.);"
@@ -44,35 +46,33 @@
 "y,subset.y,subset.w);float4 insetClamp;if(filterMode==$kFilterModeNearest){"
 "insetClamp=float4(floor(subset.xy)+$kLinearInset,ceil(subset.zw)-$kLinearInset"
 ");}else{insetClamp=float4(subset.xy+$kLinearInset,subset.zw-$kLinearInset);"
-"}float2 clampedPos=clamp(pos,insetClamp.xy,insetClamp.zw);half4 color=sample"
-"(s,clampedPos/imgSize);color=$apply_swizzle(readSwizzle,color);if(filterMode"
-"==$kFilterModeLinear){half2 error=half2(pos-clampedPos);half2 absError=abs("
-"error);bool sampleExtraX=tileModeX==$kTileModeRepeat;bool sampleExtraY=tileModeY"
-"==$kTileModeRepeat;if(sampleExtraX||sampleExtraY){float extraCoordX;float extraCoordY"
-";half4 extraColorX;half4 extraColorY;if(sampleExtraX){extraCoordX=error.x>0."
-"?insetClamp.x:insetClamp.z;extraColorX=sample(s,float2(extraCoordX,clampedPos"
-".y)/imgSize);extraColorX=$apply_swizzle(readSwizzle,extraColorX);}if(sampleExtraY"
-"){extraCoordY=error.y>0.?insetClamp.y:insetClamp.w;extraColorY=sample(s,float2"
-"(clampedPos.x,extraCoordY)/imgSize);extraColorY=$apply_swizzle(readSwizzle,"
-"extraColorY);}if(sampleExtraX&&sampleExtraY){half4 extraColorXY=sample(s,float2"
-"(extraCoordX,extraCoordY)/imgSize);extraColorXY=$apply_swizzle(readSwizzle,"
-"extraColorXY);color=mix(mix(color,extraColorX,absError.x),mix(extraColorY,extraColorXY"
-",absError.x),absError.y);}else if(sampleExtraX){color=mix(color,extraColorX"
-",absError.x);}else if(sampleExtraY){color=mix(color,extraColorY,absError.y)"
-";}}if(tileModeX==$kTileModeDecal){color*=max(1.-absError.x,0.);}if(tileModeY"
-"==$kTileModeDecal){color*=max(1.-absError.y,0.);}}return color;}$pure half4"
-" $cubic_filter_image(float2 pos,float2 imgSize,float4 subset,int tileModeX,"
-"int tileModeY,half4x4 coeffs,int readSwizzle,sampler2D s){float2 f=fract(pos"
-"-.5);pos-=1.5;pos=floor(pos)+.5;half4 wx=coeffs*half4(1.,half(f.x),half(f.x"
-"*f.x),half((f.x*f.x)*f.x));half4 wy=coeffs*half4(1.,half(f.y),half(f.y*f.y)"
-",half((f.y*f.y)*f.y));half4 color=half4(0.);for(int y=0;y<4;++y){half4 rowColor"
-"=half4(0.);for(int x=0;x<4;++x){rowColor+=wx[x]*$sample_image(pos+float2(float"
-"(x),float(y)),imgSize,subset,tileModeX,tileModeY,$kFilterModeNearest,readSwizzle"
-",s);}color+=wy[y]*rowColor;}color.w=saturate(color.w);color.xyz=clamp(color"
-".xyz,half3(0.),color.www);return color;}$pure half4 sk_image_shader(float2 coords"
-",float2 imgSize,float4 subset,int tileModeX,int tileModeY,int filterMode,int"
-" readSwizzle,int csXformFlags,int csXformSrcKind,half3x3 csXformGamutTransform"
-",int csXformDstKind,half4x4 csXformCoeffs,sampler2D s){half4 sampleColor=$sample_image"
+"}float2 clampedPos=clamp(pos,insetClamp.xy,insetClamp.zw);half4 color=$sample_image"
+"(clampedPos,imgSize,readSwizzle,s);if(filterMode==$kFilterModeLinear){half2"
+" error=half2(pos-clampedPos);half2 absError=abs(error);bool sampleExtraX=tileModeX"
+"==$kTileModeRepeat;bool sampleExtraY=tileModeY==$kTileModeRepeat;if(sampleExtraX"
+"||sampleExtraY){float extraCoordX;float extraCoordY;half4 extraColorX;half4"
+" extraColorY;if(sampleExtraX){extraCoordX=error.x>0.?insetClamp.x:insetClamp"
+".z;extraColorX=$sample_image(float2(extraCoordX,clampedPos.y),imgSize,readSwizzle"
+",s);}if(sampleExtraY){extraCoordY=error.y>0.?insetClamp.y:insetClamp.w;extraColorY"
+"=$sample_image(float2(clampedPos.x,extraCoordY),imgSize,readSwizzle,s);}if("
+"sampleExtraX&&sampleExtraY){half4 extraColorXY=$sample_image(float2(extraCoordX"
+",extraCoordY),imgSize,readSwizzle,s);color=mix(mix(color,extraColorX,absError"
+".x),mix(extraColorY,extraColorXY,absError.x),absError.y);}else if(sampleExtraX"
+"){color=mix(color,extraColorX,absError.x);}else if(sampleExtraY){color=mix("
+"color,extraColorY,absError.y);}}if(tileModeX==$kTileModeDecal){color*=max(1."
+"-absError.x,0.);}if(tileModeY==$kTileModeDecal){color*=max(1.-absError.y,0."
+");}}return color;}$pure half4 $cubic_filter_image(float2 pos,float2 imgSize"
+",float4 subset,int tileModeX,int tileModeY,half4x4 coeffs,int readSwizzle,sampler2D"
+" s){float2 f=fract(pos-.5);pos-=1.5;pos=floor(pos)+.5;half4 wx=coeffs*half4"
+"(1.,half(f.x),half(f.x*f.x),half((f.x*f.x)*f.x));half4 wy=coeffs*half4(1.,half"
+"(f.y),half(f.y*f.y),half((f.y*f.y)*f.y));half4 color=half4(0.);for(int y=0;"
+"y<4;++y){half4 rowColor=half4(0.);for(int x=0;x<4;++x){rowColor+=wx[x]*$sample_image_subset"
+"(pos+float2(float(x),float(y)),imgSize,subset,tileModeX,tileModeY,$kFilterModeNearest"
+",readSwizzle,s);}color+=wy[y]*rowColor;}color.w=saturate(color.w);color.xyz"
+"=clamp(color.xyz,half3(0.),color.www);return color;}$pure half4 sk_image_shader"
+"(float2 coords,float2 imgSize,float4 subset,int tileModeX,int tileModeY,int"
+" filterMode,int readSwizzle,int csXformFlags,int csXformSrcKind,half3x3 csXformGamutTransform"
+",int csXformDstKind,half4x4 csXformCoeffs,sampler2D s){half4 sampleColor=$sample_image_subset"
 "(coords,imgSize,subset,tileModeX,tileModeY,filterMode,readSwizzle,s);return"
 " sk_color_space_transform(sampleColor,csXformFlags,csXformSrcKind,csXformGamutTransform"
 ",csXformDstKind,csXformCoeffs);}$pure half4 sk_cubic_image_shader(float2 coords"
@@ -81,27 +81,32 @@
 ",int csXformDstKind,half4x4 csXformCoeffs,sampler2D s){half4 sampleColor=$cubic_filter_image"
 "(coords,imgSize,subset,tileModeX,tileModeY,cubicCoeffs,readSwizzle,s);return"
 " sk_color_space_transform(sampleColor,csXformFlags,csXformSrcKind,csXformGamutTransform"
-",csXformDstKind,csXformCoeffs);}$pure half4 sk_yuv_image_shader(float2 coords"
-",float2 imgSize,float4 subset,int tileModeX,int tileModeY,int filterMode,int"
-" useCubic,half4x4 cubicCoeffs,half4 channelSelectY,half4 channelSelectU,half4"
-" channelSelectV,half4 channelSelectA,half3x3 yuvToRGBMatrix,float3 yuvToRGBTranslate"
-",int csXformFlags,int csXformSrcKind,half3x3 csXformGamutTransform,int csXformDstKind"
-",half4x4 csXformCoeffs,sampler2D sY,sampler2D sU,sampler2D sV,sampler2D sA)"
-"{half4 sampleColorY=useCubic!=0?$cubic_filter_image(coords,imgSize,subset,tileModeX"
-",tileModeY,cubicCoeffs,$kReadSwizzleNormalRGBA,sY):$sample_image(coords,imgSize"
+",csXformDstKind,csXformCoeffs);}$pure half4 sk_hw_image_shader(float2 coords"
+",float2 imgSize,int readSwizzle,int csXformFlags,int csXformSrcKind,half3x3"
+" csXformGamutTransform,int csXformDstKind,half4x4 csXformCoeffs,sampler2D s"
+"){half4 sampleColor=$sample_image(coords,imgSize,readSwizzle,s);return sk_color_space_transform"
+"(sampleColor,csXformFlags,csXformSrcKind,csXformGamutTransform,csXformDstKind"
+",csXformCoeffs);}$pure half4 sk_yuv_image_shader(float2 coords,float2 imgSize"
+",float4 subset,int tileModeX,int tileModeY,int filterMode,int useCubic,half4x4"
+" cubicCoeffs,half4 channelSelectY,half4 channelSelectU,half4 channelSelectV"
+",half4 channelSelectA,half3x3 yuvToRGBMatrix,float3 yuvToRGBTranslate,int csXformFlags"
+",int csXformSrcKind,half3x3 csXformGamutTransform,int csXformDstKind,half4x4"
+" csXformCoeffs,sampler2D sY,sampler2D sU,sampler2D sV,sampler2D sA){half4 sampleColorY"
+"=useCubic!=0?$cubic_filter_image(coords,imgSize,subset,tileModeX,tileModeY,"
+"cubicCoeffs,$kReadSwizzleNormalRGBA,sY):$sample_image_subset(coords,imgSize"
 ",subset,tileModeX,tileModeY,filterMode,$kReadSwizzleNormalRGBA,sY);half4 sampleColorU"
 "=useCubic!=0?$cubic_filter_image(coords,imgSize,subset,tileModeX,tileModeY,"
-"cubicCoeffs,$kReadSwizzleNormalRGBA,sU):$sample_image(coords,imgSize,subset"
-",tileModeX,tileModeY,filterMode,$kReadSwizzleNormalRGBA,sU);half4 sampleColorV"
+"cubicCoeffs,$kReadSwizzleNormalRGBA,sU):$sample_image_subset(coords,imgSize"
+",subset,tileModeX,tileModeY,filterMode,$kReadSwizzleNormalRGBA,sU);half4 sampleColorV"
 "=useCubic!=0?$cubic_filter_image(coords,imgSize,subset,tileModeX,tileModeY,"
-"cubicCoeffs,$kReadSwizzleNormalRGBA,sY):$sample_image(coords,imgSize,subset"
-",tileModeX,tileModeY,filterMode,$kReadSwizzleNormalRGBA,sV);float Y=float(dot"
-"(channelSelectY,sampleColorY));float U=float(dot(channelSelectU,sampleColorU"
+"cubicCoeffs,$kReadSwizzleNormalRGBA,sY):$sample_image_subset(coords,imgSize"
+",subset,tileModeX,tileModeY,filterMode,$kReadSwizzleNormalRGBA,sV);float Y="
+"float(dot(channelSelectY,sampleColorY));float U=float(dot(channelSelectU,sampleColorU"
 "));float V=float(dot(channelSelectV,sampleColorV));half3 preColor=half3(half"
 "(Y),half(U),half(V));half4 sampleColor;sampleColor.xyz=saturate(yuvToRGBMatrix"
 "*preColor+half3(yuvToRGBTranslate));if(channelSelectA==half4(0.)){sampleColor"
 ".w=1.;}else{half4 sampleColorA=useCubic!=0?$cubic_filter_image(coords,imgSize"
-",subset,tileModeX,tileModeY,cubicCoeffs,$kReadSwizzleNormalRGBA,sA):$sample_image"
+",subset,tileModeX,tileModeY,cubicCoeffs,$kReadSwizzleNormalRGBA,sA):$sample_image_subset"
 "(coords,imgSize,subset,tileModeX,tileModeY,filterMode,$kReadSwizzleNormalRGBA"
 ",sA);sampleColor.w=dot(channelSelectA,sampleColorA);sampleColor.xyz*=sampleColor"
 ".w;}return sk_color_space_transform(sampleColor,csXformFlags,csXformSrcKind"
diff --git a/src/sksl/sksl_graphite_frag.sksl b/src/sksl/sksl_graphite_frag.sksl
index 02bd4df..92f1f86 100644
--- a/src/sksl/sksl_graphite_frag.sksl
+++ b/src/sksl/sksl_graphite_frag.sksl
@@ -153,14 +153,19 @@
     }
 }
 
-$pure half4 $sample_image(float2 pos,
-                          float2 imgSize,
-                          float4 subset,
-                          int tileModeX,
-                          int tileModeY,
-                          int filterMode,
-                          int readSwizzle,
-                          sampler2D s) {
+$pure half4 $sample_image(float2 pos, float2 imgSize, int readSwizzle, sampler2D s) {
+    half4 color = sample(s, pos / imgSize);
+    return $apply_swizzle(readSwizzle, color);
+}
+
+$pure half4 $sample_image_subset(float2 pos,
+                                 float2 imgSize,
+                                 float4 subset,
+                                 int tileModeX,
+                                 int tileModeY,
+                                 int filterMode,
+                                 int readSwizzle,
+                                 sampler2D s) {
     // Do hard-edge shader transitions to the border color for nearest-neighbor decal tiling at the
     // subset boundaries. Snap the input coordinates to nearest neighbor before comparing to the
     // subset rect, to avoid GPU interpolation errors. See https://crbug.com/skia/10403.
@@ -189,8 +194,7 @@
         insetClamp = float4(subset.xy + $kLinearInset, subset.zw - $kLinearInset);
     }
     float2 clampedPos = clamp(pos, insetClamp.xy, insetClamp.zw);
-    half4 color = sample(s, clampedPos / imgSize);
-    color = $apply_swizzle(readSwizzle, color);
+    half4 color = $sample_image(clampedPos, imgSize, readSwizzle, s);
 
     if (filterMode == $kFilterModeLinear) {
         // Remember the amount the coord moved for clamping. This is used to implement shader-based
@@ -210,17 +214,17 @@
             half4 extraColorY;
             if (sampleExtraX) {
                 extraCoordX = error.x > 0 ? insetClamp.x : insetClamp.z;
-                extraColorX = sample(s, float2(extraCoordX, clampedPos.y) / imgSize);
-                extraColorX = $apply_swizzle(readSwizzle, extraColorX);
+                extraColorX = $sample_image(float2(extraCoordX, clampedPos.y),
+                                            imgSize, readSwizzle, s);
             }
             if (sampleExtraY) {
                 extraCoordY = error.y > 0 ? insetClamp.y : insetClamp.w;
-                extraColorY = sample(s, float2(clampedPos.x, extraCoordY) / imgSize);
-                extraColorY = $apply_swizzle(readSwizzle, extraColorY);
+                extraColorY = $sample_image(float2(clampedPos.x, extraCoordY),
+                                            imgSize, readSwizzle, s);
             }
             if (sampleExtraX && sampleExtraY) {
-                half4 extraColorXY = sample(s, float2(extraCoordX, extraCoordY) / imgSize);
-                extraColorXY = $apply_swizzle(readSwizzle, extraColorXY);
+                half4 extraColorXY = $sample_image(float2(extraCoordX, extraCoordY),
+                                                   imgSize, readSwizzle, s);
                 color = mix(mix(color, extraColorX, absError.x),
                             mix(extraColorY, extraColorXY, absError.x),
                             absError.y);
@@ -265,9 +269,9 @@
     for (int y = 0; y < 4; ++y) {
         half4 rowColor = half4(0);
         for (int x = 0; x < 4; ++x) {
-            rowColor += wx[x] * $sample_image(pos + float2(x, y), imgSize, subset,
-                                              tileModeX, tileModeY, $kFilterModeNearest,
-                                              readSwizzle, s);
+            rowColor += wx[x] * $sample_image_subset(pos + float2(x, y), imgSize, subset,
+                                                     tileModeX, tileModeY, $kFilterModeNearest,
+                                                     readSwizzle, s);
         }
         color += wy[y] * rowColor;
     }
@@ -290,8 +294,8 @@
                             int csXformDstKind,
                             half4x4 csXformCoeffs,
                             sampler2D s) {
-    half4 sampleColor =
-        $sample_image(coords, imgSize, subset, tileModeX, tileModeY, filterMode, readSwizzle, s);
+    half4 sampleColor = $sample_image_subset(coords, imgSize, subset, tileModeX, tileModeY,
+                                             filterMode, readSwizzle, s);
     return sk_color_space_transform(sampleColor, csXformFlags, csXformSrcKind,
                                     csXformGamutTransform, csXformDstKind, csXformCoeffs);
 }
@@ -315,6 +319,20 @@
                                     csXformGamutTransform, csXformDstKind, csXformCoeffs);
 }
 
+$pure half4 sk_hw_image_shader(float2 coords,
+                               float2 imgSize,
+                               int readSwizzle,
+                               int csXformFlags,
+                               int csXformSrcKind,
+                               half3x3 csXformGamutTransform,
+                               int csXformDstKind,
+                               half4x4 csXformCoeffs,
+                               sampler2D s) {
+    half4 sampleColor = $sample_image(coords, imgSize, readSwizzle, s);
+    return sk_color_space_transform(sampleColor, csXformFlags, csXformSrcKind,
+                                    csXformGamutTransform, csXformDstKind, csXformCoeffs);
+}
+
 $pure half4 sk_yuv_image_shader(float2 coords,
                                 float2 imgSize,
                                 float4 subset,
@@ -341,18 +359,18 @@
     half4 sampleColorY = (useCubic != 0)
         ? $cubic_filter_image(coords, imgSize, subset, tileModeX, tileModeY, cubicCoeffs,
                               $kReadSwizzleNormalRGBA, sY)
-        : $sample_image(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
-                        $kReadSwizzleNormalRGBA, sY);
+        : $sample_image_subset(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
+                               $kReadSwizzleNormalRGBA, sY);
     half4 sampleColorU = (useCubic != 0)
         ? $cubic_filter_image(coords, imgSize, subset, tileModeX, tileModeY, cubicCoeffs,
                               $kReadSwizzleNormalRGBA, sU)
-        : $sample_image(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
-                        $kReadSwizzleNormalRGBA, sU);
+        : $sample_image_subset(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
+                               $kReadSwizzleNormalRGBA, sU);
     half4 sampleColorV = (useCubic != 0)
         ? $cubic_filter_image(coords, imgSize, subset, tileModeX, tileModeY, cubicCoeffs,
                               $kReadSwizzleNormalRGBA, sY)
-        : $sample_image(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
-                        $kReadSwizzleNormalRGBA, sV);
+        : $sample_image_subset(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
+                               $kReadSwizzleNormalRGBA, sV);
     float Y = dot(channelSelectY, sampleColorY);
     float U = dot(channelSelectU, sampleColorU);
     float V = dot(channelSelectV, sampleColorV);
@@ -365,8 +383,8 @@
         half4 sampleColorA = (useCubic != 0)
             ? $cubic_filter_image(coords, imgSize, subset, tileModeX, tileModeY, cubicCoeffs,
                                   $kReadSwizzleNormalRGBA, sA)
-            : $sample_image(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
-                            $kReadSwizzleNormalRGBA, sA);
+            : $sample_image_subset(coords, imgSize, subset, tileModeX, tileModeY, filterMode,
+                                   $kReadSwizzleNormalRGBA, sA);
         sampleColor.a = dot(channelSelectA, sampleColorA);
         // premul alpha
         sampleColor.rgb *= sampleColor.a;
diff --git a/tests/graphite/CombinationBuilderTest.cpp b/tests/graphite/CombinationBuilderTest.cpp
index bc633da..46ad4ef 100644
--- a/tests/graphite/CombinationBuilderTest.cpp
+++ b/tests/graphite/CombinationBuilderTest.cpp
@@ -94,14 +94,14 @@
 void big_test(const KeyContext& keyContext,
               PipelineDataGatherer* gatherer,
               skiatest::Reporter* reporter) {
-    // paintOptions (17)
-    //  |- sweepGrad_0 (2) | blendShader_0 (15)
+    // paintOptions (35)
+    //  |- sweepGrad_0 (2) | blendShader_0 (33)
     //  |                     0: kSrc (1)
     //  |                     1: (dsts) linearGrad_0 (2) | solid_0 (1)
-    //  |                     2: (srcs) linearGrad_1 (2) | blendShader_1 (3)
+    //  |                     2: (srcs) linearGrad_1 (2) | blendShader_1 (9)
     //  |                                            0: kDst (1)
     //  |                                            1: (dsts) radGrad_0 (2) | solid_1 (1)
-    //  |                                            2: (srcs) imageShader_0 (1)
+    //  |                                            2: (srcs) imageShader_0 (3)
     //  |
     //  |- 4-built-in-blend-modes (just 1 since all are PorterDuff)
 
@@ -146,7 +146,7 @@
     // now, blend modes
     paintOptions.setBlendModes(evenMoreBlendModes);                             // c array
 
-    REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == 26);
+    REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == 35);
 
     std::vector<UniquePaintParamsID> precompileIDs;
     paintOptions.priv().buildCombinations(keyContext,
@@ -157,7 +157,7 @@
                                               precompileIDs.push_back(id);
                                           });
 
-    SkASSERT(precompileIDs.size() == 26);
+    SkASSERT(precompileIDs.size() == 35);
 }
 
 template <typename T>