[graphite] Add matrix convolution ImageFilter to Precompilation system

Bug: b/259548724
Change-Id: Ib1ef773836e5ab320ddfe3b1f5adab3753fabb49
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/859396
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/graphite/FactoryFunctions.cpp b/src/gpu/graphite/FactoryFunctions.cpp
index 50853ac..91760bc 100644
--- a/src/gpu/graphite/FactoryFunctions.cpp
+++ b/src/gpu/graphite/FactoryFunctions.cpp
@@ -507,6 +507,13 @@
     return PrecompileShaders::LocalMatrix({ sk_make_sp<PrecompileImageShader>(flags) });
 }
 
+sk_sp<PrecompileShader> PrecompileShadersPriv::RawImage(
+        SkEnumBitMask<PrecompileImageShaderFlags> flags) {
+    return PrecompileShaders::LocalMatrix(
+            { sk_make_sp<PrecompileImageShader>(flags |
+                                                PrecompileImageShaderFlags::kExcludeAlpha) });
+}
+
 //--------------------------------------------------------------------------------------------------
 class PrecompileYUVImageShader : public PrecompileShader {
 public:
@@ -947,6 +954,81 @@
 }
 
 //--------------------------------------------------------------------------------------------------
+class PrecompileMatrixConvolutionShader : public PrecompileShader {
+public:
+    PrecompileMatrixConvolutionShader(sk_sp<PrecompileShader> wrapped)
+            : fWrapped(std::move(wrapped)) {
+        fNumWrappedCombos = fWrapped->numCombinations();
+
+        // When the matrix convolution ImageFilter uses a texture we know it will only ever
+        // be SkFilterMode::kNearest and SkTileMode::kClamp.
+        // TODO: add a PrecompileImageShaderFlags to further limit the raw image shader
+        // combinations. Right now we're getting two combinations for the raw shader
+        // (sk_image_shader and sk_hw_image_shader).
+        fRawImageShader =
+                PrecompileShadersPriv::RawImage(PrecompileImageShaderFlags::kExcludeCubic);
+        fNumRawImageShaderCombos = fRawImageShader->numCombinations();
+    }
+
+private:
+    int numIntrinsicCombinations() const override {
+        // The uniform version only has one option but the two texture-based versions will
+        // have as many combinations as the raw image shader.
+        return 1 + 2 * fNumRawImageShaderCombos;
+    }
+
+    int numChildCombinations() const override { return fNumWrappedCombos; }
+
+    void addToKey(const KeyContext& keyContext,
+                  PaintParamsKeyBuilder* builder,
+                  PipelineDataGatherer* gatherer,
+                  int desiredCombination) const override {
+
+        int desiredTextureCombination = 0;
+
+        const int desiredWrappedCombination = desiredCombination % fNumWrappedCombos;
+        int remainingCombinations = desiredCombination / fNumWrappedCombos;
+
+        SkKnownRuntimeEffects::StableKey stableKey = SkKnownRuntimeEffects::StableKey::kInvalid;
+        if (remainingCombinations == 0) {
+            stableKey = SkKnownRuntimeEffects::StableKey::kMatrixConvUniforms;
+        } else {
+            static constexpr SkKnownRuntimeEffects::StableKey kTextureBasedStableKeys[] = {
+                    SkKnownRuntimeEffects::StableKey::kMatrixConvTexSm,
+                    SkKnownRuntimeEffects::StableKey::kMatrixConvTexLg,
+            };
+
+            --remainingCombinations;
+            stableKey = kTextureBasedStableKeys[remainingCombinations % 2];
+            desiredTextureCombination = remainingCombinations / 2;
+            SkASSERT(desiredTextureCombination < fNumRawImageShaderCombos);
+        }
+
+        const SkRuntimeEffect* fEffect = GetKnownRuntimeEffect(stableKey);
+
+        KeyContextWithScope childContext(keyContext, KeyContext::Scope::kRuntimeEffect);
+
+        RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { sk_ref_sp(fEffect) });
+            fWrapped->priv().addToKey(childContext, builder, gatherer, desiredWrappedCombination);
+            if (stableKey != SkKnownRuntimeEffects::StableKey::kMatrixConvUniforms) {
+                fRawImageShader->priv().addToKey(childContext, builder, gatherer,
+                                                 desiredTextureCombination);
+            }
+        builder->endBlock();
+    }
+
+    sk_sp<PrecompileShader> fWrapped;
+    int fNumWrappedCombos;
+    sk_sp<PrecompileShader> fRawImageShader;
+    int fNumRawImageShaderCombos;
+};
+
+sk_sp<PrecompileShader> PrecompileShadersPriv::MatrixConvolution(
+        sk_sp<skgpu::graphite::PrecompileShader> wrapped) {
+    return sk_make_sp<PrecompileMatrixConvolutionShader>(std::move(wrapped));
+}
+
+//--------------------------------------------------------------------------------------------------
 class PrecompileMorphologyShader : public PrecompileShader {
 public:
     PrecompileMorphologyShader(sk_sp<PrecompileShader> wrapped,
diff --git a/src/gpu/graphite/FactoryFunctionsPriv.h b/src/gpu/graphite/FactoryFunctionsPriv.h
index 9a13f9a..a040d46 100644
--- a/src/gpu/graphite/FactoryFunctionsPriv.h
+++ b/src/gpu/graphite/FactoryFunctionsPriv.h
@@ -31,6 +31,8 @@
 
     sk_sp<PrecompileShader> Lighting(sk_sp<PrecompileShader> wrapped);
 
+    sk_sp<PrecompileShader> MatrixConvolution(sk_sp<PrecompileShader> wrapped);
+
     sk_sp<PrecompileShader> LinearMorphology(sk_sp<PrecompileShader> wrapped);
 
     sk_sp<PrecompileShader> SparseMorphology(sk_sp<PrecompileShader> wrapped);
@@ -42,6 +44,8 @@
 
     sk_sp<PrecompileShader> Image(SkEnumBitMask<PrecompileImageShaderFlags>);
 
+    sk_sp<PrecompileShader> RawImage(SkEnumBitMask<PrecompileImageShaderFlags>);
+
     // This factory variant should be used when the existence or non-existence of the local matrix
     // is known. If 'withLM' is true only the LMShader-wrapped shader will be created while, when
     // 'withLM' is false, no LMShader will wrap the base shader.
diff --git a/src/gpu/graphite/Precompile.cpp b/src/gpu/graphite/Precompile.cpp
index c375046..f8cf814 100644
--- a/src/gpu/graphite/Precompile.cpp
+++ b/src/gpu/graphite/Precompile.cpp
@@ -507,6 +507,28 @@
                                       processCombination);
 }
 
