/*
 * Copyright 2023 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/ganesh/GrFragmentProcessors.h"

#include "include/core/SkAlphaType.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSize.h"
#include "include/core/SkSpan.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/gpu/GpuTypes.h"
#include "include/gpu/ganesh/GrRecordingContext.h"
#include "include/gpu/ganesh/GrTypes.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkTArray.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/base/SkTLazy.h"
#include "src/core/SkBlendModeBlender.h"
#include "src/core/SkBlenderBase.h"
#include "src/core/SkColorData.h"
#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkColorSpaceXformSteps.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkRuntimeBlender.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "src/effects/SkShaderMaskFilterImpl.h"
#include "src/effects/colorfilters/SkBlendModeColorFilter.h"
#include "src/effects/colorfilters/SkColorFilterBase.h"
#include "src/effects/colorfilters/SkColorSpaceXformColorFilter.h"
#include "src/effects/colorfilters/SkComposeColorFilter.h"
#include "src/effects/colorfilters/SkGaussianColorFilter.h"
#include "src/effects/colorfilters/SkMatrixColorFilter.h"
#include "src/effects/colorfilters/SkRuntimeColorFilter.h"
#include "src/effects/colorfilters/SkTableColorFilter.h"
#include "src/effects/colorfilters/SkWorkingFormatColorFilter.h"
#include "src/gpu/ResourceKey.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrColorInfo.h"
#include "src/gpu/ganesh/GrColorSpaceXform.h"
#include "src/gpu/ganesh/GrFPArgs.h"
#include "src/gpu/ganesh/GrFragmentProcessor.h"
#include "src/gpu/ganesh/GrProxyProvider.h"
#include "src/gpu/ganesh/GrRecordingContextPriv.h"
#include "src/gpu/ganesh/GrSamplerState.h"
#include "src/gpu/ganesh/GrShaderCaps.h"
#include "src/gpu/ganesh/GrSurfaceProxy.h"
#include "src/gpu/ganesh/GrSurfaceProxyView.h"
#include "src/gpu/ganesh/GrTextureProxy.h"
#include "src/gpu/ganesh/SkGr.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
#include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
#include "src/gpu/ganesh/effects/GrColorTableEffect.h"
#include "src/gpu/ganesh/effects/GrMatrixEffect.h"
#include "src/gpu/ganesh/effects/GrPerlinNoise2Effect.h"
#include "src/gpu/ganesh/effects/GrSkSLFP.h"
#include "src/gpu/ganesh/effects/GrTextureEffect.h"
#include "src/gpu/ganesh/gradients/GrGradientShader.h"
#include "src/gpu/ganesh/image/GrImageUtils.h"
#include "src/gpu/ganesh/image/GrMippedBitmap.h"
#include "src/shaders/SkBlendShader.h"
#include "src/shaders/SkColorFilterShader.h"
#include "src/shaders/SkColorShader.h"
#include "src/shaders/SkCoordClampShader.h"
#include "src/shaders/SkEmptyShader.h"
#include "src/shaders/SkImageShader.h"
#include "src/shaders/SkLocalMatrixShader.h"
#include "src/shaders/SkPerlinNoiseShaderImpl.h"
#include "src/shaders/SkPictureShader.h"
#include "src/shaders/SkRuntimeShader.h"
#include "src/shaders/SkShaderBase.h"
#include "src/shaders/SkTransformShader.h"
#include "src/shaders/SkTriColorShader.h"
#include "src/shaders/SkWorkingColorSpaceShader.h"
#include "src/shaders/gradients/SkConicalGradient.h"
#include "src/shaders/gradients/SkGradientBaseShader.h"
#include "src/shaders/gradients/SkLinearGradient.h"
#include "src/shaders/gradients/SkRadialGradient.h"
#include "src/shaders/gradients/SkSweepGradient.h"

#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <utility>

class GrRenderTargetProxy;
class SkBitmap;
enum class SkTileMode;

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 = Make(shaderMF->shader().get(), args, ctm);
    return GrFragmentProcessor::MulInputByChildAlpha(std::move(fp));
}

std::unique_ptr<GrFragmentProcessor> Make(const SkMaskFilter* maskfilter,
                                          const GrFPArgs& args,
                                          const SkMatrix& ctm) {
    if (!maskfilter) {
        return nullptr;
    }
    auto mfb = as_MFB(maskfilter);
    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;
}

bool IsSupported(const SkMaskFilter* maskfilter) {
    if (!maskfilter) {
        return false;
    }
    auto mfb = as_MFB(maskfilter);
    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;
    }
    SkUNREACHABLE;
}

using ChildType = SkRuntimeEffect::ChildType;

GrFPResult MakeChildFP(const SkRuntimeEffect::ChildPtr& child, const GrFPArgs& childArgs) {
    std::optional<ChildType> type = child.type();
    if (!type.has_value()) {
        // We have a null child effect.
        return GrFPNullableSuccess(nullptr);
    }

    switch (*type) {
        case ChildType::kShader: {
            // Convert a SkShader into a child FP.
            SkShaders::MatrixRec mRec(SkMatrix::I());
            mRec.markTotalMatrixInvalid();
            auto childFP = GrFragmentProcessors::Make(child.shader(), childArgs, mRec);
            return childFP ? GrFPSuccess(std::move(childFP))
                           : GrFPFailure(nullptr);
        }
        case ChildType::kColorFilter: {
            // Convert a SkColorFilter into a child FP.
            auto [success, childFP] = GrFragmentProcessors::Make(childArgs.fSurfaceDrawContext,
                                                                 child.colorFilter(),
                                                                 /*inputFP=*/nullptr,
                                                                 *childArgs.fDstColorInfo,
                                                                 childArgs.fSurfaceProps);
            return success ? GrFPSuccess(std::move(childFP))
                           : GrFPFailure(nullptr);
        }
        case ChildType::kBlender: {
            // Convert a SkBlender into a child FP.
            auto childFP = GrFragmentProcessors::Make(as_BB(child.blender()),
                                                      /*srcFP=*/nullptr,
                                                      GrFragmentProcessor::DestColor(),
                                                      childArgs);
            return childFP ? GrFPSuccess(std::move(childFP))
                           : GrFPFailure(nullptr);
        }
    }

    SkUNREACHABLE;
}

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) {
    skia_private::STArray<8, std::unique_ptr<GrFragmentProcessor>> childFPs;
    for (const auto& child : children) {
        auto [success, childFP] = MakeChildFP(child, childArgs);
        if (!success) {
            return GrFPFailure(std::move(inputFP));
        }
        childFPs.push_back(std::move(childFP));
    }
    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.fSurfaceDrawContext->caps(), rtb->effect().get())) {
        return nullptr;
    }

    sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
            rtb->effect()->uniforms(),
            rtb->uniforms(),
            fpArgs.fDstColorInfo->colorSpace());
    SkASSERT(uniforms);
    GrFPArgs childArgs(fpArgs.fSurfaceDrawContext,
                       fpArgs.fDstColorInfo,
                       fpArgs.fSurfaceProps,
                       GrFPArgs::Scope::kRuntimeEffect);
    auto [success, fp] = make_effect_fp(rtb->effect(),
                                        "runtime_blender",
                                        std::move(uniforms),
                                        std::move(srcFP),
                                        std::move(dstFP),
                                        rtb->children(),
                                        childArgs);

    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;
}

