[graphite] Cubic filtering in image shader

If we decide we don't want to pass in a 4x4 matrix uniform every time we
use an image shader, we can perhaps a) pass in the B and C parameters
instead, or b) use a separate shader snippet for cubic filtering.

Bug: b/238754369
Change-Id: I6747db74321f12b6ccc78b138456f16e5d6a2767
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/585836
Commit-Queue: James Godfrey-Kittle <jamesgk@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/core/SkKeyHelpers.cpp b/src/core/SkKeyHelpers.cpp
index dfa4870..3e6dec9 100644
--- a/src/core/SkKeyHelpers.cpp
+++ b/src/core/SkKeyHelpers.cpp
@@ -17,6 +17,7 @@
 #include "src/core/SkRuntimeEffectPriv.h"
 #include "src/core/SkShaderCodeDictionary.h"
 #include "src/core/SkUniform.h"
+#include "src/shaders/SkImageShader.h"
 
 #ifdef SK_GRAPHITE_ENABLED
 #include "src/gpu/Blend.h"
@@ -361,6 +362,13 @@
     gatherer->write(SkTo<int>(imgData.fTileModes[0]));
     gatherer->write(SkTo<int>(imgData.fTileModes[1]));
     gatherer->write(SkTo<int>(imgData.fSampling.filter));
+    gatherer->write(imgData.fSampling.useCubic);
+    if (imgData.fSampling.useCubic) {
+        const SkCubicResampler& cubic = imgData.fSampling.cubic;
+        gatherer->write(SkImageShader::CubicResamplerMatrix(cubic.B, cubic.C));
+    } else {
+        gatherer->write(SkM44());
+    }
 
     gatherer->addFlags(dict->getSnippetRequirementFlags(SkBuiltInCodeSnippetID::kImageShader));
 }
diff --git a/src/core/SkShaderCodeDictionary.cpp b/src/core/SkShaderCodeDictionary.cpp
index 9231d35..914329c 100644
--- a/src/core/SkShaderCodeDictionary.cpp
+++ b/src/core/SkShaderCodeDictionary.cpp
@@ -602,6 +602,8 @@
         { "tilemodeX",   SkSLType::kInt },
         { "tilemodeY",   SkSLType::kInt },
         { "filterMode",  SkSLType::kInt },
