Remove Ganesh code from SkBlenderBase and SkBlendModeBlender

This copies SkRuntimeEffect::make_effect_fp into GrFragmentProcessors
and that copy should be removed when other parts of SkRuntimeEffect
are decoupled from Ganesh.

In order to move SkRuntimeBlender into its own header, I hoisted some
classes and functions to SkRuntimeEffectPriv. I also enforced IWYU
on SkRuntimeEffect.cpp and the newly added SkRuntimeBlender.cpp

Change-Id: I9da04a64ac4d31949532f7fc3f8010b1b58c94f3
Bug: skia:14317
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/701520
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Kevin Lubick <kjlubick@google.com>
diff --git a/gn/core.gni b/gn/core.gni
index 3c32537..5635497 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -502,6 +502,8 @@
   "$_src/core/SkRegion_path.cpp",
   "$_src/core/SkResourceCache.cpp",
   "$_src/core/SkResourceCache.h",
+  "$_src/core/SkRuntimeBlender.cpp",
+  "$_src/core/SkRuntimeBlender.h",
   "$_src/core/SkRuntimeEffect.cpp",
   "$_src/core/SkRuntimeEffectPriv.h",
   "$_src/core/SkSLTypeShared.cpp",
diff --git a/include/core/SkBlender.h b/include/core/SkBlender.h
index 7acba87..741c461 100644
--- a/include/core/SkBlender.h
+++ b/include/core/SkBlender.h
@@ -26,8 +26,6 @@
 private:
     SkBlender() = default;
     friend class SkBlenderBase;
-
-    using INHERITED = SkFlattenable;
 };
 
 #endif
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index 4d3450e..41918ed 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -8,11 +8,12 @@
 #ifndef SkRuntimeEffect_DEFINED
 #define SkRuntimeEffect_DEFINED
 
-#include "include/core/SkBlender.h"
-#include "include/core/SkColorFilter.h"
+#include "include/core/SkBlender.h"  // IWYU pragma: keep
+#include "include/core/SkColorFilter.h"  // IWYU pragma: keep
 #include "include/core/SkData.h"
-#include "include/core/SkImageInfo.h"
+#include "include/core/SkFlattenable.h"
 #include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
 #include "include/core/SkShader.h"
 #include "include/core/SkSpan.h"
 #include "include/core/SkString.h"
@@ -20,34 +21,38 @@
 #include "include/private/SkSLSampleUsage.h"
 #include "include/private/base/SkOnce.h"
 #include "include/private/base/SkTemplates.h"
+#include "include/private/base/SkTo.h"
+#include "include/private/base/SkTypeTraits.h"
 
-#include <string>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
 #include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
 #include <vector>
 
 #ifdef SK_ENABLE_SKSL
 
+#include "include/sksl/SkSLDebugTrace.h"
 #include "include/sksl/SkSLVersion.h"
 
 class GrRecordingContext;
 class SkFilterColorProgram;
 class SkImage;
-class SkRuntimeImageFilter;
+struct SkIPoint;
+struct SkImageInfo;
 
 namespace SkSL {
-class DebugTrace;
 class DebugTracePriv;
-class ErrorReporter;
 class FunctionDefinition;
 struct Program;
 enum class ProgramKind : int8_t;
 struct ProgramSettings;
 }  // namespace SkSL
 
-namespace skvm {
-class Program;
-}
-
 namespace SkSL::RP {
 class Program;
 }
diff --git a/public.bzl b/public.bzl
index 5aff944..b9112ca 100644
--- a/public.bzl
+++ b/public.bzl
@@ -622,6 +622,8 @@
     "src/core/SkRegion_path.cpp",
     "src/core/SkResourceCache.cpp",
     "src/core/SkResourceCache.h",
+    "src/core/SkRuntimeBlender.cpp",
+    "src/core/SkRuntimeBlender.h",
     "src/core/SkRuntimeEffect.cpp",
     "src/core/SkRuntimeEffectPriv.h",
     "src/core/SkSLTypeShared.cpp",
diff --git a/src/BUILD.bazel b/src/BUILD.bazel
index 8934c33..4ead2a0 100644
--- a/src/BUILD.bazel
+++ b/src/BUILD.bazel
@@ -162,6 +162,7 @@
         "src/core/SkDrawProcs.h",
         "src/core/SkMatrixPriv.h",
         "src/core/SkPathPriv.h",
+        "src/core/SkRuntimeEffectPriv.h",
         "src/encode/SkICCPriv.h",
         "src/encode/SkImageEncoderFns.h",
         "src/encode/SkImageEncoderPriv.h",
diff --git a/src/core/BUILD.bazel b/src/core/BUILD.bazel
index 30667fb..7b99be0 100644
--- a/src/core/BUILD.bazel
+++ b/src/core/BUILD.bazel
@@ -368,6 +368,8 @@
     "SkFilterColorProgram.cpp",
     "SkFilterColorProgram.h",
     "SkRuntimeEffect.cpp",
+    "SkRuntimeBlender.cpp",
+    "SkRuntimeBlender.h",
     "SkSLTypeShared.cpp",
     "SkSLTypeShared.h",
 ]
diff --git a/src/core/SkBlendModeBlender.cpp b/src/core/SkBlendModeBlender.cpp
index bb561cc..cd290b4 100644
--- a/src/core/SkBlendModeBlender.cpp
+++ b/src/core/SkBlendModeBlender.cpp
@@ -17,19 +17,11 @@
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkWriteBuffer.h"
 
-#if defined(SK_GANESH)
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
-struct GrFPArgs;
-#endif
-
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyHelpers.h"
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif
 
-#include <utility>
-
 sk_sp<SkBlender> SkBlender::Mode(SkBlendMode mode) {
 #define RETURN_SINGLETON_BLENDER(m)                            \
     case m: {                                                  \
@@ -103,15 +95,6 @@
     buffer.writeInt((int)fMode);
 }
 
-#if defined(SK_GANESH)
-std::unique_ptr<GrFragmentProcessor> SkBlendModeBlender::asFragmentProcessor(
-        std::unique_ptr<GrFragmentProcessor> srcFP,
-        std::unique_ptr<GrFragmentProcessor> dstFP,
-        const GrFPArgs& fpArgs) const {
-    return GrBlendFragmentProcessor::Make(std::move(srcFP), std::move(dstFP), fMode);
-}
-#endif
-
 bool SkBlendModeBlender::onAppendStages(const SkStageRec& rec) const {
     SkBlendMode_AppendStages(fMode, rec.fPipeline);
     return true;
diff --git a/src/core/SkBlendModeBlender.h b/src/core/SkBlendModeBlender.h
index 5a64da6..0c4c07d 100644
--- a/src/core/SkBlendModeBlender.h
+++ b/src/core/SkBlendModeBlender.h
@@ -11,20 +11,20 @@
 #include "include/core/SkFlattenable.h"
 #include "src/core/SkBlenderBase.h"
 
-#include <memory>
 #include <optional>
 
-class GrFragmentProcessor;
 class SkReadBuffer;
 class SkWriteBuffer;
 enum class SkBlendMode;
-struct GrFPArgs;
 struct SkStageRec;
 
 class SkBlendModeBlender : public SkBlenderBase {
 public:
     SkBlendModeBlender(SkBlendMode mode) : fMode(mode) {}
 
+    BlenderType type() const override { return BlenderType::kBlendMode; }
+    SkBlendMode mode() const { return fMode; }
+
     SK_FLATTENABLE_HOOKS(SkBlendModeBlender)
 
 private:
@@ -32,13 +32,6 @@
 
     std::optional<SkBlendMode> asBlendMode() const final { return fMode; }
 
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            std::unique_ptr<GrFragmentProcessor> srcFP,
-            std::unique_ptr<GrFragmentProcessor> dstFP,
-            const GrFPArgs& fpArgs) const override;
-#endif
-
 #if defined(SK_GRAPHITE)
     void addToKey(const skgpu::graphite::KeyContext&,
                   skgpu::graphite::PaintParamsKeyBuilder*,
diff --git a/src/core/SkBlenderBase.h b/src/core/SkBlenderBase.h
index 76e7582..05844c9 100644
--- a/src/core/SkBlenderBase.h
+++ b/src/core/SkBlenderBase.h
@@ -28,6 +28,10 @@
 class PipelineDataGatherer;
 }
 
+#define SK_ALL_BLENDERS(M) \
+    M(BlendMode)           \
+    M(Runtime)
+
 /**
  * Encapsulates a blend function, including non-public APIs.
  * Blends combine a source color (the result of our paint) and destination color (from the canvas)
@@ -58,17 +62,6 @@
     }
 #endif
 
-#if defined(SK_GANESH)
-    /**
-     * Returns a GrFragmentProcessor that implements this blend for the GPU backend.
-     * The GrFragmentProcessor expects premultiplied inputs and returns a premultiplied output.
-     */
-    virtual std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            std::unique_ptr<GrFragmentProcessor> srcFP,
-            std::unique_ptr<GrFragmentProcessor> dstFP,
-            const GrFPArgs& fpArgs) const = 0;
-#endif
-
     virtual SkRuntimeEffect* asRuntimeEffect() const { return nullptr; }
 
 #if defined(SK_GRAPHITE)
@@ -78,7 +71,15 @@
 #endif
 
     static SkFlattenable::Type GetFlattenableType() { return kSkBlender_Type; }
-    Type getFlattenableType() const override { return GetFlattenableType(); }
+    SkFlattenable::Type getFlattenableType() const override { return GetFlattenableType(); }
+
+    enum class BlenderType {
+    #define M(type) k ## type,
+        SK_ALL_BLENDERS(M)
+    #undef M
+    };
+
+    virtual BlenderType type() const = 0;
 
 private:
 #if defined(SK_ENABLE_SKVM)
@@ -86,8 +87,6 @@
                                   const SkColorInfo& colorInfo, skvm::Uniforms* uniforms,
                                   SkArenaAlloc* alloc) const = 0;
 #endif
