/* * 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/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/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 #include #include #include #include class SkBitmap; enum class SkTileMode; namespace GrFragmentProcessors { static std::unique_ptr make_fp_from_shader_mask_filter(const SkMaskFilterBase* maskfilter, const GrFPArgs& args, const SkMatrix& ctm) { SkASSERT(maskfilter); auto shaderMF = static_cast(maskfilter); auto fp = Make(shaderMF->shader().get(), args, ctm); return GrFragmentProcessor::MulInputByChildAlpha(std::move(fp)); } std::unique_ptr 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 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.fContext, 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 effect, const char* name, sk_sp uniforms, std::unique_ptr inputFP, std::unique_ptr destColorFP, SkSpan children, const GrFPArgs& childArgs) { skia_private::STArray<8, std::unique_ptr> 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 make_blender_fp( const SkRuntimeBlender* rtb, std::unique_ptr srcFP, std::unique_ptr dstFP, const GrFPArgs& fpArgs) { SkASSERT(rtb); if (!SkRuntimeEffectPriv::CanDraw(fpArgs.fContext->priv().caps(), rtb->effect().get())) { return nullptr; } sk_sp uniforms = SkRuntimeEffectPriv::TransformUniforms( rtb->effect()->uniforms(), rtb->uniforms(), fpArgs.fDstColorInfo->colorSpace()); SkASSERT(uniforms); GrFPArgs childArgs(fpArgs.fContext, 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 make_blender_fp( const SkBlendModeBlender* blender, std::unique_ptr srcFP, std::unique_ptr dstFP, const GrFPArgs& fpArgs) { SkASSERT(blender); return GrBlendFragmentProcessor::Make(std::move(srcFP), std::move(dstFP), blender->mode()); } std::unique_ptr Make(const SkBlenderBase* blender, std::unique_ptr srcFP, std::unique_ptr 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(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 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 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 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 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 rgb_to_hsl(std::unique_ptr 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 hsl_to_rgb(std::unique_ptr 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 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(GrRecordingContext* context, const SkRuntimeColorFilter* filter, std::unique_ptr inputFP, const GrColorInfo& colorInfo, const SkSurfaceProps& props) { sk_sp uniforms = SkRuntimeEffectPriv::TransformUniforms( filter->effect()->uniforms(), filter->uniforms(), colorInfo.colorSpace()); SkASSERT(uniforms); GrFPArgs childArgs(context, &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(GrRecordingContext* context, const SkTableColorFilter* filter, std::unique_ptr 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 inputFP, const GrColorInfo& dstColorInfo, const SkSurfaceProps& props) { sk_sp dstCS = dstColorInfo.refColorSpace(); if (!dstCS) { dstCS = SkColorSpace::MakeSRGB(); } SkAlphaType workingAT; sk_sp 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 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(cf), \ std::move(inputFP), \ dstColorInfo, \ props); SK_ALL_COLOR_FILTERS(M) #undef M } SkUNREACHABLE; } static std::unique_ptr 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 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 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 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 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 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 make_shader_fp(const SkEmptyShader* shader, const GrFPArgs&, const SkShaders::MatrixRec&) { return nullptr; } static bool needs_subset(sk_sp img, const SkRect& subset) { return subset != SkRect::Make(img->dimensions()); } static std::unique_ptr 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); // 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(std::move(fp), nullptr); } } return fp; } static std::unique_ptr 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 make_shader_fp(const SkPerlinNoiseShader* shader, const GrFPArgs& args, const SkShaders::MatrixRec& mRec) { SkASSERT(args.fContext); 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.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 nullptr; } std::unique_ptr 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 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 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(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; } auto [v, ct] = skgpu::ganesh::AsView(ctx, image, skgpu::Mipmapped::kNo); view = std::move(v); provider->assignUniqueKeyToProxy(key, view.asTextureProxy()); } const GrSamplerState sampler(static_cast(shader->tileModeX()), static_cast(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 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 uniforms = SkRuntimeEffectPriv::TransformUniforms( shader->asRuntimeEffect()->uniforms(), shader->uniformData(args.fDstColorInfo->colorSpace()), args.fDstColorInfo->colorSpace()); SkASSERT(uniforms); bool success; std::unique_ptr fp; GrFPArgs childArgs( args.fContext, 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 make_shader_fp(const SkTransformShader* shader, const GrFPArgs&, const SkShaders::MatrixRec&) { return nullptr; } static std::unique_ptr make_shader_fp(const SkTriColorShader* shader, const GrFPArgs&, const SkShaders::MatrixRec&) { return nullptr; } static std::unique_ptr make_shader_fp(const SkWorkingColorSpaceShader* shader, const GrFPArgs& args, const SkShaders::MatrixRec& mRec) { const GrColorInfo* dstInfo = args.fDstColorInfo; sk_sp dstCS = dstInfo->refColorSpace(); if (!dstCS) { dstCS = SkColorSpace::MakeSRGB(); } GrColorInfo dst = {dstInfo->colorType(), dstInfo->alphaType(), dstCS}, working = {dstInfo->colorType(), dstInfo->alphaType(), shader->workingSpace()}; GrFPArgs workingArgs(args.fContext, &working, 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, working)); return GrColorSpaceXformEffect::Make(std::move(childWithWorkingInput), working, dst); } ////////////////////////////////////////////////////////////////////////////////////////////// static std::unique_ptr 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 fp; SkTLazy 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(isRadiusIncreasing), "isFocalOnCircle", GrSkSLFP::Specialize(isFocalOnCircle), "isWellBehaved", GrSkSLFP::Specialize(isWellBehaved), "isSwapped", GrSkSLFP::Specialize(isSwapped), "isNativelyFocal", GrSkSLFP::Specialize(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 make_gradient_fp(const SkLinearGradient* shader, const GrFPArgs& args, const SkShaders::MatrixRec& mRec) { return GrGradientShader::MakeLinear(*shader, args, mRec); } static std::unique_ptr 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 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;" "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 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(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 Make(const SkShader* shader, const GrFPArgs& args, const SkMatrix& ctm) { return Make(shader, args, SkShaders::MatrixRec(ctm)); } std::unique_ptr 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(base), args, mRec); SK_ALL_SHADERS(M) #undef M } SkUNREACHABLE; } } // namespace GrFragmentProcessors