Add SkRuntimeBlender class.

This class is returned by SkRuntimeEffect::makeBlender when a runtime
blend is returned. SkRuntimeBlendBuilder is also added as a convenience
class to simplify creation and uniform setup.

Our ability to add tests is limited in this CL because the SkPaint does
not contain an SkBlender yet. We do have one test which builds an
SkBlender, but there's not much we can do with it. Testing will be
bulked up in the next CL.

Change-Id: Ib2d7d04186690ec0d2238fc990a19d9e6b786ce3
Bug: skia:12080
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/417006
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: 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 f965d19..6470126 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -249,6 +249,7 @@
 #endif
 
     friend class SkRTShader;            // fBaseProgram, fMain
+    friend class SkRuntimeBlender;      //
     friend class SkRuntimeColorFilter;  //
 
     friend class SkFilterColorProgram;
@@ -419,4 +420,18 @@
     using INHERITED = SkRuntimeEffectBuilder<sk_sp<SkShader>>;
 };
 
+/**
+ * SkRuntimeBlendBuilder is a utility to simplify creation and uniform setup of runtime blenders.
+ */
+class SK_API SkRuntimeBlendBuilder : public SkRuntimeEffectBuilder<sk_sp<SkBlender>> {
+public:
+    explicit SkRuntimeBlendBuilder(sk_sp<SkRuntimeEffect>);
+    ~SkRuntimeBlendBuilder();
+
+    sk_sp<SkBlender> makeBlender();
+
+private:
+    using INHERITED = SkRuntimeEffectBuilder<sk_sp<SkBlender>>;
+};
+
 #endif
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 7f35595..99c3914 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -1003,6 +1003,64 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+class SkRuntimeBlender : public SkBlenderBase {
+public:
+    SkRuntimeBlender(sk_sp<SkRuntimeEffect> effect, sk_sp<SkData> uniforms)
+            : fEffect(std::move(effect))
+            , fUniforms(std::move(uniforms)) {}
+
+    skvm::Color onProgram(skvm::Builder* p, skvm::Color src, skvm::Color dst,
+                          const SkColorInfo& colorInfo, skvm::Uniforms* uniforms,
+                          SkArenaAlloc* alloc) const override {
+        sk_sp<SkData> inputs = get_xformed_uniforms(fEffect.get(), fUniforms,
+                                                    colorInfo.colorSpace());
+        SkASSERT(inputs);
+
+        const size_t uniformCount = fEffect->uniformSize() / 4;
+        std::vector<skvm::Val> uniform;
+        uniform.reserve(uniformCount);
+        for (size_t i = 0; i < uniformCount; i++) {
+            int bits;
+            memcpy(&bits, (const char*)inputs->data() + 4*i, 4);
+            uniform.push_back(p->uniform32(uniforms->push(bits)).id);
+        }
+
+        // Emit the blend function as an SkVM program.
+        skvm::Coord zeroCoord = {p->splat(0.0f), p->splat(0.0f)};
+        return SkSL::ProgramToSkVM(*fEffect->fBaseProgram, fEffect->fMain, p, SkMakeSpan(uniform),
+                                   /*device=*/zeroCoord, /*local=*/zeroCoord,
+                                   src, dst, /*sampleChild=*/nullptr);
+    }
+
+    void flatten(SkWriteBuffer& buffer) const override {
+        buffer.writeString(fEffect->source().c_str());
+        buffer.writeDataAsByteArray(fUniforms.get());
+    }
+
+    SK_FLATTENABLE_HOOKS(SkRuntimeBlender)
+
+private:
+    using INHERITED = SkBlenderBase;
+
+    sk_sp<SkRuntimeEffect> fEffect;
+    sk_sp<SkData> fUniforms;
+};
+
+sk_sp<SkFlattenable> SkRuntimeBlender::CreateProc(SkReadBuffer& buffer) {
+    SkString sksl;
+    buffer.readString(&sksl);
+    sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
+
+    auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForBlender, std::move(sksl));
+    if (!buffer.validate(effect != nullptr)) {
+        return nullptr;
+    }
+
+    return effect->makeBlender(std::move(uniforms));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<SkData> uniforms,
                                             sk_sp<SkShader> childShaders[],
                                             size_t childCount,
@@ -1179,9 +1237,7 @@
     if (uniforms->size() != this->uniformSize() || !fChildren.empty()) {
         return nullptr;
     }
-    // TODO(skia:12080): create a runtime blend class
-    SkDEBUGFAIL("not yet implemented");
-    return nullptr;
+    return sk_sp<SkBlender>(new SkRuntimeBlender(sk_ref_sp(this), std::move(uniforms)));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1216,3 +1272,12 @@
                                       localMatrix,
                                       isOpaque);
 }
+
+SkRuntimeBlendBuilder::SkRuntimeBlendBuilder(sk_sp<SkRuntimeEffect> effect)
+        : INHERITED(std::move(effect)) {}
+
+SkRuntimeBlendBuilder::~SkRuntimeBlendBuilder() = default;
+
+sk_sp<SkBlender> SkRuntimeBlendBuilder::makeBlender() {
+    return this->effect()->makeBlender(this->uniforms());
+}
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index c2e24ff..69d4579 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "include/core/SkBitmap.h"
+#include "include/core/SkBlender.h"
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColorFilter.h"
 #include "include/core/SkData.h"
@@ -329,7 +330,7 @@
 
     void test(GrColor TL, GrColor TR, GrColor BL, GrColor BR,
               PreTestFn preTestCallback = nullptr) {
-        auto shader = fBuilder->makeShader(nullptr, false);
+        auto shader = fBuilder->makeShader(/*localMatrix=*/nullptr, /*isOpaque=*/false);
         if (!shader) {
             REPORT_FAILURE(fReporter, "shader", SkString("Effect didn't produce a shader"));
             return;
@@ -499,10 +500,27 @@
     // Test passes if this sequence doesn't assert.  skbug.com/10667
     SkRuntimeShaderBuilder b(std::move(effect));
     b.uniform("x") = 0.0f;
-    auto shader_0 = b.makeShader(nullptr, false);
+    auto shader_0 = b.makeShader(/*localMatrix=*/nullptr, /*isOpaque=*/false);
 
     b.uniform("x") = 1.0f;
-    auto shader_1 = b.makeShader(nullptr, true);
+    auto shader_1 = b.makeShader(/*localMatrix=*/nullptr, /*isOpaque=*/true);
+}
+
+DEF_TEST(SkRuntimeBlendBuilderReuse, r) {
+    const char* kSource = R"(
+        uniform half x;
+        half4 main(half4 s, half4 d) { return half4(x); }
+    )";
+
+    sk_sp<SkRuntimeEffect> effect = SkRuntimeEffect::MakeForBlender(SkString(kSource)).effect;
+    REPORTER_ASSERT(r, effect);
+
+    // We should be able to construct multiple SkBlenders in a row without asserting.
+    SkRuntimeBlendBuilder b(std::move(effect));
+    for (float x = 0.0f; x <= 2.0f; x += 2.0f) {
+        b.uniform("x") = x;
+        sk_sp<SkBlender> blender = b.makeBlender();
+    }
 }
 
 DEF_TEST(SkRuntimeShaderBuilderSetUniforms, r) {
@@ -530,8 +548,7 @@
     REPORTER_ASSERT(r, !b.uniform("offset").set<float>(origin, 3));
 #endif
 
-
-    auto shader = b.makeShader(nullptr, false);
+    auto shader = b.makeShader(/*localMatrix=*/nullptr, /*isOpaque=*/false);
 }
 
 DEF_TEST(SkRuntimeEffectThreaded, r) {