blob: b4a4e5ee604b6965456b008cb72503bc30dc60ce [file] [log] [blame]
/*
* 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/KeyHelpers.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkM44.h"
#include "include/core/SkScalar.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/gpu/graphite/Surface.h"
#include "src/base/SkHalf.h"
#include "src/core/SkBlendModeBlender.h"
#include "src/core/SkBlenderBase.h"
#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkDebugUtils.h"
#include "src/core/SkRuntimeBlender.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "src/core/SkYUVMath.h"
#include "src/effects/colorfilters/SkBlendModeColorFilter.h"
#include "src/effects/colorfilters/SkColorFilterBase.h"
#include "src/effects/colorfilters/SkColorSpaceXformColorFilter.h"
#include "src/effects/colorfilters/SkComposeColorFilter.h"
#include "src/effects/colorfilters/SkGaussianColorFilter.h"
#include "src/effects/colorfilters/SkMatrixColorFilter.h"
#include "src/effects/colorfilters/SkRuntimeColorFilter.h"
#include "src/effects/colorfilters/SkTableColorFilter.h"
#include "src/effects/colorfilters/SkWorkingFormatColorFilter.h"
#include "src/gpu/Blend.h"
#include "src/gpu/DitherUtils.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/DrawContext.h"
#include "src/gpu/graphite/Image_Base_Graphite.h"
#include "src/gpu/graphite/Image_Graphite.h"
#include "src/gpu/graphite/Image_YUVA_Graphite.h"
#include "src/gpu/graphite/KeyContext.h"
#include "src/gpu/graphite/KeyHelpers.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/PaintParams.h"
#include "src/gpu/graphite/PaintParamsKey.h"
#include "src/gpu/graphite/PipelineData.h"
#include "src/gpu/graphite/ReadSwizzle.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/ResourceProvider.h"
#include "src/gpu/graphite/RuntimeEffectDictionary.h"
#include "src/gpu/graphite/ShaderCodeDictionary.h"
#include "src/gpu/graphite/Surface_Graphite.h"
#include "src/gpu/graphite/Texture.h"
#include "src/gpu/graphite/TextureProxy.h"
#include "src/gpu/graphite/TextureProxyView.h"
#include "src/gpu/graphite/TextureUtils.h"
#include "src/gpu/graphite/Uniform.h"
#include "src/gpu/graphite/UniformManager.h"
#include "src/image/SkImage_Base.h"
#include "src/shaders/SkBlendShader.h"
#include "src/shaders/SkColorFilterShader.h"
#include "src/shaders/SkColorShader.h"
#include "src/shaders/SkCoordClampShader.h"
#include "src/shaders/SkEmptyShader.h"
#include "src/shaders/SkImageShader.h"
#include "src/shaders/SkLocalMatrixShader.h"
#include "src/shaders/SkPerlinNoiseShaderImpl.h"
#include "src/shaders/SkPerlinNoiseShaderType.h"
#include "src/shaders/SkPictureShader.h"
#include "src/shaders/SkRuntimeShader.h"
#include "src/shaders/SkShaderBase.h"
#include "src/shaders/SkTransformShader.h"
#include "src/shaders/SkTriColorShader.h"
#include "src/shaders/SkWorkingColorSpaceShader.h"
#include "src/shaders/gradients/SkConicalGradient.h"
#include "src/shaders/gradients/SkLinearGradient.h"
#include "src/shaders/gradients/SkRadialGradient.h"
#include "src/shaders/gradients/SkSweepGradient.h"
using namespace skia_private;
namespace skgpu::graphite {
//--------------------------------------------------------------------------------------------------
namespace {
// Automatically calls beginStruct() with the required alignment and endStruct() when it is deleted.
// Automatically registers uniform expectations in debug builds.
class ScopedUniformWriter {
public:
ScopedUniformWriter(const KeyContext& keyContext, BuiltInCodeSnippetID codeSnippetID)
: ScopedUniformWriter(keyContext.pipelineDataGatherer(),
keyContext.dict()->getEntry(codeSnippetID)) {}
~ScopedUniformWriter() {
if (fGatherer) {
fGatherer->endStruct();
}
}
private:
ScopedUniformWriter(PipelineDataGatherer* gatherer, const ShaderSnippet* snippet)
#if defined(SK_DEBUG)
: fValidator(gatherer, snippet->fUniforms, SkToBool(snippet->fUniformStructName))
#endif
{
if (snippet->fUniformStructName) {
gatherer->beginStruct(snippet->fRequiredAlignment);
fGatherer = gatherer;
} else {
fGatherer = nullptr;
}
}
PipelineDataGatherer* fGatherer;
SkDEBUGCODE(UniformExpectationsValidator fValidator;)
};
#define BEGIN_WRITE_UNIFORMS(keyContext, codeSnippetID) \
ScopedUniformWriter scope{keyContext, codeSnippetID};
void add_solid_uniform_data(const KeyContext& keyContext, const SkPMColor4f& premulColor) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kSolidColorShader)
keyContext.pipelineDataGatherer()->write(premulColor);
}
} // anonymous namespace
void SolidColorShaderBlock::AddBlock(const KeyContext& keyContext, const SkPMColor4f& premulColor) {
add_solid_uniform_data(keyContext, premulColor);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kSolidColorShader);
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_rgb_paint_color_uniform_data(const KeyContext& keyContext) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kRGBPaintColor)
keyContext.pipelineDataGatherer()->writePaintColor(keyContext.paintColor());
}
void add_alpha_only_paint_color_uniform_data(const KeyContext& keyContext) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kAlphaOnlyPaintColor)
keyContext.pipelineDataGatherer()->writePaintColor(keyContext.paintColor());
}
} // anonymous namespace
void RGBPaintColorBlock::AddBlock(const KeyContext& keyContext) {
add_rgb_paint_color_uniform_data(keyContext);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kRGBPaintColor);
}
void AlphaOnlyPaintColorBlock::AddBlock(const KeyContext& keyContext) {
add_alpha_only_paint_color_uniform_data(keyContext);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kAlphaOnlyPaintColor);
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_gradient_preamble(const GradientShaderBlocks::GradientData& gradData,
PipelineDataGatherer* gatherer) {
constexpr int kInternalStopLimit = GradientShaderBlocks::GradientData::kNumInternalStorageStops;
if (gradData.fNumStops <= kInternalStopLimit) {
if (gradData.fNumStops <= 4) {
// Round up to 4 stops.
gatherer->writeArray(SkSpan{gradData.fColors, 4});
gatherer->write(gradData.fOffsets[0]);
} else if (gradData.fNumStops <= 8) {
// Round up to 8 stops.
gatherer->writeArray(SkSpan{gradData.fColors, 8});
gatherer->writeArray(SkSpan{gradData.fOffsets, 2});
} else {
// Did kNumInternalStorageStops change?
SkUNREACHABLE;
}
}
}
// All the gradients share a common postamble of:
// numStops - for texture-based gradients
// tilemode
// colorSpace
// doUnPremul
void add_gradient_postamble(const GradientShaderBlocks::GradientData& gradData,
int bufferOffset,
PipelineDataGatherer* gatherer) {
using ColorSpace = SkGradientShader::Interpolation::ColorSpace;
constexpr int kInternalStopLimit = GradientShaderBlocks::GradientData::kNumInternalStorageStops;
static_assert(static_cast<int>(ColorSpace::kLab) == 2);
static_assert(static_cast<int>(ColorSpace::kOKLab) == 3);
static_assert(static_cast<int>(ColorSpace::kOKLabGamutMap) == 4);
static_assert(static_cast<int>(ColorSpace::kLCH) == 5);
static_assert(static_cast<int>(ColorSpace::kOKLCH) == 6);
static_assert(static_cast<int>(ColorSpace::kOKLCHGamutMap) == 7);
static_assert(static_cast<int>(ColorSpace::kHSL) == 9);
static_assert(static_cast<int>(ColorSpace::kHWB) == 10);
bool inputPremul = static_cast<bool>(gradData.fInterpolation.fInPremul);
if (gradData.fNumStops > kInternalStopLimit) {
gatherer->write(gradData.fNumStops);
if (gradData.fUseStorageBuffer) {
gatherer->write(bufferOffset);
}
}
gatherer->write(static_cast<int>(gradData.fTM));
gatherer->write(static_cast<int>(gradData.fInterpolation.fColorSpace));
gatherer->write(static_cast<int>(inputPremul));
}
void add_linear_gradient_uniform_data(const KeyContext& keyContext,
BuiltInCodeSnippetID codeSnippetID,
const GradientShaderBlocks::GradientData& gradData,
int bufferOffset) {
BEGIN_WRITE_UNIFORMS(keyContext, codeSnippetID)
add_gradient_preamble(gradData, keyContext.pipelineDataGatherer());
add_gradient_postamble(gradData, bufferOffset, keyContext.pipelineDataGatherer());
};
void add_radial_gradient_uniform_data(const KeyContext& keyContext,
BuiltInCodeSnippetID codeSnippetID,
const GradientShaderBlocks::GradientData& gradData,
int bufferOffset) {
BEGIN_WRITE_UNIFORMS(keyContext, codeSnippetID)
add_gradient_preamble(gradData, keyContext.pipelineDataGatherer());
add_gradient_postamble(gradData, bufferOffset, keyContext.pipelineDataGatherer());
};
void add_sweep_gradient_uniform_data(const KeyContext& keyContext,
BuiltInCodeSnippetID codeSnippetID,
const GradientShaderBlocks::GradientData& gradData,
int bufferOffset) {
BEGIN_WRITE_UNIFORMS(keyContext, codeSnippetID)
add_gradient_preamble(gradData, keyContext.pipelineDataGatherer());
keyContext.pipelineDataGatherer()->write(gradData.fBias);
keyContext.pipelineDataGatherer()->write(gradData.fScale);
add_gradient_postamble(gradData, bufferOffset, keyContext.pipelineDataGatherer());
};
void add_conical_gradient_uniform_data(const KeyContext& keyContext,
BuiltInCodeSnippetID codeSnippetID,
const GradientShaderBlocks::GradientData& gradData,
int bufferOffset) {
BEGIN_WRITE_UNIFORMS(keyContext, codeSnippetID)
float dRadius = gradData.fRadii[1] - gradData.fRadii[0];
bool isRadial = SkPoint::Distance(gradData.fPoints[1], gradData.fPoints[0])
< SK_ScalarNearlyZero;
// When a == 0, encode invA == 1 for radial case, and invA == 0 for linear edge case.
float a = 0;
float invA = 1;
if (!isRadial) {
a = 1 - dRadius * dRadius;
if (std::abs(a) > SK_ScalarNearlyZero) {
invA = 1.0 / (2.0 * a);
} else {
a = 0;
invA = 0;
}
} else {
// Since radius0 is being scaled by 1 / dRadius, and the original radius
// is always positive, this gives us the original sign of dRadius.
dRadius = gradData.fRadii[0] > 0 ? 1 : -1;
}
add_gradient_preamble(gradData, keyContext.pipelineDataGatherer());
keyContext.pipelineDataGatherer()->write(gradData.fRadii[0]);
keyContext.pipelineDataGatherer()->write(dRadius);
keyContext.pipelineDataGatherer()->write(a);
keyContext.pipelineDataGatherer()->write(invA);
add_gradient_postamble(gradData, bufferOffset, keyContext.pipelineDataGatherer());
};
} // anonymous namespace
// Writes the color and offset data directly in the gatherer gradient buffer and returns the
// offset the data begins at in the buffer.
static int write_color_and_offset_bufdata(int numStops,
const SkPMColor4f* colors,
const float* offsets,
const SkGradientBaseShader* shader,
FloatStorageManager* floatStorageManager) {
auto [dstData, bufferOffset] = floatStorageManager->allocateGradientData(numStops, shader);
if (dstData) {
// Data doesn't already exist so we need to write it.
// Writes all offset data, then color data. This way when binary searching through the
// offsets, there is better cache locality.
for (int i = 0, colorIdx = numStops; i < numStops; i++, colorIdx+=4) {
float offset = offsets ? offsets[i] : SkIntToFloat(i) / (numStops - 1);
SkASSERT(offset >= 0.0f && offset <= 1.0f);
dstData[i] = offset;
dstData[colorIdx + 0] = colors[i].fR;
dstData[colorIdx + 1] = colors[i].fG;
dstData[colorIdx + 2] = colors[i].fB;
dstData[colorIdx + 3] = colors[i].fA;
}
}
return bufferOffset;
}
GradientShaderBlocks::GradientData::GradientData(SkShaderBase::GradientType type,
int numStops,
bool useStorageBuffer)
: fType(type)
, fPoints{{0.0f, 0.0f}, {0.0f, 0.0f}}
, fRadii{0.0f, 0.0f}
, fBias(0.0f)
, fScale(0.0f)
, fTM(SkTileMode::kClamp)
, fNumStops(numStops)
, fUseStorageBuffer(useStorageBuffer)
, fSrcColors(nullptr)
, fSrcOffsets(nullptr) {
sk_bzero(fColors, sizeof(fColors));
sk_bzero(fOffsets, sizeof(fOffsets));
}
GradientShaderBlocks::GradientData::GradientData(SkShaderBase::GradientType type,
SkPoint point0, SkPoint point1,
float radius0, float radius1,
float bias, float scale,
SkTileMode tm,
int numStops,
const SkPMColor4f* colors,
const float* offsets,
const SkGradientBaseShader* shader,
sk_sp<TextureProxy> colorsAndOffsetsProxy,
bool useStorageBuffer,
const SkGradientShader::Interpolation& interp)
: fType(type)
, fBias(bias)
, fScale(scale)
, fTM(tm)
, fNumStops(numStops)
, fUseStorageBuffer(useStorageBuffer)
, fSrcColors(colors)
, fSrcOffsets(offsets)
, fSrcShader(shader)
, fInterpolation(interp) {
SkASSERT(fNumStops >= 1);
fPoints[0] = point0;
fPoints[1] = point1;
fRadii[0] = radius0;
fRadii[1] = radius1;
if (fNumStops <= kNumInternalStorageStops) {
memcpy(fColors, colors, fNumStops * sizeof(SkColor4f));
float* rawOffsets = fOffsets[0].ptr();
if (offsets) {
memcpy(rawOffsets, offsets, fNumStops * sizeof(float));
} else {
for (int i = 0; i < fNumStops; ++i) {
rawOffsets[i] = SkIntToFloat(i) / (fNumStops-1);
}
}
// Extend the colors and offset, if necessary, to fill out the arrays.
// The unrolled binary search implementation assumes excess stops match the last real value.
for (int i = fNumStops; i < kNumInternalStorageStops; ++i) {
fColors[i] = fColors[fNumStops-1];
rawOffsets[i] = rawOffsets[fNumStops-1];
}
} else {
if (!fUseStorageBuffer) {
fColorsAndOffsetsProxy = std::move(colorsAndOffsetsProxy);
SkASSERT(fColorsAndOffsetsProxy);
}
}
}
void GradientShaderBlocks::AddBlock(const KeyContext& keyContext, const GradientData& gradData) {
int bufferOffset = 0;
if (gradData.fNumStops > GradientData::kNumInternalStorageStops && keyContext.recorder()) {
if (gradData.fUseStorageBuffer) {
bufferOffset = write_color_and_offset_bufdata(gradData.fNumStops,
gradData.fSrcColors,
gradData.fSrcOffsets,
gradData.fSrcShader,
keyContext.floatStorageManager());
} else {
SkASSERT(gradData.fColorsAndOffsetsProxy);
keyContext.pipelineDataGatherer()->add(gradData.fColorsAndOffsetsProxy,
{SkFilterMode::kNearest, SkTileMode::kClamp});
}
}
BuiltInCodeSnippetID codeSnippetID = BuiltInCodeSnippetID::kSolidColorShader;
switch (gradData.fType) {
case SkShaderBase::GradientType::kLinear:
codeSnippetID =
gradData.fNumStops <= 4 ? BuiltInCodeSnippetID::kLinearGradientShader4
: gradData.fNumStops <= 8 ? BuiltInCodeSnippetID::kLinearGradientShader8
: gradData.fUseStorageBuffer
? BuiltInCodeSnippetID::kLinearGradientShaderBuffer
: BuiltInCodeSnippetID::kLinearGradientShaderTexture;
add_linear_gradient_uniform_data(keyContext, codeSnippetID, gradData, bufferOffset);
break;
case SkShaderBase::GradientType::kRadial:
codeSnippetID =
gradData.fNumStops <= 4 ? BuiltInCodeSnippetID::kRadialGradientShader4
: gradData.fNumStops <= 8 ? BuiltInCodeSnippetID::kRadialGradientShader8
: gradData.fUseStorageBuffer
? BuiltInCodeSnippetID::kRadialGradientShaderBuffer
: BuiltInCodeSnippetID::kRadialGradientShaderTexture;
add_radial_gradient_uniform_data(keyContext, codeSnippetID, gradData, bufferOffset);
break;
case SkShaderBase::GradientType::kSweep:
codeSnippetID =
gradData.fNumStops <= 4 ? BuiltInCodeSnippetID::kSweepGradientShader4
: gradData.fNumStops <= 8 ? BuiltInCodeSnippetID::kSweepGradientShader8
: gradData.fUseStorageBuffer
? BuiltInCodeSnippetID::kSweepGradientShaderBuffer
: BuiltInCodeSnippetID::kSweepGradientShaderTexture;
add_sweep_gradient_uniform_data(keyContext, codeSnippetID, gradData, bufferOffset);
break;
case SkShaderBase::GradientType::kConical:
codeSnippetID =
gradData.fNumStops <= 4 ? BuiltInCodeSnippetID::kConicalGradientShader4
: gradData.fNumStops <= 8 ? BuiltInCodeSnippetID::kConicalGradientShader8
: gradData.fUseStorageBuffer
? BuiltInCodeSnippetID::kConicalGradientShaderBuffer
: BuiltInCodeSnippetID::kConicalGradientShaderTexture;
add_conical_gradient_uniform_data(keyContext, codeSnippetID, gradData, bufferOffset);
break;
case SkShaderBase::GradientType::kNone:
default:
SkDEBUGFAIL("Expected a gradient shader, but it wasn't one.");
break;
}
keyContext.paintParamsKeyBuilder()->addBlock(codeSnippetID);
}
//--------------------------------------------------------------------------------------------------
void LocalMatrixShaderBlock::BeginBlock(const KeyContext& keyContext,
const LMShaderData& lmShaderData) {
const SkMatrix& m = lmShaderData.fLocalMatrix;
if (lmShaderData.fLocalMatrix.hasPerspective()) {
// Perspective local matrices are rare enough and add enough extra instructions that it's
// worth specializing since it has to perform a per-pixel division.
keyContext.paintParamsKeyBuilder()->beginBlock(
BuiltInCodeSnippetID::kLocalMatrixShaderPersp);
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kLocalMatrixShaderPersp)
keyContext.pipelineDataGatherer()->write(m);
} else {
// For an affine 2D transform, we only need to upload the upper 2x2 and XY translation.
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kLocalMatrixShader);
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kLocalMatrixShader)
// The upper 2x2 is expected to be in column major order, but SkMatrix is 3x3 row major.
keyContext.pipelineDataGatherer()->write(SkV4{m.getScaleX(), m.getSkewY(),
m.getSkewX(), m.getScaleY()});
keyContext.pipelineDataGatherer()->write(SkV2{m.getTranslateX(), m.getTranslateY()});
}
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_image_uniform_data(const KeyContext& keyContext,
const ImageShaderBlock::ImageData& imgData) {
SkASSERT(!imgData.fSampling.useCubic);
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kImageShader)
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSize.width(),
1.f/imgData.fImgSize.height()));
keyContext.pipelineDataGatherer()->write(imgData.fSubset);
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.first));
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.second));
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fSampling.filter));
}
void add_clamp_image_uniform_data(const KeyContext& keyContext,
const ImageShaderBlock::ImageData& imgData) {
SkASSERT(!imgData.fSampling.useCubic);
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kImageShaderClamp)
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSize.width(),
1.f/imgData.fImgSize.height()));
// Matches GrTextureEffect::kLinearInset, to make sure we don't touch an outer row or column
// with a weight of 0 when linear filtering.
const float kLinearInset = 0.5f + 0.00001f;
// The subset should clamp texel coordinates to an inset subset to prevent sampling neighboring
// texels when coords fall exactly at texel boundaries.
SkRect subsetInsetClamp = imgData.fSubset;
if (imgData.fSampling.filter == SkFilterMode::kNearest) {
subsetInsetClamp.roundOut(&subsetInsetClamp);
}
subsetInsetClamp.inset(kLinearInset, kLinearInset);
keyContext.pipelineDataGatherer()->write(subsetInsetClamp);
}
void add_cubic_image_uniform_data(const KeyContext& keyContext,
const ImageShaderBlock::ImageData& imgData) {
SkASSERT(imgData.fSampling.useCubic);
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kCubicImageShader)
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSize.width(),
1.f/imgData.fImgSize.height()));
keyContext.pipelineDataGatherer()->write(imgData.fSubset);
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.first));
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.second));
const SkCubicResampler& cubic = imgData.fSampling.cubic;
keyContext.pipelineDataGatherer()->writeHalf(
SkImageShader::CubicResamplerMatrix(cubic.B, cubic.C));
}
// If clampToBorderSupport is unavailable, kDecal will be substituted to clamp in most cases.
bool should_substitute_decal(const std::pair<SkTileMode, SkTileMode>& tileMode, const Caps* caps) {
return !caps->clampToBorderSupport() && (tileMode.first == SkTileMode::kDecal ||
tileMode.second == SkTileMode::kDecal);
}
bool can_do_tiling_in_hw(const ImageShaderBlock::ImageData& imgData, const Caps* caps) {
return !should_substitute_decal(imgData.fTileModes, caps) &&
imgData.fSubset.contains(SkRect::Make(imgData.fImgSize));
}
void add_sampler_data_to_key(const KeyContext& keyContext, const SamplerDesc& samplerDesc) {
if (samplerDesc.isImmutable()) {
keyContext.paintParamsKeyBuilder()->addData(samplerDesc.asSpan());
} else {
// Means we have a regular dynamic sampler. Append a default SamplerDesc to convey this,
// allowing the key to maintain and convey sampler binding order.
keyContext.paintParamsKeyBuilder()->addData({});
}
}
} // anonymous namespace
ImageShaderBlock::ImageData::ImageData(const SkSamplingOptions& sampling,
SkTileMode tileModeX,
SkTileMode tileModeY,
SkISize imgSize,
SkRect subset,
ImmutableSamplerInfo immutableSamplerInfo)
: fSampling(sampling)
, fTileModes{tileModeX, tileModeY}
, fImgSize(imgSize)
, fSubset(subset)
, fImmutableSamplerInfo(immutableSamplerInfo) {
}
void ImageShaderBlock::AddBlock(const KeyContext& keyContext, const ImageData& imgData) {
if (keyContext.recorder() && !imgData.fTextureProxy) {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
const Caps* caps = keyContext.caps();
const bool doTilingInHw = !imgData.fSampling.useCubic && can_do_tiling_in_hw(imgData, caps);
if (doTilingInHw) {
CoordNormalizeShaderBlock::CoordNormalizeData data(SkSize::Make(imgData.fImgSize));
CoordNormalizeShaderBlock::BeginBlock(keyContext, data);
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kHWImageShader);
} else if (imgData.fSampling.useCubic) {
add_cubic_image_uniform_data(keyContext, imgData);
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kCubicImageShader);
} else if (imgData.fTileModes.first == SkTileMode::kClamp &&
imgData.fTileModes.second == SkTileMode::kClamp) {
add_clamp_image_uniform_data(keyContext, imgData);
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kImageShaderClamp);
} else {
add_image_uniform_data(keyContext, imgData);
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kImageShader);
}
// Image shaders must append immutable sampler data (or '0' in the more common case where
// regular samplers are used).
// TODO(b/392623124): In precompile mode (fTextureProxy == null), we still have a need for
// immutable samplers, which must be passed in somehow.
ImmutableSamplerInfo info = imgData.fTextureProxy
? caps->getImmutableSamplerInfo(imgData.fTextureProxy->textureInfo())
: imgData.fImmutableSamplerInfo;
auto tileModeWithSubstitution = doTilingInHw ? imgData.fTileModes :
std::make_pair(SkTileMode::kClamp, SkTileMode::kClamp);
SamplerDesc samplerDesc{imgData.fSampling, tileModeWithSubstitution, info};
keyContext.pipelineDataGatherer()->add(imgData.fTextureProxy, samplerDesc);
add_sampler_data_to_key(keyContext, samplerDesc);
keyContext.paintParamsKeyBuilder()->endBlock();
if (doTilingInHw) {
// Additional block for coord normalization.
keyContext.paintParamsKeyBuilder()->endBlock();
}
}
//--------------------------------------------------------------------------------------------------
// makes use of ImageShader functions, above
namespace {
void add_yuv_image_uniform_data(const KeyContext& keyContext,
const YUVImageShaderBlock::ImageData& imgData) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kYUVImageShader)
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSize.width(),
1.f/imgData.fImgSize.height()));
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(),
1.f/imgData.fImgSizeUV.height()));
keyContext.pipelineDataGatherer()->write(imgData.fSubset);
keyContext.pipelineDataGatherer()->write(imgData.fLinearFilterUVInset);
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.first));
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.second));
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fSampling.filter));
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fSamplingUV.filter));
for (int i = 0; i < 4; ++i) {
keyContext.pipelineDataGatherer()->writeHalf(imgData.fChannelSelect[i]);
}
keyContext.pipelineDataGatherer()->writeHalf(imgData.fYUVtoRGBMatrix);
keyContext.pipelineDataGatherer()->writeHalf(imgData.fYUVtoRGBTranslate);
}
void add_cubic_yuv_image_uniform_data(const KeyContext& keyContext,
const YUVImageShaderBlock::ImageData& imgData) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kCubicYUVImageShader)
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSize.width(),
1.f/imgData.fImgSize.height()));
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(),
1.f/imgData.fImgSizeUV.height()));
keyContext.pipelineDataGatherer()->write(imgData.fSubset);
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.first));
keyContext.pipelineDataGatherer()->write(SkTo<int>(imgData.fTileModes.second));
const SkCubicResampler& cubic = imgData.fSampling.cubic;
keyContext.pipelineDataGatherer()->writeHalf(SkImageShader::CubicResamplerMatrix(cubic.B, cubic.C));
for (int i = 0; i < 4; ++i) {
keyContext.pipelineDataGatherer()->writeHalf(imgData.fChannelSelect[i]);
}
keyContext.pipelineDataGatherer()->writeHalf(imgData.fYUVtoRGBMatrix);
keyContext.pipelineDataGatherer()->writeHalf(imgData.fYUVtoRGBTranslate);
}
void add_hw_yuv_image_uniform_data(const KeyContext& keyContext,
const YUVImageShaderBlock::ImageData& imgData) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kHWYUVImageShader)
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSize.width(),
1.f/imgData.fImgSize.height()));
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(),
1.f/imgData.fImgSizeUV.height()));
keyContext.pipelineDataGatherer()->write(imgData.fSubset);
SkPoint linearFilterUVInset = imgData.fLinearFilterUVInset;
// We sign-encode whether we need to adjust the UV coords by applying `fLinearFilterUVInset` for
// nearest neighbor filtering in `linearFilterUVInset.fX`.
if (imgData.fSampling.filter == SkFilterMode::kNearest) {
linearFilterUVInset.fX = -linearFilterUVInset.fX;
}
// We sign-encode whether we need clamping for subset or mismatched Y/UV plane size draws in
// `linearFilterUVInset.fY` - only clamp tiling modes are supported though.
if (!imgData.fSubset.contains(SkRect::Make(imgData.fImgSize)) ||
imgData.fImgSize != imgData.fImgSizeUV) {
SkASSERT(imgData.fTileModes.first == SkTileMode::kClamp &&
imgData.fTileModes.second == SkTileMode::kClamp);
linearFilterUVInset.fY = -linearFilterUVInset.fY;
}
keyContext.pipelineDataGatherer()->write(linearFilterUVInset);
for (int i = 0; i < 4; ++i) {
keyContext.pipelineDataGatherer()->writeHalf(imgData.fChannelSelect[i]);
}
keyContext.pipelineDataGatherer()->writeHalf(imgData.fYUVtoRGBMatrix);
keyContext.pipelineDataGatherer()->writeHalf(imgData.fYUVtoRGBTranslate);
}
void add_hw_yuv_no_swizzle_image_uniform_data(const KeyContext& keyContext,
const YUVImageShaderBlock::ImageData& imgData) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kHWYUVNoSwizzleImageShader)
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSize.width(),
1.f/imgData.fImgSize.height()));
keyContext.pipelineDataGatherer()->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(),
1.f/imgData.fImgSizeUV.height()));
keyContext.pipelineDataGatherer()->write(imgData.fSubset);
SkPoint linearFilterUVInset = imgData.fLinearFilterUVInset;
// We sign-encode whether we need to adjust the UV coords by applying `fLinearFilterUVInset` for
// nearest neighbor filtering in `linearFilterUVInset.fX`.
if (imgData.fSampling.filter == SkFilterMode::kNearest) {
linearFilterUVInset.fX = -linearFilterUVInset.fX;
}
// We sign-encode whether we need clamping for subset or mismatched Y/UV plane size draws in
// `linearFilterUVInset.fY` - only clamp tiling modes are supported though.
if (!imgData.fSubset.contains(SkRect::Make(imgData.fImgSize)) ||
imgData.fImgSize != imgData.fImgSizeUV) {
SkASSERT(imgData.fTileModes.first == SkTileMode::kClamp &&
imgData.fTileModes.second == SkTileMode::kClamp);
linearFilterUVInset.fY = -linearFilterUVInset.fY;
}
keyContext.pipelineDataGatherer()->write(linearFilterUVInset);
keyContext.pipelineDataGatherer()->writeHalf(imgData.fYUVtoRGBMatrix);
SkV4 yuvToRGBXlateAlphaParam = {
imgData.fYUVtoRGBTranslate.fX,
imgData.fYUVtoRGBTranslate.fY,
imgData.fYUVtoRGBTranslate.fZ,
imgData.fAlphaParam
};
keyContext.pipelineDataGatherer()->writeHalf(yuvToRGBXlateAlphaParam);
}
} // anonymous namespace
YUVImageShaderBlock::ImageData::ImageData(const SkSamplingOptions& sampling,
SkTileMode tileModeX,
SkTileMode tileModeY,
SkISize imgSize,
SkRect subset)
: fSampling(sampling)
, fSamplingUV(sampling)
, fTileModes{tileModeX, tileModeY}
, fImgSize(imgSize)
, fImgSizeUV(imgSize)
, fSubset(subset) {
}
static bool can_do_yuv_tiling_in_hw(const YUVImageShaderBlock::ImageData& imgData,
const Caps* caps) {
if (should_substitute_decal(imgData.fTileModes, caps)) {
return false;
}
// Use the HW tiling shader variant if we're drawing the full rect with matched Y and UV plane
// sizes and any tiling mode, or if we're drawing a subset with clamp tiling mode.
return (imgData.fSubset.contains(SkRect::Make(imgData.fImgSize)) &&
imgData.fImgSize == imgData.fImgSizeUV) ||
(imgData.fTileModes.first == SkTileMode::kClamp &&
imgData.fTileModes.second == SkTileMode::kClamp);
}
static bool no_yuv_swizzle(const YUVImageShaderBlock::ImageData& imgData) {
// Y_U_V or U_Y_V format, reading from R channel for each texture
if (imgData.fChannelSelect[0].x == 1 &&
imgData.fChannelSelect[1].x == 1 &&
imgData.fChannelSelect[2].x == 1 &&
imgData.fChannelSelect[3].x == 1) {
return true;
}
return false;
}
void YUVImageShaderBlock::AddBlock(const KeyContext& keyContext, const ImageData& imgData) {
if (keyContext.recorder() &&
(!imgData.fTextureProxies[0] || !imgData.fTextureProxies[1] ||
!imgData.fTextureProxies[2] || !imgData.fTextureProxies[3])) {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
const Caps* caps = keyContext.caps();
const bool doTilingInHw = !imgData.fSampling.useCubic && can_do_yuv_tiling_in_hw(imgData, caps);
const bool noYUVSwizzle = no_yuv_swizzle(imgData);
// uvs are never SkTileMode::kDecal
auto uvTileModes = std::make_pair(imgData.fTileModes.first == SkTileMode::kDecal
? SkTileMode::kClamp : imgData.fTileModes.first,
imgData.fTileModes.second == SkTileMode::kDecal
? SkTileMode::kClamp : imgData.fTileModes.second);
auto yAlphaTileModes = doTilingInHw ? imgData.fTileModes :
std::make_pair(SkTileMode::kClamp, SkTileMode::kClamp);
keyContext.pipelineDataGatherer()->add(imgData.fTextureProxies[0],
{imgData.fSampling, yAlphaTileModes});
keyContext.pipelineDataGatherer()->add(imgData.fTextureProxies[1],
{imgData.fSamplingUV, uvTileModes});
keyContext.pipelineDataGatherer()->add(imgData.fTextureProxies[2],
{imgData.fSamplingUV, uvTileModes});
keyContext.pipelineDataGatherer()->add(imgData.fTextureProxies[3],
{imgData.fSampling, yAlphaTileModes});
if (doTilingInHw && noYUVSwizzle) {
add_hw_yuv_no_swizzle_image_uniform_data(keyContext, imgData);
keyContext.paintParamsKeyBuilder()->addBlock(
BuiltInCodeSnippetID::kHWYUVNoSwizzleImageShader);
} else if (doTilingInHw) {
add_hw_yuv_image_uniform_data(keyContext, imgData);
keyContext.paintParamsKeyBuilder()->addBlock(
BuiltInCodeSnippetID::kHWYUVImageShader);
} else if (imgData.fSampling.useCubic) {
add_cubic_yuv_image_uniform_data(keyContext, imgData);
keyContext.paintParamsKeyBuilder()->addBlock(
BuiltInCodeSnippetID::kCubicYUVImageShader);
} else {
add_yuv_image_uniform_data(keyContext, imgData);
keyContext.paintParamsKeyBuilder()->addBlock(
BuiltInCodeSnippetID::kYUVImageShader);
}
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_coord_normalize_uniform_data(const KeyContext& keyContext,
const CoordNormalizeShaderBlock::CoordNormalizeData& data) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kCoordNormalizeShader)
keyContext.pipelineDataGatherer()->write(data.fInvDimensions);
}
} // anonymous namespace
void CoordNormalizeShaderBlock::BeginBlock(const KeyContext& keyContext,
const CoordNormalizeData& data) {
add_coord_normalize_uniform_data(keyContext, data);
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kCoordNormalizeShader);
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_coordclamp_uniform_data(const KeyContext& keyContext,
const CoordClampShaderBlock::CoordClampData& clampData) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kCoordClampShader)
keyContext.pipelineDataGatherer()->write(clampData.fSubset);
}
} // anonymous namespace
void CoordClampShaderBlock::BeginBlock(const KeyContext& keyContext,
const CoordClampData& clampData) {
add_coordclamp_uniform_data(keyContext, clampData);
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kCoordClampShader);
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_dither_uniform_data(const KeyContext& keyContext,
const DitherShaderBlock::DitherData& ditherData) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kDitherShader)
keyContext.pipelineDataGatherer()->writeHalf(ditherData.fRange);
}
} // anonymous namespace
void DitherShaderBlock::AddBlock(const KeyContext& keyContext, const DitherData& data) {
auto gatherer = keyContext.pipelineDataGatherer();
add_dither_uniform_data(keyContext, data);
SkASSERT(data.fLUTProxy || !keyContext.recorder());
gatherer->add(data.fLUTProxy, {SkFilterMode::kNearest, SkTileMode::kRepeat});
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kDitherShader);
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_perlin_noise_uniform_data(const KeyContext& keyContext,
const PerlinNoiseShaderBlock::PerlinNoiseData& noiseData) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kPerlinNoiseShader)
auto gatherer = keyContext.pipelineDataGatherer();
gatherer->write(noiseData.fBaseFrequency);
gatherer->write(noiseData.fStitchData);
gatherer->write(static_cast<int>(noiseData.fType));
gatherer->write(noiseData.fNumOctaves);
gatherer->write(static_cast<int>(noiseData.stitching()));
static const std::pair<SkTileMode, SkTileMode> kRepeatXTileModes =
{ SkTileMode::kRepeat, SkTileMode::kClamp };
gatherer->add(noiseData.fPermutationsProxy, {SkFilterMode::kNearest, kRepeatXTileModes});
gatherer->add(noiseData.fNoiseProxy, {SkFilterMode::kNearest, kRepeatXTileModes});
}
} // anonymous namespace
void PerlinNoiseShaderBlock::AddBlock(const KeyContext& keyContext,
const PerlinNoiseData& noiseData) {
add_perlin_noise_uniform_data(keyContext, noiseData);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPerlinNoiseShader);
}
//--------------------------------------------------------------------------------------------------
void BlendComposeBlock::BeginBlock(const KeyContext& keyContext) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kBlendCompose)
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kBlendCompose);
}
//--------------------------------------------------------------------------------------------------
void PorterDuffBlenderBlock::AddBlock(const KeyContext& keyContext, SkSpan<const float> coeffs) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kPorterDuffBlender)
SkASSERT(coeffs.size() == 4);
keyContext.pipelineDataGatherer()->writeHalf(SkV4{coeffs[0], coeffs[1], coeffs[2], coeffs[3]});
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPorterDuffBlender);
}
//--------------------------------------------------------------------------------------------------
void HSLCBlenderBlock::AddBlock(const KeyContext& keyContext, SkSpan<const float> coeffs) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kHSLCBlender)
SkASSERT(coeffs.size() == 2);
keyContext.pipelineDataGatherer()->writeHalf(SkV2{coeffs[0], coeffs[1]});
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kHSLCBlender);
}
//--------------------------------------------------------------------------------------------------
void ComposeBlock::BeginBlock(const KeyContext& keyContext) {
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kCompose);
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_matrix_colorfilter_uniform_data(const KeyContext& keyContext,
const MatrixColorFilterBlock::MatrixColorFilterData& data) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kMatrixColorFilter)
auto gatherer = keyContext.pipelineDataGatherer();
gatherer->writeHalf(data.fMatrix);
gatherer->writeHalf(data.fTranslate);
if (data.fClamp) {
gatherer->writeHalf(SkV2{0.f, 1.f});
} else {
// Alpha is always clamped to 1. RGB clamp to the max finite half value.
static constexpr float kUnclamped = 65504.f; // SK_HalfMax converted back to float
SkASSERT(SkHalfToFloat(SkFloatToHalf(kUnclamped)) == kUnclamped);
SkASSERT(SkHalfToFloat(SkFloatToHalf(-kUnclamped)) == -kUnclamped);
gatherer->writeHalf(SkV2{-kUnclamped, kUnclamped});
}
}
void add_hsl_matrix_colorfilter_uniform_data(
const KeyContext& keyContext,
const MatrixColorFilterBlock::MatrixColorFilterData& data) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kHSLMatrixColorFilter)
auto gatherer = keyContext.pipelineDataGatherer();
gatherer->writeHalf(data.fMatrix);
gatherer->writeHalf(data.fTranslate);
}
} // anonymous namespace
void MatrixColorFilterBlock::AddBlock(const KeyContext& keyContext,
const MatrixColorFilterData& matrixCFData) {
if (matrixCFData.fInHSLA) {
add_hsl_matrix_colorfilter_uniform_data(keyContext, matrixCFData);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kHSLMatrixColorFilter);
} else {
add_matrix_colorfilter_uniform_data(keyContext, matrixCFData);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kMatrixColorFilter);
}
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_table_colorfilter_uniform_data(const KeyContext& keyContext,
const TableColorFilterBlock::TableColorFilterData& data) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kTableColorFilter)
keyContext.pipelineDataGatherer()->add(data.fTextureProxy, {SkFilterMode::kNearest, SkTileMode::kClamp});
}
} // anonymous namespace
void TableColorFilterBlock::AddBlock(const KeyContext& keyContext,
const TableColorFilterData& data) {
SkASSERT(data.fTextureProxy || !keyContext.recorder());
add_table_colorfilter_uniform_data(keyContext, data);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kTableColorFilter);
}
//--------------------------------------------------------------------------------------------------
namespace {
// There are three variations of color space transforms with increasing complexity, but they are
// built on the same techniques for managing variations w/o adding branches in the shader.
void add_color_space_uniforms(const KeyContext& keyContext,
BuiltInCodeSnippetID id,
const SkColorSpaceXformSteps& steps,
ReadSwizzle readSwizzle) {
SkASSERT(id == BuiltInCodeSnippetID::kColorSpaceXformPremul || // premul/unpremul/opaque
id == BuiltInCodeSnippetID::kColorSpaceXformSRGB || // + sRGB [d]encode/gamut
id == BuiltInCodeSnippetID::kColorSpaceXformColorFilter); // + everything else
BEGIN_WRITE_UNIFORMS(keyContext, id)
// To encode whether to do premul/unpremul or make the output opaque, we use
// srcDEF_args.w and dstDEF_args.w:
// - identity: {0, 1}
// - do unpremul: {-1, 1}
// - do premul: {0, 0}
// - do both: {-1, 0}
// - alpha swizzle 1: {1, 1}
// - alpha swizzle r: {1, 0}
const bool alphaSwizzleR = readSwizzle == ReadSwizzle::k000R;
const bool alphaSwizzle1 = readSwizzle == ReadSwizzle::kRGB1 ||
readSwizzle == ReadSwizzle::kRRR1;
// It doesn't make sense to unpremul/premul in opaque cases, but we might get a request to
// anyways, which we can just ignore.
const bool unpremul = alphaSwizzle1 ? false : steps.fFlags.unpremul;
const bool premul = alphaSwizzle1 ? false : steps.fFlags.premul;
const float srcW = unpremul ? -1.f :
(alphaSwizzleR || alphaSwizzle1) ? 1.f :
0.f;
const float dstW = (premul || alphaSwizzleR) ? 0.f : 1.f;
if (id == BuiltInCodeSnippetID::kColorSpaceXformPremul) {
// If either of these asserts would fail, we can't correctly use this specialized shader for
// the given transform.
SkASSERT(readSwizzle == ReadSwizzle::kRGBA || readSwizzle == ReadSwizzle::kRGB1);
// If these are both true, that implies there's a color space transfer or gamut transform.
SkASSERT(!(steps.fFlags.unpremul && steps.fFlags.premul));
// And given these assertions, the 6 cases encoded in srcW and dstW are reduced to:
// identity, do unpremul, do premul, and make opaque (alpha swizzle 1)
keyContext.pipelineDataGatherer()->writeHalf(SkV2{srcW, dstW});
return;
}
// srcW and dstW will be used later with the other transfer function values, but for the
// more complex shaders, we put the gamut matrix first for alignment.
SkMatrix gamutTransform;
const float identity[] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
// TODO: it seems odd to copy this into an SkMatrix just to write it to the gatherer
// fSrcToDstMatrix is column-major, SkMatrix is row-major.
const float* m = steps.fFlags.gamut_transform ? steps.fSrcToDstMatrix : identity;
if (readSwizzle == ReadSwizzle::kRRR1) {
gamutTransform.setAll(m[0] + m[3] + m[6], 0, 0,
m[1] + m[4] + m[7], 0, 0,
m[2] + m[5] + m[8], 0, 0);
} else if (readSwizzle == ReadSwizzle::kBGRA) {
gamutTransform.setAll(m[6], m[3], m[0],
m[7], m[4], m[1],
m[8], m[5], m[2]);
} else if (readSwizzle == ReadSwizzle::k000R) {
gamutTransform.setAll(0, 0, 0,
0, 0, 0,
0, 0, 0);
} else if (steps.fFlags.gamut_transform) {
gamutTransform.setAll(m[0], m[3], m[6],
m[1], m[4], m[7],
m[2], m[5], m[8]);
}
keyContext.pipelineDataGatherer()->writeHalf(gamutTransform);
// To encode which transfer function to apply, we use the src and dst gamma values:
// - identity: 0
// - sRGB: g > 0
// - PQ: -2
// - HLG: -1
// For the sRGB shader, we allow linear sRGB but that shader has no branches on TF type, so
// we have to replace the values with an actual identity sRGB-ish function.
const bool treatLinearAsSRGB = id == BuiltInCodeSnippetID::kColorSpaceXformSRGB;
if (steps.fFlags.linearize) {
const skcms_TFType type = skcms_TransferFunction_getType(&steps.fSrcTF);
const float srcG = type == skcms_TFType_sRGBish ? steps.fSrcTF.g :
type == skcms_TFType_PQish ? -2.f :
type == skcms_TFType_HLGish ? -1.f :
0.f;
keyContext.pipelineDataGatherer()->write(SkV4{srcG, steps.fSrcTF.a,
steps.fSrcTF.b, steps.fSrcTF.c});
keyContext.pipelineDataGatherer()->write(SkV4{steps.fSrcTF.d, steps.fSrcTF.e,
steps.fSrcTF.f, srcW});
} else if (treatLinearAsSRGB) {
// Branchless identity function with g=1 (sRGB-ish)
static constexpr skcms_TransferFunction kI = SkNamedTransferFn::kLinear;
keyContext.pipelineDataGatherer()->write(SkV4{kI.g, kI.a, kI.b, kI.c});
keyContext.pipelineDataGatherer()->write(SkV4{kI.d, kI.e, kI.f, srcW});
} else {
// Branched identity that actually skips all operations
keyContext.pipelineDataGatherer()->write(SkV4{0.f, 0.f, 0.f, 0.f});
keyContext.pipelineDataGatherer()->write(SkV4{0.f, 0.f, 0.f, srcW});
}
if (steps.fFlags.encode) {
const skcms_TFType type = skcms_TransferFunction_getType(&steps.fDstTFInv);
const float dstG = type == skcms_TFType_sRGBish ? steps.fDstTFInv.g :
type == skcms_TFType_PQish ? -2.f :
type == skcms_TFType_HLGinvish ? -1.f :
0.f;
keyContext.pipelineDataGatherer()->write(SkV4{dstG, steps.fDstTFInv.a,
steps.fDstTFInv.b, steps.fDstTFInv.c});
keyContext.pipelineDataGatherer()->write(SkV4{steps.fDstTFInv.d, steps.fDstTFInv.e,
steps.fDstTFInv.f, dstW});
} else if (treatLinearAsSRGB) {
static constexpr skcms_TransferFunction kI = SkNamedTransferFn::kLinear;
keyContext.pipelineDataGatherer()->write(SkV4{kI.g, kI.a, kI.b, kI.c});
keyContext.pipelineDataGatherer()->write(SkV4{kI.d, kI.e, kI.f, dstW});
} else {
keyContext.pipelineDataGatherer()->write(SkV4{0.f, 0.f, 0.f, 0.f});
keyContext.pipelineDataGatherer()->write(SkV4{0.f, 0.f, 0.f, dstW});
}
const bool hasOOTFUniforms = id == BuiltInCodeSnippetID::kColorSpaceXformColorFilter;
if (hasOOTFUniforms) {
SkV4 src_ootf = {0.f, 0.f, 0.f, 0.f};
SkV4 dst_ootf = {0.f, 0.f, 0.f, 0.f};
if (steps.fFlags.src_ootf) {
if (readSwizzle == ReadSwizzle::kBGRA) {
src_ootf = SkV4{
steps.fSrcOotf[2], steps.fSrcOotf[1], steps.fSrcOotf[0], steps.fSrcOotf[3]};
} else {
src_ootf = SkV4{
steps.fSrcOotf[0], steps.fSrcOotf[1], steps.fSrcOotf[2], steps.fSrcOotf[3]};
}
}
if (steps.fFlags.dst_ootf) {
if (readSwizzle == ReadSwizzle::kBGRA) {
dst_ootf = SkV4{
steps.fDstOotf[2], steps.fDstOotf[1], steps.fDstOotf[0], steps.fDstOotf[3]};
} else {
dst_ootf = SkV4{
steps.fDstOotf[0], steps.fDstOotf[1], steps.fDstOotf[2], steps.fDstOotf[3]};
}
}
keyContext.pipelineDataGatherer()->write(src_ootf);
keyContext.pipelineDataGatherer()->write(dst_ootf);
} else {
SkASSERT(!steps.fFlags.src_ootf);
SkASSERT(!steps.fFlags.dst_ootf);
}
}
} // anonymous namespace
ColorSpaceTransformBlock::ColorSpaceTransformData::ColorSpaceTransformData(const SkColorSpace* src,
SkAlphaType srcAT,
const SkColorSpace* dst,
SkAlphaType dstAT)
: fSteps(src, srcAT, dst, dstAT) {}
void ColorSpaceTransformBlock::AddBlock(const KeyContext& keyContext,
const ColorSpaceTransformData& data) {
const bool xformNeedsGamutOrXferFn = data.fSteps.fFlags.linearize ||
data.fSteps.fFlags.encode ||
data.fSteps.fFlags.src_ootf ||
data.fSteps.fFlags.dst_ootf ||
data.fSteps.fFlags.gamut_transform;
const bool swizzleNeedsGamutTransform = !(data.fReadSwizzle == ReadSwizzle::kRGBA ||
data.fReadSwizzle == ReadSwizzle::kRGB1);
// Use a specialized shader if we don't need transfer function or gamut transforms.
if (!(xformNeedsGamutOrXferFn || swizzleNeedsGamutTransform)) {
// When enabled, the most specialized is to do nothing at all. To simplify calling code,
// this adds a passthrough block vs. having callers know how to reconfigure their blocks.
if (SkToBool(keyContext.flags() & KeyGenFlags::kEnableIdentityColorSpaceXform) &&
data.fReadSwizzle == ReadSwizzle::kRGBA &&
!data.fSteps.fFlags.premul && !data.fSteps.fFlags.unpremul) {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
return;
}
add_color_space_uniforms(keyContext, BuiltInCodeSnippetID::kColorSpaceXformPremul,
data.fSteps, data.fReadSwizzle);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kColorSpaceXformPremul);
return;
}
// Use a specialized shader if we're transferring to and from sRGB-ish color spaces.
// We take this path even if linearize/encode are false since we can set coefficients
// in the sRGB transfer functions to represent identity, and that is better than using the
// most general colorspace option.
if ((!data.fSteps.fFlags.linearize || skcms_TransferFunction_isSRGBish(&data.fSteps.fSrcTF)) &&
(!data.fSteps.fFlags.encode || skcms_TransferFunction_isSRGBish(&data.fSteps.fDstTFInv))) {
add_color_space_uniforms(keyContext, BuiltInCodeSnippetID::kColorSpaceXformSRGB,
data.fSteps, data.fReadSwizzle);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kColorSpaceXformSRGB);
return;
}
// Use the most general color space transform shader if no specializations can be used.
add_color_space_uniforms(keyContext, BuiltInCodeSnippetID::kColorSpaceXformColorFilter,
data.fSteps, data.fReadSwizzle);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kColorSpaceXformColorFilter);
}
//--------------------------------------------------------------------------------------------------
namespace {
void add_analytic_clip_data(const KeyContext& keyContext,
const NonMSAAClipBlock::NonMSAAClipData& data) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kAnalyticClip)
keyContext.pipelineDataGatherer()->write(data.fRect);
keyContext.pipelineDataGatherer()->write(data.fRadiusPlusHalf);
keyContext.pipelineDataGatherer()->writeHalf(data.fEdgeSelect);
}
void add_analytic_and_atlas_clip_data(const KeyContext& keyContext,
const NonMSAAClipBlock::NonMSAAClipData& data) {
BEGIN_WRITE_UNIFORMS(keyContext, BuiltInCodeSnippetID::kAnalyticAndAtlasClip)
keyContext.pipelineDataGatherer()->write(data.fRect);
keyContext.pipelineDataGatherer()->write(data.fRadiusPlusHalf);
keyContext.pipelineDataGatherer()->writeHalf(data.fEdgeSelect);
keyContext.pipelineDataGatherer()->write(data.fTexCoordOffset);
keyContext.pipelineDataGatherer()->write(data.fMaskBounds);
if (data.fAtlasTexture) {
keyContext.pipelineDataGatherer()->write(
SkSize::Make(1.f/data.fAtlasTexture->dimensions().width(),
1.f/data.fAtlasTexture->dimensions().height()));
} else {
keyContext.pipelineDataGatherer()->write(SkSize::Make(0, 0));
}
}
} // anonymous namespace
void NonMSAAClipBlock::AddBlock(const KeyContext& keyContext, const NonMSAAClipData& data) {
if (data.fAtlasTexture) {
add_analytic_and_atlas_clip_data(keyContext, data);
keyContext.paintParamsKeyBuilder()->beginBlock(BuiltInCodeSnippetID::kAnalyticAndAtlasClip);
const Caps* caps = keyContext.caps();
ImmutableSamplerInfo info =
caps->getImmutableSamplerInfo(data.fAtlasTexture->textureInfo());
SamplerDesc samplerDesc {SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone),
{SkTileMode::kClamp, SkTileMode::kClamp},
info};
keyContext.pipelineDataGatherer()->add(data.fAtlasTexture, samplerDesc);
keyContext.paintParamsKeyBuilder()->endBlock();
} else {
add_analytic_clip_data(keyContext, data);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kAnalyticClip);
}
}
//--------------------------------------------------------------------------------------------------
void AddPrimitiveColor(const KeyContext& keyContext, bool skipColorXform) {
/**
* When skipColorXform is true, we assume the primitive color is already in the dst color space.
*/
if (skipColorXform) {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPrimitiveColor);
return;
}
/**
* If skipColorXform is false (most cases), the primitive color is assumed to be in sRGB.
*/
ColorSpaceTransformBlock::ColorSpaceTransformData toDst(sk_srgb_singleton(),
kPremul_SkAlphaType,
keyContext.dstColorInfo().colorSpace(),
keyContext.dstColorInfo().alphaType());
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPrimitiveColor);
},
/* addOuterToKey= */ [&]() -> void {
ColorSpaceTransformBlock::AddBlock(keyContext, toDst);
});
}
//--------------------------------------------------------------------------------------------------
void AddBlendModeColorFilter(const KeyContext& keyContext, SkBlendMode bm,
const SkPMColor4f& srcColor) {
Blend(keyContext,
/* addBlendToKey= */ [&] () -> void {
AddBlendMode(keyContext, bm);
},
/* addSrcToKey= */ [&]() -> void {
SolidColorShaderBlock::AddBlock(keyContext, srcColor);
},
/* addDstToKey= */ [&]() -> void {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
});
}
RuntimeEffectBlock::ShaderData::ShaderData(sk_sp<const SkRuntimeEffect> effect)
: fEffect(std::move(effect)) {}
RuntimeEffectBlock::ShaderData::ShaderData(sk_sp<const SkRuntimeEffect> effect,
sk_sp<const SkData> uniforms)
: fEffect(std::move(effect))
, fUniforms(std::move(uniforms)) {}
static bool skdata_matches(const SkData* a, const SkData* b) {
// Returns true if both SkData objects hold the same contents, or if they are both null.
// (SkData::equals supports passing null, and returns false.)
return a ? a->equals(b) : (a == b);
}
bool RuntimeEffectBlock::ShaderData::operator==(const ShaderData& rhs) const {
return fEffect == rhs.fEffect && skdata_matches(fUniforms.get(), rhs.fUniforms.get());
}
static void gather_runtime_effect_uniforms(const KeyContext& keyContext,
const SkRuntimeEffect* effect,
SkSpan<const Uniform> graphiteUniforms,
const SkData* uniformData,
PipelineDataGatherer* gatherer) {
if (!uniformData) {
return; // precompiling
}
SkDEBUGCODE(UniformExpectationsValidator uev(gatherer, graphiteUniforms);)
SkSpan<const SkRuntimeEffect::Uniform> rtsUniforms = effect->uniforms();
if (!rtsUniforms.empty() && uniformData) {
// Collect all the other uniforms from the provided SkData.
const uint8_t* uniformBase = uniformData->bytes();
for (size_t index = 0; index < rtsUniforms.size(); ++index) {
const Uniform& uniform = graphiteUniforms[index];
// Get a pointer to the offset in our data for this uniform.
const uint8_t* uniformPtr = uniformBase + rtsUniforms[index].offset;
// Pass the uniform data to the gatherer.
gatherer->write(uniform, uniformPtr);
}
}
}
// TODO(robertphillips): when BeginBlock fails we should mark the 'builder' as having failed
// and explicitly handle the failure in ShaderCodeDictionary::findOrCreate.
bool RuntimeEffectBlock::BeginBlock(const KeyContext& keyContext, const ShaderData& shaderData) {
ShaderCodeDictionary* dict = keyContext.dict();
int codeSnippetID = dict->findOrCreateRuntimeEffectSnippet(shaderData.fEffect.get());
if (codeSnippetID < 0) {
return false;
}
if (SkKnownRuntimeEffects::IsUserDefinedRuntimeEffect(codeSnippetID)) {
keyContext.rtEffectDict()->set(codeSnippetID, shaderData.fEffect);
}
const ShaderSnippet* entry = dict->getEntry(codeSnippetID);
if (!entry) {
return false;
}
gather_runtime_effect_uniforms(keyContext,
shaderData.fEffect.get(),
entry->fUniforms,
shaderData.fUniforms.get(),
keyContext.pipelineDataGatherer());
keyContext.paintParamsKeyBuilder()->beginBlock(codeSnippetID);
return true;
}
// TODO(robertphillips): fuse this with the similar code in add_children_to_key and
// PrecompileRTEffect::addToKey.
void RuntimeEffectBlock::AddNoOpEffect(const KeyContext& keyContext, SkRuntimeEffect* effect) {
if (effect->allowShader()) {
// A missing shader returns transparent black
SolidColorShaderBlock::AddBlock(keyContext, SK_PMColor4fTRANSPARENT);
} else if (effect->allowColorFilter()) {
// A "passthrough" color filter returns the input color as-is.
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
} else {
SkASSERT(effect->allowBlender());
// A "passthrough" blender performs `blend_src_over(src, dest)`.
AddFixedBlendMode(keyContext, SkBlendMode::kSrcOver);
}
}
void RuntimeEffectBlock::HandleIntrinsics(const KeyContext& keyContext, const SkRuntimeEffect* effect) {
// Runtime effects that reference color transform intrinsics have two extra children that
// are bound to the colorspace xform snippet with values to go to and from the linear srgb
// to the current working/dst color space.
if (SkRuntimeEffectPriv::UsesColorTransform(effect)) {
SkColorSpace* dstCS = keyContext.dstColorInfo().colorSpace();
if (!dstCS) {
dstCS = sk_srgb_linear_singleton(); // turn colorspace conversion into a noop
}
// TODO(b/332565302): If the runtime shader only uses one of these transforms, we could
// upload only one set of uniforms.
// NOTE: This must be kept in sync with the logic used to generate the toLinearSrgb() and
// fromLinearSrgb() expressions for each runtime effect. toLinearSrgb() is assumed to be
// the second to last child, and fromLinearSrgb() is assumed to be the last.
ColorSpaceTransformBlock::ColorSpaceTransformData dstToLinear(dstCS,
kUnpremul_SkAlphaType,
sk_srgb_linear_singleton(),
kUnpremul_SkAlphaType);
ColorSpaceTransformBlock::ColorSpaceTransformData linearToDst(sk_srgb_linear_singleton(),
kUnpremul_SkAlphaType,
dstCS,
kUnpremul_SkAlphaType);
ColorSpaceTransformBlock::AddBlock(keyContext, dstToLinear);
ColorSpaceTransformBlock::AddBlock(keyContext, linearToDst);
}
}
// ==================================================================
namespace {
void add_to_key(const KeyContext& keyContext, const SkBlendModeBlender* blender) {
SkASSERT(blender);
AddBlendMode(keyContext, blender->mode());
}
// Be sure to keep this function in sync w/ the code in PrecompileRTEffect::addToKey
void add_children_to_key(const KeyContext& keyContext,
SkSpan<const SkRuntimeEffect::ChildPtr> children,
const SkRuntimeEffect* effect) {
SkSpan<const SkRuntimeEffect::Child> childInfo = effect->children();
SkASSERT(children.size() == childInfo.size());
using ChildType = SkRuntimeEffect::ChildType;
for (size_t index = 0; index < children.size(); ++index) {
const SkRuntimeEffect::ChildPtr& child = children[index];
KeyContextForRuntimeEffect childContext(keyContext, effect, index);
std::optional<ChildType> type = child.type();
if (type == ChildType::kShader) {
AddToKey(childContext, child.shader());
} else if (type == ChildType::kColorFilter) {
AddToKey(childContext, child.colorFilter());
} else if (type == ChildType::kBlender) {
AddToKey(childContext, child.blender());
} else {
// We don't have a child effect. Substitute in a no-op effect.
switch (childInfo[index].type) {
case ChildType::kShader:
// A missing shader returns transparent black
SolidColorShaderBlock::AddBlock(childContext,
SK_PMColor4fTRANSPARENT);
break;
case ChildType::kColorFilter:
// A "passthrough" color filter returns the input color as-is.
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
break;
case ChildType::kBlender:
// A "passthrough" blender performs `blend_src_over(src, dest)`.
AddFixedBlendMode(childContext, SkBlendMode::kSrcOver);
break;
}
}
}
RuntimeEffectBlock::HandleIntrinsics(keyContext, effect);
}
void add_to_key(const KeyContext& keyContext, const SkRuntimeBlender* blender) {
SkASSERT(blender);
sk_sp<SkRuntimeEffect> effect = blender->effect();
SkASSERT(effect);
sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
effect->uniforms(),
blender->uniforms(),
keyContext.dstColorInfo().colorSpace());
SkASSERT(uniforms);
if (!RuntimeEffectBlock::BeginBlock(keyContext, { effect, std::move(uniforms) })) {
RuntimeEffectBlock::AddNoOpEffect(keyContext, effect.get());
return;
}
add_children_to_key(keyContext, blender->children(), effect.get());
keyContext.paintParamsKeyBuilder()->endBlock();
}
} // anonymous namespace
void AddToKey(const KeyContext& keyContext, const SkBlender* blender) {
if (!blender) {
// Calling code assumes a block will be appended. Add a fixed block to preserve shader
// and PaintParamsKey structure in release builds but assert since this should either not
// happen or should be changing high-level logic within PaintParams::toKey().
SkASSERT(false);
AddFixedBlendMode(keyContext, SkBlendMode::kSrcOver);
return;
}
switch (as_BB(blender)->type()) {
#define M(type) \
case SkBlenderBase::BlenderType::k##type: \
add_to_key(keyContext, \
static_cast<const Sk##type##Blender*>(blender)); \
return;
SK_ALL_BLENDERS(M)
#undef M
}
SkUNREACHABLE;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
static SkPMColor4f map_color(const SkColor4f& c,
SkColorSpace* src,
SkColorSpace* dst,
SkAlphaType dstAlphaType) {
SkPMColor4f color = {c.fR, c.fG, c.fB, c.fA};
SkColorSpaceXformSteps(src, kUnpremul_SkAlphaType, dst, dstAlphaType).apply(color.vec());
return color;
}
static void add_to_key(const KeyContext& keyContext, const SkBlendModeColorFilter* filter) {
SkASSERT(filter);
SkPMColor4f color = map_color(filter->color(), sk_srgb_singleton(),
keyContext.dstColorInfo().colorSpace(),
keyContext.dstColorInfo().alphaType());
AddBlendModeColorFilter(keyContext, filter->mode(), color);
}
static void add_to_key(const KeyContext& keyContext, const SkColorSpaceXformColorFilter* filter) {
SkASSERT(filter);
constexpr SkAlphaType kAlphaType = kPremul_SkAlphaType;
ColorSpaceTransformBlock::ColorSpaceTransformData csData(filter->src().get(), kAlphaType,
filter->dst().get(), kAlphaType);
ColorSpaceTransformBlock::AddBlock(keyContext, csData);
}
static void add_to_key(const KeyContext& keyContext, const SkComposeColorFilter* filter) {
SkASSERT(filter);
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
AddToKey(keyContext, filter->inner().get());
},
/* addOuterToKey= */ [&]() -> void {
AddToKey(keyContext, filter->outer().get());
});
}
static void add_to_key(const KeyContext& keyContext, const SkGaussianColorFilter*) {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kGaussianColorFilter);
}
static void add_to_key(const KeyContext& keyContext, const SkMatrixColorFilter* filter) {
SkASSERT(filter);
bool inHSLA = filter->domain() == SkMatrixColorFilter::Domain::kHSLA;
bool clamp = filter->clamp() == SkMatrixColorFilter::Clamp::kYes;
MatrixColorFilterBlock::MatrixColorFilterData matrixCFData(filter->matrix(), inHSLA, clamp);
MatrixColorFilterBlock::AddBlock(keyContext, matrixCFData);
}
static void add_to_key(const KeyContext& keyContext, const SkRuntimeColorFilter* filter) {
SkASSERT(filter);
sk_sp<SkRuntimeEffect> effect = filter->effect();
sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
effect->uniforms(), filter->uniforms(), keyContext.dstColorInfo().colorSpace());
SkASSERT(uniforms);
if (!RuntimeEffectBlock::BeginBlock(keyContext, { effect, std::move(uniforms) })) {
RuntimeEffectBlock::AddNoOpEffect(keyContext, effect.get());
return;
}
add_children_to_key(keyContext, filter->children(), effect.get());
keyContext.paintParamsKeyBuilder()->endBlock();
}
static void add_to_key(const KeyContext& keyContext, const SkTableColorFilter* filter) {
SkASSERT(filter);
sk_sp<TextureProxy> proxy = RecorderPriv::CreateCachedProxy(keyContext.recorder(),
filter->bitmap(),
"TableColorFilterTexture");
if (!proxy) {
SKGPU_LOG_W("Couldn't create TableColorFilter's table");
// Return the input color as-is.
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
return;
}
TableColorFilterBlock::TableColorFilterData data(std::move(proxy));
TableColorFilterBlock::AddBlock(keyContext, data);
}
static void add_to_key(const KeyContext& keyContext, const SkWorkingFormatColorFilter* filter) {
SkASSERT(filter);
const SkColorInfo& dstInfo = keyContext.dstColorInfo();
const SkAlphaType dstAT = dstInfo.alphaType();
sk_sp<SkColorSpace> dstCS = dstInfo.refColorSpace();
if (!dstCS) {
dstCS = SkColorSpace::MakeSRGB();
}
SkAlphaType workingAT;
sk_sp<SkColorSpace> workingCS = filter->workingFormat(dstCS, &workingAT);
SkColorInfo workingInfo(dstInfo.colorType(), workingAT, workingCS);
KeyContextWithColorInfo workingContext(keyContext, workingInfo);
// Use two nested compose blocks to chain (dst->working), child, and (working->dst) together
// while appearing as one block to the parent node.
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
// Inner compose
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
// Innermost (inner of inner compose)
ColorSpaceTransformBlock::ColorSpaceTransformData data1(
dstCS.get(), dstAT, workingCS.get(), workingAT);
ColorSpaceTransformBlock::AddBlock(keyContext, data1);
},
/* addOuterToKey= */ [&]() -> void {
// Middle (outer of inner compose)
AddToKey(workingContext, filter->child().get());
});
},
/* addOuterToKey= */ [&]() -> void {
// Outermost (outer of outer compose)
ColorSpaceTransformBlock::ColorSpaceTransformData data2(
workingCS.get(), workingAT, dstCS.get(), dstAT);
ColorSpaceTransformBlock::AddBlock(keyContext, data2);
});
}
void AddToKey(const KeyContext& keyContext, const SkColorFilter* filter) {
if (!filter) {
// Calling code assumes a block will be appended. Add a fixed block to preserve shader
// and PaintParamsKey structure in release builds but assert since this should either not
// happen or should be changing high-level logic within PaintParams::toKey().
SkASSERT(false);
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
return;
}
switch (as_CFB(filter)->type()) {
case SkColorFilterBase::Type::kNoop:
// Return the input color as-is.
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
return;
#define M(type) \
case SkColorFilterBase::Type::k##type: \
add_to_key(keyContext, \
static_cast<const Sk##type##ColorFilter*>(filter)); \
return;
SK_ALL_COLOR_FILTERS(M)
#undef M
}
SkUNREACHABLE;
}
// ==================================================================
static void add_to_key(const KeyContext& keyContext, const SkBlendShader* shader) {
SkASSERT(shader);
Blend(keyContext,
/* addBlendToKey= */ [&] () -> void {
AddBlendMode(keyContext, shader->mode());
},
/* addSrcToKey= */ [&]() -> void {
AddToKey(keyContext, shader->src().get());
},
/* addDstToKey= */ [&]() -> void {
AddToKey(keyContext, shader->dst().get());
});
}
static SkMatrix matrix_invert_or_identity(const SkMatrix& matrix) {
SkMatrix inverseMatrix;
if (!matrix.invert(&inverseMatrix)) {
inverseMatrix.setIdentity();
}
return inverseMatrix;
}
static void add_to_key(const KeyContext& keyContext, const SkCTMShader* shader) {
// CTM shaders are always given device coordinates, so we don't have to modify the CTM itself
// with keyContext's local transform.
SkMatrix lmInverse = matrix_invert_or_identity(shader->ctm());
LocalMatrixShaderBlock::LMShaderData lmShaderData(lmInverse);
KeyContextWithLocalMatrix newContext(keyContext, shader->ctm());
LocalMatrixShaderBlock::BeginBlock(newContext, lmShaderData);
AddToKey(newContext, shader->proxyShader().get());
keyContext.paintParamsKeyBuilder()->endBlock();
}
static void add_to_key(const KeyContext& keyContext, const SkColorShader* shader) {
SkASSERT(shader);
SkPMColor4f color = map_color(shader->color(), sk_srgb_singleton(),
keyContext.dstColorInfo().colorSpace(),
keyContext.dstColorInfo().alphaType());
SolidColorShaderBlock::AddBlock(keyContext, color);
}
static void add_to_key(const KeyContext& keyContext, const SkColorFilterShader* shader) {
SkASSERT(shader);
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
AddToKey(keyContext, shader->shader().get());
},
/* addOuterToKey= */ [&]() -> void {
AddToKey(keyContext, shader->filter().get());
});
}
static void add_to_key(const KeyContext& keyContext, const SkCoordClampShader* shader) {
SkASSERT(shader);
CoordClampShaderBlock::CoordClampData data(shader->subset());
KeyContextWithCoordClamp childContext(keyContext);
CoordClampShaderBlock::BeginBlock(keyContext, data);
AddToKey(childContext, shader->shader().get());
keyContext.paintParamsKeyBuilder()->endBlock();
}
static void add_to_key(const KeyContext& keyContext, const SkEmptyShader*) {
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kPriorOutput);
}
static void add_yuv_image_to_key(const KeyContext& keyContext,
const SkImageShader* origShader,
sk_sp<const SkImage> imageToDraw,
SkSamplingOptions sampling) {
SkASSERT(!imageToDraw->isAlphaOnly());
const Image_YUVA* yuvaImage = static_cast<const Image_YUVA*>(imageToDraw.get());
const SkYUVAInfo& yuvaInfo = yuvaImage->yuvaInfo();
// We would want to add a translation to the local matrix to handle other sitings.
SkASSERT(yuvaInfo.sitingX() == SkYUVAInfo::Siting::kCentered);
SkASSERT(yuvaInfo.sitingY() == SkYUVAInfo::Siting::kCentered);
YUVImageShaderBlock::ImageData imgData(sampling,
origShader->tileModeX(),
origShader->tileModeY(),
imageToDraw->dimensions(),
origShader->subset());
for (int locIndex = 0; locIndex < SkYUVAInfo::kYUVAChannelCount; ++locIndex) {
const TextureProxyView& view = yuvaImage->proxyView(locIndex);
if (view) {
imgData.fTextureProxies[locIndex] = view.refProxy();
// The view's swizzle has the data channel for the YUVA location in all slots, so read
// the 0th slot to determine fChannelSelect
switch(view.swizzle()[0]) {
case 'r': imgData.fChannelSelect[locIndex] = {1.f, 0.f, 0.f, 0.f}; break;
case 'g': imgData.fChannelSelect[locIndex] = {0.f, 1.f, 0.f, 0.f}; break;
case 'b': imgData.fChannelSelect[locIndex] = {0.f, 0.f, 1.f, 0.f}; break;
case 'a': imgData.fChannelSelect[locIndex] = {0.f, 0.f, 0.f, 1.f}; break;
default:
imgData.fChannelSelect[locIndex] = {0.f, 0.f, 0.f, 0.f};
SkDEBUGFAILF("Unexpected swizzle for YUVA data: %c", view.swizzle()[0]);
break;
}
} else {
// Only the A proxy view should be null, in which case we bind the Y proxy view to
// pass validation and send all 1s for the channel selection to signal opaque alpha.
SkASSERT(locIndex == 3);
imgData.fTextureProxies[locIndex] = yuvaImage->proxyView(SkYUVAInfo::kY).refProxy();
imgData.fChannelSelect[locIndex] = {1.f, 1.f, 1.f, 1.f};
// For the hardcoded sampling no-swizzle case, we use this to set constant alpha
imgData.fAlphaParam = 1;
}
}
auto [ssx, ssy] = yuvaImage->uvSubsampleFactors();
if (ssx > 1 || ssy > 1) {
// We need to adjust the image size we use for sampling to reflect the actual image size of
// the UV planes. However, since our coordinates are in Y's texel space we need to scale
// accordingly.
const TextureProxyView& view = yuvaImage->proxyView(SkYUVAInfo::kU);
imgData.fImgSizeUV = {view.dimensions().width()*ssx, view.dimensions().height()*ssy};
// This promotion of nearest to linear filtering for UV planes exists to mimic
// libjpeg[-turbo]'s do_fancy_upsampling option. We will filter the subsampled plane,
// however we want to filter at a fixed point for each logical image pixel to simulate
// nearest neighbor. In the shader we detect that the UV filtermode doesn't match the Y
// filtermode, and snap to Y pixel centers.
if (imgData.fSampling.filter == SkFilterMode::kNearest) {
imgData.fSamplingUV = SkSamplingOptions(SkFilterMode::kLinear,
imgData.fSampling.mipmap);
// Consider a logical image pixel at the edge of the subset. When computing the logical
// pixel color value we should use a blend of two values from the subsampled plane.
// Depending on where the subset edge falls in actual subsampled plane, one of those
// values may come from outside the subset. Hence, we will use the default inset
// in Y texel space of 1/2. This applies the wrap mode to the subset but allows
// linear filtering to read pixels that are just outside the subset.
imgData.fLinearFilterUVInset.fX = 0.5f;
imgData.fLinearFilterUVInset.fY = 0.5f;
} else if (imgData.fSampling.filter == SkFilterMode::kLinear) {
// We need to inset so that we aren't sampling outside the subset, but no farther.
// Start by mapping the subset to UV texel space
float scaleX = 1.f/ssx;
float scaleY = 1.f/ssy;
SkRect subsetUV = {imgData.fSubset.fLeft *scaleX,
imgData.fSubset.fTop *scaleY,
imgData.fSubset.fRight *scaleX,
imgData.fSubset.fBottom*scaleY};
// Round to UV texel borders
SkIRect iSubsetUV = subsetUV.roundOut();
// Inset in UV and map back to Y texel space. This gives us the largest possible
// inset rectangle that will not sample outside of the subset texels in UV space.
SkRect insetRectUV = {(iSubsetUV.fLeft +0.5f)*ssx,
(iSubsetUV.fTop +0.5f)*ssy,
(iSubsetUV.fRight -0.5f)*ssx,
(iSubsetUV.fBottom-0.5f)*ssy};
// Compute intersection with original inset
SkRect insetRect = imgData.fSubset.makeOutset(-0.5f, -0.5f);
(void) insetRect.intersect(insetRectUV);
// Compute max inset values to ensure we always remain within the subset.
imgData.fLinearFilterUVInset = {std::max(insetRect.fLeft - imgData.fSubset.fLeft,
imgData.fSubset.fRight - insetRect.fRight),
std::max(insetRect.fTop - imgData.fSubset.fTop,
imgData.fSubset.fBottom - insetRect.fBottom)};
}
}
float yuvM[20];
SkColorMatrix_YUV2RGB(yuvaInfo.yuvColorSpace(), yuvM);
// We drop the fourth column entirely since the transformation
// should not depend on alpha. The fifth column is sent as a separate
// vector. The fourth row is also dropped entirely because alpha should
// never be modified.
SkASSERT(yuvM[3] == 0 && yuvM[8] == 0 && yuvM[13] == 0 && yuvM[18] == 1);
SkASSERT(yuvM[15] == 0 && yuvM[16] == 0 && yuvM[17] == 0 && yuvM[19] == 0);
imgData.fYUVtoRGBMatrix.setAll(
yuvM[ 0], yuvM[ 1], yuvM[ 2],
yuvM[ 5], yuvM[ 6], yuvM[ 7],
yuvM[10], yuvM[11], yuvM[12]
);
imgData.fYUVtoRGBTranslate = {yuvM[4], yuvM[9], yuvM[14]};
SkColorSpaceXformSteps steps;
SkASSERT(steps.fFlags.mask() == 0); // By default, the colorspace should have no effect
// The actual output from the YUV image shader for non-opaque images is unpremul so
// we need to correct for the fact that the Image_YUVA_Graphite's alpha type is premul.
SkAlphaType srcAT = imageToDraw->alphaType() == kPremul_SkAlphaType
? kUnpremul_SkAlphaType
: imageToDraw->alphaType();
if (origShader->isRaw()) {
// Because we've avoided the premul alpha step in the YUV shader, we need to make sure
// it happens when drawing unpremul (i.e., non-opaque) images.
steps = SkColorSpaceXformSteps(imageToDraw->colorSpace(),
srcAT,
imageToDraw->colorSpace(),
imageToDraw->alphaType());
} else {
SkAlphaType dstAT = keyContext.dstColorInfo().alphaType();
// Setting the dst alphaType up this way is necessary because otherwise the constructor
// for SkColorSpaceXformSteps will set dstAT = srcAT when dstAT == kOpaque, and the
// premul step needed for non-opaque images won't occur.
if (dstAT == kOpaque_SkAlphaType && srcAT == kUnpremul_SkAlphaType) {
dstAT = kPremul_SkAlphaType;
}
steps = SkColorSpaceXformSteps(imageToDraw->colorSpace(),
srcAT,
keyContext.dstColorInfo().colorSpace(),
dstAT);
}
ColorSpaceTransformBlock::ColorSpaceTransformData data(steps);
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
YUVImageShaderBlock::AddBlock(keyContext, imgData);
},
/* addOuterToKey= */ [&]() -> void {
ColorSpaceTransformBlock::AddBlock(keyContext, data);
});
}
static void add_to_key(const KeyContext& keyContext,
const SkImageShader* shader) {
SkASSERT(shader);
auto [ imageToDraw, newSampling ] = GetGraphiteBacked(keyContext.recorder(),
shader->image().get(),
shader->sampling());
if (!imageToDraw) {
SKGPU_LOG_W("Couldn't convert ImageShader's image to a Graphite-backed image");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
// We must call notifyInUse() here to link the final, Graphite-backed 'imageToDraw'
// to the DrawContext that will sample it.
//
// This is necessary for two primary cases:
// 1. The original image was not Graphite-backed.
// 2. The original image was already Graphite-backed, but produced through Image::Copy, possibly
// from a different DrawContext.
//
// Failing to call this can lead to leaked Device and DrawContext memory (b/338453542).
SkASSERT(as_IB(imageToDraw)->isGraphiteBacked());
SkASSERT(keyContext.drawContext());
static_cast<Image_Base*>(imageToDraw.get())->notifyInUse(keyContext.recorder(),
keyContext.drawContext());
if (as_IB(imageToDraw)->isYUVA()) {
return add_yuv_image_to_key(keyContext,
shader,
std::move(imageToDraw),
newSampling);
}
auto view = AsView(imageToDraw.get());
SkASSERT(newSampling.mipmap == SkMipmapMode::kNone || view.mipmapped() == Mipmapped::kYes);
ImageShaderBlock::ImageData imgData(shader->sampling(),
shader->tileModeX(),
shader->tileModeY(),
view.proxy()->dimensions(),
shader->subset());
// Here we detect pixel aligned blit-like image draws. Some devices have low precision filtering
// and will produce degraded (blurry) images unexpectedly for sequential exact pixel blits when
// not using nearest filtering. This is common for canvas scrolling implementations. Forcing
// nearest filtering when possible can also be a minor perf/power optimization depending on the
// hardware.
bool samplingHasNoEffect = false;
// Cubic sampling is will not filter the same as nearest even when pixel aligned.
if (!(keyContext.flags() & KeyGenFlags::kDisableSamplingOptimization || newSampling.useCubic)) {
SkMatrix totalM = keyContext.local2Dev().asM33();
if (keyContext.localMatrix()) {
totalM.preConcat(*keyContext.localMatrix());
}
totalM.normalizePerspective();
// The matrix should be translation with only pixel aligned 2d translation.
samplingHasNoEffect = totalM.isTranslate() && SkScalarIsInt(totalM.getTranslateX()) &&
SkScalarIsInt(totalM.getTranslateY());
}
imgData.fSampling = samplingHasNoEffect ? SkFilterMode::kNearest : newSampling;
imgData.fTextureProxy = view.refProxy();
skgpu::Swizzle readSwizzle = view.swizzle();
// If the color type is alpha-only, propagate the alpha value to the other channels.
if (imageToDraw->isAlphaOnly()) {
readSwizzle = skgpu::Swizzle::Concat(readSwizzle, skgpu::Swizzle("000a"));
}
ColorSpaceTransformBlock::ColorSpaceTransformData colorXformData(
SwizzleClassToReadEnum(readSwizzle));
if (!shader->isRaw()) {
colorXformData.fSteps = SkColorSpaceXformSteps(imageToDraw->colorSpace(),
imageToDraw->alphaType(),
keyContext.dstColorInfo().colorSpace(),
keyContext.dstColorInfo().alphaType());
if (imageToDraw->isAlphaOnly() &&
!(keyContext.flags() & KeyGenFlags::kDisableAlphaOnlyImageColorization)) {
// NOTE: Alpha is not affected by colorspace conversion to the dst, and the paint color
// is already xformed to the dst, but the ColorSpaceTransformBlock is necessary to apply
// any read swizzle, which is often necessary for alpha-only color types.
Blend(keyContext,
/* addBlendToKey= */ [&] () -> void {
AddFixedBlendMode(keyContext, SkBlendMode::kDstIn);
},
/* addSrcToKey= */ [&] () -> void {
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
ImageShaderBlock::AddBlock(keyContext,
imgData);
},
/* addOuterToKey= */ [&]() -> void {
ColorSpaceTransformBlock::AddBlock(keyContext,
colorXformData);
});
},
/* addDstToKey= */ [&]() -> void {
RGBPaintColorBlock::AddBlock(keyContext);
});
return;
}
}
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
ImageShaderBlock::AddBlock(keyContext, imgData);
},
/* addOuterToKey= */ [&]() -> void {
ColorSpaceTransformBlock::AddBlock(keyContext, colorXformData);
});
}
static void add_to_key(const KeyContext& keyContext, const SkLocalMatrixShader* shader) {
SkASSERT(shader);
auto wrappedShader = shader->wrappedShader().get();
// Fold the texture's origin flip into the local matrix so that the image shader doesn't need
// additional state.
SkMatrix matrix;
SkShaderBase* wrappedShaderBase = as_SB(wrappedShader);
if (wrappedShaderBase->type() == SkShaderBase::ShaderType::kImage) {
auto imgShader = static_cast<const SkImageShader*>(wrappedShader);
// If the image is not graphite backed then we can assume the origin will be TopLeft as we
// require that in the ImageProvider utility. Also Graphite YUV images are assumed to be
// TopLeft origin.
auto imgBase = as_IB(imgShader->image());
if (imgBase->isGraphiteBacked()) {
// The YUV formats can encode their own origin including reflection and rotation,
// so we need to concat that to the local matrix transform.
if (imgBase->isYUVA()) {
auto imgYUVA = static_cast<const Image_YUVA*>(imgBase);
SkASSERT(imgYUVA);
matrix = matrix_invert_or_identity(imgYUVA->yuvaInfo().originMatrix());
} else {
auto imgGraphite = static_cast<Image*>(imgBase);
SkASSERT(imgGraphite);
const auto& view = imgGraphite->textureProxyView();
if (view.origin() == Origin::kBottomLeft) {
matrix.setScaleY(-1);
matrix.setTranslateY(view.height());
}
}
}
} else if (wrappedShaderBase->type() == SkShaderBase::ShaderType::kGradientBase) {
auto gradShader = static_cast<const SkGradientBaseShader*>(wrappedShader);
matrix = gradShader->getGradientMatrix();
// Override the conical gradient matrix since graphite uses a different algorithm
// than the ganesh and raster backends.
if (gradShader->asGradient() == SkShaderBase::GradientType::kConical) {
auto conicalShader = static_cast<const SkConicalGradient*>(gradShader);
SkMatrix conicalMatrix;
if (conicalShader->getType() == SkConicalGradient::Type::kRadial) {
SkPoint center = conicalShader->getStartCenter();
conicalMatrix.postTranslate(-center.fX, -center.fY);
float scale = sk_ieee_float_divide(1, conicalShader->getDiffRadius());
conicalMatrix.postScale(scale, scale);
} else {
auto mx = (SkConicalGradient::MapToUnitX(conicalShader->getStartCenter(),
conicalShader->getEndCenter()));
SkASSERT(mx);
conicalMatrix = mx.value_or(SkMatrix::I());
}
matrix = conicalMatrix;
}
}
SkMatrix lmInverse = matrix_invert_or_identity(shader->localMatrix());
lmInverse.postConcat(matrix);
LocalMatrixShaderBlock::LMShaderData lmShaderData(lmInverse);
KeyContextWithLocalMatrix newContext(keyContext, shader->localMatrix());
LocalMatrixShaderBlock::BeginBlock(newContext, lmShaderData);
AddToKey(newContext, wrappedShader);
keyContext.paintParamsKeyBuilder()->endBlock();
}
// If either of these change then the corresponding change must also be made in the SkSL
// perlin_noise_shader function.
static_assert((int)SkPerlinNoiseShaderType::kFractalNoise ==
(int)PerlinNoiseShaderBlock::Type::kFractalNoise);
static_assert((int)SkPerlinNoiseShaderType::kTurbulence ==
(int)PerlinNoiseShaderBlock::Type::kTurbulence);
static void add_to_key(const KeyContext& keyContext, const SkPerlinNoiseShader* shader) {
SkASSERT(shader);
SkASSERT(shader->numOctaves());
std::unique_ptr<SkPerlinNoiseShader::PaintingData> paintingData = shader->getPaintingData();
paintingData->generateBitmaps();
sk_sp<TextureProxy> perm =
RecorderPriv::CreateCachedProxy(keyContext.recorder(),
paintingData->getPermutationsBitmap(),
"PerlinNoisePermTable");
sk_sp<TextureProxy> noise =
RecorderPriv::CreateCachedProxy(keyContext.recorder(), paintingData->getNoiseBitmap(),
"PerlinNoiseNoiseTable");
if (!perm || !noise) {
SKGPU_LOG_W("Couldn't create tables for PerlinNoiseShader");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
PerlinNoiseShaderBlock::PerlinNoiseData perlinData(
static_cast<PerlinNoiseShaderBlock::Type>(shader->noiseType()),
paintingData->fBaseFrequency,
shader->numOctaves(),
{paintingData->fStitchDataInit.fWidth, paintingData->fStitchDataInit.fHeight});
perlinData.fPermutationsProxy = std::move(perm);
perlinData.fNoiseProxy = std::move(noise);
PerlinNoiseShaderBlock::AddBlock(keyContext, perlinData);
}
static void add_to_key(const KeyContext& keyContext,
const SkPictureShader* shader) {
SkASSERT(shader);
Recorder* recorder = keyContext.recorder();
const Caps* caps = recorder->priv().caps();
// TODO: We'll need additional plumbing to get the correct props from our callers. In
// particular we'll need to expand the keyContext to have the surfaceProps.
SkSurfaceProps props{};
SkMatrix totalM = keyContext.local2Dev().asM33();
if (keyContext.localMatrix()) {
totalM.preConcat(*keyContext.localMatrix());
}
auto info = SkPictureShader::CachedImageInfo::Make(shader->tile(),
totalM,
keyContext.dstColorInfo().colorType(),
keyContext.dstColorInfo().colorSpace(),
caps->maxTextureSize(),
props);
if (!info.success) {
SKGPU_LOG_W("Couldn't access PictureShaders' Image info");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
// NOTE: While this is intended to be a "scratch" surface, we don't use MakeScratch() because
// the SkPicture could contain arbitrary operations that rely on the Recorder's atlases, which
// means the Surface's device has to participate in flushing when the atlas fills up.
// TODO: Can this be an approx-fit image that's generated?
// TODO: right now we're explicitly not caching here. We could expand the ImageProvider
// API to include already Graphite-backed images, add a Recorder-local cache or add
// rendered-picture images to the global cache.
sk_sp<Surface> surface = Surface::Make(recorder,
info.imageInfo,
"PictureShaderTexture",
Budgeted::kYes,
Mipmapped::kNo,
SkBackingFit::kExact,
&info.props);
if (!surface) {
SKGPU_LOG_W("Could not create surface to render PictureShader");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
// NOTE: Don't call CachedImageInfo::makeImage() since that uses the legacy makeImageSnapshot()
// API, which results in an extra texture copy on a Graphite Surface.
surface->getCanvas()->concat(info.matrixForDraw);
surface->getCanvas()->drawPicture(shader->picture().get());
sk_sp<SkImage> img = SkSurfaces::AsImage(std::move(surface));
// TODO: 'img' did not exist when notify_in_use() was called, but ideally the DrawTask to render
// into 'surface' would be a child of the current device. While we push all tasks to the root
// list this works out okay, but will need to be addressed before we move off that system.
if (!img) {
SKGPU_LOG_W("Couldn't create SkImage for PictureShader");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
const auto shaderLM = SkMatrix::Scale(1.f/info.tileScale.width(), 1.f/info.tileScale.height());
sk_sp<SkShader> imgShader = img->makeShader(shader->tileModeX(), shader->tileModeY(),
SkSamplingOptions(shader->filter()), &shaderLM);
if (!imgShader) {
SKGPU_LOG_W("Couldn't create SkImageShader for PictureShader");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
AddToKey(keyContext, imgShader.get());
}
static void add_to_key(const KeyContext& keyContext,
const SkRuntimeShader* shader) {
SkASSERT(shader);
sk_sp<SkRuntimeEffect> effect = shader->effect();
sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
effect->uniforms(),
shader->uniformData(keyContext.dstColorInfo().colorSpace()),
keyContext.dstColorInfo().colorSpace());
SkASSERT(uniforms);
if (!RuntimeEffectBlock::BeginBlock(keyContext, { effect, std::move(uniforms) })) {
RuntimeEffectBlock::AddNoOpEffect(keyContext, effect.get());
return;
}
add_children_to_key(keyContext, shader->children(), effect.get());
keyContext.paintParamsKeyBuilder()->endBlock();
}
static void add_to_key(const KeyContext& keyContext,
const SkTransformShader* shader) {
SKGPU_LOG_W("Raster-only SkShader (SkTransformShader) encountered");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
}
static void add_to_key(const KeyContext& keyContext,
const SkTriColorShader* shader) {
SKGPU_LOG_W("Raster-only SkShader (SkTriColorShader) encountered");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
}
static void add_to_key(const KeyContext& keyContext,
const SkWorkingColorSpaceShader* shader) {
SkASSERT(shader);
const SkColorInfo& dstInfo = keyContext.dstColorInfo();
const SkAlphaType dstAT = dstInfo.alphaType();
sk_sp<SkColorSpace> dstCS = dstInfo.refColorSpace();
if (!dstCS) {
dstCS = SkColorSpace::MakeSRGB();
}
// It requires C++20 to use an auto structured binding and then reference them in the lambda.
sk_sp<SkColorSpace> inputCS, outputCS;
SkAlphaType workingAT;
std::tie(inputCS, outputCS, workingAT) = shader->workingSpace(dstCS, dstAT);
SkColorInfo workingInfo(dstInfo.colorType(), workingAT, inputCS);
KeyContextWithColorInfo workingContext(keyContext, workingInfo);
// Compose the inner shader (in the input space) with a (output->dst) transform, under the
// assumption that the child shader handles conversion between input and output CS/alpha types.
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
AddToKey(workingContext, shader->shader().get());
},
/* addOuterToKey= */ [&]() -> void {
ColorSpaceTransformBlock::ColorSpaceTransformData data(
outputCS.get(), workingAT, dstCS.get(), dstAT);
ColorSpaceTransformBlock::AddBlock(keyContext, data);
});
}
static SkBitmap create_color_and_offset_bitmap(int numStops,
const SkPMColor4f* colors,
const float* offsets) {
SkBitmap colorsAndOffsetsBitmap;
colorsAndOffsetsBitmap.allocPixels(
SkImageInfo::Make(numStops, 2, kRGBA_F16_SkColorType, kPremul_SkAlphaType));
for (int i = 0; i < numStops; i++) {
// TODO: there should be a way to directly set a premul pixel in a bitmap with
// a premul color.
SkColor4f unpremulColor = colors[i].unpremul();
colorsAndOffsetsBitmap.erase(unpremulColor, SkIRect::MakeXYWH(i, 0, 1, 1));
float offset = offsets ? offsets[i] : SkIntToFloat(i) / (numStops - 1);
SkASSERT(offset >= 0.0f && offset <= 1.0f);
int exponent;
float mantissa = frexp(offset, &exponent);
SkHalf halfE = SkFloatToHalf(exponent);
if ((int)SkHalfToFloat(halfE) != exponent) {
SKGPU_LOG_W("Encoding gradient to f16 failed");
return {};
}
#if defined(SK_DEBUG)
SkHalf halfM = SkFloatToHalf(mantissa);
float restored = ldexp(SkHalfToFloat(halfM), (int)SkHalfToFloat(halfE));
float error = abs(restored - offset);
SkASSERT(error < 0.001f);
#endif
// TODO: we're only using 2 of the f16s here. The encoding could be altered to better
// preserve precision. This encoding yields < 0.001f error for 2^20 evenly spaced stops.
colorsAndOffsetsBitmap.erase(SkColor4f{mantissa, (float)exponent, 0, 1},
SkIRect::MakeXYWH(i, 1, 1, 1));
}
return colorsAndOffsetsBitmap;
}
// Please see GrGradientShader.cpp::make_interpolated_to_dst for substantial comments
// as to why this code is structured this way.
static void make_interpolated_to_dst(const KeyContext& keyContext,
const GradientShaderBlocks::GradientData& gradData,
const SkGradientShader::Interpolation& interp,
SkColorSpace* intermediateCS) {
using ColorSpace = SkGradientShader::Interpolation::ColorSpace;
bool inputPremul = static_cast<bool>(interp.fInPremul);
switch (interp.fColorSpace) {
case ColorSpace::kLab:
case ColorSpace::kOKLab:
case ColorSpace::kOKLabGamutMap:
case ColorSpace::kLCH:
case ColorSpace::kOKLCH:
case ColorSpace::kOKLCHGamutMap:
case ColorSpace::kHSL:
case ColorSpace::kHWB:
inputPremul = false;
break;
default:
break;
}
const SkColorInfo& dstColorInfo = keyContext.dstColorInfo();
SkColorSpace* dstColorSpace =
dstColorInfo.colorSpace() ? dstColorInfo.colorSpace() : sk_srgb_singleton();
SkAlphaType intermediateAlphaType = inputPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
ColorSpaceTransformBlock::ColorSpaceTransformData data(
intermediateCS, intermediateAlphaType, dstColorSpace, dstColorInfo.alphaType());
// The gradient block and colorSpace conversion block need to be combined
// (via the Compose block) so that the localMatrix block can treat them as
// one child.
Compose(keyContext,
/* addInnerToKey= */ [&]() -> void {
GradientShaderBlocks::AddBlock(keyContext, gradData);
},
/* addOuterToKey= */ [&]() -> void {
ColorSpaceTransformBlock::AddBlock(keyContext, data);
});
}
static void add_gradient_to_key(const KeyContext& keyContext,
const SkGradientBaseShader* shader,
SkPoint point0,
SkPoint point1,
float radius0,
float radius1,
float bias,
float scale) {
SkColor4fXformer xformedColors(shader, keyContext.dstColorInfo().colorSpace());
const SkPMColor4f* colors = xformedColors.fColors.begin();
const float* positions = xformedColors.fPositions;
const int colorCount = xformedColors.fColors.size();
sk_sp<TextureProxy> proxy;
bool useStorageBuffer = keyContext.caps()->gradientBufferSupport();
if (colorCount > GradientShaderBlocks::GradientData::kNumInternalStorageStops
&& !useStorageBuffer) {
if (shader->cachedBitmap().empty()) {
SkBitmap colorsAndOffsetsBitmap =
create_color_and_offset_bitmap(colorCount, colors, positions);
if (colorsAndOffsetsBitmap.empty()) {
SKGPU_LOG_W("Couldn't create GradientShader's color and offset bitmap");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
shader->setCachedBitmap(colorsAndOffsetsBitmap);
}
proxy = RecorderPriv::CreateCachedProxy(keyContext.recorder(), shader->cachedBitmap(),
"GradientTexture");
if (!proxy) {
SKGPU_LOG_W("Couldn't create GradientShader's color and offset bitmap proxy");
keyContext.paintParamsKeyBuilder()->addBlock(BuiltInCodeSnippetID::kError);
return;
}
}
GradientShaderBlocks::GradientData data(shader->asGradient(),
point0,
point1,
radius0,
radius1,
bias,
scale,
shader->getTileMode(),
colorCount,
colors,
positions,
shader,
std::move(proxy),
useStorageBuffer,
shader->getInterpolation());
make_interpolated_to_dst(keyContext,
data,
shader->getInterpolation(),
xformedColors.fIntermediateColorSpace.get());
}
static void add_gradient_to_key(const KeyContext& keyContext,
const SkConicalGradient* shader) {
SkScalar r0 = shader->getStartRadius();
SkScalar r1 = shader->getEndRadius();
if (shader->getType() != SkConicalGradient::Type::kRadial) {
// Since we map the centers to be (0,0) and (1,0) in the gradient matrix,
// there is a scale of 1/distance-between-centers that has to be applied to the radii.
r0 /= shader->getCenterX1();
r1 /= shader->getCenterX1();
} else {
r0 /= shader->getDiffRadius();
r1 /= shader->getDiffRadius();
}
add_gradient_to_key(keyContext,
shader,
shader->getStartCenter(),
shader->getEndCenter(),
r0,
r1,
0.0f,
0.0f);
}
static void add_gradient_to_key(const KeyContext& keyContext,
const SkLinearGradient* shader) {
add_gradient_to_key(keyContext,
shader,
shader->start(),
shader->end(),
0.0f,
0.0f,
0.0f,
0.0f);
}
static void add_gradient_to_key(const KeyContext& keyContext,
const SkRadialGradient* shader) {
add_gradient_to_key(keyContext,
shader,
shader->center(),
{ 0.0f, 0.0f },
shader->radius(),
0.0f,
0.0f,
0.0f);
}
static void add_gradient_to_key(const KeyContext& keyContext,
const SkSweepGradient* shader) {
add_gradient_to_key(keyContext,
shader,
shader->center(),
{ 0.0f, 0.0f },
0.0f,
0.0f,
shader->tBias(),
shader->tScale());
}
static void add_to_key(const KeyContext& keyContext,
const SkGradientBaseShader* shader) {
SkASSERT(shader);
switch (shader->asGradient()) {
#define M(type) \
case SkShaderBase::GradientType::k##type: \
add_gradient_to_key(keyContext, \
static_cast<const Sk##type##Gradient*>(shader)); \
return;
SK_ALL_GRADIENTS(M)
#undef M
case SkShaderBase::GradientType::kNone:
SkDEBUGFAIL("Gradient shader says its type is none");
return;
}
SkUNREACHABLE;
}
void AddToKey(const KeyContext& keyContext, const SkShader* shader) {
if (!shader) {
// Calling code assumes a block will be appended. Add a fixed block to preserve shader
// and PaintParamsKey structure in release builds but assert since this should either not
// happen or should be changing high-level logic within PaintParams::toKey().
SkASSERT(false);
SolidColorShaderBlock::AddBlock(keyContext, SK_PMColor4fTRANSPARENT);
return;
}
switch (as_SB(shader)->type()) {
#define M(type) \
case SkShaderBase::ShaderType::k##type: \
add_to_key(keyContext, \
static_cast<const Sk##type##Shader*>(shader)); \
return;
SK_ALL_SHADERS(M)
#undef M
}
SkUNREACHABLE;
}
} // namespace skgpu::graphite