+        { "useCubic",    SkSLType::kInt },
+        { "cubicCoeffs", SkSLType::kFloat4x4 },
 };
 
 static constexpr SkTextureAndSampler kISTexturesAndSamplers[] = {
diff --git a/src/sksl/generated/sksl_graphite_frag.minified.sksl b/src/sksl/generated/sksl_graphite_frag.minified.sksl
index 6175a2f..115dac5 100644
--- a/src/sksl/generated/sksl_graphite_frag.minified.sksl
+++ b/src/sksl/generated/sksl_graphite_frag.minified.sksl
@@ -9,54 +9,64 @@
 "(f,low,high);case $kTileModeRepeat:{float length=high-low;return(mod(f-low,"
 "length)+low);}case $kTileModeMirror:{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 sk_image_shader(float4 coords,float4x4"
-" preLocal,float2 imgSize,float4 subset,int tileModeX,int tileModeY,int filterMode"
-",sampler2D s){float2 pos=(preLocal*coords).xy;if(tileModeX==$kTileModeDecal"
-"&&filterMode==$kFilterModeNearest){float snappedX=floor(pos.x)+0.5;if(snappedX"
-"<subset.x||snappedX>subset.z){return half4(0);}}if(tileModeY==$kTileModeDecal"
-"&&filterMode==$kFilterModeNearest){float snappedY=floor(pos.y)+0.5;if(snappedY"
-"<subset.y||snappedY>subset.w){return half4(0);}}pos.x=$tile(tileModeX,pos.x"
-",subset.x,subset.z);pos.y=$tile(tileModeY,pos.y,subset.y,subset.w);float4 insetClamp"
-";if(filterMode==$kFilterModeNearest){insetClamp=float4(floor(subset.xy)+0.5"
-",ceil(subset.zw)-0.5);}else{insetClamp=float4(subset.xy+0.5,subset.zw-0.5);"
-"}float2 clampedPos=clamp(pos,insetClamp.xy,insetClamp.zw);half4 color=sample"
-"(s,clampedPos/imgSize);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);}if(sampleExtraY){extraCoordY"
-"=error.y>0?insetClamp.y:insetClamp.w;extraColorY=sample(s,float2(clampedPos"
-".x,extraCoordY)/imgSize);}if(sampleExtraX&&sampleExtraY){half4 extraColorXY"
+"tmp))+low);}default:return f;}}$pure half4 $sample_image(float2 pos,float2 imgSize"
+",float4 subset,int tileModeX,int tileModeY,int filterMode,sampler2D s){if(tileModeX"
+"==$kTileModeDecal&&filterMode==$kFilterModeNearest){float snappedX=floor(pos"
+".x)+0.5;if(snappedX<subset.x||snappedX>subset.z){return half4(0);}}if(tileModeY"
+"==$kTileModeDecal&&filterMode==$kFilterModeNearest){float snappedY=floor(pos"
+".y)+0.5;if(snappedY<subset.y||snappedY>subset.w){return half4(0);}}pos.x=$tile"
+"(tileModeX,pos.x,subset.x,subset.z);pos.y=$tile(tileModeY,pos.y,subset.y,subset"
+".w);float4 insetClamp;if(filterMode==$kFilterModeNearest){insetClamp=float4"
+"(floor(subset.xy)+0.5,ceil(subset.zw)-0.5);}else{insetClamp=float4(subset.xy"
+"+0.5,subset.zw-0.5);}float2 clampedPos=clamp(pos,insetClamp.xy,insetClamp.zw"
+");half4 color=sample(s,clampedPos/imgSize);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);}if(sampleExtraY"
+"){extraCoordY=error.y>0?insetClamp.y:insetClamp.w;extraColorY=sample(s,float2"
+"(clampedPos.x,extraCoordY)/imgSize);}if(sampleExtraX&&sampleExtraY){half4 extraColorXY"
 "=sample(s,float2(extraCoordX,extraCoordY)/imgSize);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 float2 $tile_grad(int tileMode,float2 t){switch"
-"(tileMode){case $kTileModeClamp:t.x=clamp(t.x,0,1);break;case $kTileModeRepeat"
-":t.x=fract(t.x);break;case $kTileModeMirror:{float t_1=t.x-1;t.x=t_1-2*floor"
-"(t_1*0.5)-1;if(sk_Caps.mustDoOpBetweenFloorAndAbs){t.x=clamp(t.x,-1,1);}t.x"
-"=abs(t.x);break;}case $kTileModeDecal:if(t.x<0||t.x>1){return float2(0,-1);"
-"}break;}return t;}$pure half4 $colorize_grad_4(float4 colorsParam[4],float offsetsParam"
-"[4],float2 t){if(t.y<0){return half4(0);}else if(t.x<=offsetsParam[0]){return"
-" half4(colorsParam[0]);}else if(t.x<offsetsParam[1]){return half4(mix(colorsParam"
-"[0],colorsParam[1],(t.x-offsetsParam[0])/(offsetsParam[1]-offsetsParam[0]))"
-");}else if(t.x<offsetsParam[2]){return half4(mix(colorsParam[1],colorsParam"
-"[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam[1])));}else if(t.x<"
-"offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam[3],(t.x-offsetsParam"
-"[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4(colorsParam[3])"
-";}}$pure half4 $colorize_grad_8(float4 colorsParam[8],float offsetsParam[8]"
-",float2 t){if(t.y<0){return half4(0);}else if(t.x<offsetsParam[4]){if(t.x<offsetsParam"
-"[2]){if(t.x<=offsetsParam[0]){return half4(colorsParam[0]);}else if(t.x<offsetsParam"
+".y,0);}}return color;}$pure half4 $cubic_filter_image(float2 pos,float2 imgSize"
+",float4 subset,int tileModeX,int tileModeY,float4x4 coeffs,sampler2D s){float2"
+" f=fract(pos-0.5);pos-=1.5;pos=floor(pos)+0.5;float4 wx=coeffs*float4(1.,f."
+"x,f.x*f.x,f.x*f.x*f.x);float4 wy=coeffs*float4(1.,f.y,f.y*f.y,f.y*f.y*f.y);"
+"float4 color=float4(0);for(int y=0;y<4;++y){float4 rowColor=float4(0);for(int"
+" x=0;x<4;++x){rowColor+=wx[x]*$sample_image(pos+float2(x,y),imgSize,subset,"
+"tileModeX,tileModeY,$kFilterModeNearest,s);}color+=wy[y]*rowColor;}return half4"
+"(color);}$pure half4 sk_image_shader(float4 coords,float4x4 preLocal,float2"
+" imgSize,float4 subset,int tileModeX,int tileModeY,int filterMode,int useCubic"
+",float4x4 cubicCoeffs,sampler2D s){float2 pos=(preLocal*coords).xy;return(useCubic"
+"!=0)?$cubic_filter_image(pos,imgSize,subset,tileModeX,tileModeY,cubicCoeffs"
+",s):$sample_image(pos,imgSize,subset,tileModeX,tileModeY,filterMode,s);}$pure"
+" float2 $tile_grad(int tileMode,float2 t){switch(tileMode){case $kTileModeClamp"
+":t.x=clamp(t.x,0,1);break;case $kTileModeRepeat:t.x=fract(t.x);break;case $kTileModeMirror"
+":{float t_1=t.x-1;t.x=t_1-2*floor(t_1*0.5)-1;if(sk_Caps.mustDoOpBetweenFloorAndAbs"
+"){t.x=clamp(t.x,-1,1);}t.x=abs(t.x);break;}case $kTileModeDecal:if(t.x<0||t"
+".x>1){return float2(0,-1);}break;}return t;}$pure half4 $colorize_grad_4(float4"
+" colorsParam[4],float offsetsParam[4],float2 t){if(t.y<0){return half4(0);}"
+"else if(t.x<=offsetsParam[0]){return half4(colorsParam[0]);}else if(t.x<offsetsParam"
 "[1]){return half4(mix(colorsParam[0],colorsParam[1],(t.x-offsetsParam[0])/("
-"offsetsParam[1]-offsetsParam[0])));}else{return half4(mix(colorsParam[1],colorsParam"
-"[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam[1])));}}else{if(t.x"
-"<offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam[3],(t.x-offsetsParam"
-"[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4(mix(colorsParam"
-"[3],colorsParam[4],(t.x-offsetsParam[3])/(offsetsParam[4]-offsetsParam[3]))"
-");}}}else{if(t.x<offsetsParam[6]){if(t.x<offsetsParam[5]){return half4(mix("
-"colorsParam[4],colorsParam[5],(t.x-offsetsParam[4])/(offsetsParam[5]-offsetsParam"
+"offsetsParam[1]-offsetsParam[0])));}else if(t.x<offsetsParam[2]){return half4"
+"(mix(colorsParam[1],colorsParam[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam"
+"[1])));}else if(t.x<offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam"
+"[3],(t.x-offsetsParam[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4"
+"(colorsParam[3]);}}$pure half4 $colorize_grad_8(float4 colorsParam[8],float"
+" offsetsParam[8],float2 t){if(t.y<0){return half4(0);}else if(t.x<offsetsParam"
+"[4]){if(t.x<offsetsParam[2]){if(t.x<=offsetsParam[0]){return half4(colorsParam"
+"[0]);}else if(t.x<offsetsParam[1]){return half4(mix(colorsParam[0],colorsParam"
+"[1],(t.x-offsetsParam[0])/(offsetsParam[1]-offsetsParam[0])));}else{return half4"
+"(mix(colorsParam[1],colorsParam[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam"
+"[1])));}}else{if(t.x<offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam"
+"[3],(t.x-offsetsParam[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4"
+"(mix(colorsParam[3],colorsParam[4],(t.x-offsetsParam[3])/(offsetsParam[4]-offsetsParam"
+"[3])));}}}else{if(t.x<offsetsParam[6]){if(t.x<offsetsParam[5]){return half4"
+"(mix(colorsParam[4],colorsParam[5],(t.x-offsetsParam[4])/(offsetsParam[5]-offsetsParam"
 "[4])));}else{return half4(mix(colorsParam[5],colorsParam[6],(t.x-offsetsParam"
 "[5])/(offsetsParam[6]-offsetsParam[5])));}}else{if(t.x<offsetsParam[7]){return"
 " half4(mix(colorsParam[6],colorsParam[7],(t.x-offsetsParam[6])/(offsetsParam"
diff --git a/src/sksl/sksl_graphite_frag.sksl b/src/sksl/sksl_graphite_frag.sksl
index ea1d4ce..5369a16 100644
--- a/src/sksl/sksl_graphite_frag.sksl
+++ b/src/sksl/sksl_graphite_frag.sksl
@@ -48,16 +48,13 @@
     }
 }
 