static SkPMColor4f map_color(const SkColor4f& c, SkColorSpace* src, SkColorSpace* dst) {
    SkPMColor4f color = {c.fR, c.fG, c.fB, c.fA};
    SkColorSpaceXformSteps(src, kUnpremul_SkAlphaType, dst, kPremul_SkAlphaType).apply(color.vec());
    return color;
}
static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext*,
                                      const SkBlendModeColorFilter* filter,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo& dstColorInfo,
                                      const SkSurfaceProps& props) {
    if (filter->mode() == SkBlendMode::kDst) {
        // If the blend mode is "dest," the blend color won't factor into it at all.
        // We can return the input FP as-is.
        return GrFPSuccess(std::move(inputFP));
    }

    SkDEBUGCODE(const bool fpHasConstIO = !inputFP || inputFP->hasConstantOutputForConstantInput();)

    SkPMColor4f color = map_color(filter->color(), sk_srgb_singleton(), dstColorInfo.colorSpace());

    auto colorFP = GrFragmentProcessor::MakeColor(color);
    auto xferFP =
            GrBlendFragmentProcessor::Make(std::move(colorFP), std::move(inputFP), filter->mode());

    if (xferFP == nullptr) {
        // This is only expected to happen if the blend mode is "dest" and the input FP is null.
        // Since we already did an early-out in the "dest" blend mode case, we shouldn't get here.
        SkDEBUGFAIL("GrBlendFragmentProcessor::Make returned null unexpectedly");
        return GrFPFailure(nullptr);
    }

    // With a solid color input this should always be able to compute the blended color
    // (at least for coeff modes).
    // Occasionally, we even do better than we started; specifically, in "src" blend mode, we end up
    // ditching the input FP entirely, which turns a non-constant operation into a constant one.
    SkASSERT(filter->mode() > SkBlendMode::kLastCoeffMode ||
             xferFP->hasConstantOutputForConstantInput() >= fpHasConstIO);

    return GrFPSuccess(std::move(xferFP));
}

static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext* sdc,
                                      const SkComposeColorFilter* filter,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo& dstColorInfo,
                                      const SkSurfaceProps& props) {
    // Unfortunately, we need to clone the input before we know we need it. This lets us return
    // the original FP if either internal color filter fails.
    auto inputClone = inputFP ? inputFP->clone() : nullptr;

    auto [innerSuccess, innerFP] =
            Make(sdc, filter->inner().get(), std::move(inputFP), dstColorInfo, props);
    if (!innerSuccess) {
        return GrFPFailure(std::move(inputClone));
    }

    auto [outerSuccess, outerFP] =
            Make(sdc, filter->outer().get(), std::move(innerFP), dstColorInfo, props);
    if (!outerSuccess) {
        return GrFPFailure(std::move(inputClone));
    }

    return GrFPSuccess(std::move(outerFP));
}