-
-    using INHERITED = SkFlattenable;
 };
 
 inline SkBlenderBase* as_BB(SkBlender* blend) {
diff --git a/src/core/SkRuntimeBlender.cpp b/src/core/SkRuntimeBlender.cpp
new file mode 100644
index 0000000..e2a3912
--- /dev/null
+++ b/src/core/SkRuntimeBlender.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "src/core/SkRuntimeBlender.h"
+
+#include "include/core/SkCapabilities.h"
+#include "include/core/SkData.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkString.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkSLSampleUsage.h"
+#include "include/private/base/SkTArray.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkReadBuffer.h"
+#include "src/core/SkRuntimeEffectPriv.h"
+#include "src/core/SkWriteBuffer.h"
+#include "src/shaders/SkShaderBase.h"
+#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+#include <string>
+
+using namespace skia_private;
+
+#if defined(SK_BUILD_FOR_DEBUGGER)
+    #define SK_LENIENT_SKSL_DESERIALIZATION 1
+#else
+    #define SK_LENIENT_SKSL_DESERIALIZATION 0
+#endif
+
+sk_sp<SkFlattenable> SkRuntimeBlender::CreateProc(SkReadBuffer& buffer) {
+    if (!buffer.validate(buffer.allowSkSL())) {
+        return nullptr;
+    }
+
+    SkString sksl;
+    buffer.readString(&sksl);
+    sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
+
+    auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForBlender, std::move(sksl));
+#if !SK_LENIENT_SKSL_DESERIALIZATION
+    if (!buffer.validate(effect != nullptr)) {
+        return nullptr;
+    }
+#endif
+
+    STArray<4, SkRuntimeEffect::ChildPtr> children;
+    if (!SkRuntimeEffectPriv::ReadChildEffects(buffer, effect.get(), &children)) {
+        return nullptr;
+    }
+
+#if SK_LENIENT_SKSL_DESERIALIZATION
+    if (!effect) {
+        SkDebugf("Serialized SkSL failed to compile. Ignoring/dropping SkSL blender.\n");
+        return nullptr;
+    }
+#endif
+
+    return effect->makeBlender(std::move(uniforms), SkSpan(children));
+}
+
+bool SkRuntimeBlender::onAppendStages(const SkStageRec& rec) const {
+#ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
+    if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
+        // SkRP has support for many parts of #version 300 already, but for now, we restrict its
+        // usage in runtime effects to just #version 100.
+        return false;
+    }
+    if (const SkSL::RP::Program* program = fEffect->getRPProgram(/*debugTrace=*/nullptr)) {
+        SkSpan<const float> uniforms = SkRuntimeEffectPriv::UniformsAsSpan(
+                fEffect->uniforms(),
+                fUniforms,
+                /*alwaysCopyIntoAlloc=*/false,
+                rec.fDstCS,
+                rec.fAlloc);
+        SkShaderBase::MatrixRec matrix(SkMatrix::I());
+        matrix.markCTMApplied();
+        RuntimeEffectRPCallbacks callbacks(rec, matrix, fChildren, fEffect->fSampleUsages);
+        bool success = program->appendStages(rec.fPipeline, rec.fAlloc, &callbacks, uniforms);
+        return success;
+    }
+#endif
+    return false;
+}
+
+void SkRuntimeBlender::flatten(SkWriteBuffer& buffer) const {
+    buffer.writeString(fEffect->source().c_str());
+    buffer.writeDataAsByteArray(fUniforms.get());
+    SkRuntimeEffectPriv::WriteChildEffects(buffer, fChildren);
+}
+
+#ifdef SK_ENABLE_SKVM
+skvm::Color SkRuntimeBlender::onProgram(skvm::Builder* p, skvm::Color src, skvm::Color dst,
+                                        const SkColorInfo& colorInfo, skvm::Uniforms* uniforms,
+                                        SkArenaAlloc* alloc) const {
+    if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
+        return {};
+    }
+
+    sk_sp<const SkData> inputs = SkRuntimeEffectPriv::TransformUniforms(fEffect->uniforms(),
+                                                                        fUniforms,
+                                                                        colorInfo.colorSpace());
+    SkASSERT(inputs);
+
+    SkShaderBase::MatrixRec mRec(SkMatrix::I());
+    mRec.markTotalMatrixInvalid();
+    RuntimeEffectVMCallbacks callbacks(p, uniforms, alloc, fChildren, mRec, src, colorInfo);
+    std::vector<skvm::Val> uniform = SkRuntimeEffectPriv::MakeSkVMUniforms(p,
+                                                                           uniforms,
+                                                                           fEffect->uniformSize(),
+                                                                           *inputs);
+
+    // 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,/*debugTrace=*/nullptr,
+                               SkSpan(uniform), /*device=*/zeroCoord, /*local=*/zeroCoord,
+                               src, dst, &callbacks);
+}
+#endif
+
+#if defined(SK_GRAPHITE)
+void SkRuntimeBlender::addToKey(const skgpu::graphite::KeyContext& keyContext,
+                                skgpu::graphite::PaintParamsKeyBuilder* builder,
+                                skgpu::graphite::PipelineDataGatherer* gatherer) const {
+    using namespace skgpu::graphite;
+
+    sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
+            fEffect->uniforms(),
+            fUniforms,
+            keyContext.dstColorInfo().colorSpace());
+    SkASSERT(uniforms);
+
+    RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer,
+                                   { fEffect, std::move(uniforms) });
+
+    SkRuntimeEffectPriv::AddChildrenToKey(fChildren, fEffect->children(), keyContext, builder,
+                                          gatherer);
+
+    builder->endBlock();
+}
+#endif
diff --git a/src/core/SkRuntimeBlender.h b/src/core/SkRuntimeBlender.h
new file mode 100644
index 0000000..b060437
--- /dev/null
+++ b/src/core/SkRuntimeBlender.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkRuntimeBlender_DEFINED
+#define SkRuntimeBlender_DEFINED
+
+#include "include/core/SkData.h"
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkRefCnt.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/base/SkSpan_impl.h"
+#include "src/core/SkBlenderBase.h"
+
+#include <utility>
+#include <vector>
+
+class SkReadBuffer;
+class SkWriteBuffer;
+struct SkStageRec;
+
+class SkRuntimeBlender : public SkBlenderBase {
+public:
+    SkRuntimeBlender(sk_sp<SkRuntimeEffect> effect,
+                     sk_sp<const SkData> uniforms,
+                     SkSpan<SkRuntimeEffect::ChildPtr> children)
+            : fEffect(std::move(effect))
+            , fUniforms(std::move(uniforms))
+            , fChildren(children.begin(), children.end()) {}
+
+    SkRuntimeEffect* asRuntimeEffect() const override { return fEffect.get(); }
+
+    BlenderType type() const override { return BlenderType::kRuntime; }
+
+    bool onAppendStages(const SkStageRec& rec) const override;
+
+#ifdef SK_ENABLE_SKVM
+    skvm::Color onProgram(skvm::Builder* p, skvm::Color src, skvm::Color dst,
+                          const SkColorInfo& colorInfo, skvm::Uniforms* uniforms,
+                          SkArenaAlloc* alloc) const override;
+#endif
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext& keyContext,
+                  skgpu::graphite::PaintParamsKeyBuilder* builder,
+                  skgpu::graphite::PipelineDataGatherer* gatherer) const override;
+#endif
+
+    void flatten(SkWriteBuffer& buffer) const override;
+
+    SK_FLATTENABLE_HOOKS(SkRuntimeBlender)
+
+    sk_sp<SkRuntimeEffect> effect() const { return fEffect; }
+    sk_sp<const SkData> uniforms() const { return fUniforms; }
+    std::vector<SkRuntimeEffect::ChildPtr> children() const { return fChildren; }
+private:
+    sk_sp<SkRuntimeEffect> fEffect;
+    sk_sp<const SkData> fUniforms;
+    std::vector<SkRuntimeEffect::ChildPtr> fChildren;
+};
+
+#endif
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 0655dd8..a2d3946 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -7,55 +7,82 @@
 
 #include "include/effects/SkRuntimeEffect.h"
 
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkBlendMode.h"
+#include "include/core/SkBlender.h"
+#include "include/core/SkCanvas.h"
 #include "include/core/SkCapabilities.h"
 #include "include/core/SkColorFilter.h"
 #include "include/core/SkData.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkPaint.h"
 #include "include/core/SkSurface.h"
+#include "include/private/SkColorData.h"
+#include "include/private/base/SkAlign.h"
+#include "include/private/base/SkDebug.h"
 #include "include/private/base/SkMutex.h"
 #include "include/private/base/SkOnce.h"
+#include "include/private/base/SkTArray.h"
+#include "src/base/SkArenaAlloc.h"
 #include "src/base/SkNoDestructor.h"
-#include "src/base/SkUtils.h"
+#include "src/base/SkTLazy.h"
 #include "src/core/SkBlenderBase.h"
-#include "src/core/SkCanvasPriv.h"
 #include "src/core/SkChecksum.h"
 #include "src/core/SkColorFilterBase.h"
 #include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkColorSpaceXformSteps.h"
+#include "src/core/SkEffectPriv.h"
 #include "src/core/SkFilterColorProgram.h"
 #include "src/core/SkLRUCache.h"
-#include "src/core/SkMatrixProvider.h"
+#include "src/core/SkPicturePriv.h"
 #include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkReadBuffer.h"
