blob: 1af581e8bb7b17f603c8d1c84c3f368ab2e63cae [file] [log] [blame]
/*
* 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/effects/SkRuntimeEffect.h"
#include "include/gpu/GpuTypes.h"
#include "include/gpu/GrRecordingContext.h"
#include "include/gpu/GrTypes.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "include/private/SkColorData.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/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/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/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/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>
#include <vector>
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 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.
SkShaders::MatrixRec mRec(SkMatrix::I());
mRec.markTotalMatrixInvalid();
auto childFP = Make(child.shader(), 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] = Make(childArgs.fContext,
child.colorFilter(),
/*inputFP=*/nullptr,
*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;
}
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;
}
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(GrRecordingContext*,
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(GrRecordingContext* context,
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(context, filter->inner().get(), std::move(inputFP), dstColorInfo, props);
if (!innerSuccess) {
return GrFPFailure(std::move(inputClone));
}
auto [outerSuccess, outerFP] =
Make(context, 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(GrRecordingContext*,
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(GrRecordingContext*,
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(GrRecordingContext*,
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 = */ true,
/* 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(GrRecordingContext* context,
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(context, &colorInfo, props);
auto children = filter->children();
return make_effect_fp(filter->effect(),
"runtime_color_filter",
std::move(uniforms),
std::move(inputFP),
/*destColorFP=*/nullptr,
SkSpan(children),
childArgs);
}
static GrFPResult make_colorfilter_fp(GrRecordingContext* context,
const SkTableColorFilter* filter,
std::unique_ptr<GrFragmentProcessor> inputFP,
const GrColorInfo&,
const SkSurfaceProps&) {
auto cte = ColorTableEffect::Make(std::move(inputFP), context, filter->bitmap());
return cte ? GrFPSuccess(std::move(cte)) : GrFPFailure(nullptr);
}
static GrFPResult make_colorfilter_fp(GrRecordingContext* context,
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(context,
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(GrRecordingContext* ctx,
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(ctx, \
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.
SkDEBUGFAIL("Both src and dst shaders in blend should be valid but are not.");
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.fContext,
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) {
return GrFragmentProcessor::MakeColor(SkColorToPMColor4f(shader->color(), *args.fDstColorInfo));
}
static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkColor4Shader* shader,
const GrFPArgs& args,
const SkShaders::MatrixRec& mRec) {
SkColorSpaceXformSteps steps{shader->colorSpace().get(),
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) {
SkMatrix ctmInv;
if (!shader->ctm().invert(&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.fContext, 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);
if (shader->image()->isAlphaOnly()) {
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.fContext);
SkASSERT(shader->numOctaves());
const SkMatrix& totalMatrix = mRec.totalMatrix();
// Either we don't stitch tiles, or we have a valid tile size
SkASSERT(!shader->stitchTiles() || !shader->tileSize().isEmpty());
auto paintingData = shader->getPaintingData(totalMatrix);
paintingData->generateBitmaps();
// Like shadeSpan, we start from device space. We will account for that below with a device
// space effect.
auto context = args.fContext;
const SkBitmap& permutationsBitmap = paintingData->getPermutationsBitmap();
const SkBitmap& noiseBitmap = paintingData->getNoiseBitmap();
auto permutationsView = std::get<0>(GrMakeCachedBitmapProxyView(
context,
permutationsBitmap,
/*label=*/"PerlinNoiseShader_FragmentProcessor_PermutationsView"));
auto noiseView = std::get<0>(GrMakeCachedBitmapProxyView(
context, noiseBitmap, /*label=*/"PerlinNoiseShader_FragmentProcessor_NoiseView"));
if (permutationsView && noiseView) {
return GrFragmentProcessor::DeviceSpace(
GrMatrixEffect::Make(SkMatrix::Translate(1 - totalMatrix.getTranslateX(),
1 - totalMatrix.getTranslateY()),
GrPerlinNoise2Effect::Make(shader->noiseType(),
shader->numOctaves(),
shader->stitchTiles(),
std::move(paintingData),
std::move(permutationsView),
std::move(noiseView),
*context->priv().caps())));
}
return nullptr;
}
static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkPictureShader* shader,
const GrFPArgs& args,
const SkShaders::MatrixRec& mRec) {
auto ctx = args.fContext;
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;
auto image = info.makeImage(SkSurfaces::RenderTarget(ctx,
skgpu::Budgeted::kYes,
info.imageInfo,
msaaSampleCount,
kTopLeft_GrSurfaceOrigin,
&info.props,
createWithMips),
shader->picture().get());
if (!image) {
return nullptr;
}
auto [v, ct] = skgpu::ganesh::AsView(ctx, image, GrMipmapped::kNo);
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.fContext->priv().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);
auto children = shader->children();
bool success;
std::unique_ptr<GrFragmentProcessor> fp;
std::tie(success, fp) = make_effect_fp(shader->effect(),
"runtime_shader",
std::move(uniforms),
/*inputFP=*/nullptr,
/*destColorFP=*/nullptr,
SkSpan(children),
args);
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 SkUpdatableColorShader* shader,
const GrFPArgs&,
const SkShaders::MatrixRec&) {
return nullptr;
}
//////////////////////////////////////////////////////////////////////////////////////////////
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;
SkTLazy<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.set(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), matrix.getMaybeNull());
}
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,
"half4 main(float2 coord) {"
"return half4(half(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.fContext->priv().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 = bool(useAtanWorkaround)"
"? half(2 * atan(-coord.y, length(coord) - coord.x))"
": half(atan(-coord.y, -coord.x));"
// 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