static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext* sdc,
                                      const SkColorSpaceXformColorFilter* filter,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo&,
                                      const SkSurfaceProps&) {
    // wish our caller would let us know if our input was opaque...
    constexpr SkAlphaType alphaType = kPremul_SkAlphaType;
    return GrFPSuccess(GrColorSpaceXformEffect::Make(
            std::move(inputFP), filter->src().get(), alphaType, filter->dst().get(), alphaType));
}

static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext*,
                                      const SkGaussianColorFilter*,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo&,
                                      const SkSurfaceProps&) {
    static const SkRuntimeEffect* effect =
            SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
                                "half4 main(half4 inColor) {"
                                "half factor = 1 - inColor.a;"
                                "factor = exp(-factor * factor * 4) - 0.018;"
                                "return half4(factor);"
                                "}");
    SkASSERT(SkRuntimeEffectPriv::SupportsConstantOutputForConstantInput(effect));
    return GrFPSuccess(
            GrSkSLFP::Make(effect, "gaussian_fp", std::move(inputFP), GrSkSLFP::OptFlags::kNone));
}

static std::unique_ptr<GrFragmentProcessor> rgb_to_hsl(std::unique_ptr<GrFragmentProcessor> child) {
    static const SkRuntimeEffect* effect =
            SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
                                "half4 main(half4 color) {"
                                "return $rgb_to_hsl(color.rgb, color.a);"
                                "}");
    SkASSERT(SkRuntimeEffectPriv::SupportsConstantOutputForConstantInput(effect));
    return GrSkSLFP::Make(
            effect, "RgbToHsl", std::move(child), GrSkSLFP::OptFlags::kPreservesOpaqueInput);
}

static std::unique_ptr<GrFragmentProcessor> hsl_to_rgb(std::unique_ptr<GrFragmentProcessor> child) {
    static const SkRuntimeEffect* effect =
            SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
                                "half4 main(half4 color) {"
                                "return $hsl_to_rgb(color.rgb, color.a);"
                                "}");
    SkASSERT(SkRuntimeEffectPriv::SupportsConstantOutputForConstantInput(effect));
    return GrSkSLFP::Make(
            effect, "HslToRgb", std::move(child), GrSkSLFP::OptFlags::kPreservesOpaqueInput);
}

static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext* sdc,
                                      const SkMatrixColorFilter* filter,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo&,
                                      const SkSurfaceProps&) {
    switch (filter->domain()) {
        case SkMatrixColorFilter::Domain::kRGBA:
            return GrFPSuccess(GrFragmentProcessor::ColorMatrix(
                    std::move(inputFP),
                    filter->matrix(),
                    /* unpremulInput = */ true,
                    /* clampRGBOutput = */ filter->clamp() == SkMatrixColorFilter::Clamp::kYes,
                    /* premulOutput = */ true));

        case SkMatrixColorFilter::Domain::kHSLA: {
            auto fp = rgb_to_hsl(std::move(inputFP));
            fp = GrFragmentProcessor::ColorMatrix(std::move(fp),
                                                  filter->matrix(),
                                                  /* unpremulInput = */ false,
                                                  /* clampRGBOutput = */ false,
                                                  /* premulOutput = */ false);
            return GrFPSuccess(hsl_to_rgb(std::move(fp)));
        }
    }
    SkUNREACHABLE;
}

static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext* sdc,
                                      const SkRuntimeColorFilter* filter,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo& colorInfo,
                                      const SkSurfaceProps& props) {
    sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
            filter->effect()->uniforms(), filter->uniforms(), colorInfo.colorSpace());
    SkASSERT(uniforms);

    GrFPArgs childArgs(sdc, &colorInfo, props, GrFPArgs::Scope::kRuntimeEffect);
    return make_effect_fp(filter->effect(),
                          "runtime_color_filter",
                          std::move(uniforms),
                          std::move(inputFP),
                          /*destColorFP=*/nullptr,
                          filter->children(),
                          childArgs);
}

static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext* sdc,
                                      const SkTableColorFilter* filter,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo&,
                                      const SkSurfaceProps&) {
    SkASSERT(filter->bitmap().isImmutable());
    auto cte = ColorTableEffect::Make(
            std::move(inputFP), sdc->recordingContext(), GrMippedBitmap(filter->bitmap()));
    return cte ? GrFPSuccess(std::move(cte)) : GrFPFailure(nullptr);
}