+void create_matrix_convolution_imagefilter_pipelines(
+        const KeyContext& keyContext,
+        PipelineDataGatherer* gatherer,
+        const PaintOptions::ProcessCombination& processCombination) {
+
+    PaintOptions matrixConv;
+
+    // For matrix convolution imagefilters we know we don't have alpha-only textures and don't
+    // need cubic filtering.
+    sk_sp<PrecompileShader> imageShader = PrecompileShadersPriv::Image(
+            PrecompileImageShaderFlags::kExcludeAlpha | PrecompileImageShaderFlags::kExcludeCubic);
+
+    matrixConv.setShaders({ PrecompileShadersPriv::MatrixConvolution(imageShader) });
+
+    matrixConv.priv().buildCombinations(keyContext,
+                                        gatherer,
+                                        DrawTypeFlags::kSimpleShape,
+                                        /* withPrimitiveBlender= */ false,
+                                        Coverage::kSingleChannel,
+                                        processCombination);
+}
+
 void create_morphology_imagefilter_pipelines(
         const KeyContext& keyContext,
         PipelineDataGatherer* gatherer,
@@ -581,6 +603,10 @@
         if (fImageFilterOptions & PrecompileImageFilters::kLighting) {
             create_lighting_imagefilter_pipelines(keyContext, gatherer, processCombination);
         }
+        if (fImageFilterOptions & PrecompileImageFilters::kMatrixConvolution) {
+            create_matrix_convolution_imagefilter_pipelines(keyContext, gatherer,
+                                                            processCombination);
+        }
         if (fImageFilterOptions & PrecompileImageFilters::kMorphology) {
             create_morphology_imagefilter_pipelines(keyContext, gatherer, processCombination);
         }
diff --git a/src/gpu/graphite/Precompile.h b/src/gpu/graphite/Precompile.h
index 8e34ef5..ae3b2e0 100644
--- a/src/gpu/graphite/Precompile.h
+++ b/src/gpu/graphite/Precompile.h
@@ -157,11 +157,12 @@
 };
 
 enum class PrecompileImageFilters : uint32_t {
-    kNone              = 0x0,
-    kBlur              = 0x1,
-    kDisplacement      = 0x2,
-    kLighting          = 0x4,
-    kMorphology        = 0x8,
+    kNone              = 0x00,
+    kBlur              = 0x01,
+    kDisplacement      = 0x02,
+    kLighting          = 0x04,
+    kMatrixConvolution = 0x08,
+    kMorphology        = 0x10,
 };
 SK_MAKE_BITMASK_OPS(PrecompileImageFilters)
 
