/* * Copyright 2024 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/gpu/graphite/precompile/PrecompileShader.h" #include "include/core/SkColorSpace.h" #include "include/effects/SkRuntimeEffect.h" #include "include/gpu/graphite/precompile/PrecompileBlender.h" #include "src/core/SkColorSpacePriv.h" #include "src/core/SkKnownRuntimeEffects.h" #include "src/gpu/Blend.h" #include "src/gpu/graphite/BuiltInCodeSnippetID.h" #include "src/gpu/graphite/FactoryFunctionsPriv.h" #include "src/gpu/graphite/KeyContext.h" #include "src/gpu/graphite/KeyHelpers.h" #include "src/gpu/graphite/PaintParams.h" #include "src/gpu/graphite/PaintParamsKey.h" #include "src/gpu/graphite/PrecompileInternal.h" #include "src/gpu/graphite/ReadSwizzle.h" #include "src/gpu/graphite/precompile/PrecompileBaseComplete.h" #include "src/gpu/graphite/precompile/PrecompileBasePriv.h" #include "src/gpu/graphite/precompile/PrecompileBlenderPriv.h" #include "src/gpu/graphite/precompile/PrecompileShaderPriv.h" namespace skgpu::graphite { PrecompileShader::~PrecompileShader() = default; sk_sp PrecompileShader::makeWithLocalMatrix() { if (this->priv().isALocalMatrixShader()) { // SkShader::makeWithLocalMatrix collapses chains of localMatrix shaders so we need to // follow suit here return sk_ref_sp(this); } return PrecompileShaders::LocalMatrix({ sk_ref_sp(this) }); } sk_sp PrecompileShader::makeWithColorFilter(sk_sp cf) { if (!cf) { return sk_ref_sp(this); } return PrecompileShaders::ColorFilter({ sk_ref_sp(this) }, { std::move(cf) }); } sk_sp PrecompileShader::makeWithWorkingColorSpace(sk_sp cs) { if (!cs) { return sk_ref_sp(this); } return PrecompileShaders::WorkingColorSpace({ sk_ref_sp(this) }, { std::move(cs) }); } //-------------------------------------------------------------------------------------------------- class PrecompileEmptyShader final : public PrecompileShader { private: void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination == 0); // The empty shader only ever has one combination builder->addBlock(BuiltInCodeSnippetID::kPriorOutput); } }; sk_sp PrecompileShaders::Empty() { return sk_make_sp(); } //-------------------------------------------------------------------------------------------------- class PrecompileColorShader final : public PrecompileShader { private: bool isConstant(int desiredCombination) const override { SkASSERT(desiredCombination == 0); // The color shader only ever has one combination return true; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination == 0); // The color shader only ever has one combination // The white PMColor is just a placeholder for the actual paint params color SolidColorShaderBlock::AddBlock(keyContext, builder, gatherer, SK_PMColor4fWHITE); } }; sk_sp PrecompileShaders::Color() { return sk_make_sp(); } // The colorSpace is safe to ignore - it is just applied to the color and doesn't modify the // generated program. sk_sp PrecompileShaders::Color(sk_sp) { return sk_make_sp(); } //-------------------------------------------------------------------------------------------------- class PrecompileBlendShader final : public PrecompileShader { public: PrecompileBlendShader(SkSpan> runtimeBlendEffects, SkSpan> dsts, SkSpan> srcs, bool needsPorterDuffBased, bool needsSeparableMode) : fRuntimeBlendEffects(runtimeBlendEffects.begin(), runtimeBlendEffects.end()) , fDstOptions(dsts.begin(), dsts.end()) , fSrcOptions(srcs.begin(), srcs.end()) { fNumBlenderCombos = 0; for (const auto& rt : fRuntimeBlendEffects) { fNumBlenderCombos += rt->priv().numCombinations(); } if (needsPorterDuffBased) { ++fNumBlenderCombos; } if (needsSeparableMode) { ++fNumBlenderCombos; } SkASSERT(fNumBlenderCombos >= 1); fNumDstCombos = 0; for (const auto& d : fDstOptions) { fNumDstCombos += d->priv().numCombinations(); } fNumSrcCombos = 0; for (const auto& s : fSrcOptions) { fNumSrcCombos += s->priv().numCombinations(); } if (needsPorterDuffBased) { fPorterDuffIndex = 0; if (needsSeparableMode) { fSeparableModeIndex = 1; if (!fRuntimeBlendEffects.empty()) { fBlenderIndex = 2; } } else if (!fRuntimeBlendEffects.empty()) { fBlenderIndex = 1; } } else if (needsSeparableMode) { fSeparableModeIndex = 0; if (!fRuntimeBlendEffects.empty()) { fBlenderIndex = 1; } } else { SkASSERT(!fRuntimeBlendEffects.empty()); fBlenderIndex = 0; } } private: int numChildCombinations() const override { return fNumBlenderCombos * fNumDstCombos * fNumSrcCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < this->numCombinations()); const int desiredDstCombination = desiredCombination % fNumDstCombos; int remainingCombinations = desiredCombination / fNumDstCombos; const int desiredSrcCombination = remainingCombinations % fNumSrcCombos; remainingCombinations /= fNumSrcCombos; int desiredBlendCombination = remainingCombinations; SkASSERT(desiredBlendCombination < fNumBlenderCombos); if (desiredBlendCombination == fPorterDuffIndex || desiredBlendCombination == fSeparableModeIndex) { BlendShaderBlock::BeginBlock(keyContext, builder, gatherer); } else { const SkRuntimeEffect* blendEffect = GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kBlend); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { sk_ref_sp(blendEffect) }); SkASSERT(desiredBlendCombination >= fBlenderIndex); desiredBlendCombination -= fBlenderIndex; } AddToKey(keyContext, builder, gatherer, fSrcOptions, desiredSrcCombination); AddToKey(keyContext, builder, gatherer, fDstOptions, desiredDstCombination); if (desiredBlendCombination == fPorterDuffIndex) { CoeffBlenderBlock::AddBlock(keyContext, builder, gatherer, { 0.0f, 0.0f, 0.0f, 0.0f }); // coeffs aren't used } else if (desiredBlendCombination == fSeparableModeIndex) { BlendModeBlenderBlock::AddBlock(keyContext, builder, gatherer, SkBlendMode::kOverlay); // the blendmode is unused } else { AddToKey(keyContext, builder, gatherer, fRuntimeBlendEffects, desiredBlendCombination); } builder->endBlock(); // BlendShaderBlock or RuntimeEffectBlock } std::vector> fRuntimeBlendEffects; std::vector> fDstOptions; std::vector> fSrcOptions; int fNumBlenderCombos; int fNumDstCombos; int fNumSrcCombos; int fPorterDuffIndex = -1; int fSeparableModeIndex = -1; int fBlenderIndex = -1; }; sk_sp PrecompileShaders::Blend( SkSpan> blenders, SkSpan> dsts, SkSpan> srcs) { std::vector> tmp; tmp.reserve(blenders.size()); bool needsPorterDuffBased = false; bool needsBlendModeBased = false; for (const auto& b : blenders) { if (!b) { needsPorterDuffBased = true; // fall back to kSrcOver } else if (b->priv().asBlendMode().has_value()) { SkBlendMode bm = b->priv().asBlendMode().value(); SkSpan coeffs = skgpu::GetPorterDuffBlendConstants(bm); if (!coeffs.empty()) { needsPorterDuffBased = true; } else { needsBlendModeBased = true; } } else { tmp.push_back(b); } } if (!needsPorterDuffBased && !needsBlendModeBased && tmp.empty()) { needsPorterDuffBased = true; // fallback to kSrcOver } return sk_make_sp(SkSpan>(tmp), dsts, srcs, needsPorterDuffBased, needsBlendModeBased); } sk_sp PrecompileShaders::Blend( SkSpan blendModes, SkSpan> dsts, SkSpan> srcs) { bool needsPorterDuffBased = false; bool needsBlendModeBased = false; for (SkBlendMode bm : blendModes) { SkSpan porterDuffConstants = skgpu::GetPorterDuffBlendConstants(bm); if (!porterDuffConstants.empty()) { needsPorterDuffBased = true; } else { needsBlendModeBased = true; } } if (!needsPorterDuffBased && !needsBlendModeBased) { needsPorterDuffBased = true; // fallback to kSrcOver } return sk_make_sp(SkSpan>(), dsts, srcs, needsPorterDuffBased, needsBlendModeBased); } //-------------------------------------------------------------------------------------------------- class PrecompileCoordClampShader final : public PrecompileShader { public: PrecompileCoordClampShader(SkSpan> shaders) : fShaders(shaders.begin(), shaders.end()) { fNumShaderCombos = 0; for (const auto& s : fShaders) { fNumShaderCombos += s->priv().numCombinations(); } } private: int numChildCombinations() const override { return fNumShaderCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < fNumShaderCombos); constexpr SkRect kIgnored { 0, 0, 256, 256 }; // ignored bc we're precompiling // TODO: update CoordClampShaderBlock so this is optional CoordClampShaderBlock::CoordClampData data(kIgnored); CoordClampShaderBlock::BeginBlock(keyContext, builder, gatherer, data); AddToKey(keyContext, builder, gatherer, fShaders, desiredCombination); builder->endBlock(); } std::vector> fShaders; int fNumShaderCombos; }; sk_sp PrecompileShaders::CoordClamp(SkSpan> input) { return sk_make_sp(input); } //-------------------------------------------------------------------------------------------------- // TODO: Investigate the YUV-image use case class PrecompileImageShader final : public PrecompileShader { public: PrecompileImageShader(SkEnumBitMask flags) : fFlags(flags) {} private: // The ImageShader has 3 potential sampling/tiling variants: hardware-tiled, shader-tiled and // cubic sampling (which always uses shader-tiling) inline static constexpr int kNumSamplingTilingCombos = 3; inline static constexpr int kCubicSampled = 2; inline static constexpr int kHWTiled = 1; inline static constexpr int kShaderTiled = 0; // There are also 2 potential alpha combinations: alpha-only and not-alpha-only inline static constexpr int kNumAlphaCombinations = 2; inline static constexpr int kAlphaOnly = 1; inline static constexpr int kNonAlphaOnly = 0; int numIntrinsicCombinations() const override { int numSamplingTilingCombos = (fFlags & PrecompileImageShaderFlags::kExcludeCubic) ? 2 : kNumSamplingTilingCombos; if (fFlags & PrecompileImageShaderFlags::kExcludeAlpha) { // RawImageShaders don't blend alpha-only images w/ the paint color return numSamplingTilingCombos; } return numSamplingTilingCombos * kNumAlphaCombinations; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < this->numIntrinsicCombinations()); int desiredAlphaCombo, desiredSamplingTilingCombo; if (fFlags & PrecompileImageShaderFlags::kExcludeAlpha) { desiredAlphaCombo = kNonAlphaOnly; desiredSamplingTilingCombo = desiredCombination; } else { desiredAlphaCombo = desiredCombination % kNumAlphaCombinations; desiredSamplingTilingCombo = desiredCombination / kNumAlphaCombinations; } SkDEBUGCODE(int numSamplingTilingCombos = (fFlags & PrecompileImageShaderFlags::kExcludeCubic) ? 2 : kNumSamplingTilingCombos;) SkASSERT(desiredSamplingTilingCombo < numSamplingTilingCombos); static constexpr SkSamplingOptions kDefaultCubicSampling(SkCubicResampler::Mitchell()); static constexpr SkSamplingOptions kDefaultSampling; constexpr ReadSwizzle kIgnoredSwizzle = ReadSwizzle::kRGBA; // ImageShaderBlock will use hardware tiling when the subset covers the entire image, so we // create subset + image size combinations where subset == imgSize (for a shader that uses // hardware tiling) and subset < imgSize (for a shader that does shader-based tiling). static constexpr SkRect kSubset = SkRect::MakeWH(1.0f, 1.0f); static constexpr SkISize kHWTileableSize = SkISize::Make(1, 1); static constexpr SkISize kShaderTileableSize = SkISize::Make(2, 2); ImageShaderBlock::ImageData imgData( desiredSamplingTilingCombo == kCubicSampled ? kDefaultCubicSampling : kDefaultSampling, SkTileMode::kClamp, SkTileMode::kClamp, desiredSamplingTilingCombo == kHWTiled ? kHWTileableSize : kShaderTileableSize, kSubset, kIgnoredSwizzle); if (desiredAlphaCombo == kAlphaOnly) { SkASSERT(!(fFlags & PrecompileImageShaderFlags::kExcludeAlpha)); Blend(keyContext, builder, gatherer, /* addBlendToKey= */ [&] () -> void { AddKnownModeBlend(keyContext, builder, gatherer, SkBlendMode::kDstIn); }, /* addSrcToKey= */ [&] () -> void { ImageShaderBlock::AddBlock(keyContext, builder, gatherer, imgData); }, /* addDstToKey= */ [&]() -> void { RGBPaintColorBlock::AddBlock(keyContext, builder, gatherer); }); } else { ImageShaderBlock::AddBlock(keyContext, builder, gatherer, imgData); } } SkEnumBitMask fFlags; }; sk_sp PrecompileShaders::Image() { return PrecompileShaders::LocalMatrix( { sk_make_sp(PrecompileImageShaderFlags::kNone) }); } sk_sp PrecompileShaders::RawImage() { // Raw images do not perform color space conversion, but in Graphite, this is represented as // an identity color space xform, not as a distinct shader return PrecompileShaders::LocalMatrix( { sk_make_sp(PrecompileImageShaderFlags::kExcludeAlpha) }); } sk_sp PrecompileShadersPriv::Image( SkEnumBitMask flags) { return PrecompileShaders::LocalMatrix({ sk_make_sp(flags) }); } sk_sp PrecompileShadersPriv::RawImage( SkEnumBitMask flags) { return PrecompileShaders::LocalMatrix( { sk_make_sp(flags | PrecompileImageShaderFlags::kExcludeAlpha) }); } //-------------------------------------------------------------------------------------------------- class PrecompilePerlinNoiseShader final : public PrecompileShader { public: PrecompilePerlinNoiseShader() {} private: void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination == 0); // The Perlin noise shader only ever has one combination // TODO: update PerlinNoiseShaderBlock so the NoiseData is optional static const PerlinNoiseShaderBlock::PerlinNoiseData kIgnoredNoiseData( PerlinNoiseShaderBlock::Type::kFractalNoise, { 0.0f, 0.0f }, 2, {1, 1}); PerlinNoiseShaderBlock::AddBlock(keyContext, builder, gatherer, kIgnoredNoiseData); } }; sk_sp PrecompileShaders::MakeFractalNoise() { return sk_make_sp(); } sk_sp PrecompileShaders::MakeTurbulence() { return sk_make_sp(); } //-------------------------------------------------------------------------------------------------- class PrecompileGradientShader final : public PrecompileShader { public: PrecompileGradientShader(SkShaderBase::GradientType type) : fType(type) {} private: /* * The gradients currently have two specializations based on the number of stops. */ inline static constexpr int kNumStopVariants = 2; inline static constexpr int kStopVariants[kNumStopVariants] = { 4, 8 }; int numIntrinsicCombinations() const override { return kNumStopVariants; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { const int intrinsicCombination = desiredCombination / this->numChildCombinations(); SkDEBUGCODE(int childCombination = desiredCombination % this->numChildCombinations();) SkASSERT(intrinsicCombination < kNumStopVariants); SkASSERT(childCombination == 0); GradientShaderBlocks::GradientData gradData(fType, kStopVariants[intrinsicCombination]); constexpr SkAlphaType kAlphaType = kPremul_SkAlphaType; ColorSpaceTransformBlock::ColorSpaceTransformData csData(sk_srgb_singleton(), kAlphaType, sk_srgb_singleton(), kAlphaType); Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { GradientShaderBlocks::AddBlock(keyContext, builder, gatherer, gradData); }, /* addOuterToKey= */ [&]() -> void { ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, csData); }); } SkShaderBase::GradientType fType; }; sk_sp PrecompileShaders::LinearGradient() { sk_sp s = sk_make_sp(SkShaderBase::GradientType::kLinear); return PrecompileShaders::LocalMatrix({ std::move(s) }); } sk_sp PrecompileShaders::RadialGradient() { sk_sp s = sk_make_sp(SkShaderBase::GradientType::kRadial); return PrecompileShaders::LocalMatrix({ std::move(s) }); } sk_sp PrecompileShaders::SweepGradient() { sk_sp s = sk_make_sp(SkShaderBase::GradientType::kSweep); return PrecompileShaders::LocalMatrix({ std::move(s) }); } sk_sp PrecompileShaders::TwoPointConicalGradient() { sk_sp s = sk_make_sp(SkShaderBase::GradientType::kConical); return PrecompileShaders::LocalMatrix({ std::move(s) }); } //-------------------------------------------------------------------------------------------------- // The PictureShader ultimately turns into an SkImageShader optionally wrapped in a // LocalMatrixShader. The PrecompileImageShader already captures that use case so just reuse it. // Note that this means each precompile PictureShader will add 24 combinations: // 2 (pictureshader LM) x 2 (imageShader LM) x 6 (imageShader variations) sk_sp PrecompileShaders::Picture() { // Note: We don't need to consider the PrecompileYUVImageShader since the image // being drawn was created internally by Skia (as non-YUV). return PrecompileShadersPriv::LocalMatrixBothVariants({ PrecompileShaders::Image() }); } sk_sp PrecompileShadersPriv::Picture(bool withLM) { sk_sp s = PrecompileShaders::Image(); if (withLM) { return PrecompileShaders::LocalMatrix({ std::move(s) }); } return s; } //-------------------------------------------------------------------------------------------------- // In the main Skia API the SkLocalMatrixShader is optimized away when the LM is the identity // or omitted. The PrecompileLocalMatrixShader captures this by adding two intrinsic options. // One with the LMShader wrapping the child and one without the LMShader. class PrecompileLocalMatrixShader final : public PrecompileShader { public: enum class Flags { kNone, kIncludeWithOutVariant, }; PrecompileLocalMatrixShader(SkSpan> wrapped, Flags flags = Flags::kNone) : fWrapped(wrapped.begin(), wrapped.end()) , fFlags(flags) { fNumWrappedCombos = 0; for (const auto& s : fWrapped) { fNumWrappedCombos += s->priv().numCombinations(); } } bool isConstant(int desiredCombination) const override { SkASSERT(desiredCombination < this->numCombinations()); /* * Regardless of whether the LocalMatrixShader elides itself or not, we always want * the Constant-ness of the wrapped shader. */ int desiredWrappedCombination = desiredCombination / kNumIntrinsicCombinations; SkASSERT(desiredWrappedCombination < fNumWrappedCombos); std::pair, int> wrapped = PrecompileBase::SelectOption(SkSpan(fWrapped), desiredWrappedCombination); if (wrapped.first) { return wrapped.first->priv().isConstant(wrapped.second); } return false; } private: // The LocalMatrixShader has two potential variants: with and without the LocalMatrixShader inline static constexpr int kNumIntrinsicCombinations = 2; inline static constexpr int kWithLocalMatrix = 1; inline static constexpr int kWithoutLocalMatrix = 0; bool isALocalMatrixShader() const override { return true; } int numIntrinsicCombinations() const override { if (fFlags != Flags::kIncludeWithOutVariant) { return 1; // just kWithLocalMatrix } return kNumIntrinsicCombinations; } int numChildCombinations() const override { return fNumWrappedCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < this->numCombinations()); int desiredLMCombination, desiredWrappedCombination; if (fFlags != Flags::kIncludeWithOutVariant) { desiredLMCombination = kWithLocalMatrix; desiredWrappedCombination = desiredCombination; } else { desiredLMCombination = desiredCombination % kNumIntrinsicCombinations; desiredWrappedCombination = desiredCombination / kNumIntrinsicCombinations; } SkASSERT(desiredWrappedCombination < fNumWrappedCombos); if (desiredLMCombination == kWithLocalMatrix) { LocalMatrixShaderBlock::LMShaderData kIgnoredLMShaderData(SkMatrix::I()); LocalMatrixShaderBlock::BeginBlock(keyContext, builder, gatherer, kIgnoredLMShaderData); } AddToKey(keyContext, builder, gatherer, fWrapped, desiredWrappedCombination); if (desiredLMCombination == kWithLocalMatrix) { builder->endBlock(); } } std::vector> fWrapped; int fNumWrappedCombos; Flags fFlags; }; sk_sp PrecompileShaders::LocalMatrix( SkSpan> wrapped) { return sk_make_sp(std::move(wrapped)); } sk_sp PrecompileShadersPriv::LocalMatrixBothVariants( SkSpan> wrapped) { return sk_make_sp( std::move(wrapped), PrecompileLocalMatrixShader::Flags::kIncludeWithOutVariant); } //-------------------------------------------------------------------------------------------------- class PrecompileColorFilterShader final : public PrecompileShader { public: PrecompileColorFilterShader(SkSpan> shaders, SkSpan> colorFilters) : fShaders(shaders.begin(), shaders.end()) , fColorFilters(colorFilters.begin(), colorFilters.end()) { fNumShaderCombos = 0; for (const auto& s : fShaders) { fNumShaderCombos += s->priv().numCombinations(); } fNumColorFilterCombos = 0; for (const auto& cf : fColorFilters) { fNumColorFilterCombos += cf->priv().numCombinations(); } } private: int numChildCombinations() const override { return fNumShaderCombos * fNumColorFilterCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < this->numCombinations()); int desiredShaderCombination = desiredCombination % fNumShaderCombos; int desiredColorFilterCombination = desiredCombination / fNumShaderCombos; SkASSERT(desiredColorFilterCombination < fNumColorFilterCombos); Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { AddToKey(keyContext, builder, gatherer, fShaders, desiredShaderCombination); }, /* addOuterToKey= */ [&]() -> void { AddToKey(keyContext, builder, gatherer, fColorFilters, desiredColorFilterCombination); }); } std::vector> fShaders; std::vector> fColorFilters; int fNumShaderCombos; int fNumColorFilterCombos; }; sk_sp PrecompileShaders::ColorFilter( SkSpan> shaders, SkSpan> colorFilters) { return sk_make_sp(std::move(shaders), std::move(colorFilters)); } //-------------------------------------------------------------------------------------------------- class PrecompileWorkingColorSpaceShader final : public PrecompileShader { public: PrecompileWorkingColorSpaceShader(SkSpan> shaders, SkSpan> colorSpaces) : fShaders(shaders.begin(), shaders.end()) , fColorSpaces(colorSpaces.begin(), colorSpaces.end()) { fNumShaderCombos = 0; for (const auto& s : fShaders) { fNumShaderCombos += s->priv().numCombinations(); } } private: int numChildCombinations() const override { return fNumShaderCombos * fColorSpaces.size(); } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < this->numCombinations()); int desiredShaderCombination = desiredCombination % fNumShaderCombos; int desiredColorSpaceCombination = desiredCombination / fNumShaderCombos; SkASSERT(desiredColorSpaceCombination < (int) fColorSpaces.size()); const SkColorInfo& dstInfo = keyContext.dstColorInfo(); const SkAlphaType dstAT = dstInfo.alphaType(); sk_sp dstCS = dstInfo.refColorSpace(); if (!dstCS) { dstCS = SkColorSpace::MakeSRGB(); } sk_sp workingCS = fColorSpaces[desiredColorSpaceCombination]; SkColorInfo workingInfo(dstInfo.colorType(), dstAT, workingCS); KeyContextWithColorInfo workingContext(keyContext, workingInfo); Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { AddToKey(keyContext, builder, gatherer, fShaders, desiredShaderCombination); }, /* addOuterToKey= */ [&]() -> void { ColorSpaceTransformBlock::ColorSpaceTransformData data( workingCS.get(), dstAT, dstCS.get(), dstAT); ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, data); }); } std::vector> fShaders; std::vector> fColorSpaces; int fNumShaderCombos; }; sk_sp PrecompileShaders::WorkingColorSpace( SkSpan> shaders, SkSpan> colorSpaces) { return sk_make_sp(std::move(shaders), std::move(colorSpaces)); } //-------------------------------------------------------------------------------------------------- // In Graphite this acts as a non-elidable LocalMatrixShader class PrecompileCTMShader final : public PrecompileShader { public: PrecompileCTMShader(SkSpan> wrapped) : fWrapped(wrapped.begin(), wrapped.end()) { fNumWrappedCombos = 0; for (const auto& s : fWrapped) { fNumWrappedCombos += s->priv().numCombinations(); } } bool isConstant(int desiredCombination) const override { SkASSERT(desiredCombination < fNumWrappedCombos); std::pair, int> wrapped = PrecompileBase::SelectOption(SkSpan(fWrapped), desiredCombination); if (wrapped.first) { return wrapped.first->priv().isConstant(wrapped.second); } return false; } private: int numChildCombinations() const override { return fNumWrappedCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < fNumWrappedCombos); LocalMatrixShaderBlock::LMShaderData kIgnoredLMShaderData(SkMatrix::I()); LocalMatrixShaderBlock::BeginBlock(keyContext, builder, gatherer, kIgnoredLMShaderData); AddToKey(keyContext, builder, gatherer, fWrapped, desiredCombination); builder->endBlock(); } std::vector> fWrapped; int fNumWrappedCombos; }; sk_sp PrecompileShadersPriv::CTM(SkSpan> wrapped) { return sk_make_sp(std::move(wrapped)); } //-------------------------------------------------------------------------------------------------- class PrecompileBlurShader final : public PrecompileShader { public: PrecompileBlurShader(sk_sp wrapped) : fWrapped(std::move(wrapped)) { fNumWrappedCombos = fWrapped->priv().numCombinations(); } private: // 6 known 1D blur effects + 6 known 2D blur effects inline static constexpr int kNumIntrinsicCombinations = 12; int numIntrinsicCombinations() const override { return kNumIntrinsicCombinations; } int numChildCombinations() const override { return fNumWrappedCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < this->numCombinations()); using namespace SkKnownRuntimeEffects; int desiredBlurCombination = desiredCombination % kNumIntrinsicCombinations; int desiredWrappedCombination = desiredCombination / kNumIntrinsicCombinations; SkASSERT(desiredWrappedCombination < fNumWrappedCombos); static const StableKey kIDs[kNumIntrinsicCombinations] = { StableKey::k1DBlur4, StableKey::k1DBlur8, StableKey::k1DBlur12, StableKey::k1DBlur16, StableKey::k1DBlur20, StableKey::k1DBlur28, StableKey::k2DBlur4, StableKey::k2DBlur8, StableKey::k2DBlur12, StableKey::k2DBlur16, StableKey::k2DBlur20, StableKey::k2DBlur28, }; const SkRuntimeEffect* fEffect = GetKnownRuntimeEffect(kIDs[desiredBlurCombination]); KeyContextWithScope childContext(keyContext, KeyContext::Scope::kRuntimeEffect); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { sk_ref_sp(fEffect) }); fWrapped->priv().addToKey(childContext, builder, gatherer, desiredWrappedCombination); builder->endBlock(); } sk_sp fWrapped; int fNumWrappedCombos; }; sk_sp PrecompileShadersPriv::Blur(sk_sp wrapped) { return sk_make_sp(std::move(wrapped)); } //-------------------------------------------------------------------------------------------------- class PrecompileMatrixConvolutionShader final : public PrecompileShader { public: PrecompileMatrixConvolutionShader(sk_sp wrapped) : fWrapped(std::move(wrapped)) { fNumWrappedCombos = fWrapped->priv().numCombinations(); // When the matrix convolution ImageFilter uses a texture we know it will only ever // be SkFilterMode::kNearest and SkTileMode::kClamp. // TODO: add a PrecompileImageShaderFlags to further limit the raw image shader // combinations. Right now we're getting two combinations for the raw shader // (sk_image_shader and sk_hw_image_shader). fRawImageShader = PrecompileShadersPriv::RawImage(PrecompileImageShaderFlags::kExcludeCubic); fNumRawImageShaderCombos = fRawImageShader->priv().numCombinations(); } private: int numIntrinsicCombinations() const override { // The uniform version only has one option but the two texture-based versions will // have as many combinations as the raw image shader. return 1 + 2 * fNumRawImageShaderCombos; } int numChildCombinations() const override { return fNumWrappedCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { int desiredTextureCombination = 0; const int desiredWrappedCombination = desiredCombination % fNumWrappedCombos; int remainingCombinations = desiredCombination / fNumWrappedCombos; SkKnownRuntimeEffects::StableKey stableKey = SkKnownRuntimeEffects::StableKey::kInvalid; if (remainingCombinations == 0) { stableKey = SkKnownRuntimeEffects::StableKey::kMatrixConvUniforms; } else { static constexpr SkKnownRuntimeEffects::StableKey kTextureBasedStableKeys[] = { SkKnownRuntimeEffects::StableKey::kMatrixConvTexSm, SkKnownRuntimeEffects::StableKey::kMatrixConvTexLg, }; --remainingCombinations; stableKey = kTextureBasedStableKeys[remainingCombinations % 2]; desiredTextureCombination = remainingCombinations / 2; SkASSERT(desiredTextureCombination < fNumRawImageShaderCombos); } const SkRuntimeEffect* fEffect = GetKnownRuntimeEffect(stableKey); KeyContextWithScope childContext(keyContext, KeyContext::Scope::kRuntimeEffect); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { sk_ref_sp(fEffect) }); fWrapped->priv().addToKey(childContext, builder, gatherer, desiredWrappedCombination); if (stableKey != SkKnownRuntimeEffects::StableKey::kMatrixConvUniforms) { fRawImageShader->priv().addToKey(childContext, builder, gatherer, desiredTextureCombination); } builder->endBlock(); } sk_sp fWrapped; int fNumWrappedCombos; sk_sp fRawImageShader; int fNumRawImageShaderCombos; }; sk_sp PrecompileShadersPriv::MatrixConvolution( sk_sp wrapped) { return sk_make_sp(std::move(wrapped)); } //-------------------------------------------------------------------------------------------------- class PrecompileMorphologyShader final : public PrecompileShader { public: PrecompileMorphologyShader(sk_sp wrapped, SkKnownRuntimeEffects::StableKey stableKey) : fWrapped(std::move(wrapped)) , fStableKey(stableKey) { fNumWrappedCombos = fWrapped->priv().numCombinations(); SkASSERT(stableKey == SkKnownRuntimeEffects::StableKey::kLinearMorphology || stableKey == SkKnownRuntimeEffects::StableKey::kSparseMorphology); } private: int numChildCombinations() const override { return fNumWrappedCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < fNumWrappedCombos); const SkRuntimeEffect* effect = GetKnownRuntimeEffect(fStableKey); KeyContextWithScope childContext(keyContext, KeyContext::Scope::kRuntimeEffect); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { sk_ref_sp(effect) }); fWrapped->priv().addToKey(childContext, builder, gatherer, desiredCombination); builder->endBlock(); } sk_sp fWrapped; int fNumWrappedCombos; SkKnownRuntimeEffects::StableKey fStableKey; }; sk_sp PrecompileShadersPriv::LinearMorphology(sk_sp wrapped) { return sk_make_sp( std::move(wrapped), SkKnownRuntimeEffects::StableKey::kLinearMorphology); } sk_sp PrecompileShadersPriv::SparseMorphology(sk_sp wrapped) { return sk_make_sp( std::move(wrapped), SkKnownRuntimeEffects::StableKey::kSparseMorphology); } //-------------------------------------------------------------------------------------------------- class PrecompileDisplacementShader final : public PrecompileShader { public: PrecompileDisplacementShader(sk_sp displacement, sk_sp color) : fDisplacement(std::move(displacement)) , fColor(std::move(color)) { fNumDisplacementCombos = fDisplacement->priv().numCombinations(); fNumColorCombos = fColor->priv().numCombinations(); } private: int numChildCombinations() const override { return fNumDisplacementCombos * fNumColorCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < this->numChildCombinations()); const int desiredDisplacementCombination = desiredCombination % fNumDisplacementCombos; const int desiredColorCombination = desiredCombination / fNumDisplacementCombos; SkASSERT(desiredColorCombination < fNumColorCombos); const SkRuntimeEffect* fEffect = GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kDisplacement); KeyContextWithScope childContext(keyContext, KeyContext::Scope::kRuntimeEffect); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { sk_ref_sp(fEffect) }); fDisplacement->priv().addToKey(childContext, builder, gatherer, desiredDisplacementCombination); fColor->priv().addToKey(childContext, builder, gatherer, desiredColorCombination); builder->endBlock(); } sk_sp fDisplacement; int fNumDisplacementCombos; sk_sp fColor; int fNumColorCombos; }; //-------------------------------------------------------------------------------------------------- sk_sp PrecompileShadersPriv::Displacement(sk_sp displacement, sk_sp color) { return sk_make_sp(std::move(displacement), std::move(color)); } //-------------------------------------------------------------------------------------------------- class PrecompileLightingShader final : public PrecompileShader { public: PrecompileLightingShader(sk_sp wrapped) : fWrapped(std::move(wrapped)) { fNumWrappedCombos = fWrapped->priv().numCombinations(); } private: int numChildCombinations() const override { return fNumWrappedCombos; } void addToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, int desiredCombination) const override { SkASSERT(desiredCombination < fNumWrappedCombos); const SkRuntimeEffect* normalEffect = GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kNormal); const SkRuntimeEffect* lightingEffect = GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kLighting); KeyContextWithScope childContext(keyContext, KeyContext::Scope::kRuntimeEffect); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { sk_ref_sp(lightingEffect) }); RuntimeEffectBlock::BeginBlock(childContext, builder, gatherer, { sk_ref_sp(normalEffect) }); fWrapped->priv().addToKey(childContext, builder, gatherer, desiredCombination); builder->endBlock(); builder->endBlock(); } sk_sp fWrapped; int fNumWrappedCombos; }; sk_sp PrecompileShadersPriv::Lighting(sk_sp wrapped) { return sk_make_sp(std::move(wrapped)); } //-------------------------------------------------------------------------------------------------- } // namespace skgpu::graphite