+#include "src/core/SkRuntimeBlender.h"
 #include "src/core/SkRuntimeEffectPriv.h"
-#include "src/core/SkVM.h"
+#include "src/core/SkStreamPriv.h"
 #include "src/core/SkWriteBuffer.h"
 #include "src/shaders/SkLocalMatrixShader.h"
+#include "src/shaders/SkShaderBase.h"
 #include "src/sksl/SkSLAnalysis.h"
 #include "src/sksl/SkSLBuiltinTypes.h"
 #include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramKind.h"
 #include "src/sksl/SkSLProgramSettings.h"
 #include "src/sksl/SkSLUtil.h"
 #include "src/sksl/analysis/SkSLProgramUsage.h"
 #include "src/sksl/codegen/SkSLRasterPipelineBuilder.h"
-#include "src/sksl/codegen/SkSLVMCodeGenerator.h"
-#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLLayout.h"
+#include "src/sksl/ir/SkSLModifiers.h"
 #include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLProgramElement.h"
+#include "src/sksl/ir/SkSLStatement.h"
+#include "src/sksl/ir/SkSLType.h"
 #include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
 #include "src/sksl/tracing/SkSLDebugTracePriv.h"
 
+#include <algorithm>
+#include <tuple>
+
+class SkColorSpace;
+class SkSurfaceProps;
+struct SkIPoint;
+
 #if defined(SK_GANESH)
+#include "include/gpu/GpuTypes.h"
 #include "include/gpu/GrRecordingContext.h"
+#include "include/gpu/GrTypes.h"
 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
-#include "src/gpu/SkBackingFit.h"
 #include "src/gpu/ganesh/GrCaps.h"
 #include "src/gpu/ganesh/GrColorInfo.h"
 #include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrImageInfo.h"
+#include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrFragmentProcessors.h"
 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
-#include "src/gpu/ganesh/SurfaceFillContext.h"
-#include "src/gpu/ganesh/effects/GrMatrixEffect.h"
-#include "src/gpu/ganesh/effects/GrSkSLFP.h"
-#include "src/gpu/ganesh/image/SkImage_Ganesh.h"
 #endif
 
 #if defined(SK_GRAPHITE)
@@ -66,13 +93,11 @@
 
 // Set `skia_enable_sksl_in_raster_pipeline = true` in your GN args to use Raster Pipeline SkSL.
 #ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
-#include "src/core/SkStreamPriv.h"
 #include "src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h"
+
 constexpr bool kRPEnableLiveTrace = false;
 #endif
 
-#include <algorithm>
-
 using namespace skia_private;
 
 #if defined(SK_BUILD_FOR_DEBUGGER)
@@ -237,7 +262,7 @@
     return fRPProgram.get();
 }
 
