blob: af3f52510fce4393cf52a530c8153cc605d0c747 [file] [log] [blame] [edit]
/*
* 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/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/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());
}
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;
}
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};
}
}
Coverage get_renderer_coverage(Coverage coverage,
const SkShader* clipShader,
const NonMSAAClip& nonMSAAClip) {
return (clipShader || !nonMSAAClip.isEmpty()) && coverage == Coverage::kNone ?
Coverage::kSingleChannel : coverage;
}
SkEnumBitMask<DstUsage> get_dst_usage(const Caps* caps,
TextureFormat targetFormat,
const PaintParams& paint,
Coverage rendererCoverage) {
if (paint.finalBlender()) {
return DstUsage::kDstReadRequired;
}
SkBlendMode finalBlendMode = paint.finalBlendMode();
SkEnumBitMask<DstUsage> dstUsage =
CanUseHardwareBlending(caps, targetFormat, finalBlendMode, rendererCoverage)
? DstUsage::kNone
: DstUsage::kDstReadRequired;
if (finalBlendMode > SkBlendMode::kLastCoeffMode) {
dstUsage |= DstUsage::kAdvancedBlend;
}
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 (!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 SkColor4f& color, SkBlendMode finalBlendMode)
: fColor(color)
, fFinalBlend({nullptr, finalBlendMode})
, fShader(nullptr)
, fImageShader(nullptr)
, fColorFilter(nullptr)
, fPrimitiveBlender(nullptr)
, fSkipColorXform(false)
, fDither(false) {}
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;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
ShadingParams::ShadingParams(const Caps* caps,
const PaintParams& paint,
const NonMSAAClip& nonMSAAClip,
const SkShader* clipShader,
Coverage coverage,
TextureFormat targetFormat)
: fPaint(paint)
, fNonMSAAClip(nonMSAAClip)
, fClipShader(clipShader)
, fRendererCoverage(get_renderer_coverage(coverage, fClipShader, fNonMSAAClip))
, fTargetFormat(targetFormat)
, fDstUsage(get_dst_usage(caps, fTargetFormat, paint, fRendererCoverage)) {}
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;
if (canSkipBlendStep) {
AddPrimitiveColor(keyContext, fPaint.skipPrimitiveColorXform());
return false;
}
bool srcIsOpaque = false;
Blend(keyContext,
/* addBlendToKey= */ [&] () -> void {
AddToKey(keyContext, fPaint.primitiveBlender());
},
/* addSrcToKey= */ [&] () -> void {
srcIsOpaque = this->addPaintColorToKey(keyContext);
},
/* addDstToKey= */ [&] () -> void {
AddPrimitiveColor(keyContext, fPaint.skipPrimitiveColorXform());
});
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 {
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);
});
} 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);
}
}
std::optional<ShadingParams::Result> ShadingParams::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
bool dependsOnDst = fRendererCoverage != Coverage::kNone;
if (fPaint.finalBlender()) {
AddToKey(keyContext, fPaint.finalBlender());
// Cannot inspect runtime blenders to pessimistically assume they will always use the dst.
dependsOnDst = true;
} else {
if (!(fDstUsage & DstUsage::kDstReadRequired)) {
// With no shader blending, be as explicit as possible about the final blend
AddFixedBlendMode(keyContext, fPaint.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, fPaint.finalBlendMode());
}
// Blend modes can be analyzed to determine if specific src colors still depend on the dst.
dependsOnDst |= blendmode_depends_on_dst(fPaint.finalBlendMode(), isOpaque);
}
// Optional Root Node 2 is the clip
this->handleClipping(keyContext);
UniquePaintParamsID paintID =
keyContext.recorder()->priv().shaderCodeDictionary()->findOrCreate(
keyContext.paintParamsKeyBuilder());
if (!paintID.isValid()) {
return {};
} else {
return Result{paintID,
fDstUsage | (dependsOnDst ? DstUsage::kDependsOnDst : DstUsage::kNone)};
}
}
} // namespace skgpu::graphite