Implement SkBlender support in Ganesh.

SkBlenderBase::asFragmentProcessor now returns a working runtime-blender
FP. This FP is appended to the end of the paintFP chain by
skpaint_to_grpaint_impl when an SkPaint contains an SkBlender, and the
GrPaint's XferProcessor factory is set to kSrc.

Unit tests have been added to verify basic functionality is working as
expected; more thorough drawing tests will be added once the CPU side is
running as well (since we don't want GMs that render differently on CPU
and GPU).

Change-Id: I255abd057fa75d638a9f2612c1a353be4de9e24c
Bug: skia:12080
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/419358
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
diff --git a/src/core/SkBlenderBase.h b/src/core/SkBlenderBase.h
index 7d5ef18..1a10fb0 100644
--- a/src/core/SkBlenderBase.h
+++ b/src/core/SkBlenderBase.h
@@ -13,6 +13,8 @@
 #include "src/core/SkArenaAlloc.h"
 #include "src/core/SkVM.h"
 
+struct GrFPArgs;
+class GrFragmentProcessor;
 class SkRuntimeEffect;
 
 /**
@@ -28,6 +30,15 @@
         return this->onProgram(p, src, dst, colorInfo, uniforms, alloc);
     }
 
+#if SK_SUPPORT_GPU
+    /**
+     *  Returns a GrFragmentProcessor that implements this blend for the GPU backend.
+     *  The GrFragmentProcessor expects a premultiplied input and returns a premultiplied output.
+     */
+    virtual std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
+            std::unique_ptr<GrFragmentProcessor> inputFP, const GrFPArgs& fpArgs) const = 0;
+#endif
+
     virtual SkRuntimeEffect* asRuntimeEffect() const { return nullptr; }
 
     static SkFlattenable::Type GetFlattenableType() { return kSkBlender_Type; }
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 15d961f..e07af38 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -1033,6 +1033,18 @@
                                    src, dst, /*sampleChild=*/nullptr);
     }
 
+#if SK_SUPPORT_GPU
+    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
+            std::unique_ptr<GrFragmentProcessor> inputFP, const GrFPArgs& args) const override {
+        sk_sp<SkData> uniforms = get_xformed_uniforms(fEffect.get(), fUniforms,
+                                                      args.fDstColorInfo->colorSpace());
+        SkASSERT(uniforms);
+
+        return GrSkSLFP::MakeWithData(fEffect, "runtime_blender", std::move(inputFP),
+                                      std::move(uniforms), /*childFPs=*/{});
+    }
+#endif
+
     void flatten(SkWriteBuffer& buffer) const override {
         buffer.writeString(fEffect->source().c_str());
         buffer.writeDataAsByteArray(fUniforms.get());
diff --git a/src/core/SkXfermodeInterpretation.cpp b/src/core/SkXfermodeInterpretation.cpp
index 95675a5..bae6ca1 100644
--- a/src/core/SkXfermodeInterpretation.cpp
+++ b/src/core/SkXfermodeInterpretation.cpp
@@ -13,6 +13,9 @@
 }
 
 SkXfermodeInterpretation SkInterpretXfermode(const SkPaint& paint, bool dstIsOpaque) {
+    if (paint.getBlender()) {
+        return kNormal_SkXfermodeInterpretation;
+    }
     switch (paint.getBlendMode()) {
         case SkBlendMode::kSrcOver:
             return kSrcOver_SkXfermodeInterpretation;
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index dc2f2ef..09124a9 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -319,7 +319,8 @@
  */
 static bool can_use_draw_texture(const SkPaint& paint, bool useCubicResampler, SkMipmapMode mm) {
     return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() &&
-            !paint.getImageFilter() && !useCubicResampler && mm == SkMipmapMode::kNone);
+            !paint.getImageFilter() && !paint.getBlender() && !useCubicResampler &&
+            mm == SkMipmapMode::kNone);
 }
 
 static SkPMColor4f texture_color(SkColor4f paintColor, float entryAlpha, GrColorType srcColorType,
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 18113b5..e434d04 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -19,6 +19,7 @@
 #include "include/private/SkTemplates.h"
 #include "src/core/SkAutoMalloc.h"
 #include "src/core/SkBlendModePriv.h"
+#include "src/core/SkBlenderBase.h"
 #include "src/core/SkColorFilterBase.h"
 #include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkImagePriv.h"
@@ -438,11 +439,19 @@
     }
 #endif
 