-[[maybe_unused]] static SkSpan<const float> uniforms_as_span(
+SkSpan<const float> SkRuntimeEffectPriv::UniformsAsSpan(
         SkSpan<const SkRuntimeEffect::Uniform> uniforms,
         sk_sp<const SkData> originalData,
         bool alwaysCopyIntoAlloc,
@@ -262,87 +287,73 @@
 }
 
 #ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
-class RuntimeEffectRPCallbacks : public SkSL::RP::Callbacks {
-public:
-    RuntimeEffectRPCallbacks(const SkStageRec& s,
-                             const SkShaderBase::MatrixRec& m,
-                             SkSpan<const SkRuntimeEffect::ChildPtr> c,
-                             SkSpan<const SkSL::SampleUsage> u)
-            : fStage(s), fMatrix(m), fChildren(c), fSampleUsages(u) {}
+bool RuntimeEffectRPCallbacks::appendShader(int index) {
+    if (SkShader* shader = fChildren[index].shader()) {
+        if (fSampleUsages[index].isPassThrough()) {
+            // Given a passthrough sample, the total-matrix is still as valid as before.
+            return as_SB(shader)->appendStages(fStage, fMatrix);
+        }
+        // For a non-passthrough sample, we need to explicitly mark the total-matrix as invalid.
+        SkShaderBase::MatrixRec nonPassthroughMatrix = fMatrix;
+        nonPassthroughMatrix.markTotalMatrixInvalid();
+        return as_SB(shader)->appendStages(fStage, nonPassthroughMatrix);
+    }
+    // Return the paint color when a null child shader is evaluated.
+    fStage.fPipeline->append_constant_color(fStage.fAlloc, fStage.fPaintColor);
+    return true;
+}
+bool RuntimeEffectRPCallbacks::appendColorFilter(int index) {
+    if (SkColorFilter* colorFilter = fChildren[index].colorFilter()) {
+        return as_CFB(colorFilter)->appendStages(fStage, /*shaderIsOpaque=*/false);
+    }
+    // Return the original color as-is when a null child color filter is evaluated.
+    return true;
+}
+bool RuntimeEffectRPCallbacks::appendBlender(int index) {
+    if (SkBlender* blender = fChildren[index].blender()) {
+        return as_BB(blender)->appendStages(fStage);
+    }
+    // Return a source-over blend when a null blender is evaluated.
+    fStage.fPipeline->append(SkRasterPipelineOp::srcover);
+    return true;
+}
 
-    bool appendShader(int index) override {
-        if (SkShader* shader = fChildren[index].shader()) {
-            if (fSampleUsages[index].isPassThrough()) {
-                // Given a passthrough sample, the total-matrix is still as valid as before.
-                return as_SB(shader)->appendStages(fStage, fMatrix);
-            }
-            // For a non-passthrough sample, we need to explicitly mark the total-matrix as invalid.
-            SkShaderBase::MatrixRec nonPassthroughMatrix = fMatrix;
-            nonPassthroughMatrix.markTotalMatrixInvalid();
-            return as_SB(shader)->appendStages(fStage, nonPassthroughMatrix);
-        }
-        // Return the paint color when a null child shader is evaluated.
-        fStage.fPipeline->append_constant_color(fStage.fAlloc, fStage.fPaintColor);
-        return true;
-    }
-    bool appendColorFilter(int index) override {
-        if (SkColorFilter* colorFilter = fChildren[index].colorFilter()) {
-            return as_CFB(colorFilter)->appendStages(fStage, /*shaderIsOpaque=*/false);
-        }
-        // Return the original color as-is when a null child color filter is evaluated.
-        return true;
-    }
-    bool appendBlender(int index) override {
-        if (SkBlender* blender = fChildren[index].blender()) {
-            return as_BB(blender)->appendStages(fStage);
-        }
-        // Return a source-over blend when a null blender is evaluated.
-        fStage.fPipeline->append(SkRasterPipelineOp::srcover);
-        return true;
-    }
-
-    // TODO: If an effect calls these intrinsics more than once, we could cache and re-use the steps
-    // object(s), rather than re-creating them in the arena repeatedly.
-    void toLinearSrgb(const void* color) override {
-        if (fStage.fDstCS) {
-            SkColorSpaceXformSteps xform{fStage.fDstCS,              kUnpremul_SkAlphaType,
-                                         sk_srgb_linear_singleton(), kUnpremul_SkAlphaType};
-            if (xform.flags.mask()) {
-                // We have a non-identity colorspace transform; apply it.
-                this->applyColorSpaceXform(xform, color);
-            }
+// TODO: If an effect calls these intrinsics more than once, we could cache and re-use the steps
+// object(s), rather than re-creating them in the arena repeatedly.
+void RuntimeEffectRPCallbacks::toLinearSrgb(const void* color) {
+    if (fStage.fDstCS) {
+        SkColorSpaceXformSteps xform{fStage.fDstCS,              kUnpremul_SkAlphaType,
+                                     sk_srgb_linear_singleton(), kUnpremul_SkAlphaType};
+        if (xform.flags.mask()) {
+            // We have a non-identity colorspace transform; apply it.
+            this->applyColorSpaceXform(xform, color);
         }
     }
+}
 
-    void fromLinearSrgb(const void* color) override {
-        if (fStage.fDstCS) {
-            SkColorSpaceXformSteps xform{sk_srgb_linear_singleton(), kUnpremul_SkAlphaType,
-                                         fStage.fDstCS,              kUnpremul_SkAlphaType};
-            if (xform.flags.mask()) {
-                // We have a non-identity colorspace transform; apply it.
-                this->applyColorSpaceXform(xform, color);
-            }
+void RuntimeEffectRPCallbacks::fromLinearSrgb(const void* color) {
+    if (fStage.fDstCS) {
+        SkColorSpaceXformSteps xform{sk_srgb_linear_singleton(), kUnpremul_SkAlphaType,
+                                     fStage.fDstCS,              kUnpremul_SkAlphaType};
+        if (xform.flags.mask()) {
+            // We have a non-identity colorspace transform; apply it.
+            this->applyColorSpaceXform(xform, color);
         }
     }
+}
 
-private:
-    void applyColorSpaceXform(const SkColorSpaceXformSteps& tempXform, const void* color) {
-        // Copy the transform steps into our alloc.
-        SkColorSpaceXformSteps* xform = fStage.fAlloc->make<SkColorSpaceXformSteps>(tempXform);
+void RuntimeEffectRPCallbacks::applyColorSpaceXform(const SkColorSpaceXformSteps& tempXform,
+                                                    const void* color) {
+    // Copy the transform steps into our alloc.
+    SkColorSpaceXformSteps* xform = fStage.fAlloc->make<SkColorSpaceXformSteps>(tempXform);
 
-        // Put the color into src.rgba (and temporarily stash the execution mask there instead).
-        fStage.fPipeline->append(SkRasterPipelineOp::exchange_src, color);
-        // Add the color space transform to our raster pipeline.
-        xform->apply(fStage.fPipeline);
-        // Restore the execution mask, and move the color back into program data.
-        fStage.fPipeline->append(SkRasterPipelineOp::exchange_src, color);
-    }
-
-    const SkStageRec& fStage;
-    const SkShaderBase::MatrixRec& fMatrix;
-    SkSpan<const SkRuntimeEffect::ChildPtr> fChildren;
-    SkSpan<const SkSL::SampleUsage> fSampleUsages;
-};
+    // Put the color into src.rgba (and temporarily stash the execution mask there instead).
+    fStage.fPipeline->append(SkRasterPipelineOp::exchange_src, color);
+    // Add the color space transform to our raster pipeline.
+    xform->apply(fStage.fPipeline);
+    // Restore the execution mask, and move the color back into program data.
+    fStage.fPipeline->append(SkRasterPipelineOp::exchange_src, color);
+}
 #endif  // SK_ENABLE_SKSL_IN_RASTER_PIPELINE
 
 bool SkRuntimeEffectPriv::CanDraw(const SkCapabilities* caps, const SkSL::Program* program) {
@@ -413,9 +424,9 @@
  * children() of `effect`. If it's nullptr, this is skipped, allowing deserialization of children,
  * even when the effect could not be constructed (ie, due to malformed SkSL).
  */
-static bool read_child_effects(SkReadBuffer& buffer,
-                               const SkRuntimeEffect* effect,
-                               TArray<SkRuntimeEffect::ChildPtr>* children) {
+bool SkRuntimeEffectPriv::ReadChildEffects(SkReadBuffer& buffer,
+                                           const SkRuntimeEffect* effect,
+                                           TArray<SkRuntimeEffect::ChildPtr>* children) {
     size_t childCount = buffer.read32();
     if (effect && !buffer.validate(childCount == effect->children().size())) {
         return false;
@@ -448,19 +459,20 @@
     return buffer.isValid();
 }
 
-static void write_child_effects(SkWriteBuffer& buffer,
-                                const std::vector<SkRuntimeEffect::ChildPtr>& children) {
+void SkRuntimeEffectPriv::WriteChildEffects(
+        SkWriteBuffer& buffer, const std::vector<SkRuntimeEffect::ChildPtr>& children) {
     buffer.write32(children.size());
     for (const auto& child : children) {
         buffer.writeFlattenable(child.flattenable());
     }
 }
 
+
 #ifdef SK_ENABLE_SKVM
-static std::vector<skvm::Val> make_skvm_uniforms(skvm::Builder* p,
-                                                 skvm::Uniforms* uniforms,
-                                                 size_t inputSize,
-                                                 const SkData& inputs) {
+std::vector<skvm::Val> SkRuntimeEffectPriv::MakeSkVMUniforms(skvm::Builder* p,
+                                                             skvm::Uniforms* uniforms,
+                                                             size_t inputSize,
+                                                             const SkData& inputs) {
     SkASSERTF(!(inputSize & 3), "inputSize was %zu, expected a multiple of 4", inputSize);
 
     const int32_t* data = reinterpret_cast<const int32_t*>(inputs.data());
@@ -797,11 +809,11 @@
     };
     static_assert(sizeof(Options) == sizeof(KnownOptions));
     fHash = SkChecksum::Hash32(&options.forceUnoptimized,
-                      sizeof(options.forceUnoptimized), fHash);
+                               sizeof(options.forceUnoptimized), fHash);
     fHash = SkChecksum::Hash32(&options.allowPrivateAccess,
-                      sizeof(options.allowPrivateAccess), fHash);
+                               sizeof(options.allowPrivateAccess), fHash);
     fHash = SkChecksum::Hash32(&options.maxVersionAllowed,
-                      sizeof(options.maxVersionAllowed), fHash);
+                               sizeof(options.maxVersionAllowed), fHash);
 
     fFilterColorProgram = SkFilterColorProgram::Make(this);
 }
@@ -837,70 +849,12 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-#if defined(SK_GANESH)
-static GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
-                                 const char* name,
-                                 sk_sp<const SkData> uniforms,
-                                 std::unique_ptr<GrFragmentProcessor> inputFP,
-                                 std::unique_ptr<GrFragmentProcessor> destColorFP,
-                                 SkSpan<const SkRuntimeEffect::ChildPtr> children,
-                                 const GrFPArgs& childArgs) {
-    STArray<8, std::unique_ptr<GrFragmentProcessor>> childFPs;
-    for (const auto& child : children) {
-        std::optional<ChildType> type = child.type();
-        if (type == ChildType::kShader) {
-            // Convert a SkShader into a child FP.
-            SkShaderBase::MatrixRec mRec(SkMatrix::I());
-            mRec.markTotalMatrixInvalid();
-            auto childFP = as_SB(child.shader())->asFragmentProcessor(childArgs, mRec);
-            if (!childFP) {
-                return GrFPFailure(std::move(inputFP));
-            }
-            childFPs.push_back(std::move(childFP));
-        } else if (type == ChildType::kColorFilter) {
-            // Convert a SkColorFilter into a child FP.
-            auto [success, childFP] = as_CFB(child.colorFilter())
-                                              ->asFragmentProcessor(/*inputFP=*/nullptr,
-                                                                    childArgs.fContext,
-                                                                    *childArgs.fDstColorInfo,
-                                                                    childArgs.fSurfaceProps);
-            if (!success) {
-                return GrFPFailure(std::move(inputFP));
-            }
-            childFPs.push_back(std::move(childFP));
-        } else if (type == ChildType::kBlender) {
-            // Convert a SkBlender into a child FP.
-            auto childFP = as_BB(child.blender())->asFragmentProcessor(
-                    /*srcFP=*/nullptr,
-                    GrFragmentProcessor::DestColor(),
-                    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);
-        }
-    }
-    auto fp = GrSkSLFP::MakeWithData(std::move(effect),
-                                     name,
-                                     childArgs.fDstColorInfo->refColorSpace(),
-                                     std::move(inputFP),
-                                     std::move(destColorFP),
-                                     std::move(uniforms),
-                                     SkSpan(childFPs));
-    SkASSERT(fp);
-    return GrFPSuccess(std::move(fp));
-}
-#endif
-
 #if defined(SK_GRAPHITE)
-static void add_children_to_key(SkSpan<const SkRuntimeEffect::ChildPtr> children,
-                                SkSpan<const SkRuntimeEffect::Child> childInfo,
-                                const skgpu::graphite::KeyContext& keyContext,
-                                skgpu::graphite::PaintParamsKeyBuilder* builder,
-                                skgpu::graphite::PipelineDataGatherer* gatherer) {
+void SkRuntimeEffectPriv::AddChildrenToKey(SkSpan<const SkRuntimeEffect::ChildPtr> children,
+                                           SkSpan<const SkRuntimeEffect::Child> childInfo,
+                                           const skgpu::graphite::KeyContext& keyContext,
+                                           skgpu::graphite::PaintParamsKeyBuilder* builder,
+                                           skgpu::graphite::PipelineDataGatherer* gatherer) {
     using namespace skgpu::graphite;
 
     SkASSERT(children.size() == childInfo.size());
@@ -937,85 +891,61 @@
 #endif
 
 #if defined(SK_ENABLE_SKVM)
-class RuntimeEffectVMCallbacks : public SkSL::SkVMCallbacks {
-public:
-    RuntimeEffectVMCallbacks(skvm::Builder* builder,
-                             skvm::Uniforms* uniforms,
-                             SkArenaAlloc* alloc,
-                             const std::vector<SkRuntimeEffect::ChildPtr>& children,
-                             const SkShaderBase::MatrixRec& mRec,
-                             skvm::Color inColor,
-                             const SkColorInfo& colorInfo)
-            : fBuilder(builder)
-            , fUniforms(uniforms)
-            , fAlloc(alloc)
-            , fChildren(children)
-            , fMRec(mRec)
-            , fInColor(inColor)
-            , fColorInfo(colorInfo) {}
 
-    skvm::Color sampleShader(int ix, skvm::Coord coord) override {
-        // We haven't tracked device coords and the runtime effect could have arbitrarily
-        // manipulated the passed coords. We should be in a state where any pending matrix was
-        // already applied before the runtime effect's code could have manipulated the coords
-        // and the total matrix from child shader to device space is flagged as unknown.
-        SkASSERT(!fMRec.hasPendingMatrix());
-        SkASSERT(!fMRec.totalMatrixIsValid());
-        if (SkShader* shader = fChildren[ix].shader()) {
-            return as_SB(shader)->program(fBuilder,
-                                          coord,
-                                          coord,
-                                          fInColor,
-                                          fMRec,
-                                          fColorInfo,
-                                          fUniforms,
-                                          fAlloc);
-        }
-        return fInColor;
+skvm::Color RuntimeEffectVMCallbacks::sampleShader(int ix, skvm::Coord coord) {
+    // We haven't tracked device coords and the runtime effect could have arbitrarily
+    // manipulated the passed coords. We should be in a state where any pending matrix was
+    // already applied before the runtime effect's code could have manipulated the coords
+    // and the total matrix from child shader to device space is flagged as unknown.
+    SkASSERT(!fMRec.hasPendingMatrix());
+    SkASSERT(!fMRec.totalMatrixIsValid());
+    if (SkShader* shader = fChildren[ix].shader()) {
+        return as_SB(shader)->program(fBuilder,
+                                      coord,
+                                      coord,
+                                      fInColor,
+                                      fMRec,
+                                      fColorInfo,
+                                      fUniforms,
+                                      fAlloc);
     }
+    return fInColor;
+}
 
-    skvm::Color sampleColorFilter(int ix, skvm::Color color) override {
-        if (SkColorFilter* colorFilter = fChildren[ix].colorFilter()) {
-            return as_CFB(colorFilter)->program(fBuilder, color, fColorInfo, fUniforms, fAlloc);
-        }
+skvm::Color RuntimeEffectVMCallbacks::sampleColorFilter(int ix, skvm::Color color) {
+    if (SkColorFilter* colorFilter = fChildren[ix].colorFilter()) {
+        return as_CFB(colorFilter)->program(fBuilder, color, fColorInfo, fUniforms, fAlloc);
+    }
+    return color;
+}
+
+skvm::Color RuntimeEffectVMCallbacks::sampleBlender(int ix, skvm::Color src, skvm::Color dst) {
+    if (SkBlender* blender = fChildren[ix].blender()) {
+        return as_BB(blender)->program(fBuilder, src, dst, fColorInfo, fUniforms, fAlloc);
+    }
+    return blend(SkBlendMode::kSrcOver, src, dst);
+}
+
+skvm::Color RuntimeEffectVMCallbacks::toLinearSrgb(skvm::Color color) {
+    if (!fColorInfo.colorSpace()) {
+        // These intrinsics do nothing when color management is disabled
         return color;
     }
+    return SkColorSpaceXformSteps{fColorInfo.colorSpace(),    kUnpremul_SkAlphaType,
+                                  sk_srgb_linear_singleton(), kUnpremul_SkAlphaType}
+            .program(fBuilder, fUniforms, color);
+}
 
-    skvm::Color sampleBlender(int ix, skvm::Color src, skvm::Color dst) override {
-        if (SkBlender* blender = fChildren[ix].blender()) {
-            return as_BB(blender)->program(fBuilder, src, dst, fColorInfo, fUniforms, fAlloc);
-        }
-        return blend(SkBlendMode::kSrcOver, src, dst);
+skvm::Color RuntimeEffectVMCallbacks::fromLinearSrgb(skvm::Color color) {
+    if (!fColorInfo.colorSpace()) {
+        // These intrinsics do nothing when color management is disabled
+        return color;
     }
+    return SkColorSpaceXformSteps{sk_srgb_linear_singleton(), kUnpremul_SkAlphaType,
+                                  fColorInfo.colorSpace(),    kUnpremul_SkAlphaType}
+            .program(fBuilder, fUniforms, color);
+}
 
-    skvm::Color toLinearSrgb(skvm::Color color) override {
-        if (!fColorInfo.colorSpace()) {
-            // These intrinsics do nothing when color management is disabled
-            return color;
-        }
-        return SkColorSpaceXformSteps{fColorInfo.colorSpace(),    kUnpremul_SkAlphaType,
-                                      sk_srgb_linear_singleton(), kUnpremul_SkAlphaType}
-                .program(fBuilder, fUniforms, color);
-    }
-
-    skvm::Color fromLinearSrgb(skvm::Color color) override {
-        if (!fColorInfo.colorSpace()) {
-            // These intrinsics do nothing when color management is disabled
-            return color;
-        }
-        return SkColorSpaceXformSteps{sk_srgb_linear_singleton(), kUnpremul_SkAlphaType,
-                                      fColorInfo.colorSpace(),    kUnpremul_SkAlphaType}
-                .program(fBuilder, fUniforms, color);
-    }
-
-    skvm::Builder* fBuilder;
-    skvm::Uniforms* fUniforms;
-    SkArenaAlloc* fAlloc;
-    const std::vector<SkRuntimeEffect::ChildPtr>& fChildren;
-    const SkShaderBase::MatrixRec& fMRec;
-    const skvm::Color fInColor;
-    const SkColorInfo& fColorInfo;
-};
 #endif  // defined(SK_ENABLE_SKVM)
 
 class SkRuntimeColorFilter : public SkColorFilterBase {
@@ -1039,13 +969,13 @@
         SkASSERT(uniforms);
 
         GrFPArgs childArgs(context, &colorInfo, props);
-        return make_effect_fp(fEffect,
-                              "runtime_color_filter",
-                              std::move(uniforms),
-                              std::move(inputFP),
-                              /*destColorFP=*/nullptr,
-                              SkSpan(fChildren),
-                              childArgs);
+        return GrFragmentProcessors::make_effect_fp(fEffect,
+                                                    "runtime_color_filter",
+                                                    std::move(uniforms),
+                                                    std::move(inputFP),
+                                                    /*destColorFP=*/nullptr,
+                                                    SkSpan(fChildren),
+                                                    childArgs);
     }
 #endif
 
@@ -1064,7 +994,8 @@
         RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer,
                                        { fEffect, std::move(uniforms) });
 
-        add_children_to_key(fChildren, fEffect->children(), keyContext, builder, gatherer);
+        SkRuntimeEffectPriv::AddChildrenToKey(fChildren, fEffect->children(), keyContext, builder,
+                                              gatherer);
 
         builder->endBlock();
     }
@@ -1078,11 +1009,12 @@
             return false;
         }
         if (const SkSL::RP::Program* program = fEffect->getRPProgram(/*debugTrace=*/nullptr)) {
-            SkSpan<const float> uniforms = uniforms_as_span(fEffect->uniforms(),
-                                                            fUniforms,
-                                                            /*alwaysCopyIntoAlloc=*/false,
-                                                            rec.fDstCS,
-                                                            rec.fAlloc);
+            SkSpan<const float> uniforms = SkRuntimeEffectPriv::UniformsAsSpan(
+                    fEffect->uniforms(),
+                    fUniforms,
+                    /*alwaysCopyIntoAlloc=*/false,
+                    rec.fDstCS,
+                    rec.fAlloc);
             SkShaderBase::MatrixRec matrix(SkMatrix::I());
             matrix.markCTMApplied();
             RuntimeEffectRPCallbacks callbacks(rec, matrix, fChildren, fEffect->fSampleUsages);
@@ -1109,8 +1041,8 @@
         SkShaderBase::MatrixRec mRec(SkMatrix::I());
         mRec.markTotalMatrixInvalid();
         RuntimeEffectVMCallbacks callbacks(p, uniforms, alloc, fChildren, mRec, c, colorInfo);
-        std::vector<skvm::Val> uniform = make_skvm_uniforms(p, uniforms, fEffect->uniformSize(),
-                                                            *inputs);
+        std::vector<skvm::Val> uniform = SkRuntimeEffectPriv::MakeSkVMUniforms(
+                p, uniforms, fEffect->uniformSize(), *inputs);
 
         // There should be no way for the color filter to use device coords, but we need to supply
         // something. (Uninitialized values can trigger asserts in skvm::Builder).
@@ -1164,7 +1096,7 @@
     void flatten(SkWriteBuffer& buffer) const override {
         buffer.writeString(fEffect->source().c_str());
         buffer.writeDataAsByteArray(fUniforms.get());
-        write_child_effects(buffer, fChildren);
+        SkRuntimeEffectPriv::WriteChildEffects(buffer, fChildren);
     }
 
     SkRuntimeEffect* asRuntimeEffect() const override { return fEffect.get(); }
@@ -1194,7 +1126,7 @@
 #endif
 
     STArray<4, SkRuntimeEffect::ChildPtr> children;
-    if (!read_child_effects(buffer, effect.get(), &children)) {
+    if (!SkRuntimeEffectPriv::ReadChildEffects(buffer, effect.get(), &children)) {
         return nullptr;
     }
 
@@ -1258,13 +1190,13 @@
 
         bool success;
         std::unique_ptr<GrFragmentProcessor> fp;
-        std::tie(success, fp) = make_effect_fp(fEffect,
-                                               "runtime_shader",
-                                               std::move(uniforms),
-                                               /*inputFP=*/nullptr,
-                                               /*destColorFP=*/nullptr,
-                                               SkSpan(fChildren),
-                                               args);
+        std::tie(success, fp) = GrFragmentProcessors::make_effect_fp(fEffect,
+                                                                     "runtime_shader",
+                                                                     std::move(uniforms),
+                                                                     /*inputFP=*/nullptr,
+                                                                     /*destColorFP=*/nullptr,
+                                                                     SkSpan(fChildren),
+                                                                     args);
         if (!success) {
             return nullptr;
         }
@@ -1292,7 +1224,8 @@
         RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer,
                                        { fEffect, std::move(uniforms) });
 
-        add_children_to_key(fChildren, fEffect->children(), keyContext, builder, gatherer);
+        SkRuntimeEffectPriv::AddChildrenToKey(fChildren, fEffect->children(), keyContext, builder,
+                                              gatherer);
 
         builder->endBlock();
     }
@@ -1310,12 +1243,12 @@
             if (!newMRec.has_value()) {
                 return false;
             }
-            SkSpan<const float> uniforms =
-                    uniforms_as_span(fEffect->uniforms(),
-                                     this->uniformData(rec.fDstCS),
-                                     /*alwaysCopyIntoAlloc=*/fUniformData == nullptr,
-                                     rec.fDstCS,
-                                     rec.fAlloc);
+            SkSpan<const float> uniforms = SkRuntimeEffectPriv::UniformsAsSpan(
+                    fEffect->uniforms(),
+                    this->uniformData(rec.fDstCS),
+                    /*alwaysCopyIntoAlloc=*/fUniformData == nullptr,
+                    rec.fDstCS,
+                    rec.fAlloc);
             RuntimeEffectRPCallbacks callbacks(rec, *newMRec, fChildren, fEffect->fSampleUsages);
             bool success = program->appendStages(rec.fPipeline, rec.fAlloc, &callbacks, uniforms);
             return success;
@@ -1359,8 +1292,8 @@
                                            *newMRec,
                                            paint,
                                            colorInfo);
-        std::vector<skvm::Val> uniform = make_skvm_uniforms(p, uniforms, fEffect->uniformSize(),
-                                                            *inputs);
+        std::vector<skvm::Val> uniform = SkRuntimeEffectPriv::MakeSkVMUniforms(
+                p, uniforms, fEffect->uniformSize(), *inputs);
 
         return SkSL::ProgramToSkVM(*fEffect->fBaseProgram, fEffect->fMain, p, fDebugTrace.get(),
                                    SkSpan(uniform), device, local, paint, paint, &callbacks);
@@ -1370,7 +1303,7 @@
     void flatten(SkWriteBuffer& buffer) const override {
         buffer.writeString(fEffect->source().c_str());
         buffer.writeDataAsByteArray(this->uniformData(nullptr).get());
-        write_child_effects(buffer, fChildren);
+        SkRuntimeEffectPriv::WriteChildEffects(buffer, fChildren);
     }
 
     SkRuntimeEffect* asRuntimeEffect() const override { return fEffect.get(); }
@@ -1426,7 +1359,7 @@
 #endif
 
     STArray<4, SkRuntimeEffect::ChildPtr> children;
-    if (!read_child_effects(buffer, effect.get(), &children)) {
+    if (!SkRuntimeEffectPriv::ReadChildEffects(buffer, effect.get(), &children)) {
         return nullptr;
     }
 
@@ -1449,165 +1382,6 @@
     return effect->makeShader(std::move(uniforms), SkSpan(children), localM.getMaybeNull());
 }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class SkRuntimeBlender : public SkBlenderBase {
-public:
-    SkRuntimeBlender(sk_sp<SkRuntimeEffect> effect,
-                     sk_sp<const SkData> uniforms,
-                     SkSpan<SkRuntimeEffect::ChildPtr> children)
-            : fEffect(std::move(effect))
-            , fUniforms(std::move(uniforms))
-            , fChildren(children.begin(), children.end()) {}
-
-    SkRuntimeEffect* asRuntimeEffect() const override { return fEffect.get(); }
-
-    bool onAppendStages(const SkStageRec& rec) const override {
-#ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
-        if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
-            // SkRP has support for many parts of #version 300 already, but for now, we restrict its
-            // usage in runtime effects to just #version 100.
-            return false;
-        }
-        if (const SkSL::RP::Program* program = fEffect->getRPProgram(/*debugTrace=*/nullptr)) {
-            SkSpan<const float> uniforms = uniforms_as_span(fEffect->uniforms(),
-                                                            fUniforms,
-                                                            /*alwaysCopyIntoAlloc=*/false,
-                                                            rec.fDstCS,
-                                                            rec.fAlloc);
-            SkShaderBase::MatrixRec matrix(SkMatrix::I());
-            matrix.markCTMApplied();
-            RuntimeEffectRPCallbacks callbacks(rec, matrix, fChildren, fEffect->fSampleUsages);
-            bool success = program->appendStages(rec.fPipeline, rec.fAlloc, &callbacks, uniforms);
-            return success;
-        }
-#endif
-        return false;
-    }
-
-#ifdef SK_ENABLE_SKVM
-    skvm::Color onProgram(skvm::Builder* p, skvm::Color src, skvm::Color dst,
-                          const SkColorInfo& colorInfo, skvm::Uniforms* uniforms,
-                          SkArenaAlloc* alloc) const override {
-        if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
-            return {};
-        }
-
-        sk_sp<const SkData> inputs = SkRuntimeEffectPriv::TransformUniforms(fEffect->uniforms(),
-                                                                            fUniforms,
-                                                                            colorInfo.colorSpace());
-        SkASSERT(inputs);
-
-        SkShaderBase::MatrixRec mRec(SkMatrix::I());
-        mRec.markTotalMatrixInvalid();
-        RuntimeEffectVMCallbacks callbacks(p, uniforms, alloc, fChildren, mRec, src, colorInfo);
-        std::vector<skvm::Val> uniform = make_skvm_uniforms(p, uniforms, fEffect->uniformSize(),
-                                                            *inputs);
-
-        // 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,/*debugTrace=*/nullptr,
-                                   SkSpan(uniform), /*device=*/zeroCoord, /*local=*/zeroCoord,
-                                   src, dst, &callbacks);
-    }
-#endif
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            std::unique_ptr<GrFragmentProcessor> srcFP,
-            std::unique_ptr<GrFragmentProcessor> dstFP,
-            const GrFPArgs& args) const override {
-        if (!SkRuntimeEffectPriv::CanDraw(args.fContext->priv().caps(), fEffect.get())) {
-            return nullptr;
-        }
-
-        sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
-                fEffect->uniforms(),
-                fUniforms,
-                args.fDstColorInfo->colorSpace());
-        SkASSERT(uniforms);
-        auto [success, fp] = make_effect_fp(fEffect,
-                                            "runtime_blender",
-                                            std::move(uniforms),
-                                            std::move(srcFP),
-                                            std::move(dstFP),
-                                            SkSpan(fChildren),
-                                            args);
-
-        return success ? std::move(fp) : nullptr;
-    }
-#endif
-
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext& keyContext,
-                  skgpu::graphite::PaintParamsKeyBuilder* builder,
-                  skgpu::graphite::PipelineDataGatherer* gatherer) const override {
-        using namespace skgpu::graphite;
-
-        sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
-                fEffect->uniforms(),
-                fUniforms,
-                keyContext.dstColorInfo().colorSpace());
-        SkASSERT(uniforms);
-
-        RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer,
-                                       { fEffect, std::move(uniforms) });
-
-        add_children_to_key(fChildren, fEffect->children(), keyContext, builder, gatherer);
-
-        builder->endBlock();
-    }
-#endif
-
-    void flatten(SkWriteBuffer& buffer) const override {
-        buffer.writeString(fEffect->source().c_str());
-        buffer.writeDataAsByteArray(fUniforms.get());
-        write_child_effects(buffer, fChildren);
-    }
-
-    SK_FLATTENABLE_HOOKS(SkRuntimeBlender)
-
-private:
-    using INHERITED = SkBlenderBase;
-
-    sk_sp<SkRuntimeEffect> fEffect;
-    sk_sp<const SkData> fUniforms;
-    std::vector<SkRuntimeEffect::ChildPtr> fChildren;
-};
-
-sk_sp<SkFlattenable> SkRuntimeBlender::CreateProc(SkReadBuffer& buffer) {
-    if (!buffer.validate(buffer.allowSkSL())) {
-        return nullptr;
-    }
-
-    SkString sksl;
-    buffer.readString(&sksl);
-    sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
-
-    auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForBlender, std::move(sksl));
-#if !SK_LENIENT_SKSL_DESERIALIZATION
-    if (!buffer.validate(effect != nullptr)) {
-        return nullptr;
-    }
-#endif
-
-    STArray<4, SkRuntimeEffect::ChildPtr> children;
-    if (!read_child_effects(buffer, effect.get(), &children)) {
-        return nullptr;
-    }
-
-#if SK_LENIENT_SKSL_DESERIALIZATION
-    if (!effect) {
-        SkDebugf("Serialized SkSL failed to compile. Ignoring/dropping SkSL blender.\n");
-        return nullptr;
-    }
-#endif
-
-    return effect->makeBlender(std::move(uniforms), SkSpan(children));
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
 sk_sp<SkShader> SkRuntimeEffectPriv::MakeDeferredShader(const SkRuntimeEffect* effect,
                                                         UniformsCallback uniformsCallback,
                                                         SkSpan<SkRuntimeEffect::ChildPtr> children,
diff --git a/src/core/SkRuntimeEffectPriv.h b/src/core/SkRuntimeEffectPriv.h
index d077eb2..47d3c4f 100644
--- a/src/core/SkRuntimeEffectPriv.h
+++ b/src/core/SkRuntimeEffectPriv.h
@@ -8,13 +8,40 @@
 #ifndef SkRuntimeEffectPriv_DEFINED
 #define SkRuntimeEffectPriv_DEFINED
 
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkString.h"
 #include "include/effects/SkRuntimeEffect.h"
-#include "include/private/SkColorData.h"
-#include "src/core/SkVM.h"
+#include "include/private/SkSLSampleUsage.h"
+#include "include/private/base/SkAssert.h"
+#include "include/private/base/SkSpan_impl.h"
+#include "include/private/base/SkTArray.h"
+#include "src/shaders/SkShaderBase.h"
 
+#include <cstddef>
+#include <cstdint>
 #include <functional>
+#include <memory>
+#include <vector>
 
 #ifdef SK_ENABLE_SKSL
+#include "include/sksl/SkSLVersion.h"
+
+#ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
+#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h"
+#endif
+
+#ifdef SK_ENABLE_SKVM
+#include "src/sksl/codegen/SkSLVMCodeGenerator.h"
+#endif
+
+class SkArenaAlloc;
+class SkColorSpace;
+class SkData;
+class SkMatrix;
+class SkReadBuffer;
+class SkShader;
+class SkWriteBuffer;
+struct SkStageRec;
 
 namespace SkSL {
 class Context;
@@ -87,9 +114,36 @@
     static sk_sp<const SkData> TransformUniforms(SkSpan<const SkRuntimeEffect::Uniform> uniforms,
                                                  sk_sp<const SkData> originalData,
                                                  const SkColorSpace* dstCS);
+    static SkSpan<const float> UniformsAsSpan(
+        SkSpan<const SkRuntimeEffect::Uniform> uniforms,
+        sk_sp<const SkData> originalData,
+        bool alwaysCopyIntoAlloc,
+        const SkColorSpace* destColorSpace,
+        SkArenaAlloc* alloc);
 
     static bool CanDraw(const SkCapabilities*, const SkSL::Program*);
     static bool CanDraw(const SkCapabilities*, const SkRuntimeEffect*);
+
+    static bool ReadChildEffects(SkReadBuffer& buffer,
+                                 const SkRuntimeEffect* effect,
+                                 skia_private::TArray<SkRuntimeEffect::ChildPtr>* children);
+    static void WriteChildEffects(SkWriteBuffer &buffer,
+                                  const std::vector<SkRuntimeEffect::ChildPtr> &children);
+
+#ifdef SK_ENABLE_SKVM
+    static std::vector<skvm::Val> MakeSkVMUniforms(skvm::Builder*,
+                                                   skvm::Uniforms*,
+                                                   size_t inputSize,
+                                                   const SkData& inputs);
+#endif
+
+#if defined(SK_GRAPHITE)
+static void AddChildrenToKey(SkSpan<const SkRuntimeEffect::ChildPtr> children,
+                             SkSpan<const SkRuntimeEffect::Child> childInfo,
+                             const skgpu::graphite::KeyContext& keyContext,
+                             skgpu::graphite::PaintParamsKeyBuilder* builder,
+                             skgpu::graphite::PipelineDataGatherer* gatherer);
+#endif
 };
 
 // These internal APIs for creating runtime effects vary from the public API in two ways:
@@ -132,6 +186,73 @@
     return result.effect.release();
 }
 
