Allow sampling from SkBlenders.

Runtime shaders, color filters, and blenders are all able to sample from
a blender. These use the blend-function signature; both a src-color and
dst-color must be passed to sample. i.e.: sample(blender, s, d)

Change-Id: I3738e6b0b4af6d1d79e62ca1815c80d6a1ae9d6f
Bug: skia:12257
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/432056
Commit-Queue: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index b879e67..a5c3e3b 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -82,6 +82,7 @@
         enum class Type {
             kShader,
             kColorFilter,
+            kBlender,
         };
 
         SkString name;
@@ -161,13 +162,15 @@
     static Result MakeForBlender(std::unique_ptr<SkSL::Program> program, const Options&);
     static Result MakeForBlender(std::unique_ptr<SkSL::Program> program);
 
-    // Object that allows passing either an SkShader or SkColorFilter as a child
+    // Object that allows passing a SkShader, SkColorFilter or SkBlender as a child
     struct ChildPtr {
         ChildPtr() = default;
         ChildPtr(sk_sp<SkShader> s) : shader(std::move(s)) {}
         ChildPtr(sk_sp<SkColorFilter> cf) : colorFilter(std::move(cf)) {}
+        ChildPtr(sk_sp<SkBlender> b) : blender(std::move(b)) {}
         sk_sp<SkShader> shader;
         sk_sp<SkColorFilter> colorFilter;
+        sk_sp<SkBlender> blender;
     };
 
     sk_sp<SkShader> makeShader(sk_sp<SkData> uniforms,
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 7655595..d137599 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -130,7 +130,7 @@
 
 static SkRuntimeEffect::Child::Type child_type(const SkSL::Type& type) {
     switch (type.typeKind()) {
-        // TODO(skia:12257): add support for kBlender
+        case SkSL::Type::TypeKind::kBlender:     return SkRuntimeEffect::Child::Type::kBlender;
         case SkSL::Type::TypeKind::kColorFilter: return SkRuntimeEffect::Child::Type::kColorFilter;
         case SkSL::Type::TypeKind::kShader:      return SkRuntimeEffect::Child::Type::kShader;
         default: SkUNREACHABLE;
@@ -147,12 +147,17 @@
     for (size_t i = 0; i < effectPtrs.size(); ++i) {
         switch (reflected[i].type) {
             case SkRuntimeEffect::Child::Type::kShader:
-                if (effectPtrs[i].colorFilter) {
+                if (effectPtrs[i].colorFilter || effectPtrs[i].blender) {
                     return false;
                 }
                 continue;
             case SkRuntimeEffect::Child::Type::kColorFilter:
-                if (effectPtrs[i].shader) {
+                if (effectPtrs[i].shader || effectPtrs[i].blender) {
+                    return false;
+                }
+                continue;
+            case SkRuntimeEffect::Child::Type::kBlender:
+                if (effectPtrs[i].shader || effectPtrs[i].colorFilter) {
                     return false;
                 }
                 continue;
@@ -178,6 +183,8 @@
             children->emplace_back(buffer.readShader());
         } else if (child.type == SkRuntimeEffect::Child::Type::kColorFilter) {
             children->emplace_back(buffer.readColorFilter());
+        } else if (child.type == SkRuntimeEffect::Child::Type::kBlender) {
+            children->emplace_back(buffer.readBlender());
         } else {
             return false;
         }
@@ -190,8 +197,10 @@
                                 const std::vector<SkRuntimeEffect::ChildPtr>& children) {
     buffer.write32(children.size());
     for (const auto& child : children) {
-        buffer.writeFlattenable(child.shader ? (const SkFlattenable*)child.shader.get()
-                                             : child.colorFilter.get());
+        buffer.writeFlattenable(
+                child.shader ? (const SkFlattenable*)child.shader.get() :
+           child.colorFilter ? (const SkFlattenable*)child.colorFilter.get() :
+                               (const SkFlattenable*)child.blender.get());
     }
 }
 
@@ -569,9 +578,10 @@
     // of those, we are unable to use this per-effect program, and callers will need to fall back
     // to another (slower) implementation.
 
-    // We also require that any children are *also* color filters (not shaders). In theory we could
-    // detect the coords being passed to shader children, and replicate those calls, but that's
-    // very complicated, and has diminishing returns. (eg, for table lookup color filters).
+    // We also require that any children are *also* color filters (not shaders or blenders). In
+    // theory we could detect the coords being passed to shader children, and replicate those calls,
+    // but that's very complicated, and has diminishing returns. (eg, for table lookup color
+    // filters).
     if (!std::all_of(effect->fChildren.begin(),
                      effect->fChildren.end(),
                      [](const SkRuntimeEffect::Child& c) {
@@ -777,12 +787,14 @@
     SkSTArray<8, std::unique_ptr<GrFragmentProcessor>> childFPs;
     for (const auto& child : children) {
         if (child.shader) {
+            // Convert a SkShader into a child FP.
             auto childFP = as_SB(child.shader)->asFragmentProcessor(childArgs);
             if (!childFP) {
                 return GrFPFailure(std::move(inputFP));
             }
             childFPs.push_back(std::move(childFP));
         } else if (child.colorFilter) {
+            // Convert a SkColorFilter into a child FP.
             auto [success, childFP] = as_CFB(child.colorFilter)
                                               ->asFragmentProcessor(/*inputFP=*/nullptr,
                                                                     childArgs.fContext,
@@ -791,7 +803,17 @@
                 return GrFPFailure(std::move(inputFP));
             }
             childFPs.push_back(std::move(childFP));
+        } else if (child.blender) {
+            // Convert a SkBlender into a child FP.
+            auto childFP = as_BB(child.blender)->asFragmentProcessor(/*srcFP=*/nullptr,
+                                                                     /*dstFP=*/nullptr,
+                                                                     childArgs);
+            if (!childFP) {
+                return GrFPFailure(std::move(inputFP));
+            }
+            childFPs.push_back(std::move(childFP));
         } else {
+            // We have a null child effect.
             childFPs.push_back(nullptr);
         }
     }
@@ -860,8 +882,10 @@
             return color;
         };
         auto sampleBlender = [&](int ix, skvm::Color src, skvm::Color dst) {
-            // TODO(skia:12257): implement sample(blender, src, dst)
-            return src;
+            if (SkBlender* blender = fChildren[ix].blender.get()) {
+                return as_BB(blender)->program(p, src, dst, colorInfo, uniforms, alloc);
+            }
+            return blend(SkBlendMode::kSrcOver, src, dst);
         };
 
         std::vector<skvm::Val> uniform = make_skvm_uniforms(p, uniforms, fEffect->uniformSize(),
@@ -891,8 +915,9 @@
         auto evalChild = [&](int index, SkPMColor4f inColor) {
             const auto& child = fChildren[index];
 
-            // Guaranteed by initFilterColorInfo
+            // SkFilterColorProgram::Make has guaranteed that any children will be color filters.
             SkASSERT(!child.shader);
+            SkASSERT(!child.blender);
             return child.colorFilter ? as_CFB(child.colorFilter)->onFilterColor4f(inColor, dstCS)
                                      : inColor;
         };
@@ -1036,8 +1061,10 @@
             return color;
         };
         auto sampleBlender = [&](int ix, skvm::Color src, skvm::Color dst) {
-            // TODO(skia:12257): implement sample(blender, src, dst)
-            return src;
+            if (SkBlender* blender = fChildren[ix].blender.get()) {
+                return as_BB(blender)->program(p, src, dst, colorInfo, uniforms, alloc);
+            }
+            return blend(SkBlendMode::kSrcOver, src, dst);
         };
 
         std::vector<skvm::Val> uniform = make_skvm_uniforms(p, uniforms, fEffect->uniformSize(),
@@ -1144,8 +1171,10 @@
             return color;
         };
         auto sampleBlender = [&](int ix, skvm::Color src, skvm::Color dst) {
-            // TODO(skia:12257): implement sample(blender, src, dst)
-            return src;
+            if (SkBlender* blender = fChildren[ix].blender.get()) {
+                return as_BB(blender)->program(p, src, dst, colorInfo, uniforms, alloc);
+            }
+            return blend(SkBlendMode::kSrcOver, src, dst);
         };
 
         std::vector<skvm::Val> uniform = make_skvm_uniforms(p, uniforms, fEffect->uniformSize(),
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index 290a2be..d631bb8 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -146,8 +146,10 @@
             }
 
             String sampleBlender(int index, String src, String dst) override {
-                // TODO(skia:12257): invokeChild does not yet allow sampling from a blender
-                return "half4(1)";
+                if (!fSelf->childProcessor(index)) {
+                    return String::printf("blend_src_over(%s, %s)", src.c_str(), dst.c_str());
+                }
+                return String(fSelf->invokeChild(index, src.c_str(), dst.c_str(), fArgs).c_str());
             }
 
             GrGLSLSkSLFP*                 fSelf;
diff --git a/src/sksl/SkSLBuiltinTypes.cpp b/src/sksl/SkSLBuiltinTypes.cpp
index 11cb7d2..28ad637 100644
--- a/src/sksl/SkSLBuiltinTypes.cpp
+++ b/src/sksl/SkSLBuiltinTypes.cpp
@@ -180,6 +180,6 @@
         , fSkCaps(Type::MakeSpecialType("$sk_Caps", "O", Type::TypeKind::kOther))
         , fColorFilter(Type::MakeSpecialType("colorFilter", "CF", Type::TypeKind::kColorFilter))
         , fShader(Type::MakeSpecialType("shader", "SH", Type::TypeKind::kShader))
-        , fBlender(Type::MakeSpecialType("$blender", "B", Type::TypeKind::kBlender)) {}
+        , fBlender(Type::MakeSpecialType("blender", "B", Type::TypeKind::kBlender)) {}
 
 }  // namespace SkSL
diff --git a/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
index c6e2c90..3c1b2c2 100644
--- a/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
@@ -141,7 +141,7 @@
     const FunctionDeclaration& function = c.function();
     const ExpressionArray& arguments = c.arguments();
     if (function.isBuiltin() && function.name() == "sample") {
-        SkASSERT(arguments.size() == 2);
+        SkASSERT(arguments.size() >= 2);
         const Expression* child = arguments[0].get();
         SkASSERT(child->type().isEffectChild());
         SkASSERT(child->is<VariableReference>());
@@ -164,20 +164,44 @@
         SkASSERT(found);
 
         // Shaders require a coordinate argument. Color filters require a color argument.
-        // When we call sampleChild, the other value remains empty.
+        // Blenders require two color arguments.
         String sampleOutput;
         {
-            AutoOutputBuffer outputToBuffer(this);
-            this->writeExpression(*arguments.back(), Precedence::kSequence);
-            if (child->type().typeKind() == Type::TypeKind::kShader) {
-                SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fFloat2);
-                sampleOutput = fCallbacks->sampleShader(index, outputToBuffer.fBuffer.str());
+            AutoOutputBuffer exprBuffer(this);
+            this->writeExpression(*arguments[1], Precedence::kSequence);
 
-            } else {
-                SkASSERT(child->type().typeKind() == Type::TypeKind::kColorFilter);
-                SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fHalf4 ||
-                         arguments[1]->type() == *fProgram.fContext->fTypes.fFloat4);
-                sampleOutput = fCallbacks->sampleColorFilter(index, outputToBuffer.fBuffer.str());
+            switch (child->type().typeKind()) {
+                case Type::TypeKind::kShader: {
+                    SkASSERT(arguments.size() == 2);
+                    SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fFloat2);
+                    sampleOutput = fCallbacks->sampleShader(index, exprBuffer.fBuffer.str());
+                    break;
+                }
+                case Type::TypeKind::kColorFilter: {
+                    SkASSERT(arguments.size() == 2);
+                    SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fHalf4 ||
+                             arguments[1]->type() == *fProgram.fContext->fTypes.fFloat4);
+                    sampleOutput = fCallbacks->sampleColorFilter(index, exprBuffer.fBuffer.str());
+                    break;
+                }
+                case Type::TypeKind::kBlender: {
+                    SkASSERT(arguments.size() == 3);
+                    SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fHalf4 ||
+                             arguments[1]->type() == *fProgram.fContext->fTypes.fFloat4);
+                    SkASSERT(arguments[2]->type() == *fProgram.fContext->fTypes.fHalf4 ||
+                             arguments[2]->type() == *fProgram.fContext->fTypes.fFloat4);
+
+                    AutoOutputBuffer exprBuffer2(this);
+                    this->writeExpression(*arguments[2], Precedence::kSequence);
+
+                    sampleOutput = fCallbacks->sampleBlender(index, exprBuffer.fBuffer.str(),
+                                                                    exprBuffer2.fBuffer.str());
+                    break;
+                }
+                default: {
+                    SkDEBUGFAILF("cannot sample from type '%s'",
+                                 child->type().description().c_str());
+                }
             }
         }
         this->write(sampleOutput);
diff --git a/src/sksl/codegen/SkSLVMCodeGenerator.cpp b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
index f36dfea..0e8f16c 100644
--- a/src/sksl/codegen/SkSLVMCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
@@ -873,7 +873,7 @@
     if (intrinsicKind == k_sample_IntrinsicKind) {
         // Sample is very special. The first argument is a child (shader/colorFilter/blender),
         // which is opaque and can't be evaluated.
-        SkASSERT(nargs == 2);
+        SkASSERT(nargs >= 2);
         const Expression* child = c.arguments()[0].get();
         SkASSERT(child->type().isEffectChild());
         SkASSERT(child->is<VariableReference>());
@@ -887,16 +887,43 @@
         Value argVal = this->writeExpression(*arg);
         skvm::Color color;
 
-        if (child->type().typeKind() == Type::TypeKind::kShader) {
-            SkASSERT(arg->type() == *fProgram.fContext->fTypes.fFloat2);
-            skvm::Coord coord = {f32(argVal[0]), f32(argVal[1])};
-            color = fSampleShader(fp_it->second, coord);
-        } else {
-            SkASSERT(child->type().typeKind() == Type::TypeKind::kColorFilter);
-            SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
-                     arg->type() == *fProgram.fContext->fTypes.fFloat4);
-            skvm::Color inColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])};
-            color = fSampleColorFilter(fp_it->second, inColor);
+        switch (child->type().typeKind()) {
+            case Type::TypeKind::kShader: {
+                SkASSERT(nargs == 2);
+                SkASSERT(arg->type() == *fProgram.fContext->fTypes.fFloat2);
+                skvm::Coord coord = {f32(argVal[0]), f32(argVal[1])};
+                color = fSampleShader(fp_it->second, coord);
+                break;
+            }
+            case Type::TypeKind::kColorFilter: {
+                SkASSERT(nargs == 2);
+                SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
+                         arg->type() == *fProgram.fContext->fTypes.fFloat4);
+                skvm::Color inColor = {f32(argVal[0]), f32(argVal[1]),
+                                       f32(argVal[2]), f32(argVal[3])};
+                color = fSampleColorFilter(fp_it->second, inColor);
+                break;
+            }
+            case Type::TypeKind::kBlender: {
+                SkASSERT(nargs == 3);
+                SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
+                         arg->type() == *fProgram.fContext->fTypes.fFloat4);
+                skvm::Color srcColor = {f32(argVal[0]), f32(argVal[1]),
+                                        f32(argVal[2]), f32(argVal[3])};
+
+                arg = c.arguments()[2].get();
+                argVal = this->writeExpression(*arg);
+                SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
+                         arg->type() == *fProgram.fContext->fTypes.fFloat4);
+                skvm::Color dstColor = {f32(argVal[0]), f32(argVal[1]),
+                                        f32(argVal[2]), f32(argVal[3])};
+
+                color = fSampleBlender(fp_it->second, srcColor, dstColor);
+                break;
+            }
+            default: {
+                SkDEBUGFAILF("cannot sample from type '%s'", child->type().description().c_str());
+            }
         }
 
         Value result(4);