-$pure half4 sk_image_shader(float4 coords,
-                            float4x4 preLocal,
-                            float2 imgSize,
-                            float4 subset,
-                            int tileModeX,
-                            int tileModeY,
-                            int filterMode,
-                            sampler2D s) {
-    float2 pos = (preLocal * coords).xy;
-
+$pure half4 $sample_image(float2 pos,
+                          float2 imgSize,
+                          float4 subset,
+                          int tileModeX,
+                          int tileModeY,
+                          int filterMode,
+                          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.
@@ -137,6 +134,50 @@
     return color;
 }
 
+$pure half4 $cubic_filter_image(float2 pos,
+                                float2 imgSize,
+                                float4 subset,
+                                int tileModeX,
+                                int tileModeY,
+                                float4x4 coeffs,
+                                sampler2D s) {
+    // Determine pos's fractional offset f between texel centers.
+    float2 f = fract(pos - 0.5);
+    // Sample 16 points at 1-pixel intervals from [p - 1.5 ... p + 1.5].
+    pos -= 1.5;
+    // Snap to texel centers to prevent sampling neighboring texels.
+    pos = floor(pos) + 0.5;
+
+    float4 wx = coeffs * float4(1.0, f.x, f.x * f.x, f.x * f.x * f.x);
+    float4 wy = coeffs * float4(1.0, f.y, f.y * f.y, f.y * f.y * f.y);
+    float4 color = float4(0);
+    for (int y = 0; y < 4; ++y) {
+        float4 rowColor = float4(0);
+        for (int x = 0; x < 4; ++x) {
+            rowColor += wx[x] * $sample_image(pos + float2(x, y), imgSize, subset,
+                                              tileModeX, tileModeY, $kFilterModeNearest, s);
+        }
+        color += wy[y] * rowColor;
+    }
+    return half4(color);
+}
+
+$pure half4 sk_image_shader(float4 coords,
+                            float4x4 preLocal,
+                            float2 imgSize,
+                            float4 subset,
+                            int tileModeX,
+                            int tileModeY,
+                            int filterMode,
+                            int useCubic,
+                            float4x4 cubicCoeffs,
+                            sampler2D s) {
+    float2 pos = (preLocal * coords).xy;
+    return (useCubic != 0)
+        ? $cubic_filter_image(pos, imgSize, subset, tileModeX, tileModeY, cubicCoeffs, s)
+        : $sample_image(pos, imgSize, subset, tileModeX, tileModeY, filterMode, s);
+}
+
 $pure float2 $tile_grad(int tileMode, float2 t) {
     switch (tileMode) {
         case $kTileModeClamp: