| /* | |
| * 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/effects/colorfilters/SkColorFilterBase.h" | |
| #include "src/gpu/Blend.h" | |
| #include "src/gpu/DitherUtils.h" | |
| #include "src/gpu/graphite/ContextUtils.h" | |
| #include "src/gpu/graphite/KeyContext.h" | |
| #include "src/gpu/graphite/KeyHelpers.h" | |
| #include "src/gpu/graphite/Log.h" | |
| #include "src/gpu/graphite/PaintParamsKey.h" | |
| #include "src/gpu/graphite/PipelineData.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| #include "src/gpu/graphite/Uniform.h" | |
| #include "src/shaders/SkShaderBase.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.shader() && !as_SB(p.shader())->isConstant(); | |
| } | |
| bool blendmode_depends_on_dst(SkBlendMode blendMode, bool srcIsOpaque) { | |
| if (blendMode == SkBlendMode::kSrc || blendMode == SkBlendMode::kClear) { | |
| // src and clear blending never depends on dst | |
| return false; | |
| } | |
| if (blendMode == SkBlendMode::kSrcOver || blendMode == SkBlendMode::kDstOut) { | |
| // src-over depends on dst if src is transparent (a != 1) | |
| // dst-out simplifies to kClear if a == 1 | |
| return !srcIsOpaque; | |
| } | |
| return true; | |
| } | |
| } // anonymous namespace | |
| PaintParams::PaintParams(const SkPaint& paint, | |
| sk_sp<SkBlender> primitiveBlender, | |
| const NonMSAAClip& nonMSAAClip, | |
| sk_sp<SkShader> clipShader, | |
| Coverage coverage, | |
| TextureFormat targetFormat, | |
| bool skipColorXform) | |
| : fColor(paint.getColor4f()) | |
| , fFinalBlender(paint.refBlender()) | |
| , fShader(paint.refShader()) | |
| , fColorFilter(paint.refColorFilter()) | |
| , fPrimitiveBlender(std::move(primitiveBlender)) | |
| , fNonMSAAClip(nonMSAAClip) | |
| , fClipShader(std::move(clipShader)) | |
| , fRendererCoverage(coverage) | |
| , fTargetFormat(targetFormat) | |
| , fSkipColorXform(skipColorXform) | |
| , fDither(paint.isDither()) { | |
| if (!fPrimitiveBlender) { | |
| SkColor4f constantColor; // if filled in, will be un-premul sRGB | |
| // fColor is un-premul sRGB | |
| if (fShader && as_SB(fShader)->isConstant(&constantColor)) { | |
| float origA = fColor.fA; | |
| fColor = constantColor; | |
| fColor.fA *= origA; | |
| fShader = nullptr; | |
| } | |
| if (!fShader && fColorFilter) { | |
| fColor = fColorFilter->filterColor4f(fColor, | |
| sk_srgb_singleton(), | |
| sk_srgb_singleton()); | |
| fColorFilter = nullptr; | |
| } | |
| } | |
| } | |
| PaintParams::PaintParams(const PaintParams& other) = default; | |
| PaintParams::~PaintParams() = default; | |
| PaintParams& PaintParams::operator=(const PaintParams& other) = default; | |
| std::optional<SkBlendMode> PaintParams::asFinalBlendMode() const { | |
| return fFinalBlender ? as_BB(fFinalBlender)->asBlendMode() | |
| : SkBlendMode::kSrcOver; | |
| } | |
| sk_sp<SkBlender> PaintParams::refFinalBlender() const { return fFinalBlender; } | |
| sk_sp<SkShader> PaintParams::refShader() const { return fShader; } | |
| sk_sp<SkColorFilter> PaintParams::refColorFilter() const { return fColorFilter; } | |
| sk_sp<SkBlender> PaintParams::refPrimitiveBlender() const { return fPrimitiveBlender; } | |
| 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; | |
| } | |
| void AddFixedBlendMode(const KeyContext& keyContext, SkBlendMode bm) { | |
| SkASSERT(bm <= SkBlendMode::kLastMode); | |
| BuiltInCodeSnippetID id = static_cast<BuiltInCodeSnippetID>(kFixedBlendIDOffset + | |
| static_cast<int>(bm)); | |
| keyContext.paintParamsKeyBuilder()->addBlock(id); | |
| } | |
| void AddBlendMode(const KeyContext& keyContext, SkBlendMode bm) { | |
| // For non-fixed blends, coefficient blend modes are combined into the same shader snippet. | |
| // The same goes for the HSLC advanced blends. The remaining advanced blends are fairly unique | |
| // in their implementations. To avoid having to compile all of their SkSL, they are treated as | |
| // fixed blend modes. | |
| SkSpan<const float> coeffs = skgpu::GetPorterDuffBlendConstants(bm); | |
| if (!coeffs.empty()) { | |
| PorterDuffBlenderBlock::AddBlock(keyContext, coeffs); | |
| } else if (bm >= SkBlendMode::kHue) { | |
| ReducedBlendModeInfo blendInfo = GetReducedBlendModeInfo(bm); | |
| HSLCBlenderBlock::AddBlock(keyContext, blendInfo.fUniformData); | |
| } else { | |
| AddFixedBlendMode(keyContext, bm); | |
| } | |
| } | |
| void AddDitherBlock(const KeyContext& keyContext, SkColorType ct) { | |
| static const SkBitmap gLUT = skgpu::MakeDitherLUT(); | |
| sk_sp<TextureProxy> proxy = RecorderPriv::CreateCachedProxy(keyContext.recorder(), gLUT, | |
| "DitherLUT"); | |
| if (keyContext.recorder() && !proxy) { | |
| SKGPU_LOG_W("Couldn't create dither shader's LUT"); | |
| keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput); | |
| return; | |
| } | |
| DitherShaderBlock::DitherData data(skgpu::DitherRangeForConfig(ct), std::move(proxy)); | |
| DitherShaderBlock::AddBlock(keyContext, data); | |
| } | |
| bool PaintParams::addPaintColorToKey(const KeyContext& keyContext) const { | |
| if (fShader) { | |
| AddToKey(keyContext, fShader.get()); | |
| return fShader->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 PaintParams::handlePrimitiveColor(const KeyContext& keyContext) const { | |
| /** | |
| * If no primitive blending is required, simply add the paint color. | |
| */ | |
| if (!fPrimitiveBlender) { | |
| 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. | |
| */ | |
| const bool canSkipBlendStep = | |
| fSkipColorXform && | |
| as_BB(fPrimitiveBlender.get())->asBlendMode().has_value() && | |
| as_BB(fPrimitiveBlender.get())->asBlendMode().value() == SkBlendMode::kDst; | |
| if (canSkipBlendStep) { | |
| AddPrimitiveColor(keyContext, fSkipColorXform); | |
| return false; | |
| } | |
| bool srcIsOpaque = false; | |
| Blend(keyContext, | |
| /* addBlendToKey= */ [&] () -> void { | |
| AddToKey(keyContext, fPrimitiveBlender.get()); | |
| }, | |
| /* addSrcToKey= */ [&] () -> void { | |
| srcIsOpaque = this->addPaintColorToKey(keyContext); | |
| }, | |
| /* addDstToKey= */ [&] () -> void { | |
| AddPrimitiveColor(keyContext, fSkipColorXform); | |
| }); | |
| std::optional<SkBlendMode> primBlend = as_BB(fPrimitiveBlender.get())->asBlendMode(); | |
| 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.value() == SkBlendMode::kSrc || primBlend.value() == 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 PaintParams::handlePaintAlpha(const KeyContext& keyContext) const { | |
| if (!fShader && !fPrimitiveBlender) { | |
| // 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(fColor, | |
| keyContext.dstColorInfo()).premul(); | |
| SolidColorShaderBlock::AddBlock(keyContext, paintColor); | |
| return fColor.isOpaque(); | |
| } | |
| if (!fColor.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 PaintParams::handleColorFilter(const KeyContext& keyContext) const { | |
| if (fColorFilter) { | |
| bool srcIsOpaque = false; | |
| Compose(keyContext, | |
| /* addInnerToKey= */ [&]() -> void { | |
| srcIsOpaque = this->handlePaintAlpha(keyContext); | |
| }, | |
| /* addOuterToKey= */ [&]() -> void { | |
| AddToKey(keyContext, fColorFilter.get()); | |
| }); | |
| return srcIsOpaque && fColorFilter->isAlphaUnchanged(); | |
| } else { | |
| return this->handlePaintAlpha(keyContext); | |
| } | |
| } | |
| bool PaintParams::handleDithering(const KeyContext& keyContext) const { | |
| #ifndef SK_IGNORE_GPU_DITHER | |
| SkColorType ct = keyContext.dstColorInfo().colorType(); | |
| if (should_dither(*this, 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 PaintParams::handleClipping(const KeyContext& keyContext) const { | |
| if (!fNonMSAAClip.isEmpty()) { | |
| 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.get()); | |
| }); | |
| } else { | |
| // Without a clip shader, the analytic clip can be the clipping root node. | |
| NonMSAAClipBlock::AddBlock(keyContext, data); | |
| } | |
| } else if (fClipShader) { | |
| // Since there's no analytic clip, the clipping root node can be fClipShader directly. | |
| AddToKey(keyContext, fClipShader.get()); | |
| } | |
| } | |
| std::optional<PaintParams::Result> PaintParams::toKey(const KeyContext& keyContext) const { | |
| // Root Node 0 is the source color, which is the output of all effects post dithering | |
| bool isOpaque = this->handleDithering(keyContext); | |
| // Root Node 1 is the final blender | |
| std::optional<SkBlendMode> finalBlendMode = this->asFinalBlendMode(); | |
| bool usesAdvancedBlend = finalBlendMode.has_value() && | |
| (int)finalBlendMode.value() > (int)SkBlendMode::kLastCoeffMode; | |
| Coverage finalCoverage = fRendererCoverage; | |
| if ((fClipShader || !fNonMSAAClip.isEmpty()) && fRendererCoverage == Coverage::kNone) { | |
| finalCoverage = Coverage::kSingleChannel; | |
| } | |
| bool dependsOnDst = fClipShader || !fNonMSAAClip.isEmpty(); | |
| bool dstReadReq = !CanUseHardwareBlending(keyContext.recorder()->priv().caps(), | |
| fTargetFormat, | |
| finalBlendMode, | |
| finalCoverage); | |
| if (finalBlendMode.has_value()) { | |
| if (!dstReadReq) { | |
| // With no shader blending, be as explicit as possible about the final blend | |
| AddFixedBlendMode(keyContext, finalBlendMode.value()); | |
| } 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.value()); | |
| } | |
| // Blend modes can be analyzed to determine if specific src colors still depend on the dst. | |
| dependsOnDst |= blendmode_depends_on_dst(finalBlendMode.value(), isOpaque); | |
| } else { | |
| AddToKey(keyContext, fFinalBlender.get()); | |
| // Cannot inspect runtime blenders to pessimistically assume they will always use the dst. | |
| dependsOnDst = true; | |
| } | |
| // Optional Root Node 2 is the clip | |
| this->handleClipping(keyContext); | |
| return Result{dependsOnDst, dstReadReq, usesAdvancedBlend}; | |
| } | |
| // TODO(b/330864257): Can be deleted once keys are determined by the Device draw. | |
| void PaintParams::notifyImagesInUse(Recorder* recorder, | |
| DrawContext* drawContext) const { | |
| if (fShader) { | |
| NotifyImagesInUse(recorder, drawContext, fShader.get()); | |
| } | |
| if (fPrimitiveBlender) { | |
| NotifyImagesInUse(recorder, drawContext, fPrimitiveBlender.get()); | |
| } | |
| if (fColorFilter) { | |
| NotifyImagesInUse(recorder, drawContext, fColorFilter.get()); | |
| } | |
| if (fFinalBlender) { | |
| NotifyImagesInUse(recorder, drawContext, fFinalBlender.get()); | |
| } | |
| if (fClipShader) { | |
| NotifyImagesInUse(recorder, drawContext, fClipShader.get()); | |
| } | |
| } | |
| } // namespace skgpu::graphite |