diff --git a/src/sksl/generated/sksl_rt_blend.dehydrated.sksl b/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
index 16fbb2c..2b3400f 100644
--- a/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
+++ b/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
@@ -1,4 +1,4 @@
-static uint8_t SKSL_INCLUDE_sksl_rt_blend[] = {56,0,
+static uint8_t SKSL_INCLUDE_sksl_rt_blend[] = {74,0,
 1,115,
 6,115,104,97,100,101,114,
 6,99,111,111,114,100,115,
@@ -8,7 +8,11 @@
 1,102,
 11,99,111,108,111,114,70,105,108,116,101,114,
 5,99,111,108,111,114,
-47,7,0,
+1,98,
+7,98,108,101,110,100,101,114,
+3,115,114,99,
+3,100,115,116,
+47,12,0,
 51,1,0,
 16,2,0,
 48,2,0,4,0,3,
@@ -29,8 +33,24 @@
 28,11,0,
 16,25,0,2,7,0,9,0,
 45,6,0,
-45,11,0,1,0,
-5,0,
+45,11,0,
+51,12,0,
+16,58,0,
+48,13,0,60,0,3,
+51,14,0,
+16,68,0,
+45,6,0,3,
+51,15,0,
+16,72,0,
+45,6,0,3,
+50,16,0,3,
+45,5,0,
+45,11,0,
+28,17,0,
+16,25,0,3,12,0,14,0,15,0,
+45,6,0,
+45,17,0,1,0,
+10,0,
 19,
 20,};
 static constexpr size_t SKSL_INCLUDE_sksl_rt_blend_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_blend);