diff --git a/tests/graphite/PaintParamsKeyTest.cpp b/tests/graphite/PaintParamsKeyTest.cpp
index 0da04b7..d9f8ee8 100644
--- a/tests/graphite/PaintParamsKeyTest.cpp
+++ b/tests/graphite/PaintParamsKeyTest.cpp
@@ -248,6 +248,7 @@
     M(Blur)              \
     M(Displacement)      \
     M(Lighting)          \
+    M(MatrixConvolution) \
     M(Morphology)
 
 enum class ImageFilterType {
@@ -1227,6 +1228,38 @@
     SkUNREACHABLE;
 }
 
+sk_sp<SkImageFilter> matrix_convolution_imagefilter(
+        SkRandom* rand,
+        SkEnumBitMask<PrecompileImageFilters>* imageFilterMask) {
+
+    int kernelSize = 1;
+
+    int option = rand->nextULessThan(3);
+    switch (option) {
+        case 0: kernelSize = 3;  break;
+        case 1: kernelSize = 7;  break;
+        case 2: kernelSize = 11; break;
+    }
+
+    int center = (kernelSize * kernelSize - 1) / 2;
+    std::vector<float> kernel(kernelSize * kernelSize, SkIntToScalar(1));
+    kernel[center] = 2.0f - kernelSize * kernelSize;
+
+    sk_sp<SkImageFilter> matrixConvIF;
+    matrixConvIF = SkImageFilters::MatrixConvolution({ kernelSize, kernelSize },
+                                                     /* kernel= */ kernel.data(),
+                                                     /* gain= */ 0.3f,
+                                                     /* bias= */ 100.0f,
+                                                     /* kernelOffset= */ { 1, 1 },
+                                                     SkTileMode::kMirror,
+                                                     /* convolveAlpha= */ false,
+                                                     /* input= */ nullptr);
+    SkASSERT(matrixConvIF);
+    *imageFilterMask |= PrecompileImageFilters::kMatrixConvolution;
+
+    return matrixConvIF;
+}
+
 sk_sp<SkImageFilter> morphology_imagefilter(
         SkRandom* rand,
         SkEnumBitMask<PrecompileImageFilters>* imageFilterMask) {
@@ -1265,6 +1298,9 @@
         case ImageFilterType::kLighting:
             imgFilter = lighting_imagefilter(rand, &imageFilterMask);
             break;
+        case ImageFilterType::kMatrixConvolution:
+            imgFilter = matrix_convolution_imagefilter(rand, &imageFilterMask);
+            break;
         case ImageFilterType::kMorphology:
             imgFilter = morphology_imagefilter(rand, &imageFilterMask);
             break;
@@ -1669,6 +1705,7 @@
             ImageFilterType::kBlur,
             ImageFilterType::kDisplacement,
             ImageFilterType::kLighting,
+            ImageFilterType::kMatrixConvolution,
             ImageFilterType::kMorphology,
 #endif
     };