static GrFPResult make_colorfilter_fp(skgpu::ganesh::SurfaceDrawContext* sdc,
                                      const SkWorkingFormatColorFilter* filter,
                                      std::unique_ptr<GrFragmentProcessor> inputFP,
                                      const GrColorInfo& dstColorInfo,
                                      const SkSurfaceProps& props) {
    sk_sp<SkColorSpace> dstCS = dstColorInfo.refColorSpace();
    if (!dstCS) {
        dstCS = SkColorSpace::MakeSRGB();
    }

    SkAlphaType workingAT;
    sk_sp<SkColorSpace> workingCS = filter->workingFormat(dstCS, &workingAT);

    GrColorInfo dst = {dstColorInfo.colorType(), dstColorInfo.alphaType(), dstCS},
                working = {dstColorInfo.colorType(), workingAT, workingCS};

    auto [ok, fp] = Make(sdc,
                         filter->child().get(),
                         GrColorSpaceXformEffect::Make(std::move(inputFP), dst, working),
                         working,
                         props);

    return ok ? GrFPSuccess(GrColorSpaceXformEffect::Make(std::move(fp), working, dst))
              : GrFPFailure(std::move(fp));
}

GrFPResult Make(skgpu::ganesh::SurfaceDrawContext* sdc,
                const SkColorFilter* cf,
                std::unique_ptr<GrFragmentProcessor> inputFP,
                const GrColorInfo& dstColorInfo,
                const SkSurfaceProps& props) {
    if (!cf) {
        return GrFPFailure(nullptr);
    }
    auto cfb = as_CFB(cf);
    switch (cfb->type()) {
        case SkColorFilterBase::Type::kNoop:
            return GrFPFailure(nullptr);
#define M(type)                                                                   \
    case SkColorFilterBase::Type::k##type:                                        \
        return make_colorfilter_fp(sdc,                                           \
                                   static_cast<const Sk##type##ColorFilter*>(cf), \
                                   std::move(inputFP),                            \
                                   dstColorInfo,                                  \
                                   props);
            SK_ALL_COLOR_FILTERS(M)
#undef M
    }
    SkUNREACHABLE;
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkBlendShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    auto fpA = Make(shader->dst().get(), args, mRec);
    auto fpB = Make(shader->src().get(), args, mRec);
    if (!fpA || !fpB) {
        // This is unexpected. Both src and dst shaders should be valid. Just fail.
        return nullptr;
    }
    return GrBlendFragmentProcessor::Make(std::move(fpB), std::move(fpA), shader->mode());
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkColorFilterShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    auto shaderFP = Make(shader->shader().get(), args, mRec);
    if (!shaderFP) {
        return nullptr;
    }

    // TODO I guess, but it shouldn't come up as used today.
    SkASSERT(shader->alpha() == 1.0f);

    auto [success, fp] = Make(args.fSurfaceDrawContext,
                              shader->filter().get(),
                              std::move(shaderFP),
                              *args.fDstColorInfo,
                              args.fSurfaceProps);
    // If the filter FP could not be created, we still want to return the shader FP, so checking
    // success can be omitted here.
    return std::move(fp);
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkColorShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    SkColorSpaceXformSteps steps{sk_srgb_singleton(),
                                 kUnpremul_SkAlphaType,
                                 args.fDstColorInfo->colorSpace(),
                                 kUnpremul_SkAlphaType};
    SkColor4f color = shader->color();
    steps.apply(color.vec());
    return GrFragmentProcessor::MakeColor(color.premul());
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkCoordClampShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    static const SkRuntimeEffect* effect =
            SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
                                "uniform shader c;"
                                "uniform float4 s;"
                                "half4 main(float2 p) {"
                                    "return c.eval(clamp(p, s.LT, s.RB));"
                                "}");

    auto fp = Make(shader->shader().get(), args, mRec.applied());
    if (!fp) {
        return nullptr;
    }

    GrSkSLFP::OptFlags flags = GrSkSLFP::OptFlags::kNone;
    if (fp->compatibleWithCoverageAsAlpha()) {
        flags |= GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
    }
    if (fp->preservesOpaqueInput()) {
        flags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
    }
    fp = GrSkSLFP::Make(effect,
                        "clamp_fp",
                        /*inputFP=*/nullptr,
                        flags,
                        "c",
                        std::move(fp),
                        "s",
                        shader->subset());

    auto [total, ok] = mRec.applyForFragmentProcessor({});
    if (!ok) {
        return nullptr;
    }
    return GrMatrixEffect::Make(total, std::move(fp));
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkCTMShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    auto ctmInv = shader->ctm().invert();
    if (!ctmInv) {
        return nullptr;
    }

    auto base = Make(shader->proxyShader().get(), args, shader->ctm());
    if (!base) {
        return nullptr;
    }

    // In order for the shader to be evaluated with the original CTM, we explicitly evaluate it
    // at sk_FragCoord, and pass that through the inverse of the original CTM. This avoids requiring
    // local coords for the shader and mapping from the draw's local to device and then back.
    return GrFragmentProcessor::DeviceSpace(GrMatrixEffect::Make(*ctmInv, std::move(base)));
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkEmptyShader* shader,
                                                           const GrFPArgs&,
                                                           const SkShaders::MatrixRec&) {
    return nullptr;
}

static bool needs_subset(sk_sp<const SkImage> img, const SkRect& subset) {
    return subset != SkRect::Make(img->dimensions());
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkImageShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    SkTileMode tileModes[2] = {shader->tileModeX(), shader->tileModeY()};
    const SkRect shaderSubset = shader->subset();
    const SkRect* subset = needs_subset(shader->image(), shaderSubset) ? &shaderSubset : nullptr;
    auto fp = skgpu::ganesh::AsFragmentProcessor(
            args.fSurfaceDrawContext, shader->image(), shader->sampling(), tileModes, SkMatrix::I(),
            subset);
    if (!fp) {
        return nullptr;
    }

    auto [total, ok] = mRec.applyForFragmentProcessor({});
    if (!ok) {
        return nullptr;
    }
    fp = GrMatrixEffect::Make(total, std::move(fp));

    if (!shader->isRaw()) {
        fp = GrColorSpaceXformEffect::Make(std::move(fp),
                                           shader->image()->colorSpace(),
                                           shader->image()->alphaType(),
                                           args.fDstColorInfo->colorSpace(),
                                           kPremul_SkAlphaType);

        // Alpha-only image shaders are tinted by the input color (typically the paint color).
        // We suppress that behavior when sampled from a runtime effect.
        if (shader->image()->isAlphaOnly() && args.fScope != GrFPArgs::Scope::kRuntimeEffect) {
            fp = GrBlendFragmentProcessor::Make<SkBlendMode::kDstIn>(std::move(fp), nullptr);
        }
    }

    return fp;
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkLocalMatrixShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    return Make(shader->wrappedShader().get(), args, mRec.concat(shader->localMatrix()));
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkPerlinNoiseShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    SkASSERT(args.fSurfaceDrawContext);
    SkASSERT(shader->numOctaves());

    // Either we don't stitch tiles, or we have a valid tile size
    SkASSERT(!shader->stitchTiles() || !shader->tileSize().isEmpty());

    auto paintingData = shader->getPaintingData();
    paintingData->generateBitmaps();

    GrRecordingContext* context = args.fSurfaceDrawContext->recordingContext();

    const SkBitmap& permutationsBitmap = paintingData->getPermutationsBitmap();
    const SkBitmap& noiseBitmap = paintingData->getNoiseBitmap();

    auto permutationsView = std::get<0>(GrMakeCachedBitmapProxyView(
            context,
            GrMippedBitmap(permutationsBitmap),
            /*label=*/"PerlinNoiseShader_FragmentProcessor_PermutationsView"));

    auto noiseView = std::get<0>(
            GrMakeCachedBitmapProxyView(context,
                                        GrMippedBitmap(noiseBitmap),
                                        /*label=*/"PerlinNoiseShader_FragmentProcessor_NoiseView"));

    if (!permutationsView || !noiseView) {
        return nullptr;
    }

    std::unique_ptr<GrFragmentProcessor> fp =
            GrPerlinNoise2Effect::Make(shader->noiseType(),
                                       shader->numOctaves(),
                                       shader->stitchTiles(),
                                       std::move(paintingData),
                                       std::move(permutationsView),
                                       std::move(noiseView),
                                       *context->priv().caps());
    if (!fp) {
        return nullptr;
    }
    auto [total, ok] = mRec.applyForFragmentProcessor({});
    if (!ok) {
        return nullptr;
    }
    return GrMatrixEffect::Make(total, std::move(fp));
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkPictureShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    auto ctx = args.fSurfaceDrawContext->recordingContext();
    SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType());
    if (dstColorType == kUnknown_SkColorType) {
        dstColorType = kRGBA_8888_SkColorType;
    }
    sk_sp<SkColorSpace> dstCS = SkColorSpace::MakeSRGB();
    if (args.fDstColorInfo->colorSpace()) {
        dstCS = sk_ref_sp(args.fDstColorInfo->colorSpace());
    }

    auto info = SkPictureShader::CachedImageInfo::Make(shader->tile(),
                                                       mRec.totalMatrix(),
                                                       dstColorType,
                                                       dstCS.get(),
                                                       ctx->priv().caps()->maxTextureSize(),
                                                       args.fSurfaceProps);
    if (!info.success) {
        return nullptr;
    }

    // Gotta be sure the GPU can support our requested colortype (might be FP16)
    if (!ctx->colorTypeSupportedAsSurface(info.imageInfo.colorType())) {
        info.imageInfo = info.imageInfo.makeColorType(kRGBA_8888_SkColorType);
    }

    static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
    skgpu::UniqueKey key;
    std::tuple keyData = {dstCS->toXYZD50Hash(),
                          dstCS->transferFnHash(),
                          static_cast<uint32_t>(dstColorType),
                          shader->picture()->uniqueID(),
                          shader->tile(),
                          info.tileScale,
                          info.props};
    skgpu::UniqueKey::Builder builder(
            &key, kDomain, sizeof(keyData) / sizeof(uint32_t), "Picture Shader Image");
    memcpy(&builder[0], &keyData, sizeof(keyData));
    builder.finish();

    GrProxyProvider* provider = ctx->priv().proxyProvider();
    GrSurfaceProxyView view;
    if (auto proxy = provider->findOrCreateProxyByUniqueKey(key)) {
        view = GrSurfaceProxyView(proxy, kTopLeft_GrSurfaceOrigin, skgpu::Swizzle());
    } else {
        const int msaaSampleCount = 0;
        const bool createWithMips = false;
        const bool kUnprotected = false;
        auto image = info.makeImage(SkSurfaces::RenderTarget(ctx,
                                                             skgpu::Budgeted::kYes,
                                                             info.imageInfo,
                                                             msaaSampleCount,
                                                             kTopLeft_GrSurfaceOrigin,
                                                             &info.props,
                                                             createWithMips,
                                                             kUnprotected),
                                    shader->picture().get());
        if (!image) {
            return nullptr;
        }
        // Images from a picture shader shouldn't ever be drawn into a surface backed by the same
        // texture so there will never be need to make a copy of the image. So just pass in nullptr
        // for targetSurface of the view.
        constexpr GrRenderTargetProxy* targetSurface = nullptr;
        auto [v, ct] = skgpu::ganesh::AsView(ctx, image, skgpu::Mipmapped::kNo, targetSurface);
        view = std::move(v);
        provider->assignUniqueKeyToProxy(key, view.asTextureProxy());
    }

    const GrSamplerState sampler(static_cast<GrSamplerState::WrapMode>(shader->tileModeX()),
                                 static_cast<GrSamplerState::WrapMode>(shader->tileModeY()),
                                 shader->filter());
    auto fp = GrTextureEffect::Make(
            std::move(view), kPremul_SkAlphaType, SkMatrix::I(), sampler, *ctx->priv().caps());
    SkMatrix scale = SkMatrix::Scale(info.tileScale.width(), info.tileScale.height());
    auto [total, ok] = mRec.applyForFragmentProcessor(scale);
    if (!ok) {
        return nullptr;
    }
    return GrMatrixEffect::Make(total, std::move(fp));
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkRuntimeShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    if (!SkRuntimeEffectPriv::CanDraw(args.fSurfaceDrawContext->caps(),
                                      shader->asRuntimeEffect())) {
        return nullptr;
    }

    sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
            shader->asRuntimeEffect()->uniforms(),
            shader->uniformData(args.fDstColorInfo->colorSpace()),
            args.fDstColorInfo->colorSpace());
    SkASSERT(uniforms);

    bool success;
    std::unique_ptr<GrFragmentProcessor> fp;
    GrFPArgs childArgs(args.fSurfaceDrawContext, args.fDstColorInfo, args.fSurfaceProps,
                       GrFPArgs::Scope::kRuntimeEffect);
    std::tie(success, fp) = make_effect_fp(shader->effect(),
                                           "runtime_shader",
                                           std::move(uniforms),
                                           /*inputFP=*/nullptr,
                                           /*destColorFP=*/nullptr,
                                           shader->children(),
                                           childArgs);
    if (!success) {
        return nullptr;
    }

    auto [total, ok] = mRec.applyForFragmentProcessor({});
    if (!ok) {
        return nullptr;
    }
    return GrMatrixEffect::Make(total, std::move(fp));
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkTransformShader* shader,
                                                           const GrFPArgs&,
                                                           const SkShaders::MatrixRec&) {
    return nullptr;
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkTriColorShader* shader,
                                                           const GrFPArgs&,
                                                           const SkShaders::MatrixRec&) {
    return nullptr;
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkWorkingColorSpaceShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    const GrColorInfo* dstInfo = args.fDstColorInfo;
    SkAlphaType dstAT = dstInfo->alphaType();
    sk_sp<SkColorSpace> dstCS = dstInfo->refColorSpace();
    if (!dstCS) {
        dstCS = SkColorSpace::MakeSRGB();
    }


    auto [inputCS, outputCS, workingAT] = shader->workingSpace(dstCS, dstAT);

    GrColorInfo dst    = {dstInfo->colorType(), dstAT,     dstCS},
                input  = {dstInfo->colorType(), workingAT, inputCS},
                output = {dstInfo->colorType(), workingAT, outputCS};

    GrFPArgs workingArgs(args.fSurfaceDrawContext, &input, args.fSurfaceProps, args.fScope);

    auto childFP = Make(shader->shader().get(), workingArgs, mRec);
    if (!childFP) {
        return nullptr;
    }

    auto childWithWorkingInput = GrFragmentProcessor::Compose(
            std::move(childFP), GrColorSpaceXformEffect::Make(nullptr, dst, input));
    // Assuming that childFP operates on inputCS/workingAT values and returns outputCS/workingAT
    // values, then we only need to transform from output to dst.
    return GrColorSpaceXformEffect::Make(std::move(childWithWorkingInput), output, dst);
}

//////////////////////////////////////////////////////////////////////////////////////////////

static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkConicalGradient* shader,
                                                             const GrFPArgs& args,
                                                             const SkShaders::MatrixRec& mRec) {
    // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
    // was opaque. Thus, all of these layout FPs disable that optimization.
    std::unique_ptr<GrFragmentProcessor> fp;
    std::optional<SkMatrix> matrix;
    switch (shader->getType()) {
        case SkConicalGradient::Type::kStrip: {
            static const SkRuntimeEffect* kEffect =
                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
                        "uniform half r0_2;"
                        "half4 main(float2 p) {"
                            // validation flag, set to negative to discard fragment later.
                            "half v = 1;"
                            "float t = r0_2 - p.y * p.y;"
                            "if (t >= 0) {"
                                "t = p.x + sqrt(t);"
                            "} else {"
                                "v = -1;"
                            "}"
                            "return half4(half(t), v, 0, 0);"
                        "}"
                    );
            float r0 = shader->getStartRadius() / shader->getCenterX1();
            fp = GrSkSLFP::Make(kEffect,
                                "TwoPointConicalStripLayout",
                                /*inputFP=*/nullptr,
                                GrSkSLFP::OptFlags::kNone,
                                "r0_2",
                                r0 * r0);
        } break;

        case SkConicalGradient::Type::kRadial: {
            static const SkRuntimeEffect* kEffect =
                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
                        "uniform half r0;"
                        "uniform half lengthScale;"
                        "half4 main(float2 p) {"
                            // validation flag, set to negative to discard fragment later
                            "half v = 1;"
                            "float t = length(p) * lengthScale - r0;"
                            "return half4(half(t), v, 0, 0);"
                        "}"
                    );
            float dr = shader->getDiffRadius();
            float r0 = shader->getStartRadius() / dr;
            bool isRadiusIncreasing = dr >= 0;
            fp = GrSkSLFP::Make(kEffect,
                                "TwoPointConicalRadialLayout",
                                /*inputFP=*/nullptr,
                                GrSkSLFP::OptFlags::kNone,
                                "r0",
                                r0,
                                "lengthScale",
                                isRadiusIncreasing ? 1.0f : -1.0f);

            // GPU radial matrix is different from the original matrix, since we map the diff radius
            // to have |dr| = 1, so manually compute the final gradient matrix here.

            // Map center to (0, 0)
            matrix = SkMatrix::Translate(-shader->getStartCenter().fX,
                                         -shader->getStartCenter().fY);
            // scale |diffRadius| to 1
            matrix->postScale(1 / dr, 1 / dr);
        } break;

        case SkConicalGradient::Type::kFocal: {
            static const SkRuntimeEffect* kEffect =
                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
                        // Optimization flags, all specialized:
                        "uniform int isRadiusIncreasing;"
                        "uniform int isFocalOnCircle;"
                        "uniform int isWellBehaved;"
                        "uniform int isSwapped;"
                        "uniform int isNativelyFocal;"

                        "uniform half invR1;"  // 1/r1
                        "uniform half fx;"     // focalX = r0/(r0-r1)

                        "half4 main(float2 p) {"
                            "float t = -1;"
                            "half v = 1;" // validation flag,set to negative to discard fragment later

                            "float x_t = -1;"
                            "if (bool(isFocalOnCircle)) {"
                                "x_t = dot(p, p) / p.x;"
                            "} else if (bool(isWellBehaved)) {"
                                "x_t = length(p) - p.x * invR1;"
                            "} else {"
                                "float temp = p.x * p.x - p.y * p.y;"

                                // Only do sqrt if temp >= 0; this is significantly slower than
                                // checking temp >= 0 in the if statement that checks r(t) >= 0.
                                // But GPU may break if we sqrt a negative float. (Although I
                                // haven't observed that on any devices so far, and the old
                                // approach also does sqrt negative value without a check.) If
                                // the performance is really critical, maybe we should just
                                // compute the area where temp and x_t are always valid and drop
                                // all these ifs.
                                "if (temp >= 0) {"
                                    "if (bool(isSwapped) || !bool(isRadiusIncreasing)) {"
                                        "x_t = -sqrt(temp) - p.x * invR1;"
                                    "} else {"
                                        "x_t = sqrt(temp) - p.x * invR1;"
                                    "}"
                                "}"
                            "}"

                            // The final calculation of t from x_t has lots of static
                            // optimizations but only do them when x_t is positive (which
                            // can be assumed true if isWellBehaved is true)
                            "if (!bool(isWellBehaved)) {"
                                // This will still calculate t even though it will be ignored
                                // later in the pipeline to avoid a branch
                                "if (x_t <= 0.0) {"
                                    "v = -1;"
                                "}"
                            "}"
                            "if (bool(isRadiusIncreasing)) {"
                                "if (bool(isNativelyFocal)) {"
                                    "t = x_t;"
                                "} else {"
                                    "t = x_t + fx;"
                                "}"
                            "} else {"
                                "if (bool(isNativelyFocal)) {"
                                    "t = -x_t;"
                                "} else {"
                                    "t = -x_t + fx;"
                                "}"
                            "}"

                            "if (bool(isSwapped)) {"
                                "t = 1 - t;"
                            "}"

                            "return half4(half(t), v, 0, 0);"
                        "}"
                    );

            const SkConicalGradient::FocalData& focalData = shader->getFocalData();
            bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
                 isFocalOnCircle = focalData.isFocalOnCircle(),
                 isWellBehaved = focalData.isWellBehaved(), isSwapped = focalData.isSwapped(),
                 isNativelyFocal = focalData.isNativelyFocal();

            fp = GrSkSLFP::Make(kEffect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
                                GrSkSLFP::OptFlags::kNone,
                                "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
                                "isFocalOnCircle",    GrSkSLFP::Specialize<int>(isFocalOnCircle),
                                "isWellBehaved",      GrSkSLFP::Specialize<int>(isWellBehaved),
                                "isSwapped",          GrSkSLFP::Specialize<int>(isSwapped),
                                "isNativelyFocal",    GrSkSLFP::Specialize<int>(isNativelyFocal),
                                "invR1",              1.0f / focalData.fR1,
                                "fx",                 focalData.fFocalX);
        } break;
    }
    return GrGradientShader::MakeGradientFP(
            *shader, args, mRec, std::move(fp), SkOptAddressOrNull(matrix));
}

static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkLinearGradient* shader,
                                                             const GrFPArgs& args,
                                                             const SkShaders::MatrixRec& mRec) {
    return GrGradientShader::MakeLinear(*shader, args, mRec);
}

static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkRadialGradient* shader,
                                                             const GrFPArgs& args,
                                                             const SkShaders::MatrixRec& mRec) {
    static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(
            SkRuntimeEffect::MakeForShader,
            "float4 main(float2 coord) {"
                "return float4(length(coord), 1, 0, 0);"  // y = 1 for always valid
            "}");
    // The radial gradient never rejects a pixel so it doesn't change opacity
    auto fp = GrSkSLFP::Make(
            effect, "RadialLayout", /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kPreservesOpaqueInput);
    return GrGradientShader::MakeGradientFP(*shader, args, mRec, std::move(fp));
}

static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkSweepGradient* shader,
                                                             const GrFPArgs& args,
                                                             const SkShaders::MatrixRec& mRec) {
    // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
    // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
    // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
    // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
    // using atan instead.
    int useAtanWorkaround =
            args.fSurfaceDrawContext->caps()->shaderCaps()->fAtan2ImplementedAsAtanYOverX;
    static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
        "uniform half bias;"
        "uniform half scale;"
        "uniform int useAtanWorkaround;"  // specialized

        "half4 main(float2 coord) {"
            "half angle;"
            "if (bool(useAtanWorkaround)) {"
                "angle = half(2 * atan(-coord.y, length(coord) - coord.x));"
            "} else {"
                // Hardcode pi/2 for the angle when x == 0, to avoid undefined behavior in this
                // case. This hasn't proven to be necessary in the atan workaround case.
                "angle = (coord.x != 0) ? half(atan(-coord.y, -coord.x)) :"
                                        " sign(coord.y) * -1.5707963267949;"
            "}"

            // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
            "half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;"
            "return half4(t, 1, 0, 0);" // y = 1 for always valid
        "}"
    );

    // The sweep gradient never rejects a pixel so it doesn't change opacity
    auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
                             GrSkSLFP::OptFlags::kPreservesOpaqueInput,
                             "bias", shader->tBias(),
                             "scale", shader->tScale(),
                             "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
    return GrGradientShader::MakeGradientFP(*shader, args, mRec, std::move(fp));
}