diff --git a/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl b/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
index 32a4139..3550ce5 100644
--- a/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
+++ b/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
@@ -1,4 +1,4 @@
-static uint8_t SKSL_INCLUDE_sksl_rt_colorfilter[] = {56,0,
+static uint8_t SKSL_INCLUDE_sksl_rt_colorfilter[] = {74,0,
 1,115,
 6,115,104,97,100,101,114,
 6,99,111,111,114,100,115,
@@ -8,7 +8,11 @@
 1,102,
 11,99,111,108,111,114,70,105,108,116,101,114,
 5,99,111,108,111,114,
-47,7,0,
+1,98,
+7,98,108,101,110,100,101,114,
+3,115,114,99,
+3,100,115,116,
+47,12,0,
 51,1,0,
 16,2,0,
 48,2,0,4,0,3,
@@ -29,8 +33,24 @@
 28,11,0,
 16,25,0,2,7,0,9,0,
 45,6,0,
-45,11,0,1,0,
-5,0,
+45,11,0,
+51,12,0,
+16,58,0,
+48,13,0,60,0,3,
+51,14,0,
+16,68,0,
+45,6,0,3,
+51,15,0,
+16,72,0,
+45,6,0,3,
+50,16,0,3,
+45,5,0,
+45,11,0,
+28,17,0,
+16,25,0,3,12,0,14,0,15,0,
+45,6,0,
+45,17,0,1,0,
+10,0,
 19,
 20,};
 static constexpr size_t SKSL_INCLUDE_sksl_rt_colorfilter_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_colorfilter);