+#ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
+class RuntimeEffectRPCallbacks : public SkSL::RP::Callbacks {
+public:
+    RuntimeEffectRPCallbacks(const SkStageRec& s,
+                             const SkShaderBase::MatrixRec& m,
+                             SkSpan<const SkRuntimeEffect::ChildPtr> c,
+                             SkSpan<const SkSL::SampleUsage> u)
+            : fStage(s), fMatrix(m), fChildren(c), fSampleUsages(u) {}
+
+    bool appendShader(int index) override;
+    bool appendColorFilter(int index) override;
+    bool appendBlender(int index) override;
+
+    // TODO: If an effect calls these intrinsics more than once, we could cache and re-use the steps
+    // object(s), rather than re-creating them in the arena repeatedly.
+    void toLinearSrgb(const void* color) override;
+
+    void fromLinearSrgb(const void* color) override;
+
+private:
+    void applyColorSpaceXform(const SkColorSpaceXformSteps& tempXform, const void* color);
+
+    const SkStageRec& fStage;
+    const SkShaderBase::MatrixRec& fMatrix;
+    SkSpan<const SkRuntimeEffect::ChildPtr> fChildren;
+    SkSpan<const SkSL::SampleUsage> fSampleUsages;
+};
+#endif  // SK_ENABLE_SKSL_IN_RASTER_PIPELINE
+
+#if defined(SK_ENABLE_SKVM)
+class RuntimeEffectVMCallbacks : public SkSL::SkVMCallbacks {
+public:
+    RuntimeEffectVMCallbacks(skvm::Builder* builder,
+                             skvm::Uniforms* uniforms,
+                             SkArenaAlloc* alloc,
+                             const std::vector<SkRuntimeEffect::ChildPtr>& children,
+                             const SkShaderBase::MatrixRec& mRec,
+                             skvm::Color inColor,
+                             const SkColorInfo& colorInfo)
+            : fBuilder(builder)
+            , fUniforms(uniforms)
+            , fAlloc(alloc)
+            , fChildren(children)
+            , fMRec(mRec)
+            , fInColor(inColor)
+            , fColorInfo(colorInfo) {}
+
+    skvm::Color sampleShader(int ix, skvm::Coord coord) override;
+
+    skvm::Color sampleColorFilter(int ix, skvm::Color color) override;
+
+    skvm::Color sampleBlender(int ix, skvm::Color src, skvm::Color dst) override;
+
+    skvm::Color toLinearSrgb(skvm::Color color) override;
+
+    skvm::Color fromLinearSrgb(skvm::Color color) override;
+
+    skvm::Builder* fBuilder;
+    skvm::Uniforms* fUniforms;
+    SkArenaAlloc* fAlloc;
+    const std::vector<SkRuntimeEffect::ChildPtr>& fChildren;
+    const SkShaderBase::MatrixRec& fMRec;
+    const skvm::Color fInColor;
+    const SkColorInfo& fColorInfo;
+};
+#endif  // defined(SK_ENABLE_SKVM)
+
 #endif  // SK_ENABLE_SKSL
 
 #endif  // SkRuntimeEffectPriv_DEFINED