-    // When the xfermode is null on the SkPaint (meaning kSrcOver) we need the XPFactory field on
-    // the GrPaint to also be null (also kSrcOver).
-    SkASSERT(!grPaint->getXPFactory());
-    if (!skPaint.isSrcOver()) {
-        grPaint->setXPFactory(SkBlendMode_AsXPFactory(skPaint.getBlendMode()));
+    SkBlender* blender = skPaint.getBlender();
+    if (blender) {
+        // Apply the custom blend, and force the XP to kSrc. We don't honor the SkBlendMode when a
+        // custom blend is applied.
+        paintFP = as_BB(blender)->asFragmentProcessor(std::move(paintFP), fpArgs);
+        grPaint->setXPFactory(SkBlendMode_AsXPFactory(SkBlendMode::kSrc));
+    } else {
+        // When the xfermode is null on the SkPaint (meaning kSrcOver) we need the XPFactory field
+        // on the GrPaint to also be null (also kSrcOver).
+        SkASSERT(!grPaint->getXPFactory());
+        if (!skPaint.isSrcOver()) {
+            grPaint->setXPFactory(SkBlendMode_AsXPFactory(skPaint.getBlendMode()));
+        }
     }
 
     if (GrColorTypeClampType(dstColorInfo.colorType()) == GrClampType::kManual) {
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index fbf0d47..77eac35 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -178,12 +178,24 @@
 
         // Copy the incoming coords to a local variable. Code in main might modify the coords
         // parameter. fSampleCoord could be a varying, so writes to it would be illegal.
-        SkString coordsVarName = args.fFragBuilder->newTmpVarName("coords");
-        const char* coords = coordsVarName.c_str();
+        const char* coords = "float2(0)";
+        SkString coordsVarName;
         if (fp.referencesSampleCoords()) {
+            coordsVarName = args.fFragBuilder->newTmpVarName("coords");
+            coords = coordsVarName.c_str();
             args.fFragBuilder->codeAppendf("float2 %s = %s;\n", coords, args.fSampleCoord);
         }
 
+        // For blend effects, we need to copy the dest-color to a local variable as well.
+        const char* destColor = "half4(1)";
+        SkString destColorVarName;
+        if (fp.willReadDstColor()) {
+            destColorVarName = args.fFragBuilder->newTmpVarName("destColor");
+            destColor = destColorVarName.c_str();
+            args.fFragBuilder->codeAppendf(
+                    "half4 %s = %s;\n", destColor, args.fFragBuilder->dstColor());
+        }
+
         FPCallbacks callbacks(this,
                               args,
                               inputColorCopy.c_str(),
@@ -191,7 +203,7 @@
                               fp.uniformData(),
                               fp.uniformFlags());
         SkSL::PipelineStage::ConvertProgram(
-                program, coords, args.fInputColor, "half4(1)", &callbacks);
+                program, coords, args.fInputColor, destColor, &callbacks);
     }
 
     void onSetData(const GrGLSLProgramDataManager& pdman,
@@ -269,6 +281,10 @@
     if (fEffect->usesSampleCoords()) {
         this->setUsesSampleCoordsDirectly();
     }
+
+    if (fEffect->allowBlender()) {
+        this->setWillReadDstColor();
+    }
 }
 
 GrSkSLFP::GrSkSLFP(const GrSkSLFP& other)
@@ -286,6 +302,10 @@
         this->setUsesSampleCoordsDirectly();
     }
 
+    if (fEffect->allowBlender()) {
+        this->setWillReadDstColor();
+    }
+
     this->cloneAndRegisterAllChildProcessors(other);
 }
 
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.h b/src/gpu/glsl/GrGLSLShaderBuilder.h
index a047240..af7f249 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.h
@@ -48,8 +48,8 @@
                              GrGLSLColorSpaceXformHelper* colorXformHelper = nullptr);
 
     /** Does the work of appendTextureLookup and blends the result by dst, treating the texture
-        lookup a the src input to the blend. The dst is assumed to be half4 and the result is always
-        a half4. If dst is nullptr we use half4(1) as the blend dst. */
+        lookup as the src input to the blend. The dst is assumed to be half4 and the result is
+        always a half4. If dst is nullptr we use half4(1) as the blend dst. */
     void appendTextureLookupAndBlend(const char* dst,
                                      SkBlendMode,
                                      SamplerHandle,
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index 6fccf5d..d0a6bb8 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -304,6 +304,40 @@
                  "sample(colorFilter, float2, half4)");
 }
 
