| /* | |
| * 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/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&) { | |
| auto cte = ColorTableEffect::Make(std::move(inputFP), sdc->recordingContext(), | |
| 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, | |
| 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<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 |