| /* | |
| * 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 "src/gpu/graphite/precompile/PaintOption.h" | |
| #include "include/core/SkBlender.h" | |
| #include "include/gpu/graphite/precompile/PrecompileBlender.h" | |
| #include "include/gpu/graphite/precompile/PrecompileColorFilter.h" | |
| #include "include/gpu/graphite/precompile/PrecompileShader.h" | |
| #include "src/core/SkBlendModePriv.h" | |
| #include "src/core/SkColorSpacePriv.h" | |
| #include "src/gpu/graphite/ContextUtils.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/precompile/PrecompileBasePriv.h" | |
| #include "src/gpu/graphite/precompile/PrecompileBlenderPriv.h" | |
| #include "src/gpu/graphite/precompile/PrecompileColorFiltersPriv.h" | |
| #include "src/gpu/graphite/precompile/PrecompileShaderPriv.h" | |
| namespace skgpu::graphite { | |
| PaintOption::PaintOption(bool opaquePaintColor, | |
| const std::pair<sk_sp<PrecompileBlender>, int>& finalBlender, | |
| const std::pair<sk_sp<PrecompileShader>, int>& shader, | |
| const std::pair<sk_sp<PrecompileColorFilter>, int>& colorFilter, | |
| bool hasPrimitiveBlender, | |
| SkBlendMode primitiveBlendMode, | |
| bool skipColorXform, | |
| const std::pair<sk_sp<PrecompileShader>, int>& clipShader, | |
| Coverage coverage, | |
| TextureFormat targetFormat, | |
| bool dither, | |
| bool analyticClip) | |
| : fOpaquePaintColor(opaquePaintColor) | |
| , fFinalBlender(finalBlender) | |
| , fShader(shader) | |
| , fColorFilter(colorFilter) | |
| , fPrimitiveBlendMode(primitiveBlendMode) | |
| , fHasPrimitiveBlender(hasPrimitiveBlender) | |
| , fSkipColorXform(skipColorXform) | |
| , fClipShader(clipShader) | |
| , fRendererCoverage(coverage) | |
| , fTargetFormat(targetFormat) | |
| , fDither(dither) | |
| , fAnalyticClip(analyticClip) { | |
| if (this->finalBlender() && this->finalBlender()->priv().asBlendMode() == SkBlendMode::kClear) { | |
| // Convert all kClear final blends to kSrc + SolidColor(transparent), all other paint | |
| // effects can be discarded (we have to keep the analytic clip and clip shadow, though). | |
| fFinalBlender = {PrecompileBlenders::Mode(SkBlendMode::kSrc), 0}; | |
| fOpaquePaintColor = false; | |
| fShader = { nullptr, 0 }; | |
| fColorFilter = { nullptr, 0 }; | |
| fHasPrimitiveBlender = false; | |
| fDither = false; | |
| } else if (!fHasPrimitiveBlender) { | |
| if (fShader.first && fShader.first->priv().isConstant(fShader.second)) { | |
| fShader = { nullptr, 0 }; | |
| } | |
| if (!fShader.first && fColorFilter.first) { | |
| fColorFilter = { nullptr, 0 }; | |
| } | |
| } | |
| } | |
| void PaintOption::toKey(const KeyContext& keyContext) const { | |
| // Don't bother checking the uniform data manager, precompile doesn't depend on those values | |
| SkDEBUGCODE(keyContext.paintParamsKeyBuilder()->checkReset();) | |
| // Root Node 0 is the source color, which is the output of all effects post dithering | |
| // TODO(michaelludwig): This will be used to change from src-over to src in certain scenarios. | |
| keyContext.paintParamsKeyBuilder()->addRootBlockHeader(RootBlockType::kSrcColor); | |
| [[maybe_unused]] bool isOpaque = this->handleDithering(keyContext); | |
| // Root Node 1 is the final blender | |
| keyContext.paintParamsKeyBuilder()->addRootBlockHeader(RootBlockType::kFinalBlend); | |
| std::optional<SkBlendMode> finalBlendMode = | |
| this->finalBlender() ? this->finalBlender()->priv().asBlendMode() | |
| : SkBlendMode::kSrcOver; | |
| if (!finalBlendMode) { | |
| SkASSERT(this->finalBlender()); | |
| fFinalBlender.first->priv().addToKey(keyContext, fFinalBlender.second); | |
| } else { | |
| // Clears are converted to kSrc + SolidColor in the constructor | |
| SkASSERT(finalBlendMode != SkBlendMode::kClear); | |
| const bool hasAnalyticClip = fClipShader.first || fAnalyticClip; | |
| Coverage effectiveCoverage = fRendererCoverage; | |
| if (effectiveCoverage == Coverage::kNone && hasAnalyticClip) { | |
| effectiveCoverage = Coverage::kSingleChannel; | |
| } | |
| // If the KeyContext has opted into prioritizing Src (no blending) and we don't need | |
| // blending or only need it for the renderer (e.g. inner fill eligible), then try to keep | |
| // the final blend snippet as Src when it wouldn't impact the rendering. | |
| // NOTE: The first two terms here are equivalent to PaintParams::toKey()'s check for | |
| // (dstUsage == kNone || dstUsage = kDstOnlyUsedByRenderer) | |
| const bool optimizeSrcBlend = | |
| !hasAnalyticClip && | |
| (finalBlendMode == SkBlendMode::kSrc || finalBlendMode == SkBlendMode::kSrcOver) && | |
| SkToBool(keyContext.flags() & KeyGenFlags::kPreferFixedSrcBlend); | |
| const bool dstReadReq = !CanUseHardwareBlending(keyContext.caps(), | |
| fTargetFormat, | |
| *finalBlendMode, | |
| effectiveCoverage); | |
| // Match PaintParams::toKey's logic to convert kSrcOver to kSrc | |
| if (finalBlendMode == SkBlendMode::kSrcOver && isOpaque) { | |
| const bool dstUsageNone = !hasAnalyticClip && fRendererCoverage == Coverage::kNone; | |
| if (dstUsageNone && optimizeSrcBlend) { | |
| SkASSERT(CanUseHardwareBlending(keyContext.caps(), fTargetFormat, | |
| SkBlendMode::kSrc, fRendererCoverage)); | |
| finalBlendMode = SkBlendMode::kSrc; | |
| } | |
| } | |
| if (!dstReadReq || (finalBlendMode == SkBlendMode::kSrc && optimizeSrcBlend)) { | |
| AddFixedBlendMode(keyContext, *finalBlendMode); | |
| } else { | |
| AddBlendMode(keyContext, *finalBlendMode); | |
| } | |
| } | |
| // Optional Root Node 2 is the clip | |
| if (fClipShader.first || fAnalyticClip) { | |
| keyContext.paintParamsKeyBuilder()->addRootBlockHeader(RootBlockType::kClip); | |
| this->handleClipping(keyContext); | |
| } | |
| } | |
| bool PaintOption::addPaintColorToKey(const KeyContext& keyContext) const { | |
| if (fShader.first) { | |
| fShader.first->priv().addToKey(keyContext, fShader.second); | |
| return fShader.first->priv().isOpaque(fShader.second); | |
| } else { | |
| RGBPaintColorBlock::AddBlock(keyContext); | |
| return true; | |
| } | |
| } | |
| bool PaintOption::handlePrimitiveColor(const KeyContext& keyContext) const { | |
| if (!fHasPrimitiveBlender) { | |
| return this->addPaintColorToKey(keyContext); | |
| } | |
| if (fSkipColorXform && fPrimitiveBlendMode == SkBlendMode::kDst) { | |
| AddPrimitiveColor(keyContext, fSkipColorXform); | |
| return false; | |
| } | |
| bool srcIsOpaque = false; | |
| Blend(keyContext, | |
| /* addBlendToKey= */ [&] () -> void { | |
| /** | |
| * TODO: Allow clients to provide precompile SkBlender options for primitive | |
| * blending. For now we have a back door to internally specify an SkBlendMode. | |
| */ | |
| AddToKey(keyContext, GetBlendModeSingleton(fPrimitiveBlendMode)); | |
| }, | |
| /* addSrcToKey= */ [&]() -> void { | |
| srcIsOpaque = this->addPaintColorToKey(keyContext); | |
| }, | |
| /* addDstToKey= */ [&]() -> void { | |
| AddPrimitiveColor(keyContext, fSkipColorXform); | |
| }); | |
| // NOTE: PaintOption only takes an SkBlendMode for primitive blending, but if it is expanded | |
| // to accept SkBlenders, then this if should only be taken when the blender is a blend mode. | |
| if (/* primBlend.has_value() && */srcIsOpaque) { | |
| return fPrimitiveBlendMode == SkBlendMode::kSrc || | |
| fPrimitiveBlendMode == SkBlendMode::kSrcOver; | |
| } else { | |
| return false; | |
| } | |
| } | |
| bool PaintOption::handlePaintAlpha(const KeyContext& keyContext) const { | |
| if (!fShader.first && !fHasPrimitiveBlender) { | |
| // If there is no shader and no primitive blending the input to the colorFilter stage | |
| // is just the premultiplied paint color. | |
| SolidColorShaderBlock::AddBlock(keyContext, SK_PMColor4fWHITE); | |
| return fOpaquePaintColor; | |
| } | |
| if (!fOpaquePaintColor) { | |
| Blend(keyContext, | |
| /* addBlendToKey= */ [&] () -> void { | |
| AddFixedBlendMode(keyContext, SkBlendMode::kSrcIn); | |
| }, | |
| /* addSrcToKey= */ [&]() -> void { | |
| this->handlePrimitiveColor(keyContext); | |
| }, | |
| /* addDstToKey= */ [&]() -> void { | |
| AlphaOnlyPaintColorBlock::AddBlock(keyContext); | |
| }); | |
| return false; | |
| } else { | |
| return this->handlePrimitiveColor(keyContext); | |
| } | |
| } | |
| bool PaintOption::handleColorFilter(const KeyContext& keyContext) const { | |
| if (fColorFilter.first) { | |
| bool srcIsOpaque = false; | |
| Compose(keyContext, | |
| /* addInnerToKey= */ [&]() -> void { | |
| srcIsOpaque = this->handlePaintAlpha(keyContext); | |
| }, | |
| /* addOuterToKey= */ [&]() -> void { | |
| fColorFilter.first->priv().addToKey(keyContext, fColorFilter.second); | |
| }); | |
| return srcIsOpaque && fColorFilter.first->priv().isAlphaUnchanged(fColorFilter.second); | |
| } else { | |
| return this->handlePaintAlpha(keyContext); | |
| } | |
| } | |
| // This should be kept in sync w/ SkPaintPriv::ShouldDither and PaintParams::should_dither | |
| bool PaintOption::shouldDither(SkColorType dstCT) const { | |
| // The paint dither flag can veto. | |
| if (!fDither) { | |
| return false; | |
| } | |
| if (dstCT == kUnknown_SkColorType) { | |
| return false; | |
| } | |
| // We always dither 565 or 4444 when requested. | |
| if (dstCT == kRGB_565_SkColorType || dstCT == kARGB_4444_SkColorType) { | |
| return true; | |
| } | |
| // Otherwise, dither is only needed for non-const paints. | |
| return fShader.first && !fShader.first->priv().isConstant(fShader.second); | |
| } | |
| bool PaintOption::handleDithering(const KeyContext& keyContext) const { | |
| #ifndef SK_IGNORE_GPU_DITHER | |
| SkColorType ct = keyContext.dstColorInfo().colorType(); | |
| if (this->shouldDither(ct)) { | |
| bool srcIsOpaque = false; | |
| Compose(keyContext, | |
| /* addInnerToKey= */ [&]() -> void { | |
| srcIsOpaque = this->handleColorFilter(keyContext); | |
| }, | |
| /* addOuterToKey= */ [&]() -> void { | |
| AddDitherBlock(keyContext, ct); | |
| }); | |
| return srcIsOpaque; | |
| } else | |
| #endif | |
| { | |
| return this->handleColorFilter(keyContext); | |
| } | |
| } | |
| void PaintOption::handleClipping(const KeyContext& keyContext) const { | |
| SkASSERT(fAnalyticClip || fClipShader.first); | |
| if (fAnalyticClip) { | |
| #if defined(SK_GRAPHITE_USE_LEGACY_RRECT_CLIP_SHADER) | |
| NonMSAAClipBlock::NonMSAAClipData data( | |
| /* rect= */ {}, | |
| /* radiusPlusHalf= */ {}, | |
| /* edgeSelect= */ {}, | |
| /* texCoordOffset= */ {}, | |
| /* maskBounds= */ {}, | |
| // TODO: the kAnalyticAndAtlasClip vs. kAnalyticClip decision is based on this | |
| // being a valid TextureProxy. | |
| /* atlasTexture= */ nullptr); | |
| if (fClipShader.first) { | |
| // For both an analytic clip and clip shader, we need to compose them together into | |
| // a single clipping root node. | |
| Blend(keyContext, | |
| /* addBlendToKey= */ [&]() -> void { | |
| AddFixedBlendMode(keyContext, SkBlendMode::kModulate); | |
| }, | |
| /* addSrcToKey= */ [&]() -> void { | |
| NonMSAAClipBlock::AddBlock(keyContext, data); | |
| }, | |
| /* addDstToKey= */ [&]() -> void { | |
| fClipShader.first->priv().addToKey(keyContext, fClipShader.second); | |
| }); | |
| } else { | |
| // Without a clip shader, the analytic clip can be the clipping root node. | |
| NonMSAAClipBlock::AddBlock(keyContext, data); | |
| } | |
| #else | |
| NonMSAAClip clip{ | |
| .fAnalyticClip = { | |
| .fBounds = {0, 0, 1, 1}, // just needs to be non-empty | |
| }, | |
| // TODO: kAnalyticAndAtlasClip vs. kAnalyticClip decision is based on this being | |
| // a valid TextureProxy | |
| .fAtlasClip = { | |
| .fMaskBounds = {}, | |
| .fOutPos = {}, | |
| .fAtlasTexture = nullptr, | |
| }, | |
| }; | |
| if (fClipShader.first) { | |
| // For both an analytic clip and clip shader, we need to compose them together into | |
| // a single clipping root node. | |
| Blend(keyContext, | |
| /* addBlendToKey= */ [&]() -> void { | |
| AddFixedBlendMode(keyContext, SkBlendMode::kModulate); | |
| }, | |
| /* addSrcToKey= */ [&]() -> void { | |
| AddAnalyticClip(keyContext, clip); | |
| }, | |
| /* addDstToKey= */ [&]() -> void { | |
| fClipShader.first->priv().addToKey(keyContext, fClipShader.second); | |
| }); | |
| } else { | |
| // Without a clip shader, the analytic clip can be the clipping root node. | |
| AddAnalyticClip(keyContext, clip); | |
| } | |
| #endif // SK_GRAPHITE_USE_LEGACY_RRECT_CLIP_SHADER | |
| } else { | |
| // Since there's no analytic clip, the clipping root node can be fClipShader directly. | |
| SkASSERT(fClipShader.first); | |
| fClipShader.first->priv().addToKey(keyContext, fClipShader.second); | |
| } | |
| } | |
| } // namespace skgpu::graphite |