+using PreTestFn = std::function<void(SkCanvas*, SkPaint*)>;
+
+void paint_canvas(SkCanvas* canvas, SkPaint* paint, const PreTestFn& preTestCallback) {
+    canvas->save();
+    if (preTestCallback) {
+        preTestCallback(canvas, paint);
+    }
+    canvas->drawPaint(*paint);
+    canvas->restore();
+}
+
+static void verify_2x2_surface_results(skiatest::Reporter* r,
+                                       const SkRuntimeEffect* effect,
+                                       SkSurface* surface,
+                                       std::array<GrColor, 4> expected) {
+    std::array<GrColor, 4> actual;
+    SkImageInfo info = surface->imageInfo();
+    if (!surface->readPixels(info, actual.data(), info.minRowBytes(), /*srcX=*/0, /*srcY=*/0)) {
+        REPORT_FAILURE(r, "readPixels", SkString("readPixels failed"));
+        return;
+    }
+
+    if (actual != expected) {
+        REPORT_FAILURE(r, "Runtime effect didn't match expectations",
+                       SkStringPrintf("\n"
+                                      "Expected: [ %08x %08x %08x %08x ]\n"
+                                      "Got     : [ %08x %08x %08x %08x ]\n"
+                                      "SkSL:\n%s\n",
+                                      expected[0], expected[1], expected[2], expected[3],
+                                      actual[0],   actual[1],   actual[2],   actual[3],
+                                      effect->source().c_str()));
+    }
+}
+
 class TestEffect {
 public:
     TestEffect(skiatest::Reporter* r, sk_sp<SkSurface> surface)
@@ -322,14 +356,12 @@
     SkRuntimeShaderBuilder::BuilderUniform uniform(const char* name) {
         return fBuilder->uniform(name);
     }
+
     SkRuntimeShaderBuilder::BuilderChild child(const char* name) {
         return fBuilder->child(name);
     }
 
-    using PreTestFn = std::function<void(SkCanvas*, SkPaint*)>;
-
-    void test(GrColor TL, GrColor TR, GrColor BL, GrColor BR,
-              PreTestFn preTestCallback = nullptr) {
+    void test(std::array<GrColor, 4> expected, PreTestFn preTestCallback = nullptr) {
         auto shader = fBuilder->makeShader(/*localMatrix=*/nullptr, /*isOpaque=*/false);
         if (!shader) {
             REPORT_FAILURE(fReporter, "shader", SkString("Effect didn't produce a shader"));
@@ -341,34 +373,13 @@
         paint.setShader(std::move(shader));
         paint.setBlendMode(SkBlendMode::kSrc);
 
-        canvas->save();
-        if (preTestCallback) {
-            preTestCallback(canvas, &paint);
-        }
-        canvas->drawPaint(paint);
-        canvas->restore();
+        paint_canvas(canvas, &paint, preTestCallback);
 
-        GrColor actual[4];
-        SkImageInfo info = fSurface->imageInfo();
-        if (!fSurface->readPixels(info, actual, info.minRowBytes(), 0, 0)) {
-            REPORT_FAILURE(fReporter, "readPixels", SkString("readPixels failed"));
-            return;
-        }
-
-        GrColor expected[4] = {TL, TR, BL, BR};
-        if (0 != memcmp(actual, expected, sizeof(actual))) {
-            REPORT_FAILURE(fReporter, "Runtime effect didn't match expectations",
-                           SkStringPrintf("\n"
-                                          "Expected: [ %08x %08x %08x %08x ]\n"
-                                          "Got     : [ %08x %08x %08x %08x ]\n"
-                                          "SkSL:\n%s\n",
-                                          TL, TR, BL, BR, actual[0], actual[1], actual[2],
-                                          actual[3], fBuilder->effect()->source().c_str()));
-        }
+        verify_2x2_surface_results(fReporter, fBuilder->effect(), fSurface.get(), expected);
     }
 
     void test(GrColor expected, PreTestFn preTestCallback = nullptr) {
-        this->test(expected, expected, expected, expected, preTestCallback);
+        this->test({expected, expected, expected, expected}, preTestCallback);
     }
 
 private:
@@ -377,6 +388,52 @@
     SkTLazy<SkRuntimeShaderBuilder> fBuilder;
 };
 
+class TestBlend {
+public:
+    TestBlend(skiatest::Reporter* r, sk_sp<SkSurface> surface)
+            : fReporter(r), fSurface(std::move(surface)) {}
+
+    void build(const char* src) {
+        auto [effect, errorText] = SkRuntimeEffect::MakeForBlender(SkString(src));
+        if (!effect) {
+            REPORT_FAILURE(fReporter, "effect",
+                           SkStringPrintf("Effect didn't compile: %s", errorText.c_str()));
+            return;
+        }
+        fBuilder.init(std::move(effect));
+    }
+
+    SkRuntimeBlendBuilder::BuilderUniform uniform(const char* name) {
+        return fBuilder->uniform(name);
+    }
+
+    void test(std::array<GrColor, 4> expected, PreTestFn preTestCallback = nullptr) {
+        auto blender = fBuilder->makeBlender();
+        if (!blender) {
+            REPORT_FAILURE(fReporter, "blender", SkString("Effect didn't produce a blender"));
+            return;
+        }
+
+        SkCanvas* canvas = fSurface->getCanvas();
+        SkPaint paint;
+        paint.experimental_setBlender(std::move(blender));
+        paint.setColor(SK_ColorGRAY);
+
+        paint_canvas(canvas, &paint, preTestCallback);
+
+        verify_2x2_surface_results(fReporter, fBuilder->effect(), fSurface.get(), expected);
+    }
+
+    void test(GrColor expected, PreTestFn preTestCallback = nullptr) {
+        this->test({expected, expected, expected, expected}, preTestCallback);
+    }
+
+private:
+    skiatest::Reporter*            fReporter;
+    sk_sp<SkSurface>               fSurface;
+    SkTLazy<SkRuntimeBlendBuilder> fBuilder;
+};
+
 // Produces a 2x2 bitmap shader, with opaque colors:
 // [  Red, Green ]
 // [ Blue, White ]
@@ -404,7 +461,7 @@
 
     // Local coords
     effect.build("half4 main(float2 p) { return half4(half2(p - 0.5), 0, 1); }");
-    effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
 
     // Use of a simple uniform. (Draw twice with two values to ensure it's updated).
     effect.build("uniform float4 gColor; half4 main(float2 p) { return half4(gColor); }");
@@ -425,25 +482,25 @@
     // make sure we're not saturating unexpectedly.
     effect.build(
             "half4 main(float2 p) { return half4(0.498 * (half2(sk_FragCoord.xy) - 0.5), 0, 1); }");
-    effect.test(0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F,
+    effect.test({0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F},
                 [](SkCanvas* canvas, SkPaint*) { canvas->rotate(45.0f); });
 
     // Runtime effects should use relaxed precision rules by default
     effect.build("half4 main(float2 p) { return float4(p - 0.5, 0, 1); }");
-    effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
 
     // ... and support *returning* float4 (aka vec4), not just half4
     effect.build("float4 main(float2 p) { return float4(p - 0.5, 0, 1); }");
-    effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
     effect.build("vec4 main(float2 p) { return float4(p - 0.5, 0, 1); }");
-    effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
 
     // Mutating coords should work. (skbug.com/10918)
     effect.build("vec4 main(vec2 p) { p -= 0.5; return vec4(p, 0, 1); }");
-    effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
     effect.build("void moveCoords(inout vec2 p) { p -= 0.5; }"
                  "vec4 main(vec2 p) { moveCoords(p); return vec4(p, 0, 1); }");
-    effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+    effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
 
     //
     // Sampling children
@@ -462,13 +519,13 @@
     effect.build("uniform shader child;"
                  "half4 main(float2 p) { return sample(child, p); }");
     effect.child("child") = rgbwShader;
-    effect.test(0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF);
+    effect.test({0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF});
 
     // Sampling with explicit coordinates (reflecting about the diagonal)
     effect.build("uniform shader child;"
                  "half4 main(float2 p) { return sample(child, p.yx); }");
     effect.child("child") = rgbwShader;
-    effect.test(0xFF0000FF, 0xFFFF0000, 0xFF00FF00, 0xFFFFFFFF);
+    effect.test({0xFF0000FF, 0xFFFF0000, 0xFF00FF00, 0xFFFFFFFF});
 
     //
     // Helper functions
@@ -488,6 +545,73 @@
     test_RuntimeEffect_Shaders(r, ctxInfo.directContext());
 }
 
+static void test_RuntimeEffect_Blenders(skiatest::Reporter* r, GrRecordingContext* rContext) {
+    SkImageInfo info = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    sk_sp<SkSurface> surface = rContext
+                                    ? SkSurface::MakeRenderTarget(rContext, SkBudgeted::kNo, info)
+                                    : SkSurface::MakeRaster(info);
+    REPORTER_ASSERT(r, surface);
+    TestBlend effect(r, surface);
+
+    using float4 = std::array<float, 4>;
+    using int4 = std::array<int, 4>;
+
+    // Use of a simple uniform. (Draw twice with two values to ensure it's updated).
+    effect.build("uniform float4 gColor; half4 main(half4 s, half4 d) { return half4(gColor); }");
+    effect.uniform("gColor") = float4{ 0.0f, 0.25f, 0.75f, 1.0f };
+    effect.test(0xFFBF4000);
+    effect.uniform("gColor") = float4{ 1.0f, 0.0f, 0.0f, 0.498f };
+    effect.test(0x7F0000FF);  // Unlike SkShaders, we don't clamp here
+
+    // Same, with integer uniforms
+    effect.build("uniform int4 gColor;"
+                 "half4 main(half4 s, half4 d) { return half4(gColor) / 255.0; }");
+    effect.uniform("gColor") = int4{ 0x00, 0x40, 0xBF, 0xFF };
+    effect.test(0xFFBF4000);
+    effect.uniform("gColor") = int4{ 0xFF, 0x00, 0x00, 0x7F };
+    effect.test(0x7F0000FF);  // Unlike SkShaders, we don't clamp here
+
+    // Verify that mutating the source and destination colors is allowed
+    effect.build("half4 main(half4 s, half4 d) { s += d; d += s; return half4(1); }");
+    effect.test(0xFFFFFFFF);
+
+    // Verify that we can write out the source color (ignoring the dest color)
+    // This is equivalent to the kSrc blend mode.
+    effect.build("half4 main(half4 s, half4 d) { return s; }");
+    effect.test(0xFF888888);
+
+    // Fill the destination with a variety of colors (using the RGBW shader)
+    SkPaint paint;
+    paint.setShader(make_RGBW_shader());
+    paint.setBlendMode(SkBlendMode::kSrc);
+    surface->getCanvas()->drawPaint(paint);
+
+    // Verify that we can read back the dest color exactly as-is (ignoring the source color)
+    // This is equivalent to the kDst blend mode.
+    effect.build("half4 main(half4 s, half4 d) { return d; }");
+    effect.test({0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF});
+
+    // Verify that we can invert the destination color (including the alpha channel).
+    // The expected outputs are the exact inverse of the previous test.
+    effect.build("half4 main(half4 s, half4 d) { return half4(1) - d; }");
+    effect.test({0x00FFFF00, 0x00FF00FF, 0x0000FFFF, 0x00000000});
+
+    // Verify that color values are clamped to 0 and 1.
+    effect.build("half4 main(half4 s, half4 d) { return half4(-1); }");
+    effect.test(0x00000000);
+    effect.build("half4 main(half4 s, half4 d) { return half4(2); }");
+    effect.test(0xFFFFFFFF);
+}
+
+DEF_TEST(SkRuntimeEffect_Blender_CPU, r) {
+    // TODO(skia:12080): add CPU support for SkBlender
+//  test_RuntimeEffect_Blenders(r, /*rContext=*/nullptr);
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRuntimeEffect_Blender_GPU, r, ctxInfo) {
+    test_RuntimeEffect_Blenders(r, ctxInfo.directContext());
+}
+
 DEF_TEST(SkRuntimeShaderBuilderReuse, r) {
     const char* kSource = R"(
         uniform half x;