Support mixing shaders and color filters in runtime effects

Needs more testing, but includes a GM that demonstrates the ultimate
benefit of this: our 3D color LUT demo working as a color filter.

Bug: skia:11813
Change-Id: I97c129c54bcf2cb788c0806b5d9e907ff058bb69
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/406296
Reviewed-by: Mike Klein <mtklein@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/gm/runtimeshader.cpp b/gm/runtimeshader.cpp
index 7c0792e..e96f2de 100644
--- a/gm/runtimeshader.cpp
+++ b/gm/runtimeshader.cpp
@@ -20,8 +20,9 @@
 #include "tools/Resources.h"
 
 enum RT_Flags {
-    kAnimate_RTFlag = 0x1,
-    kBench_RTFlag   = 0x2,
+    kAnimate_RTFlag     = 0x1,
+    kBench_RTFlag       = 0x2,
+    kColorFilter_RTFlag = 0x4,
 };
 
 class RuntimeShaderGM : public skiagm::GM {
@@ -30,7 +31,9 @@
             : fName(name), fSize(size), fFlags(flags), fSkSL(sksl) {}
 
     void onOnceBeforeDraw() override {
-        auto [effect, error] = SkRuntimeEffect::MakeForShader(fSkSL);
+        auto [effect, error] = (fFlags & kColorFilter_RTFlag)
+                                       ? SkRuntimeEffect::MakeForColorFilter(fSkSL)
+                                       : SkRuntimeEffect::MakeForShader(fSkSL);
         if (!effect) {
             SkDebugf("RuntimeShader error: %s\n", error.c_str());
         }
@@ -340,6 +343,87 @@
 };
 DEF_GM(return new ColorCubeRT;)
 
+// Same as above, but demonstrating how to implement this as a runtime color filter (that samples
+// a shader child for the LUT).
+class ColorCubeColorFilterRT : public RuntimeShaderGM {
+public:
+    ColorCubeColorFilterRT() : RuntimeShaderGM("color_cube_cf_rt", {512, 512}, R"(
+        uniform shader color_cube;
+
+        uniform float rg_scale;
+        uniform float rg_bias;
+        uniform float b_scale;
+        uniform float inv_size;
+
+        half4 main(half4 inColor) {
+            float4 c = unpremul(inColor);
+
+            // Map to cube coords:
+            float3 cubeCoords = float3(c.rg * rg_scale + rg_bias, c.b * b_scale);
+
+            // Compute slice coordinate
+            float2 coords1 = float2((floor(cubeCoords.b) + cubeCoords.r) * inv_size, cubeCoords.g);
+            float2 coords2 = float2(( ceil(cubeCoords.b) + cubeCoords.r) * inv_size, cubeCoords.g);
+
+            // Two bilinear fetches, plus a manual lerp for the third axis:
+            half4 color = mix(sample(color_cube, coords1), sample(color_cube, coords2),
+                              fract(cubeCoords.b));
+
+            // Premul again
+            color.rgb *= color.a;
+
+            return color;
+        }
+    )", kColorFilter_RTFlag) {}
+
+    sk_sp<SkImage> fMandrill, fMandrillSepia, fIdentityCube, fSepiaCube;
+
+    void onOnceBeforeDraw() override {
+        fMandrill      = GetResourceAsImage("images/mandrill_256.png");
+        fMandrillSepia = GetResourceAsImage("images/mandrill_sepia.png");
+        fIdentityCube  = GetResourceAsImage("images/lut_identity.png");
+        fSepiaCube     = GetResourceAsImage("images/lut_sepia.png");
+
+        this->RuntimeShaderGM::onOnceBeforeDraw();
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        // First we draw the unmodified image, and a copy that was sepia-toned in Photoshop:
+        canvas->drawImage(fMandrill,      0,   0);
+        canvas->drawImage(fMandrillSepia, 0, 256);
+
+        // LUT dimensions should be (kSize^2, kSize)
+        constexpr float kSize = 16.0f;
+
+        const SkSamplingOptions sampling(SkFilterMode::kLinear);
+
+        float uniforms[] = {
+                (kSize - 1) / kSize,  // rg_scale
+                0.5f / kSize,         // rg_bias
+                kSize - 1,            // b_scale
+                1.0f / kSize,         // inv_size
+        };
+
+        SkPaint paint;
+
+        // TODO: Should we add SkImage::makeNormalizedShader() to handle this automatically?
+        SkMatrix normalize = SkMatrix::Scale(1.0f / (kSize * kSize), 1.0f / kSize);
+
+        // Now draw the image with an identity color cube - it should look like the original
+        SkRuntimeEffect::ChildPtr children[] = {fIdentityCube->makeShader(sampling, normalize)};
+        paint.setColorFilter(fEffect->makeColorFilter(
+                SkData::MakeWithCopy(uniforms, sizeof(uniforms)), SkMakeSpan(children)));
+        canvas->drawImage(fMandrill, 256, 0, sampling, &paint);
+
+        // ... and with a sepia-tone color cube. This should match the sepia-toned image.
+        children[0] = fSepiaCube->makeShader(sampling, normalize);
+        paint.setColorFilter(fEffect->makeColorFilter(
+                SkData::MakeWithCopy(uniforms, sizeof(uniforms)), SkMakeSpan(children)));
+        canvas->drawImage(fMandrill, 256, 256, sampling, &paint);
+    }
+};
+DEF_GM(return new ColorCubeColorFilterRT;)
+
 class DefaultColorRT : public RuntimeShaderGM {
 public:
     DefaultColorRT() : RuntimeShaderGM("default_color_rt", {512, 256}, R"(
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index f3c4f06..ac9fe97 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -8,10 +8,12 @@
 #ifndef SkRuntimeEffect_DEFINED
 #define SkRuntimeEffect_DEFINED
 
+#include "include/core/SkColorFilter.h"
 #include "include/core/SkData.h"
 #include "include/core/SkImageInfo.h"
 #include "include/core/SkMatrix.h"
 #include "include/core/SkShader.h"
+#include "include/core/SkSpan.h"
 #include "include/core/SkString.h"
 #include "include/private/SkOnce.h"
 #include "include/private/SkSLSampleUsage.h"
@@ -19,10 +21,8 @@
 #include <vector>
 
 class GrRecordingContext;
-class SkColorFilter;
 class SkFilterColorProgram;
 class SkImage;
-class SkShader;
 
 namespace SkSL {
 class FunctionDefinition;
@@ -42,6 +42,7 @@
  */
 class SK_API SkRuntimeEffect : public SkRefCnt {
 public:
+    // Reflected description of a uniform variable in the effect's SkSL
     struct Uniform {
         enum class Type {
             kFloat,
@@ -72,6 +73,7 @@
         size_t sizeInBytes() const;
     };
 
+    // Reflected description of a uniform child (shader or colorFilter) in the effect's SkSL
     struct Child {
         enum class Type {
             kShader,
@@ -131,11 +133,23 @@
 
     static Result MakeForShader(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)) {}
+        ChildPtr(sk_sp<SkColorFilter> cf) : colorFilter(std::move(cf)) {}
+        sk_sp<SkShader> shader;
+        sk_sp<SkColorFilter> colorFilter;
+    };
+
     sk_sp<SkShader> makeShader(sk_sp<SkData> uniforms,
                                sk_sp<SkShader> children[],
                                size_t childCount,
                                const SkMatrix* localMatrix,
                                bool isOpaque) const;
+    sk_sp<SkShader> makeShader(sk_sp<SkData> uniforms,
+                               SkSpan<ChildPtr> children,
+                               const SkMatrix* localMatrix,
+                               bool isOpaque) const;
 
     sk_sp<SkImage> makeImage(GrRecordingContext*,
                              sk_sp<SkData> uniforms,
@@ -149,6 +163,8 @@
     sk_sp<SkColorFilter> makeColorFilter(sk_sp<SkData> uniforms,
                                          sk_sp<SkColorFilter> children[],
                                          size_t childCount) const;
+    sk_sp<SkColorFilter> makeColorFilter(sk_sp<SkData> uniforms,
+                                         SkSpan<ChildPtr> children) const;
 
     const SkString& source() const { return fSkSL; }
 
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 7646883..db02bfe 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -442,7 +442,18 @@
     // color, an immediate color, or the results of a previous sample call). If the color is none
     // 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).
+    if (!std::all_of(effect->fChildren.begin(),
+                     effect->fChildren.end(),
+                     [](const SkRuntimeEffect::Child& c) {
+                         return c.type == SkRuntimeEffect::Child::Type::kColorFilter;
+                     })) {
+        return nullptr;
+    }
+
     // When we run this program later, these uniform values are replaced with either the results of
     // the child (using the SampleCall), or the input color (if the child is nullptr).
     // These Uniform ids are loads from the *first* arg ptr.
@@ -605,15 +616,46 @@
     return uniforms ? uniforms : baseUniforms;
 }
 
+#if SK_SUPPORT_GPU
+static std::unique_ptr<GrFragmentProcessor> make_effect_fp(
+        sk_sp<SkRuntimeEffect> effect,
+        const char* name,
+        sk_sp<SkData> uniforms,
+        SkSpan<const SkRuntimeEffect::ChildPtr> children,
+        const GrFPArgs& childArgs) {
+    auto fp = GrSkSLFP::Make(std::move(effect), name, std::move(uniforms));
+    for (const auto& child : children) {
+        if (child.shader) {
+            auto childFP = as_SB(child.shader)->asFragmentProcessor(childArgs);
+            if (!childFP) {
+                return nullptr;
+            }
+            fp->addChild(std::move(childFP));
+        } else if (child.colorFilter) {
+            auto [success, childFP] = as_CFB(child.colorFilter)
+                                              ->asFragmentProcessor(/*inputFP=*/nullptr,
+                                                                    childArgs.fContext,
+                                                                    *childArgs.fDstColorInfo);
+            if (!success) {
+                return nullptr;
+            }
+            fp->addChild(std::move(childFP));
+        } else {
+            fp->addChild(nullptr);
+        }
+    }
+    return std::move(fp);
+}
+#endif
+
 class SkRuntimeColorFilter : public SkColorFilterBase {
 public:
     SkRuntimeColorFilter(sk_sp<SkRuntimeEffect> effect,
                          sk_sp<SkData> uniforms,
-                         sk_sp<SkColorFilter> children[],
-                         size_t childCount)
+                         SkSpan<SkRuntimeEffect::ChildPtr> children)
             : fEffect(std::move(effect))
             , fUniforms(std::move(uniforms))
-            , fChildren(children, children + childCount) {}
+            , fChildren(children.begin(), children.end()) {}
 
 #if SK_SUPPORT_GPU
     GrFPResult asFragmentProcessor(std::unique_ptr<GrFragmentProcessor> inputFP,
@@ -623,18 +665,14 @@
                 get_xformed_uniforms(fEffect.get(), fUniforms, colorInfo.colorSpace());
         SkASSERT(uniforms);
 
-        auto fp = GrSkSLFP::Make(fEffect, "Runtime_Color_Filter", std::move(uniforms));
-        for (const auto& child : fChildren) {
-            std::unique_ptr<GrFragmentProcessor> childFP;
-            if (child) {
-                bool success;
-                std::tie(success, childFP) = as_CFB(child)->asFragmentProcessor(
-                        /*inputFP=*/nullptr, context, colorInfo);
-                if (!success) {
-                    return GrFPFailure(std::move(inputFP));
-                }
-            }
-            fp->addChild(std::move(childFP));
+        GrFPArgs childArgs(context, SkSimpleMatrixProvider(SkMatrix::I()), &colorInfo);
+        auto fp = make_effect_fp(fEffect,
+                                 "Runtime_Color_Filter",
+                                 std::move(uniforms),
+                                 SkMakeSpan(fChildren),
+                                 childArgs);
+        if (!fp) {
+            return GrFPFailure(std::move(inputFP));
         }
 
         // Runtime effect scripts are written to take an input color, not a fragment processor.
@@ -659,9 +697,13 @@
         // something. (Uninitialized values can trigger asserts in skvm::Builder).
         skvm::Coord zeroCoord = { p->splat(0.0f), p->splat(0.0f) };
 
-        auto sampleChild = [&](int ix, skvm::Coord /*coord*/, skvm::Color color) {
-            if (fChildren[ix]) {
-                return as_CFB(fChildren[ix])->program(p, color, dst, uniforms, alloc);
+        auto sampleChild = [&](int ix, skvm::Coord coord, skvm::Color color) {
+            if (fChildren[ix].shader) {
+                SkSimpleMatrixProvider mats{SkMatrix::I()};
+                return as_SB(fChildren[ix].shader)
+                        ->program(p, coord, coord, color, mats, nullptr, dst, uniforms, alloc);
+            } else if (fChildren[ix].colorFilter) {
+                return as_CFB(fChildren[ix].colorFilter)->program(p, color, dst, uniforms, alloc);
             } else {
                 return color;
             }
@@ -694,8 +736,12 @@
         SkASSERT(inputs);
 
         auto evalChild = [&](int index, SkPMColor4f inColor) {
-            const SkColorFilter* child = fChildren[index].get();
-            return child ? as_CFB(child)->onFilterColor4f(inColor, dstCS) : inColor;
+            const auto& child = fChildren[index];
+
+            // Guaranteed by initFilterColorInfo
+            SkASSERT(!child.shader);
+            return child.colorFilter ? as_CFB(child.colorFilter)->onFilterColor4f(inColor, dstCS)
+                                     : inColor;
         };
 
         return program->eval(color, inputs->data(), evalChild);
@@ -715,7 +761,8 @@
         }
         buffer.write32(fChildren.size());
         for (const auto& child : fChildren) {
-            buffer.writeFlattenable(child.get());
+            buffer.writeFlattenable(child.shader ? (const SkFlattenable*)child.shader.get()
+                                                 : child.colorFilter.get());
         }
     }
 
@@ -724,7 +771,7 @@
 private:
     sk_sp<SkRuntimeEffect> fEffect;
     sk_sp<SkData> fUniforms;
-    std::vector<sk_sp<SkColorFilter>> fChildren;
+    std::vector<SkRuntimeEffect::ChildPtr> fChildren;
 };
 
 sk_sp<SkFlattenable> SkRuntimeColorFilter::CreateProc(SkReadBuffer& buffer) {
@@ -742,25 +789,36 @@
         return nullptr;
     }
 
-    std::vector<sk_sp<SkColorFilter>> children(childCount);
-    for (size_t i = 0; i < children.size(); ++i) {
-        children[i] = buffer.readColorFilter();
+    SkSTArray<4, SkRuntimeEffect::ChildPtr> children(childCount);
+    for (const auto& child : effect->children()) {
+        if (child.type == SkRuntimeEffect::Child::Type::kShader) {
+            children.emplace_back(buffer.readShader());
+        } else {
+            SkASSERT(child.type == SkRuntimeEffect::Child::Type::kColorFilter);
+            children.emplace_back(buffer.readColorFilter());
+        }
+    }
+    if (!buffer.isValid()) {
+        return nullptr;
     }
 
-    return effect->makeColorFilter(std::move(uniforms), children.data(), children.size());
+    return effect->makeColorFilter(std::move(uniforms), SkMakeSpan(children));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 class SkRTShader : public SkShaderBase {
 public:
-    SkRTShader(sk_sp<SkRuntimeEffect> effect, sk_sp<SkData> uniforms, const SkMatrix* localMatrix,
-               sk_sp<SkShader>* children, size_t childCount, bool isOpaque)
+    SkRTShader(sk_sp<SkRuntimeEffect> effect,
+               sk_sp<SkData> uniforms,
+               const SkMatrix* localMatrix,
+               SkSpan<SkRuntimeEffect::ChildPtr> children,
+               bool isOpaque)
             : SkShaderBase(localMatrix)
             , fEffect(std::move(effect))
             , fIsOpaque(isOpaque)
             , fUniforms(std::move(uniforms))
-            , fChildren(children, children + childCount) {}
+            , fChildren(children.begin(), children.end()) {}
 
     bool isOpaque() const override { return fIsOpaque; }
 
@@ -780,12 +838,12 @@
         GrFPArgs childArgs = args;
         childArgs.fInputColorIsOpaque = false;
 
-        auto fp = GrSkSLFP::Make(fEffect, "runtime_shader", std::move(uniforms));
-        for (const auto& child : fChildren) {
-            auto childFP = child ? as_SB(child)->asFragmentProcessor(childArgs) : nullptr;
-            fp->addChild(std::move(childFP));
+        auto result = make_effect_fp(
+                fEffect, "runtime_shader", std::move(uniforms), SkMakeSpan(fChildren), childArgs);
+        if (!result) {
+            return nullptr;
         }
-        std::unique_ptr<GrFragmentProcessor> result = std::move(fp);
+
         // If the shader was created with isOpaque = true, we *force* that result here.
         // CPU does the same thing (in SkShaderBase::program).
         if (fIsOpaque) {
@@ -826,11 +884,12 @@
         local = SkShaderBase::ApplyMatrix(p,inv,local,uniforms);
 
         auto sampleChild = [&](int ix, skvm::Coord coord, skvm::Color color) {
-            if (fChildren[ix]) {
+            if (fChildren[ix].shader) {
                 SkOverrideDeviceMatrixProvider mats{matrices, SkMatrix::I()};
-                return as_SB(fChildren[ix])->program(p, device, coord, color,
-                                                     mats, nullptr, dst,
-                                                     uniforms, alloc);
+                return as_SB(fChildren[ix].shader)
+                        ->program(p, device, coord, color, mats, nullptr, dst, uniforms, alloc);
+            } else if (fChildren[ix].colorFilter) {
+                return as_CFB(fChildren[ix].colorFilter)->program(p, color, dst, uniforms, alloc);
             } else {
                 return color;
             }
@@ -870,7 +929,8 @@
         }
         buffer.write32(fChildren.size());
         for (const auto& child : fChildren) {
-            buffer.writeFlattenable(child.get());
+            buffer.writeFlattenable(child.shader ? (const SkFlattenable*)child.shader.get()
+                                                 : child.colorFilter.get());
         }
     }
 
@@ -888,7 +948,7 @@
     bool fIsOpaque;
 
     sk_sp<SkData> fUniforms;
-    std::vector<sk_sp<SkShader>> fChildren;
+    std::vector<SkRuntimeEffect::ChildPtr> fChildren;
 };
 
 sk_sp<SkFlattenable> SkRTShader::CreateProc(SkReadBuffer& buffer) {
@@ -914,43 +974,65 @@
         return nullptr;
     }
 
-    std::vector<sk_sp<SkShader>> children(childCount);
-    for (size_t i = 0; i < children.size(); ++i) {
-        children[i] = buffer.readShader();
+    SkSTArray<4, SkRuntimeEffect::ChildPtr> children(childCount);
+    for (const auto& child : effect->children()) {
+        if (child.type == SkRuntimeEffect::Child::Type::kShader) {
+            children.emplace_back(buffer.readShader());
+        } else {
+            SkASSERT(child.type == SkRuntimeEffect::Child::Type::kColorFilter);
+            children.emplace_back(buffer.readColorFilter());
+        }
+    }
+    if (!buffer.isValid()) {
+        return nullptr;
     }
 
-    return effect->makeShader(std::move(uniforms), children.data(), children.size(), localMPtr,
-                              isOpaque);
+    return effect->makeShader(std::move(uniforms), SkMakeSpan(children), localMPtr, isOpaque);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<SkData> uniforms,
-                                            sk_sp<SkShader> children[],
+                                            sk_sp<SkShader> childShaders[],
                                             size_t childCount,
                                             const SkMatrix* localMatrix,
                                             bool isOpaque) const {
+    SkSTArray<4, ChildPtr> children(childCount);
+    for (size_t i = 0; i < childCount; ++i) {
+        children.emplace_back(childShaders[i]);
+    }
+    return this->makeShader(std::move(uniforms), SkMakeSpan(children), localMatrix, isOpaque);
+}
+
+sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<SkData> uniforms,
+                                            SkSpan<ChildPtr> children,
+                                            const SkMatrix* localMatrix,
+                                            bool isOpaque) const {
     if (!this->allowShader()) {
         return nullptr;
     }
+    if (children.size() != fChildren.size()) {
+        return nullptr;
+    }
+    // Verify that all child objects match the declared type in the SkSL
+    for (size_t i = 0; i < children.size(); ++i) {
+        if (fChildren[i].type == Child::Type::kShader) {
+            if (children[i].colorFilter) {
+                return nullptr;
+            }
+        } else {
+            SkASSERT(fChildren[i].type == Child::Type::kColorFilter);
+            if (children[i].shader) {
+                return nullptr;
+            }
+        }
+    }
     if (!uniforms) {
         uniforms = SkData::MakeEmpty();
     }
-    // Verify that all child objects are shaders (to match the C++ types here).
-    // TODO(skia:11813) When we support shader and colorFilter children (with different samplng
-    // semantics), the 'children' parameter will contain both types, so this will be more complex.
-    if (!std::all_of(fChildren.begin(), fChildren.end(), [](const Child& c) {
-            return c.type == Child::Type::kShader;
-        })) {
-        return nullptr;
-    }
-    return uniforms->size() == this->uniformSize() && childCount == fChildren.size()
-                   ? sk_sp<SkShader>(new SkRTShader(sk_ref_sp(this),
-                                                    std::move(uniforms),
-                                                    localMatrix,
-                                                    children,
-                                                    childCount,
-                                                    isOpaque))
+    return uniforms->size() == this->uniformSize()
+                   ? sk_sp<SkShader>(new SkRTShader(
+                             sk_ref_sp(this), std::move(uniforms), localMatrix, children, isOpaque))
                    : nullptr;
 }
 
@@ -1033,30 +1115,47 @@
 }
 
 sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<SkData> uniforms,
-                                                      sk_sp<SkColorFilter> children[],
+                                                      sk_sp<SkColorFilter> childColorFilters[],
                                                       size_t childCount) const {
+    SkSTArray<4, ChildPtr> children(childCount);
+    for (size_t i = 0; i < childCount; ++i) {
+        children.emplace_back(childColorFilters[i]);
+    }
+    return this->makeColorFilter(std::move(uniforms), SkMakeSpan(children));
+}
+
+sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<SkData> uniforms,
+                                                      SkSpan<ChildPtr> children) const {
     if (!this->allowColorFilter()) {
         return nullptr;
     }
+    if (children.size() != fChildren.size()) {
+        return nullptr;
+    }
+    // Verify that all child objects match the declared type in the SkSL
+    for (size_t i = 0; i < children.size(); ++i) {
+        if (fChildren[i].type == Child::Type::kShader) {
+            if (children[i].colorFilter) {
+                return nullptr;
+            }
+        } else {
+            SkASSERT(fChildren[i].type == Child::Type::kColorFilter);
+            if (children[i].shader) {
+                return nullptr;
+            }
+        }
+    }
     if (!uniforms) {
         uniforms = SkData::MakeEmpty();
     }
-    // Verify that all child objects are color filters (to match the C++ types here).
-    // TODO(skia:11813) When we support shader and colorFilter children (with different samplng
-    // semantics), the 'children' parameter will contain both types, so this will be more complex.
-    if (!std::all_of(fChildren.begin(), fChildren.end(), [](const Child& c) {
-            return c.type == Child::Type::kColorFilter;
-        })) {
-        return nullptr;
-    }
-    return uniforms->size() == this->uniformSize() && childCount == fChildren.size()
+    return uniforms->size() == this->uniformSize()
                    ? sk_sp<SkColorFilter>(new SkRuntimeColorFilter(
-                             sk_ref_sp(this), std::move(uniforms), children, childCount))
+                             sk_ref_sp(this), std::move(uniforms), children))
                    : nullptr;
 }
 
 sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<SkData> uniforms) const {
-    return this->makeColorFilter(std::move(uniforms), nullptr, 0);
+    return this->makeColorFilter(std::move(uniforms), /*children=*/{});
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////