diff --git a/src/sksl/generated/sksl_rt_shader.dehydrated.sksl b/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
index 3d36e1f..795c531 100644
--- a/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
+++ b/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
@@ -1,4 +1,4 @@
-static uint8_t SKSL_INCLUDE_sksl_rt_shader[] = {76,0,
+static uint8_t SKSL_INCLUDE_sksl_rt_shader[] = {94,0,
 12,115,107,95,70,114,97,103,67,111,111,114,100,
 6,102,108,111,97,116,52,
 1,115,
@@ -10,7 +10,11 @@
 1,102,
 11,99,111,108,111,114,70,105,108,116,101,114,
 5,99,111,108,111,114,
-47,8,0,
+1,98,
+7,98,108,101,110,100,101,114,
+3,115,114,99,
+3,100,115,116,
+47,13,0,
 51,1,0,
 35,
 34,0,2,0,0,255,255,255,255,255,15,0,255,255,255,255,0,2,0,
@@ -35,8 +39,24 @@
 28,13,0,
 16,45,0,2,9,0,11,0,
 45,8,0,
-45,13,0,2,0,
-6,0,
+45,13,0,
+51,14,0,
+16,78,0,
+48,15,0,80,0,3,
+51,16,0,
+16,88,0,
+45,8,0,3,
+51,17,0,
+16,92,0,
+45,8,0,3,
+50,18,0,3,
+45,7,0,
+45,13,0,
+28,19,0,
+16,45,0,3,14,0,16,0,17,0,
+45,8,0,
+45,19,0,2,0,
+11,0,
 0,0,
 19,
 53,
diff --git a/src/sksl/sksl_rt_blend.sksl b/src/sksl/sksl_rt_blend.sksl
index 06b4a42..02d931d 100644
--- a/src/sksl/sksl_rt_blend.sksl
+++ b/src/sksl/sksl_rt_blend.sksl
@@ -1,2 +1,3 @@
 half4 sample(shader s, float2 coords);
 half4 sample(colorFilter f, half4 color);
+half4 sample(blender b, half4 src, half4 dst);
diff --git a/src/sksl/sksl_rt_colorfilter.sksl b/src/sksl/sksl_rt_colorfilter.sksl
index 06b4a42..02d931d 100644
--- a/src/sksl/sksl_rt_colorfilter.sksl
+++ b/src/sksl/sksl_rt_colorfilter.sksl
@@ -1,2 +1,3 @@
 half4 sample(shader s, float2 coords);
 half4 sample(colorFilter f, half4 color);
+half4 sample(blender b, half4 src, half4 dst);
diff --git a/src/sksl/sksl_rt_shader.sksl b/src/sksl/sksl_rt_shader.sksl
index e259cc3..5e24a04 100644
--- a/src/sksl/sksl_rt_shader.sksl
+++ b/src/sksl/sksl_rt_shader.sksl
@@ -2,3 +2,4 @@
 
 half4 sample(shader s, float2 coords);
 half4 sample(colorFilter f, half4 color);
+half4 sample(blender b, half4 src, half4 dst);
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index 9e2ebda..c139588 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -12,6 +12,7 @@
 #include "include/core/SkData.h"
 #include "include/core/SkPaint.h"
 #include "include/core/SkSurface.h"
+#include "include/effects/SkBlenders.h"
 #include "include/effects/SkRuntimeEffect.h"
 #include "include/gpu/GrDirectContext.h"
 #include "src/core/SkColorSpacePriv.h"
@@ -618,10 +619,10 @@
     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);
+    SkPaint rgbwPaint;
+    rgbwPaint.setShader(make_RGBW_shader());
+    rgbwPaint.setBlendMode(SkBlendMode::kSrc);
+    surface->getCanvas()->drawPaint(rgbwPaint);
 
     // Verify that we can read back the dest color exactly as-is (ignoring the source color)
     // This is equivalent to the kDst blend mode.
@@ -643,13 +644,27 @@
     // Sampling children
     //
 
-    // Sampling a null child should return the paint color
+    // Sampling a null shader/color filter should return the paint color.
     effect.build("uniform shader child;"
                  "half4 main(half4 s, half4 d) { return sample(child, s.rg); }");
     effect.child("child") = nullptr;
     effect.test(0xFF00FFFF,
                 [](SkCanvas*, SkPaint* paint) { paint->setColor4f({1.0f, 1.0f, 0.0f, 1.0f}); });
 
+    effect.build("uniform colorFilter child;"
+                 "half4 main(half4 s, half4 d) { return sample(child, s); }");
+    effect.child("child") = nullptr;
+    effect.test(0xFF00FFFF,
+                [](SkCanvas*, SkPaint* paint) { paint->setColor4f({1.0f, 1.0f, 0.0f, 1.0f}); });
+
+    // Sampling a null blender should do a src-over blend. Draw 50% black over RGBW to verify this.
+    surface->getCanvas()->drawPaint(rgbwPaint);
+    effect.build("uniform blender child;"
+                 "half4 main(half4 s, half4 d) { return sample(child, s, d); }");
+    effect.child("child") = nullptr;
+    effect.test({0xFF000080, 0xFF008000, 0xFF800000, 0xFF808080},
+                [](SkCanvas*, SkPaint* paint) { paint->setColor4f({0.0f, 0.0f, 0.0f, 0.497f}); });
+
     // Sampling a shader at various coordinates
     effect.build("uniform shader child;"
                  "uniform half2 pos;"
@@ -672,6 +687,22 @@
                  "half4 main(half4 s, half4 d) { return sample(child, half4(1)); }");
     effect.child("child") = SkColorFilters::Blend(0xFF012345, SkBlendMode::kSrc);
     effect.test(0xFF452301);
+
+    // Sampling a built-in blender
+    surface->getCanvas()->drawPaint(rgbwPaint);
+    effect.build("uniform blender child;"
+                 "half4 main(half4 s, half4 d) { return sample(child, s, d); }");
+    effect.child("child") = SkBlender::Mode(SkBlendMode::kPlus);
+    effect.test({0xFF4523FF, 0xFF45FF01, 0xFFFF2301, 0xFFFFFFFF},
+                [](SkCanvas*, SkPaint* paint) { paint->setColor(0xFF012345); });
+
+    // Sampling a runtime-effect blender
+    surface->getCanvas()->drawPaint(rgbwPaint);
+    effect.build("uniform blender child;"
+                 "half4 main(half4 s, half4 d) { return sample(child, s, d); }");
+    effect.child("child") = SkBlenders::Arithmetic(0, 1, 1, 0, /*enforcePremul=*/false);
+    effect.test({0xFF4523FF, 0xFF45FF01, 0xFFFF2301, 0xFFFFFFFF},
+                [](SkCanvas*, SkPaint* paint) { paint->setColor(0xFF012345); });
 }
 
 DEF_TEST(SkRuntimeEffect_Blender_CPU, r) {