Add Make functions for runtime blends.

This does basic plumbing work, but lacks a real implementation for the
custom blend subclass. That subclass is added in the followup CL of this
CL chain.

Change-Id: I8db0eb42737a739b49741ffd6f4a283a2c0a9d62
Bug: skia:12080
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/417005
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index c5204a7..f965d19 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 class GrRecordingContext;
+class SkBlender;
 class SkFilterColorProgram;
 class SkImage;
 
@@ -120,6 +121,10 @@
     // Most shaders don't use the input color, so that parameter is optional.
     static Result MakeForShader(SkString sksl, const Options&);
 
+    // Blend SkSL requires an entry point that looks like:
+    //     vec4 main(vec4 srcColor, vec4 dstColor) { ... }
+    static Result MakeForBlender(SkString sksl, const Options&);
+
     // We can't use a default argument for `options` due to a bug in Clang.
     // https://bugs.llvm.org/show_bug.cgi?id=36684
     static Result MakeForColorFilter(SkString sksl) {
@@ -128,11 +133,16 @@
     static Result MakeForShader(SkString sksl) {
         return MakeForShader(std::move(sksl), Options{});
     }
+    static Result MakeForBlender(SkString sksl) {
+        return MakeForBlender(std::move(sksl), Options{});
+    }
 
     static Result MakeForColorFilter(std::unique_ptr<SkSL::Program> program);
 
     static Result MakeForShader(std::unique_ptr<SkSL::Program> program);
 
+    static Result MakeForBlender(std::unique_ptr<SkSL::Program> program);
+
     // Object that allows passing either an SkShader or SkColorFilter as a child
     struct ChildPtr {
         ChildPtr(sk_sp<SkShader> s) : shader(std::move(s)) {}
@@ -166,6 +176,8 @@
     sk_sp<SkColorFilter> makeColorFilter(sk_sp<SkData> uniforms,
                                          SkSpan<ChildPtr> children) const;
 
+    sk_sp<SkBlender> makeBlender(sk_sp<SkData> uniforms) const;
+
     const SkString& source() const { return fSkSL; }
 
     template <typename T>
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index f365183..7f35595 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -11,6 +11,7 @@
 #include "include/effects/SkRuntimeEffect.h"
 #include "include/private/SkChecksum.h"
 #include "include/private/SkMutex.h"
+#include "src/core/SkBlenderBase.h"
 #include "src/core/SkCanvasPriv.h"
 #include "src/core/SkColorFilterBase.h"
 #include "src/core/SkColorSpacePriv.h"
@@ -191,14 +192,14 @@
         default: SkUNREACHABLE;
     }
 
-
     if (sampleCoordsUsage.fRead || sampleCoordsUsage.fWrite) {
         flags |= kUsesSampleCoords_Flag;
     }
 
-    // Color filters are not allowed to depend on position (local or device) in any way.
-    // The signature of main, and the declarations in sksl_rt_colorfilter should guarantee this.
-    if (flags & kAllowColorFilter_Flag) {
+    // Color filters and blends are not allowed to depend on position (local or device) in any way.
+    // The signature of main, and the declarations in sksl_rt_colorfilter/sksl_rt_blend should
+    // guarantee this.
+    if (flags & (kAllowColorFilter_Flag | kAllowBlender_Flag)) {
         SkASSERT(!(flags & kUsesSampleCoords_Flag));
         SkASSERT(!SkSL::Analysis::ReferencesFragCoords(*program));
     }
@@ -222,6 +223,12 @@
 
             // Child effects that can be sampled ('shader' or 'colorFilter')
             if (varType.isEffectChild()) {
+                // Runtime blends currently don't support child effects.
+                if (kind == SkSL::ProgramKind::kRuntimeBlender) {
+                    RETURN_FAILURE("'%s' is not allowed in runtime blend",
+                                   varType.displayName().c_str());
+                }
+
                 Child c;
                 c.name  = SkString(var.name());
                 c.type  = child_type(varType);
@@ -294,6 +301,12 @@
     return result;
 }
 
+SkRuntimeEffect::Result SkRuntimeEffect::MakeForBlender(SkString sksl, const Options& options) {
+    auto result = Make(std::move(sksl), options, SkSL::ProgramKind::kRuntimeBlender);
+    SkASSERT(!result.effect || result.effect->allowBlender());
+    return result;
+}
+
 SkRuntimeEffect::Result SkRuntimeEffect::MakeForColorFilter(std::unique_ptr<SkSL::Program> program) {
     auto result = Make(std::move(program), SkSL::ProgramKind::kRuntimeColorFilter);
     SkASSERT(!result.effect || result.effect->allowColorFilter());
@@ -306,6 +319,12 @@
     return result;
 }
 
+SkRuntimeEffect::Result SkRuntimeEffect::MakeForBlender(std::unique_ptr<SkSL::Program> program) {
+    auto result = Make(std::move(program), SkSL::ProgramKind::kRuntimeBlender);
+    SkASSERT(!result.effect || result.effect->allowBlender());
+    return result;
+}
+
 sk_sp<SkRuntimeEffect> SkMakeCachedRuntimeEffect(SkRuntimeEffect::Result (*make)(SkString sksl),
                                                  SkString sksl) {
     SK_BEGIN_REQUIRE_DENSE
@@ -581,7 +600,7 @@
     sk_sp<SkData> uniforms = nullptr;
     auto writableData = [&]() {
         if (!uniforms) {
-            uniforms =  SkData::MakeWithCopy(baseUniforms->data(), baseUniforms->size());
+            uniforms = SkData::MakeWithCopy(baseUniforms->data(), baseUniforms->size());
         }
         return uniforms->writable_data();
     };
@@ -1150,6 +1169,21 @@
     return this->makeColorFilter(std::move(uniforms), /*children=*/{});
 }
 
+sk_sp<SkBlender> SkRuntimeEffect::makeBlender(sk_sp<SkData> uniforms) const {
+    if (!this->allowBlender()) {
+        return nullptr;
+    }
+    if (!uniforms) {
+        uniforms = SkData::MakeEmpty();
+    }
+    if (uniforms->size() != this->uniformSize() || !fChildren.empty()) {
+        return nullptr;
+    }
+    // TODO(skia:12080): create a runtime blend class
+    SkDEBUGFAIL("not yet implemented");
+    return nullptr;
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 void SkRuntimeEffect::RegisterFlattenables() {
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index 5e889ac..c2e24ff 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -138,7 +138,7 @@
     test_invalid("uniform shader child;"
                  "half4 main(half4 c) { return sample(child, c); }",
                  "no match for sample(shader, half4)");
-    // Coords and color in a differet order
+    // Coords and color in a different order
     test_invalid("uniform shader child;"
                  "half4 main(half4 c) { return sample(child, c, c.rg); }",
                  "no match for sample(shader, half4, half2)");
@@ -168,6 +168,56 @@
                  "sample(colorFilter, half2, half4)");
 }
 
+DEF_TEST(SkRuntimeEffectForBlender, r) {
+    // Tests that the blender factory rejects or accepts certain SkSL constructs
+    auto test_valid = [r](const char* sksl) {
+        auto [effect, errorText] = SkRuntimeEffect::MakeForBlender(SkString(sksl));
+        REPORTER_ASSERT(r, effect, "%s", errorText.c_str());
+    };
+
+    auto test_invalid = [r](const char* sksl, const char* expected) {
+        auto [effect, errorText] = SkRuntimeEffect::MakeForBlender(SkString(sksl));
+        REPORTER_ASSERT(r, !effect);
+        REPORTER_ASSERT(r,
+                        errorText.contains(expected),
+                        "Expected error message to contain \"%s\". Actual message: \"%s\"",
+                        expected,
+                        errorText.c_str());
+    };
+
+    // Color filters must use the 'half4 main(half4, half4)' signature. Any mixture of
+    // float4/vec4/half4 is allowed.
+    test_valid("half4  main(half4  s, half4  d) { return s; }");
+    test_valid("float4 main(float4 s, float4 d) { return d; }");
+    test_valid("float4 main(half4  s, float4 d) { return s; }");
+    test_valid("half4  main(float4 s, half4  d) { return d; }");
+    test_valid("vec4   main(half4  s, half4  d) { return s; }");
+    test_valid("half4  main(vec4   s, vec4   d) { return d; }");
+    test_valid("vec4   main(vec4   s, vec4   d) { return s; }");
+
+    // Invalid return types
+    test_invalid("void  main(half4 s, half4 d) {}",                "'main' must return");
+    test_invalid("half3 main(half4 s, half4 d) { return s.rgb; }", "'main' must return");
+
+    // Invalid argument types (some are valid as shaders/color filters)
+    test_invalid("half4 main() { return half4(1); }",                    "'main' parameter");
+    test_invalid("half4 main(half4 c) { return c; }",                    "'main' parameter");
+    test_invalid("half4 main(float2 p) { return half4(1); }",            "'main' parameter");
+    test_invalid("half4 main(float2 p, half4 c) { return c; }",          "'main' parameter");
+    test_invalid("half4 main(float2 p, half4 a, half4 b) { return a; }", "'main' parameter");
+    test_invalid("half4 main(half4 a, half4 b, half4 c) { return a; }",  "'main' parameter");
+
+    // sk_FragCoord should not be available
+    test_invalid("half4 main(half4 s, half4 d) { return sk_FragCoord.xy01; }",
+                 "unknown identifier");
+
+    // Child shaders are currently unsupported in blends
+    test_invalid("uniform shader sh; half4 main(half4 s, half4 d) { return s; }",
+                 "'shader' is not allowed in runtime blend");
+    test_invalid("uniform shader sh; half4 main(half4 s, half4 d) { return sample(sh, s.rg); }",
+                 "unknown identifier 'sample'");
+}
+
 DEF_TEST(SkRuntimeEffectForShader, r) {
     // Tests that the shader factory rejects or accepts certain SkSL constructs
     auto test_valid = [r](const char* sksl) {