diff --git a/src/effects/imagefilters/SkBlendImageFilter.cpp b/src/effects/imagefilters/SkBlendImageFilter.cpp
index 23080e2..d904cf0 100644
--- a/src/effects/imagefilters/SkBlendImageFilter.cpp
+++ b/src/effects/imagefilters/SkBlendImageFilter.cpp
@@ -42,12 +42,14 @@
 #include "src/gpu/ganesh/GrColorSpaceXform.h"
 #include "src/gpu/ganesh/GrFPArgs.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrFragmentProcessors.h"
 #include "src/gpu/ganesh/GrImageInfo.h"
 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
 #include "src/gpu/ganesh/GrSamplerState.h"
 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
 #include "src/gpu/ganesh/SurfaceFillContext.h"
 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
+
 #endif
 
 namespace {
@@ -329,7 +331,7 @@
         SkSurfaceProps props{}; // default OK; blend-image filters don't render text
         GrFPArgs args(rContext, &info.colorInfo(), props);
 
-        fp = as_BB(fBlender)->asFragmentProcessor(std::move(fgFP), std::move(fp), args);
+        fp = GrFragmentProcessors::Make(as_BB(fBlender), std::move(fgFP), std::move(fp), args);
     }
 
     auto sfc = rContext->priv().makeSFC(
diff --git a/src/gpu/ganesh/GrFragmentProcessors.cpp b/src/gpu/ganesh/GrFragmentProcessors.cpp
index 6bfc76a..6be7343 100644
--- a/src/gpu/ganesh/GrFragmentProcessors.cpp
+++ b/src/gpu/ganesh/GrFragmentProcessors.cpp
@@ -7,15 +7,46 @@
 
 #include "src/gpu/ganesh/GrFragmentProcessors.h"
 
+#include "include/core/SkColorSpace.h"  // IWYU pragma: keep
+#include "include/core/SkData.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/gpu/GrRecordingContext.h"
+#include "include/private/base/SkAssert.h"
+#include "include/private/base/SkTArray.h"
+#include "src/core/SkBlendModeBlender.h"
+#include "src/core/SkBlenderBase.h"
+#include "src/core/SkColorFilterBase.h"
 #include "src/core/SkMaskFilterBase.h"
+#include "src/core/SkRuntimeBlender.h"
+#include "src/core/SkRuntimeEffectPriv.h"
 #include "src/effects/SkShaderMaskFilterImpl.h"
+#include "src/gpu/ganesh/GrCaps.h"
+#include "src/gpu/ganesh/GrColorInfo.h"
+#include "src/gpu/ganesh/GrFPArgs.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrRecordingContextPriv.h"
+#include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
+#include "src/gpu/ganesh/effects/GrSkSLFP.h"
 #include "src/shaders/SkShaderBase.h"
 
 #include <memory>
+#include <optional>
 #include <utility>
+#include <vector>
 
 namespace GrFragmentProcessors {
+static std::unique_ptr<GrFragmentProcessor>
+        make_fp_from_shader_mask_filter(const SkMaskFilterBase* maskfilter,
+                                        const GrFPArgs& args,
+                                        const SkMatrix& ctm) {
+    SkASSERT(maskfilter);
+    auto shaderMF = static_cast<const SkShaderMaskFilterImpl*>(maskfilter);
+    auto fp = as_SB(shaderMF->shader())->asFragmentProcessor(args, SkShaderBase::MatrixRec(ctm));
+    return GrFragmentProcessor::MulInputByChildAlpha(std::move(fp));
+}
+
 std::unique_ptr<GrFragmentProcessor> Make(const SkMaskFilter* maskfilter,
                                           const GrFPArgs& args,
                                           const SkMatrix& ctm) {
@@ -23,12 +54,130 @@
         return nullptr;
     }
     auto mfb = as_MFB(maskfilter);
-    if (mfb->type() != SkMaskFilterBase::Type::kShader) {
+    switch (mfb->type()) {
+        case SkMaskFilterBase::Type::kShader:
+            return make_fp_from_shader_mask_filter(mfb, args, ctm);
+        case SkMaskFilterBase::Type::kBlur:
+        case SkMaskFilterBase::Type::kEmboss:
+        case SkMaskFilterBase::Type::kSDF:
+        case SkMaskFilterBase::Type::kTable:
+            return nullptr;
+    }
+    SkUNREACHABLE;
+}
+
+using ChildType = SkRuntimeEffect::ChildType;
+
+GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
+                          const char* name,
+                          sk_sp<const SkData> uniforms,
+                          std::unique_ptr<GrFragmentProcessor> inputFP,
+                          std::unique_ptr<GrFragmentProcessor> destColorFP,
+                          SkSpan<const SkRuntimeEffect::ChildPtr> children,
+                          const GrFPArgs& childArgs) {
+    skia_private::STArray<8, std::unique_ptr<GrFragmentProcessor>> childFPs;
+    for (const auto& child : children) {
+        std::optional<ChildType> type = child.type();
+        if (type == ChildType::kShader) {
+            // Convert a SkShader into a child FP.
+            SkShaderBase::MatrixRec mRec(SkMatrix::I());
+            mRec.markTotalMatrixInvalid();
+            auto childFP = as_SB(child.shader())->asFragmentProcessor(childArgs, mRec);
+            if (!childFP) {
+                return GrFPFailure(std::move(inputFP));
+            }
+            childFPs.push_back(std::move(childFP));
+        } else if (type == ChildType::kColorFilter) {
+            // Convert a SkColorFilter into a child FP.
+            auto [success, childFP] = as_CFB(child.colorFilter())
+                                              ->asFragmentProcessor(/*inputFP=*/nullptr,
+                                                                    childArgs.fContext,
+                                                                    *childArgs.fDstColorInfo,
+                                                                    childArgs.fSurfaceProps);
+            if (!success) {
+                return GrFPFailure(std::move(inputFP));
+            }
+            childFPs.push_back(std::move(childFP));
+        } else if (type == ChildType::kBlender) {
+            // Convert a SkBlender into a child FP.
+            auto childFP = GrFragmentProcessors::Make(as_BB(child.blender()),
+                                                      /*srcFP=*/nullptr,
+                                                      GrFragmentProcessor::DestColor(),
+                                                      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);
+        }
+    }
+    auto fp = GrSkSLFP::MakeWithData(std::move(effect),
+                                     name,
+                                     childArgs.fDstColorInfo->refColorSpace(),
+                                     std::move(inputFP),
+                                     std::move(destColorFP),
+                                     std::move(uniforms),
+                                     SkSpan(childFPs));
+    SkASSERT(fp);
+    return GrFPSuccess(std::move(fp));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_blender_fp(
+        const SkRuntimeBlender* rtb,
+        std::unique_ptr<GrFragmentProcessor> srcFP,
+        std::unique_ptr<GrFragmentProcessor> dstFP,
+        const GrFPArgs& fpArgs) {
+    SkASSERT(rtb);
+    if (!SkRuntimeEffectPriv::CanDraw(fpArgs.fContext->priv().caps(), rtb->effect().get())) {
         return nullptr;
     }
-    auto shaderMF = static_cast<const SkShaderMaskFilterImpl*>(maskfilter);
-    auto fp = as_SB(shaderMF->shader())->asFragmentProcessor(args, SkShaderBase::MatrixRec(ctm));
-    return GrFragmentProcessor::MulInputByChildAlpha(std::move(fp));
+
+    sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
+            rtb->effect()->uniforms(),
+            rtb->uniforms(),
+            fpArgs.fDstColorInfo->colorSpace());
+    SkASSERT(uniforms);
+    auto children = rtb->children();
+    auto [success, fp] = make_effect_fp(rtb->effect(),
+                                        "runtime_blender",
+                                        std::move(uniforms),
+                                        std::move(srcFP),
+                                        std::move(dstFP),
+                                        SkSpan(children),
+                                        fpArgs);
+
+    return success ? std::move(fp) : nullptr;
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_blender_fp(
+        const SkBlendModeBlender* blender,
+        std::unique_ptr<GrFragmentProcessor> srcFP,
+        std::unique_ptr<GrFragmentProcessor> dstFP,
+        const GrFPArgs& fpArgs) {
+    SkASSERT(blender);
+    return GrBlendFragmentProcessor::Make(std::move(srcFP), std::move(dstFP), blender->mode());
+}
+
+std::unique_ptr<GrFragmentProcessor> Make(const SkBlenderBase* blender,
+                                          std::unique_ptr<GrFragmentProcessor> srcFP,
+                                          std::unique_ptr<GrFragmentProcessor> dstFP,
+                                          const GrFPArgs& fpArgs) {
+    if (!blender) {
+        return nullptr;
+    }
+    switch (blender->type()) {
+#define M(type)                                                                \
+    case SkBlenderBase::BlenderType::k##type:                                  \
+        return make_blender_fp(static_cast<const Sk##type##Blender*>(blender), \
+                               std::move(srcFP),                               \
+                               std::move(dstFP),                               \
+                               fpArgs);
+        SK_ALL_BLENDERS(M)
+#undef M
+    }
+    SkUNREACHABLE;
 }
 
 bool IsSupported(const SkMaskFilter* maskfilter) {
@@ -36,9 +185,15 @@
         return false;
     }
     auto mfb = as_MFB(maskfilter);
-    if (mfb->type() != SkMaskFilterBase::Type::kShader) {
-        return false;
+    switch (mfb->type()) {
+        case SkMaskFilterBase::Type::kShader:
+            return true;
+        case SkMaskFilterBase::Type::kBlur:
+        case SkMaskFilterBase::Type::kEmboss:
+        case SkMaskFilterBase::Type::kSDF:
+        case SkMaskFilterBase::Type::kTable:
+            return false;
     }
-    return true;
+    SkUNREACHABLE;
 }
 }  // namespace GrFragmentProcessors
diff --git a/src/gpu/ganesh/GrFragmentProcessors.h b/src/gpu/ganesh/GrFragmentProcessors.h
index 11125b6..0dbc37b 100644
--- a/src/gpu/ganesh/GrFragmentProcessors.h
+++ b/src/gpu/ganesh/GrFragmentProcessors.h
@@ -8,19 +8,46 @@
 #ifndef GrFragmentProcessors_DEFINED
 #define GrFragmentProcessors_DEFINED
 
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkSpan.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+#include <tuple>
 #include <memory>
 
 class GrFragmentProcessor;
+class SkBlenderBase;
+class SkData;
 class SkMaskFilter;
-struct GrFPArgs;
 class SkMatrix;
+struct GrFPArgs;
+
+using GrFPResult = std::tuple<bool, std::unique_ptr<GrFragmentProcessor>>;
 
 namespace GrFragmentProcessors {
 std::unique_ptr<GrFragmentProcessor> Make(const SkMaskFilter*,
                                           const GrFPArgs&,
                                           const SkMatrix& ctm);
 
+/**
+ * Returns a GrFragmentProcessor that implements this blend for the Ganesh GPU backend.
+ * The GrFragmentProcessor expects premultiplied inputs and returns a premultiplied output.
+ */
+std::unique_ptr<GrFragmentProcessor> Make(const SkBlenderBase*,
+                                          std::unique_ptr<GrFragmentProcessor> srcFP,
+                                          std::unique_ptr<GrFragmentProcessor> dstFP,
+                                          const GrFPArgs& fpArgs);
+
 bool IsSupported(const SkMaskFilter*);
+
+// TODO(kjlubick, brianosman) remove this after all related effects have been migrated
+GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
+                          const char* name,
+                          sk_sp<const SkData> uniforms,
+                          std::unique_ptr<GrFragmentProcessor> inputFP,
+                          std::unique_ptr<GrFragmentProcessor> destColorFP,
+                          SkSpan<const SkRuntimeEffect::ChildPtr> children,
+                          const GrFPArgs& childArgs);
 }
 
 #endif
