| /* | |
| * Copyright 2022 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/PaintParams.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkShader.h" | |
| #include "src/core/SkBlendModeBlender.h" | |
| #include "src/core/SkBlenderBase.h" | |
| #include "src/core/SkColorSpacePriv.h" | |
| #include "src/core/SkImageInfoPriv.h" | |
| #include "src/core/SkMeshPriv.h" | |
| #include "src/effects/colorfilters/SkColorFilterBase.h" | |
| #include "src/gpu/Blend.h" | |
| #include "src/gpu/DitherUtils.h" | |
| #include "src/gpu/graphite/ContextUtils.h" | |
| #include "src/gpu/graphite/DrawContext.h" | |
| #include "src/gpu/graphite/KeyContext.h" | |
| #include "src/gpu/graphite/KeyHelpers.h" | |
| #include "src/gpu/graphite/PaintParamsKey.h" | |
| #include "src/gpu/graphite/PipelineData.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| namespace skgpu::graphite { | |
| namespace { | |
| // This should be kept in sync w/ SkPaintPriv::ShouldDither and PaintOption::shouldDither | |
| bool should_dither(const PaintParams& p, SkColorType dstCT) { | |
| // The paint dither flag can veto. | |
| if (!p.dither()) { | |
| 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 p.imageShader() || (p.shader() && !as_SB(p.shader())->isConstant()); | |
| } | |
| std::pair<const SkBlender*, SkBlendMode> get_final_blend(const SkBlender* blender) { | |
| if (!blender) { | |
| return {nullptr, SkBlendMode::kSrcOver}; | |
| } | |
| auto optionalBlendMode = as_BB(blender)->asBlendMode(); | |
| if (optionalBlendMode.has_value()) { | |
| return {nullptr, *optionalBlendMode}; | |
| } else { | |
| return {blender, SkBlendMode::kSrc}; | |
| } | |
| } | |
| // This produces the final DstUsage flags except for the src-over case, which returns flags assuming | |
| // that it is opaque and optimized to src. Once toKey() determines opacity, the flags only need to | |
| // be adjusted if that assumption no longer holds. | |
| SkEnumBitMask<DstUsage> get_dst_usage(const Caps* caps, | |
| TextureFormat targetFormat, | |
| const PaintParams& paint, | |
| Coverage rendererCoverage, | |
| const SkShader* clipShader, | |
| const NonMSAAClip& nonMSAAClip) { | |
| SkEnumBitMask<DstUsage> dstUsage = DstUsage::kDependsOnDst; | |
| if (paint.finalBlender()) { | |
| dstUsage |= DstUsage::kDstReadRequired; | |
| } else { | |
| const bool hasAnalyticClip = clipShader || !nonMSAAClip.isEmpty(); | |
| Coverage effectiveCoverage = rendererCoverage; | |
| if (effectiveCoverage == Coverage::kNone && hasAnalyticClip) { | |
| effectiveCoverage = Coverage::kSingleChannel; | |
| } | |
| const SkBlendMode finalBlendMode = paint.finalBlendMode(); | |
| if (!CanUseHardwareBlending(caps, targetFormat, finalBlendMode, effectiveCoverage)) { | |
| dstUsage |= DstUsage::kDstReadRequired; | |
| } | |
| if (finalBlendMode > SkBlendMode::kLastCoeffMode) { | |
| dstUsage |= DstUsage::kAdvancedBlend; | |
| } | |
| if (!hasAnalyticClip && (finalBlendMode == SkBlendMode::kSrc || | |
| finalBlendMode == SkBlendMode::kSrcOver)) { | |
| if (rendererCoverage != Coverage::kNone) { | |
| // For kSrc, kDstOnlyUsedByRenderer is the correct final usage. For kSrcOver, | |
| // we optimistically add it on the assumption the rest of the paint will be | |
| // opaque. toKey() must remove this flag if it's not opaque. | |
| dstUsage |= DstUsage::kDstOnlyUsedByRenderer; | |
| } else { | |
| // For kSrc, we definitely now do not depend on the dst so we can remove that | |
| // flag entirely. Optimistically we also remove it for kSrcOver under the | |
| // assumption that the paint is opaque; if toKey() finds that is not the case, | |
| // it must restore the flag. | |
| SkASSERT(dstUsage == DstUsage::kDependsOnDst); | |
| dstUsage = DstUsage::kNone; | |
| } | |
| } | |
| } | |
| return dstUsage; | |
| } | |
| } // anonymous namespace | |
| PaintParams::PaintParams(const SkPaint& paint, | |
| const SimpleImage* imageOverride, | |
| const SkBlender* primitiveBlender, | |
| bool skipColorXform, | |
| bool ignoreShader) | |
| : fColor(paint.getColor4f()) | |
| , fFinalBlend(get_final_blend(paint.getBlender())) | |
| , fShader(ignoreShader ? nullptr : paint.getShader()) | |
| , fImageShader(imageOverride) | |
| , fColorFilter(paint.getColorFilter()) | |
| , fPrimitiveBlender(primitiveBlender) | |
| , fSkipColorXform(skipColorXform) | |
| , fDither(paint.isDither()) { | |
| if (fFinalBlend.second == SkBlendMode::kClear) { | |
| // None of the other effects are relevant, the final src color for blending is transparent | |
| // and we can consolidate its blend mode to kSrc. This is helpful to restrict the number of | |
| // pipelines that will actually have DstUsage::kNone. | |
| fFinalBlend.second = SkBlendMode::kSrc; | |
| fColor = SkColors::kTransparent; | |
| fShader = nullptr; | |
| fImageShader = nullptr; | |
| fColorFilter = nullptr; | |
| fPrimitiveBlender = nullptr; | |
| fDither = false; | |
| } else if (!fPrimitiveBlender) { | |
| // NOTE: We can still have an alpha-only fImageShader and still want to try simplifying the | |
| // paint's shader to a solid color for the alpha image's colorization. | |
| SkColor4f constantColor; | |
| if (fShader && as_SB(fShader)->isConstant(&constantColor)) { | |
| // The original fColor and `constantColor` are un-premul sRGB, but we need to preserve | |
| // the paint's alpha. | |
| float origA = fColor.fA; | |
| fColor = constantColor; | |
| fColor.fA *= origA; | |
| fShader = nullptr; | |
| } | |
| // We can't apply the color filter to the color if a shader modifies it, including when the | |
| // image shader is alpha-only. The image's per-pixel alpha modulates the paint color | |
| // *before* the color filter is evaluated. | |
| if (!fShader && !fImageShader && fColorFilter) { | |
| fColor = fColorFilter->filterColor4f(fColor, | |
| sk_srgb_singleton(), | |
| sk_srgb_singleton()); | |
| fColorFilter = nullptr; | |
| } | |
| } | |
| } | |
| PaintParams::PaintParams(const SkPaint& paint, | |
| const SkBlender* primitiveBlender, | |
| bool skipColorXform, | |
| bool ignoreShader) | |
| : PaintParams(paint, | |
| /*imageOverride=*/nullptr, | |
| primitiveBlender, | |
| skipColorXform, | |
| ignoreShader) {} | |
| PaintParams::PaintParams(const SkPaint& paint, const SimpleImage& imageOverride, float xtraAlpha) | |
| : PaintParams(paint, | |
| &imageOverride, | |
| /*primitiveBlender=*/nullptr, | |
| /*skipColorXform=*/false, | |
| // For color images, the paint's original shader is ignored. | |
| /*ignoreShader=*/!SkColorTypeIsAlphaOnly(imageOverride.fImage->colorType())) { | |
| // Multiply in the extra alpha that's allowed to be set on an ImageSetEntry. Accepting it here | |
| // avoids needing to modify the SkPaint providing the base color. | |
| fColor.fA *= xtraAlpha; | |
| } | |
| PaintParams::PaintParams(const SkPaint& paint, const SkColor4f& colorOverride) | |
| : PaintParams(paint, | |
| /*imageOverride=*/nullptr, | |
| /*primitiveBlender=*/nullptr, | |
| /*skipColorXform=*/false, | |
| /*ignoreShader*/true) { | |
| float newAlpha = fColor.fA * colorOverride.fA; | |
| fColor = colorOverride.withAlpha(newAlpha); | |
| } | |
| PaintParams::PaintParams(const SkColor4f& color, SkBlendMode finalBlendMode) | |
| : fColor(finalBlendMode == SkBlendMode::kClear ? SkColors::kTransparent : color) | |
| , fFinalBlend({nullptr, finalBlendMode == SkBlendMode::kClear ? SkBlendMode::kSrc | |
| : finalBlendMode}) | |
| , fShader(nullptr) | |
| , fImageShader(nullptr) | |
| , fColorFilter(nullptr) | |
| , fPrimitiveBlender(nullptr) | |
| , fSkipColorXform(false) | |
| , fDither(false) {} | |
| PaintParams PaintParams::makeWithPrimitiveColor(const SkBlender* primitiveBlender, | |
| const SkColor4f& primitiveColorOverride) const { | |
| PaintParams copy = *this; | |
| copy.fPrimitiveBlender = primitiveBlender; | |
| copy.fPrimitiveColorOverride = primitiveColorOverride; | |
| return copy; | |
| } | |
| SkColor4f PaintParams::Color4fPrepForDst(SkColor4f srcColor, const SkColorInfo& dstColorInfo) { | |
| // xform from sRGB to the destination colorspace | |
| SkColorSpaceXformSteps steps(sk_srgb_singleton(), kUnpremul_SkAlphaType, | |
| dstColorInfo.colorSpace(), kUnpremul_SkAlphaType); | |
| SkColor4f result = srcColor; | |
| steps.apply(result.vec()); | |
| return result; | |
| } | |
| PaintParams PaintParams::makeWithMesh(const SkMesh& mesh) const { | |
| PaintParams copy = *this; | |
| copy.fMeshSpec = mesh.spec(); | |
| copy.fMeshChildren = mesh.children(); | |
| copy.fPrimitiveColorSpace = SkMeshSpecificationPriv::ColorSpace(*mesh.spec()); | |
| copy.fPrimitiveAlphaType = SkMeshSpecificationPriv::AlphaType(*mesh.spec()); | |
| return copy; | |
| } | |
| #if defined(SK_DEBUG) | |
| PaintParams PaintParams::MakeOpaque(const PaintParams& paint) { | |
| PaintParams opaque = paint; | |
| opaque.fFinalBlend = {nullptr, SkBlendMode::kSrc}; | |
| opaque.fColor = opaque.fColor.makeOpaque(); | |
| return opaque; | |
| } | |
| #endif | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| ShadingParams::ShadingParams(const Caps* caps, | |
| const PaintParams& paint, | |
| const NonMSAAClip& nonMSAAClip, | |
| const SkShader* clipShader, | |
| Coverage coverage, | |
| TextureFormat targetFormat) | |
| : fPaint(paint) | |
| , fNonMSAAClip(nonMSAAClip) | |
| , fClipShader(clipShader) | |
| , fDstUsage(get_dst_usage(caps, targetFormat, paint, coverage, fClipShader, fNonMSAAClip)) | |
| #if defined(SK_DEBUG) | |
| , fCoverage(coverage) | |
| #endif | |
| {} | |
| bool ShadingParams::addPaintColorToKey(const KeyContext& keyContext) const { | |
| const auto& simpleImage = fPaint.imageShader(); | |
| if (simpleImage) { | |
| // There is an implicit image shader, match handling of SkModifyPaintForDrawImageRect | |
| if (fPaint.shader()) { | |
| // Alpha-only images for drawImageRect() get colorized with the paint's shader. This | |
| // differs from alpha-only image shaders that might be encountered within an SkShader | |
| // graph, which get colorized by the paint's opaque color. | |
| SkASSERT(SkColorTypeIsAlphaOnly(simpleImage->fImage->colorType())); | |
| Blend(keyContext, | |
| /* addBlendToKey */ [&] () -> void { | |
| AddFixedBlendMode(keyContext, SkBlendMode::kDstIn); | |
| }, | |
| /* addSrcToKey = */ [&] () -> void { | |
| // Since colorization is handled here, disable paint color-colorization later. | |
| AddToKey(keyContext.withExtraFlags( | |
| KeyGenFlags::kDisableAlphaOnlyImageColorization), | |
| *simpleImage); | |
| }, | |
| /* addDstToKey = */ [&] () -> void { | |
| AddToKey(keyContext, fPaint.shader()); | |
| }); | |
| return false; // Colorizing with an alpha-only texture probably isn't opaque | |
| } else { | |
| // Encode the image structure directly, which includes handling alpha-only images that | |
| // combine with the paint's color (RGB1) stored on `keyContext`. | |
| AddToKey(keyContext, *simpleImage); | |
| return simpleImage->fImage->isOpaque(); | |
| } | |
| } else if (fPaint.shader()) { | |
| AddToKey(keyContext, fPaint.shader()); | |
| return fPaint.shader()->isOpaque(); | |
| } else { | |
| RGBPaintColorBlock::AddBlock(keyContext); | |
| return true; // rgb1, always opaque | |
| } | |
| } | |
| /** | |
| * Primitive blend blocks are used to blend either the paint color or the output of another shader | |
| * with a primitive color emitted by certain draw geometry calls (drawVertices, drawAtlas, etc.). | |
| * Dst: primitiveColor Src: Paint color/shader output | |
| */ | |
| bool ShadingParams::handlePrimitiveColor(const KeyContext& keyContext) const { | |
| // If no primitive blending is required, simply add the paint color. | |
| if (!fPaint.primitiveBlender()) { | |
| return this->addPaintColorToKey(keyContext); | |
| } | |
| // If no color space conversion is required and the primitive blend mode is kDst, the src | |
| // branch of the blend does not matter and we can simply emit the primitive color. | |
| std::optional<SkBlendMode> primBlend = as_BB(fPaint.primitiveBlender())->asBlendMode(); | |
| const bool canSkipBlendStep = fPaint.skipPrimitiveColorXform() && | |
| primBlend == SkBlendMode::kDst; | |
| std::optional<SkPMColor4f> primColorOverride; | |
| if (fPaint.primitiveColorOverride()) { | |
| primColorOverride = PaintParams::Color4fPrepForDst(*fPaint.primitiveColorOverride(), | |
| keyContext.dstColorInfo()).premul(); | |
| } | |
| if (canSkipBlendStep) { | |
| if (primColorOverride) { | |
| SolidColorShaderBlock::AddBlock(keyContext, *primColorOverride); | |
| } else { | |
| AddPrimitiveColor(keyContext, fPaint.skipPrimitiveColorXform(), | |
| fPaint.primitiveColorSpace(), fPaint.primitiveAlphaType()); | |
| } | |
| return false; | |
| } | |
| bool srcIsOpaque = false; | |
| Blend(keyContext, | |
| /* addBlendToKey= */ [&] () -> void { | |
| AddToKey(keyContext, fPaint.primitiveBlender()); | |
| }, | |
| /* addSrcToKey= */ [&] () -> void { | |
| srcIsOpaque = this->addPaintColorToKey(keyContext); | |
| }, | |
| /* addDstToKey= */ [&] () -> void { | |
| if (primColorOverride) { | |
| SolidColorShaderBlock::AddBlock(keyContext, *primColorOverride); | |
| } else { | |
| AddPrimitiveColor(keyContext, fPaint.skipPrimitiveColorXform(), | |
| fPaint.primitiveColorSpace(), fPaint.primitiveAlphaType()); | |
| } | |
| }); | |
| if (primBlend.has_value() && srcIsOpaque) { | |
| // If the input paint/shader is opaque, the result is only opaque if the primitive blend | |
| // mode is kSrc or kSrcOver. All other modes can introduce transparency. | |
| return *primBlend == SkBlendMode::kSrc || *primBlend == SkBlendMode::kSrcOver; | |
| } | |
| // If the input was already transparent, or if it's a runtime/complex blend mode, | |
| // the result cannot be considered opaque. | |
| return false; | |
| } | |
| // Apply the paint's alpha value. | |
| bool ShadingParams::handlePaintAlpha(const KeyContext& keyContext) const { | |
| if (!fPaint.shader() && !fPaint.imageShader() && !fPaint.primitiveBlender()) { | |
| // If there is no shader and no primitive blending the input to the colorFilter stage | |
| // is just the premultiplied paint color. | |
| SkPMColor4f paintColor = PaintParams::Color4fPrepForDst(fPaint.color(), | |
| keyContext.dstColorInfo()).premul(); | |
| SolidColorShaderBlock::AddBlock(keyContext, paintColor); | |
| return fPaint.color().isOpaque(); | |
| } | |
| if (!fPaint.color().isOpaque()) { | |
| Blend(keyContext, | |
| /* addBlendToKey= */ [&] () -> void { | |
| AddFixedBlendMode(keyContext, SkBlendMode::kSrcIn); | |
| }, | |
| /* addSrcToKey= */ [&]() -> void { | |
| this->handlePrimitiveColor(keyContext); | |
| }, | |
| /* addDstToKey= */ [&]() -> void { | |
| AlphaOnlyPaintColorBlock::AddBlock(keyContext); | |
| }); | |
| // The result is guaranteed to be non-opaque because we're blending with fColor's alpha. | |
| return false; | |
| } else { | |
| return this->handlePrimitiveColor(keyContext); | |
| } | |
| } | |
| bool ShadingParams::handleColorFilter(const KeyContext& keyContext) const { | |
| if (fPaint.colorFilter()) { | |
| bool srcIsOpaque = false; | |
| Compose(keyContext, | |
| /* addInnerToKey= */ [&]() -> void { | |
| srcIsOpaque = this->handlePaintAlpha(keyContext); | |
| }, | |
| /* addOuterToKey= */ [&]() -> void { | |
| AddToKey(keyContext, fPaint.colorFilter()); | |
| }); | |
| return srcIsOpaque && fPaint.colorFilter()->isAlphaUnchanged(); | |
| } else { | |
| return this->handlePaintAlpha(keyContext); | |
| } | |
| } | |
| bool ShadingParams::handleDithering(const KeyContext& keyContext) const { | |
| #ifndef SK_IGNORE_GPU_DITHER | |
| SkColorType ct = keyContext.dstColorInfo().colorType(); | |
| if (should_dither(fPaint, 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 ShadingParams::handleClipping(const KeyContext& keyContext) const { | |
| SkASSERT(!fNonMSAAClip.isEmpty() || fClipShader); | |
| if (!fNonMSAAClip.isEmpty()) { | |
| #if defined(SK_GRAPHITE_USE_LEGACY_RRECT_CLIP_SHADER) | |
| const AnalyticClip& analyticClip = fNonMSAAClip.fAnalyticClip; | |
| SkPoint radiusPair; | |
| SkRect analyticBounds; | |
| if (!analyticClip.isEmpty()) { | |
| float radius = analyticClip.fRadius + 0.5f; | |
| // N.B.: Because the clip data is normally used with depth-based clipping, | |
| // the shape is inverted from its usual state. We re-invert here to | |
| // match what the shader snippet expects. | |
| radiusPair = {(analyticClip.fInverted) ? radius : -radius, 1.0f/radius}; | |
| analyticBounds = analyticClip.fBounds.makeOutset(0.5f).asSkRect(); | |
| } else { | |
| // This will generate no analytic clip. | |
| radiusPair = { -0.5f, 1.f }; | |
| analyticBounds = { 0, 0, 0, 0 }; | |
| } | |
| const AtlasClip& atlasClip = fNonMSAAClip.fAtlasClip; | |
| SkISize maskSize = atlasClip.fMaskBounds.size(); | |
| SkRect texMaskBounds = SkRect::MakeXYWH(atlasClip.fOutPos.x(), atlasClip.fOutPos.y(), | |
| maskSize.width(), maskSize.height()); | |
| // Outset bounds to capture some of the padding (necessary for inverse clip) | |
| texMaskBounds.outset(0.5f, 0.5f); | |
| SkPoint texCoordOffset = SkPoint::Make(atlasClip.fOutPos.x() - atlasClip.fMaskBounds.left(), | |
| atlasClip.fOutPos.y() - atlasClip.fMaskBounds.top()); | |
| NonMSAAClipBlock::NonMSAAClipData data( | |
| analyticBounds, | |
| radiusPair, | |
| analyticClip.edgeSelectRect(), | |
| texCoordOffset, | |
| texMaskBounds, | |
| atlasClip.fAtlasTexture); | |
| if (fClipShader) { | |
| // 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 { | |
| AddToKey(keyContext, fClipShader); | |
| }); | |
| } else { | |
| // Without a clip shader, the analytic clip can be the clipping root node. | |
| NonMSAAClipBlock::AddBlock(keyContext, data); | |
| } | |
| #else | |
| if (fClipShader) { | |
| // 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, fNonMSAAClip); | |
| }, | |
| /* addDstToKey= */ [&]() -> void { | |
| AddToKey(keyContext, fClipShader); | |
| }); | |
| } else { | |
| // Without a clip shader, the analytic clip can be the clipping root node. | |
| AddAnalyticClip(keyContext, fNonMSAAClip); | |
| } | |
| #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); | |
| AddToKey(keyContext, fClipShader); | |
| } | |
| } | |
| std::optional<ShadingParams::Result> ShadingParams::toKey(const KeyContext& keyContext) const { | |
| SkDEBUGCODE(keyContext.pipelineDataGatherer()->checkReset()); | |
| SkDEBUGCODE(keyContext.paintParamsKeyBuilder()->checkReset()); | |
| SkDEBUGCODE(const Caps* caps = keyContext.caps();) | |
| SkDEBUGCODE(TextureFormat format = keyContext.drawContext()->target().proxy()->format();) | |
| SkDEBUGCODE(bool paintDependsOnDst = true;) | |
| // Root Node 0 is the source color, which is the output of all effects post dithering | |
| keyContext.paintParamsKeyBuilder()->addRootBlockHeader(RootBlockType::kSrcColor); | |
| bool isOpaque = this->handleDithering(keyContext); | |
| // Root Node 1 is the final blender | |
| keyContext.paintParamsKeyBuilder()->addRootBlockHeader(RootBlockType::kFinalBlend); | |
| SkEnumBitMask<DstUsage> dstUsage = fDstUsage; | |
| if (fPaint.finalBlender()) { | |
| AddToKey(keyContext, fPaint.finalBlender()); | |
| } else { | |
| // We converted kClear blends to kSrc; the PaintParams constructor already set every other | |
| // paint effect to match a transparent solid color. | |
| SkBlendMode finalBlendMode = fPaint.finalBlendMode(); | |
| SkASSERT(finalBlendMode != SkBlendMode::kClear); | |
| // If the KeyContext has opted into prioritizing Src (no blending) and we actually don't | |
| // need blending or only need blending due to the renderer (e.g. inner fill eligible), then | |
| // try to keep the final blend snippet as Src when it wouldn't impact the rendering. | |
| const bool optimizeSrcBlend = | |
| (dstUsage == DstUsage::kNone || | |
| SkToBool(dstUsage & DstUsage::kDstOnlyUsedByRenderer)) && | |
| SkToBool(keyContext.flags() & KeyGenFlags::kPreferFixedSrcBlend); | |
| // fDstUsage was almost fully specified, except for kSrcOver, which was assumed to be | |
| // opaque and eligible for conversion to kSrc. If we're src over and not opaque, or not | |
| // eligible for reducing to kSrc, we have to adjust flags. | |
| if (finalBlendMode == SkBlendMode::kSrcOver) { | |
| if (isOpaque) { | |
| if (dstUsage == DstUsage::kNone && optimizeSrcBlend) { | |
| // We can change the blend mode here without re-checking | |
| // CanUseHardwareBlending() because DstUsage::kNone implies there's no analytic | |
| // coverage and we're just changing from one Porter-Duff blend mode to another. | |
| SkASSERT(CanUseHardwareBlending(caps, format, SkBlendMode::kSrc, fCoverage)); | |
| finalBlendMode = SkBlendMode::kSrc; | |
| } else { | |
| // We don't have to remove kDstOnlyUsedByRenderer, but since we aren't | |
| // optimizing to Src, add the optimistically avoided kDependsOnDst | |
| dstUsage |= DstUsage::kDependsOnDst; | |
| } | |
| } else { | |
| // Definitely not eligible for conversion to kSrc, remove optimistically added flag | |
| dstUsage &= ~DstUsage::kDstOnlyUsedByRenderer; | |
| dstUsage |= DstUsage::kDependsOnDst; | |
| } | |
| } | |
| SkDEBUGCODE(paintDependsOnDst = finalBlendMode != SkBlendMode::kSrc;) | |
| // Reset isOpaque to false if we aren't src-over to ensure later assert logic is narrow. | |
| SkDEBUGCODE(isOpaque &= finalBlendMode == SkBlendMode::kSrcOver;) | |
| if (!(dstUsage & DstUsage::kDstReadRequired) || | |
| (finalBlendMode == SkBlendMode::kSrc && optimizeSrcBlend)) { | |
| // With no shader blending, be as explicit as possible about the final blend. We also | |
| // keep a fixed Src mode if it means a follow-up inner fill could be used. | |
| AddFixedBlendMode(keyContext, finalBlendMode); | |
| } else { | |
| // With shader blending, use AddBlendMode() to select the more universal blend functions | |
| // when possible. Technically we could always use a fixed blend mode but would then | |
| // over-generate when encountering certain classes of blends. This is most problematic | |
| // on devices that wouldn't support dual-source blending, so help them out by at least | |
| // not requiring lots of pipelines. | |
| AddBlendMode(keyContext, finalBlendMode); | |
| } | |
| } | |
| // Optional Root Node 2 is the clip | |
| if (fClipShader || !fNonMSAAClip.isEmpty()) { | |
| keyContext.paintParamsKeyBuilder()->addRootBlockHeader(RootBlockType::kClip); | |
| this->handleClipping(keyContext); | |
| } | |
| // Optional Root Node 3 is a mesh shader | |
| if (fPaint.meshSpec()) { | |
| keyContext.paintParamsKeyBuilder()->addRootBlockHeader(RootBlockType::kMeshShader); | |
| MeshShaderBlock::AddBlock(keyContext, fPaint.meshSpec(), fPaint.meshChildren()); | |
| } | |
| // If dstUsage is not kNone, then kDependsOnDst must be set (all other bits only apply *because* | |
| // the shading depends on dst). | |
| SkASSERT(dstUsage == DstUsage::kNone || (dstUsage & DstUsage::kDependsOnDst)); | |
| // If dstUsage is kNone, then there cannot be renderer coverage, analytic clipping, or the paint | |
| // depending on the dst color. | |
| SkASSERT(dstUsage != DstUsage::kNone || !(paintDependsOnDst || | |
| fClipShader || | |
| !fNonMSAAClip.isEmpty() || | |
| fCoverage != Coverage::kNone)); | |
| // If kDstOnlyUsedByRenderer is set, the paint shouldn't depend on the dst and the dst usage | |
| // when the Renderer has Coverage::kNone should equal kNone. | |
| SkDEBUGCODE(auto dstUsageNoCoverage = | |
| get_dst_usage(caps, format, fPaint, Coverage::kNone, fClipShader, fNonMSAAClip);) | |
| // This checks isOpaque in addition to !paintDependsOnDst to handle the case where src-over + | |
| // opaque wasn't converted to src for *this* pipeline but remains kDstOnlyUsedByRenderer for | |
| // a possible inner fill. | |
| SkASSERT(!(dstUsage & DstUsage::kDstOnlyUsedByRenderer) || | |
| ((isOpaque || !paintDependsOnDst) && dstUsageNoCoverage == DstUsage::kNone)); | |
| UniquePaintParamsID paintID = | |
| keyContext.recorder()->priv().shaderCodeDictionary()->findOrCreate( | |
| keyContext.paintParamsKeyBuilder()); | |
| if (!paintID.isValid()) { | |
| return {}; | |
| } else { | |
| return Result{paintID, dstUsage}; | |
| } | |
| } | |
| UniquePaintParamsID ShadingParams::optimizeForOpacity(const KeyContext& keyContext, | |
| UniquePaintParamsID origPaint) const { | |
| // The builder should be starting from exactly the same state toKey() ended in. | |
| PaintParamsKeyBuilder* builder = keyContext.paintParamsKeyBuilder(); | |
| SkASSERT(*builder == keyContext.recorder()->priv().shaderCodeDictionary()->lookup(origPaint)); | |
| // We only support optimizing opacity when our dst usage is none and the final blend mode can | |
| // be switched to kSrc. This requires that there is no analytic clipping that adds a 3rd render | |
| // node, or an SkBlender that may have child blocks. With these conditions, we can simply | |
| // replace the last block ID with kSrc. | |
| // | |
| // Assuming the originally generated key returned kNone or kDstOnlyUsedByRenderer, these | |
| // requirements should be met. If these assumptions are violated, it'll be detected in | |
| // debug-only builds that regenerate the opaque coverage-less PaintParams from scratch. | |
| BuiltInCodeSnippetID oldID = builder->replaceLastBlock(BuiltInCodeSnippetID::kFixedBlend_Src); | |
| // We should only be calling into optimizeForOpacity for src and src-over blends | |
| SkASSERT(oldID == BuiltInCodeSnippetID::kFixedBlend_Src || | |
| oldID == BuiltInCodeSnippetID::kFixedBlend_SrcOver); | |
| // And if we are already kSrc, the opaque paint ID is the original ID so skip lookup | |
| if (oldID == BuiltInCodeSnippetID::kFixedBlend_Src) { | |
| return origPaint; | |
| } | |
| // NOTE: If we choose to include paint-alpha multiplication in pipelines by default to avoid | |
| // 2x combinations just because the SkPaint changed from 1 to anything else, we could include | |
| // removing the paint alpha multiplication as part of this rewriting if we restructure paint | |
| // alpha handling to maintain an equivalent ShaderNode structure while just eliding the multiply | |
| UniquePaintParamsID opaqueID = | |
| keyContext.recorder()->priv().shaderCodeDictionary()->findOrCreate(builder); | |
| SkASSERT(opaqueID == this->validateOpacityOptimization(keyContext)); | |
| return opaqueID; | |
| } | |
| #if defined(SK_DEBUG) | |
| UniquePaintParamsID ShadingParams::validateOpacityOptimization(const KeyContext& keyContext) const { | |
| // Validate that the modified paint ID matches what we would have reached with a ShadingParams | |
| // and PaintParams adjusted to use kSrc blending and have no renderer coverage. These extracted | |
| // uniforms should match what was originally extracted as well. | |
| PaintParams opaquePaint = PaintParams::MakeOpaque(fPaint); | |
| NonMSAAClip emptyClip{}; | |
| ShadingParams opaqueShading{keyContext.recorder()->priv().caps(), | |
| opaquePaint, | |
| /*nonMSAAClip=*/emptyClip, | |
| /*clipShader=*/nullptr, | |
| Coverage::kNone, | |
| keyContext.targetFormat()}; | |
| // Create a new KeyContext that writes to a different key builder and pipeline data gatherer. | |
| // Since the opaqueKeyContext will inherit the drawContext (and therefore storageContext) from | |
| // the existing keyContext, verify that the cache hit by checking the size of the storageContext | |
| // cache before and after creation. | |
| SkASSERT(keyContext.drawContext()->storageContext()); | |
| SkDEBUGCODE(const int scSize = keyContext.drawContext()->storageContext()->size()); | |
| const Layout layout = keyContext.pipelineDataGatherer()->uniformManager()->layout(); | |
| PaintParamsKeyBuilder opaqueBuilder{keyContext.dict()}; | |
| PipelineDataGatherer opaqueGatherer{layout}; | |
| KeyContext opaqueContext{keyContext.recorder(), | |
| keyContext.drawContext(), | |
| &opaqueBuilder, | |
| &opaqueGatherer, | |
| keyContext.local2Dev(), | |
| keyContext.clipDrawBounds(), | |
| keyContext.dstColorInfo(), | |
| keyContext.flags(), | |
| opaqueShading.fPaint.color()}; | |
| auto result = opaqueShading.toKey(opaqueContext); | |
| SkASSERT(result.has_value()); | |
| auto [actualOpaqueID, actualDstUsage] = *result; | |
| SkASSERT(actualDstUsage == DstUsage::kNone); | |
| opaqueGatherer.checkEquivalent(keyContext.pipelineDataGatherer()); | |
| SkASSERT(keyContext.drawContext()->storageContext()->size() == scSize); | |
| return actualOpaqueID; | |
| } | |
| #endif | |
| } // namespace skgpu::graphite |