Convert Clamped and Tiled gradient effects to GrSkSLFP

Change-Id: If9a3a2e71fada81447ad697b2dc9efd4dec77a28
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/423019
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/gradients/GrGradientShader.cpp b/src/gpu/gradients/GrGradientShader.cpp
index 3746baa..18db1ee 100644
--- a/src/gpu/gradients/GrGradientShader.cpp
+++ b/src/gpu/gradients/GrGradientShader.cpp
@@ -7,9 +7,6 @@
 
 #include "src/gpu/gradients/GrGradientShader.h"
 
-#include "src/gpu/gradients/generated/GrClampedGradientEffect.h"
-#include "src/gpu/gradients/generated/GrTiledGradientEffect.h"
-
 #include "src/gpu/gradients/GrGradientBitmapCache.h"
 #include "src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h"
 
@@ -215,6 +212,149 @@
     return make_textured_colorizer(colors + offset, positions + offset, count, premul, args);
 }
 
+// This top-level effect implements clamping on the layout coordinate and requires specifying the
+// border colors that are used when outside the clamped boundary. Gradients with the
+// SkTileMode::kClamp should use the colors at their first and last stop (after adding default stops
+// for t=0,t=1) as the border color. This will automatically replicate the edge color, even when
+// there is a hard stop.
+//
+// The SkTileMode::kDecal can be produced by specifying transparent black as the border colors,
+// regardless of the gradient's stop colors.
+static std::unique_ptr<GrFragmentProcessor> make_clamped_gradient(
+        std::unique_ptr<GrFragmentProcessor> colorizer,
+        std::unique_ptr<GrFragmentProcessor> gradLayout,
+        SkPMColor4f leftBorderColor,
+        SkPMColor4f rightBorderColor,
+        bool makePremul,
+        bool colorsAreOpaque) {
+    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
+        uniform shader colorizer;
+        uniform shader gradLayout;
+
+        uniform half4 leftBorderColor;  // t < 0.0
+        uniform half4 rightBorderColor; // t > 1.0
+
+        uniform int makePremul;              // specialized
+        uniform int layoutPreservesOpacity;  // specialized
+
+        half4 main(float2 coord) {
+            half4 t = sample(gradLayout, coord);
+            half4 outColor;
+
+            // If t.x is below 0, use the left border color without invoking the child processor.
+            // If any t.x is above 1, use the right border color. Otherwise, t is in the [0, 1]
+            // range assumed by the colorizer FP, so delegate to the child processor.
+            if (!bool(layoutPreservesOpacity) && t.y < 0) {
+                // layout has rejected this fragment (rely on sksl to remove this branch if the
+                // layout FP preserves opacity is false)
+                outColor = half4(0);
+            } else if (t.x < 0) {
+                outColor = leftBorderColor;
+            } else if (t.x > 1.0) {
+                outColor = rightBorderColor;
+            } else {
+                // Always sample from (x, 0), discarding y, since the layout FP can use y as a
+                // side-channel.
+                outColor = sample(colorizer, t.x0);
+            }
+            if (bool(makePremul)) {
+                outColor.rgb *= outColor.a;
+            }
+            return outColor;
+        }
+    )");
+
+    // If the layout does not preserve opacity, remove the opaque optimization,
+    // but otherwise respect the provided color opacity state (which should take
+    // into account the opacity of the border colors).
+    bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
+    GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
+    if (colorsAreOpaque && layoutPreservesOpacity) {
+        optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
+    }
+
+    return GrSkSLFP::Make(effect, "ClampedGradient", /*inputFP=*/nullptr, optFlags,
+                          "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
+                          "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
+                          "leftBorderColor", leftBorderColor,
+                          "rightBorderColor", rightBorderColor,
+                          "makePremul", GrSkSLFP::Specialize<int>(makePremul),
+                          "layoutPreservesOpacity",
+                              GrSkSLFP::Specialize<int>(layoutPreservesOpacity));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_tiled_gradient(
+        const GrFPArgs& args,
+        std::unique_ptr<GrFragmentProcessor> colorizer,
+        std::unique_ptr<GrFragmentProcessor> gradLayout,
+        bool mirror,
+        bool makePremul,
+        bool colorsAreOpaque) {
+    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
+        uniform shader colorizer;
+        uniform shader gradLayout;
+
+        uniform int mirror;                  // specialized
+        uniform int makePremul;              // specialized
+        uniform int layoutPreservesOpacity;  // specialized
+        uniform int useFloorAbsWorkaround;   // specialized
+
+        half4 main(float2 coord) {
+            half4 t = sample(gradLayout, coord);
+
+            if (!bool(layoutPreservesOpacity) && t.y < 0) {
+                // layout has rejected this fragment (rely on sksl to remove this branch if the
+                // layout FP preserves opacity is false)
+                return half4(0);
+            } else {
+                if (bool(mirror)) {
+                    half t_1 = t.x - 1;
+                    half tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1;
+                    if (bool(useFloorAbsWorkaround)) {
+                        // At this point the expected value of tiled_t should between -1 and 1, so
+                        // this clamp has no effect other than to break up the floor and abs calls
+                        // and make sure the compiler doesn't merge them back together.
+                        tiled_t = clamp(tiled_t, -1, 1);
+                    }
+                    t.x = abs(tiled_t);
+                } else {
+                    // Simple repeat mode
+                    t.x = fract(t.x);
+                }
+
+                // Always sample from (x, 0), discarding y, since the layout FP can use y as a
+                // side-channel.
+                half4 outColor = sample(colorizer, t.x0);
+                if (bool(makePremul)) {
+                    outColor.rgb *= outColor.a;
+                }
+                return outColor;
+            }
+        }
+    )");
+
+    // If the layout does not preserve opacity, remove the opaque optimization,
+    // but otherwise respect the provided color opacity state (which should take
+    // into account the opacity of the border colors).
+    bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
+    GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
+    if (colorsAreOpaque && layoutPreservesOpacity) {
+        optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
+    }
+    const bool useFloorAbsWorkaround =
+            args.fContext->priv().caps()->shaderCaps()->mustDoOpBetweenFloorAndAbs();
+
+    return GrSkSLFP::Make(effect, "TiledGradient", /*inputFP=*/nullptr, optFlags,
+                          "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
+                          "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
+                          "mirror", GrSkSLFP::Specialize<int>(mirror),
+                          "makePremul", GrSkSLFP::Specialize<int>(makePremul),
+                          "layoutPreservesOpacity",
+                                GrSkSLFP::Specialize<int>(layoutPreservesOpacity),
+                          "useFloorAbsWorkaround",
+                                GrSkSLFP::Specialize<int>(useFloorAbsWorkaround));
+}
+
 // Combines the colorizer and layout with an appropriately configured top-level effect based on the
 // gradient's tile mode
 static std::unique_ptr<GrFragmentProcessor> make_gradient(
@@ -288,29 +428,28 @@
     std::unique_ptr<GrFragmentProcessor> gradient;
     switch(shader.getTileMode()) {
         case SkTileMode::kRepeat:
-            gradient = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout),
-                                                   /* mirror */ false, makePremul, allOpaque);
+            gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
+                                           /* mirror */ false, makePremul, allOpaque);
             break;
         case SkTileMode::kMirror:
-            gradient = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout),
-                                                   /* mirror */ true, makePremul, allOpaque);
+            gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
+                                           /* mirror */ true, makePremul, allOpaque);
             break;
         case SkTileMode::kClamp:
             // For the clamped mode, the border colors are the first and last colors, corresponding
             // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
             // appropriate. If there is a hard stop, this grabs the expected outer colors for the
             // border.
-            gradient = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout),
-                                                     colors[0], colors[shader.fColorCount - 1],
-                                                     makePremul, allOpaque);
+            gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
+                                             colors[0], colors[shader.fColorCount - 1],
+                                             makePremul, allOpaque);
             break;
         case SkTileMode::kDecal:
             // Even if the gradient colors are opaque, the decal borders are transparent so
             // disable that optimization
-            gradient = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout),
-                                                     SK_PMColor4fTRANSPARENT,
-                                                     SK_PMColor4fTRANSPARENT,
-                                                     makePremul, /* colorsAreOpaque */ false);
+            gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
+                                             SK_PMColor4fTRANSPARENT, SK_PMColor4fTRANSPARENT,
+                                             makePremul, /* colorsAreOpaque */ false);
             break;
     }