diff --git a/src/gpu/ganesh/SkGr.cpp b/src/gpu/ganesh/SkGr.cpp
index b3697af..3199e1b 100644
--- a/src/gpu/ganesh/SkGr.cpp
+++ b/src/gpu/ganesh/SkGr.cpp
@@ -397,9 +397,10 @@
 
             SkPMColor4f shaderInput = origColor.makeOpaque().premul();
             paintFP = GrFragmentProcessor::OverrideInput(std::move(paintFP), shaderInput);
-            paintFP = as_BB(primColorBlender)->asFragmentProcessor(std::move(paintFP),
-                                                                   /*dstFP=*/nullptr,
-                                                                   fpArgs);
+            paintFP = GrFragmentProcessors::Make(as_BB(primColorBlender),
+                                                 /*srcFP=*/std::move(paintFP),
+                                                 /*dstFP=*/nullptr,
+                                                 fpArgs);
             if (!paintFP) {
                 return false;
             }
@@ -442,9 +443,10 @@
             grPaint->setColor4f(SK_PMColor4fWHITE);  // won't be used.
             if (blender_requires_shader(primColorBlender)) {
                 paintFP = GrFragmentProcessor::MakeColor(origColor.makeOpaque().premul());
-                paintFP = as_BB(primColorBlender)->asFragmentProcessor(std::move(paintFP),
-                                                                       /*dstFP=*/nullptr,
-                                                                       fpArgs);
+                paintFP = GrFragmentProcessors::Make(as_BB(primColorBlender),
+                                                     /*srcFP=*/std::move(paintFP),
+                                                     /*dstFP=*/nullptr,
+                                                     fpArgs);
                 if (!paintFP) {
                     return false;
                 }
@@ -512,10 +514,10 @@
     } else {
         // Apply a custom blend against the surface color, and force the XP to kSrc so that the
         // computed result is applied directly to the canvas while still honoring the alpha.
-        paintFP = as_BB(skPaint.getBlender())->asFragmentProcessor(
-                std::move(paintFP),
-                GrFragmentProcessor::SurfaceColor(),
-                fpArgs);
+        paintFP = GrFragmentProcessors::Make(as_BB(skPaint.getBlender()),
+                                             std::move(paintFP),
+                                             GrFragmentProcessor::SurfaceColor(),
+                                             fpArgs);
         if (!paintFP) {
             return false;
         }
diff --git a/toolchain/linux_trampolines/clang_trampoline_linux.sh b/toolchain/linux_trampolines/clang_trampoline_linux.sh
index cef4942..2207d9c 100755
--- a/toolchain/linux_trampolines/clang_trampoline_linux.sh
+++ b/toolchain/linux_trampolines/clang_trampoline_linux.sh
@@ -88,6 +88,7 @@
   "src/core/SkReadPixelsRec.cpp"
   "src/core/SkRecorder.cpp"
   "src/core/SkRect.cpp"
+  "src/core/SkRuntime"
   "src/core/SkScalar.cpp"
   "src/core/SkStream.cpp"
   "src/core/SkString.cpp"