static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkGradientBaseShader* shader,
                                                           const GrFPArgs& args,
                                                           const SkShaders::MatrixRec& mRec) {
    SkASSERT(shader);

    switch (shader->asGradient()) {
#define M(type)                               \
    case SkShaderBase::GradientType::k##type: \
        return make_gradient_fp(static_cast<const Sk##type##Gradient*>(shader), args, mRec);
        SK_ALL_GRADIENTS(M)
#undef M
        case SkShaderBase::GradientType::kNone:
            SkDEBUGFAIL("Gradient shader says its type is none");
            return nullptr;
    }
    SkUNREACHABLE;
}

std::unique_ptr<GrFragmentProcessor> Make(const SkShader* shader,
                                          const GrFPArgs& args,
                                          const SkMatrix& ctm) {
    return Make(shader, args, SkShaders::MatrixRec(ctm));
}

std::unique_ptr<GrFragmentProcessor> Make(const SkShader* shader,
                                          const GrFPArgs& args,
                                          const SkShaders::MatrixRec& mRec) {
    if (!shader) {
        return nullptr;
    }
    auto base = as_SB(shader);
    switch (base->type()) {
#define M(type)                             \
    case SkShaderBase::ShaderType::k##type: \
        return make_shader_fp(static_cast<const Sk##type##Shader*>(base), args, mRec);
        SK_ALL_SHADERS(M)
#undef M
    }
    SkUNREACHABLE;
}

}  // namespace GrFragmentProcessors
