/* * 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(PipelineDataGatherer* gatherer, const ShaderCodeDictionary* dict, BuiltInCodeSnippetID codeSnippetID) : ScopedUniformWriter(gatherer, 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(gatherer, dict, codeSnippetID) \ ScopedUniformWriter scope{gatherer, dict, codeSnippetID}; void add_solid_uniform_data(const ShaderCodeDictionary* dict, const SkPMColor4f& premulColor, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kSolidColorShader) gatherer->write(premulColor); } } // anonymous namespace void SolidColorShaderBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkPMColor4f& premulColor) { add_solid_uniform_data(keyContext.dict(), premulColor, gatherer); builder->addBlock(BuiltInCodeSnippetID::kSolidColorShader); } //-------------------------------------------------------------------------------------------------- namespace { void add_rgb_paint_color_uniform_data(const ShaderCodeDictionary* dict, const SkPMColor4f& premulColor, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kRGBPaintColor) gatherer->writePaintColor(premulColor); } void add_alpha_only_paint_color_uniform_data(const ShaderCodeDictionary* dict, const SkPMColor4f& premulColor, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kAlphaOnlyPaintColor) gatherer->writePaintColor(premulColor); } } // anonymous namespace void RGBPaintColorBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer) { add_rgb_paint_color_uniform_data(keyContext.dict(), keyContext.paintColor(), gatherer); builder->addBlock(BuiltInCodeSnippetID::kRGBPaintColor); } void AlphaOnlyPaintColorBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer) { add_alpha_only_paint_color_uniform_data(keyContext.dict(), keyContext.paintColor(), gatherer); builder->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(ColorSpace::kLab) == 2); static_assert(static_cast(ColorSpace::kOKLab) == 3); static_assert(static_cast(ColorSpace::kOKLabGamutMap) == 4); static_assert(static_cast(ColorSpace::kLCH) == 5); static_assert(static_cast(ColorSpace::kOKLCH) == 6); static_assert(static_cast(ColorSpace::kOKLCHGamutMap) == 7); static_assert(static_cast(ColorSpace::kHSL) == 9); static_assert(static_cast(ColorSpace::kHWB) == 10); bool inputPremul = static_cast(gradData.fInterpolation.fInPremul); if (gradData.fNumStops > kInternalStopLimit) { gatherer->write(gradData.fNumStops); if (gradData.fUseStorageBuffer) { gatherer->write(bufferOffset); } } gatherer->write(static_cast(gradData.fTM)); gatherer->write(static_cast(gradData.fInterpolation.fColorSpace)); gatherer->write(static_cast(inputPremul)); } void add_linear_gradient_uniform_data(const ShaderCodeDictionary* dict, BuiltInCodeSnippetID codeSnippetID, const GradientShaderBlocks::GradientData& gradData, int bufferOffset, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, codeSnippetID) add_gradient_preamble(gradData, gatherer); add_gradient_postamble(gradData, bufferOffset, gatherer); }; void add_radial_gradient_uniform_data(const ShaderCodeDictionary* dict, BuiltInCodeSnippetID codeSnippetID, const GradientShaderBlocks::GradientData& gradData, int bufferOffset, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, codeSnippetID) add_gradient_preamble(gradData, gatherer); add_gradient_postamble(gradData, bufferOffset, gatherer); }; void add_sweep_gradient_uniform_data(const ShaderCodeDictionary* dict, BuiltInCodeSnippetID codeSnippetID, const GradientShaderBlocks::GradientData& gradData, int bufferOffset, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, codeSnippetID) add_gradient_preamble(gradData, gatherer); gatherer->write(gradData.fBias); gatherer->write(gradData.fScale); add_gradient_postamble(gradData, bufferOffset, gatherer); }; void add_conical_gradient_uniform_data(const ShaderCodeDictionary* dict, BuiltInCodeSnippetID codeSnippetID, const GradientShaderBlocks::GradientData& gradData, int bufferOffset, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, 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, gatherer); gatherer->write(gradData.fRadii[0]); gatherer->write(dRadius); gatherer->write(a); gatherer->write(invA); add_gradient_postamble(gradData, bufferOffset, gatherer); }; } // 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, PipelineDataGatherer* gatherer) { auto [dstData, bufferOffset] = gatherer->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) { SkColor4f unpremulColor = colors[i].unpremul(); float offset = offsets ? offsets[i] : SkIntToFloat(i) / (numStops - 1); SkASSERT(offset >= 0.0f && offset <= 1.0f); dstData[i] = offset; dstData[colorIdx + 0] = unpremulColor.fR; dstData[colorIdx + 1] = unpremulColor.fG; dstData[colorIdx + 2] = unpremulColor.fB; dstData[colorIdx + 3] = unpremulColor.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 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, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const GradientData& gradData) { auto dict = keyContext.dict(); 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, gatherer); } else { SkASSERT(gradData.fColorsAndOffsetsProxy); gatherer->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(dict, codeSnippetID, gradData, bufferOffset, gatherer); 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(dict, codeSnippetID, gradData, bufferOffset, gatherer); 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(dict, codeSnippetID, gradData, bufferOffset, gatherer); 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(dict, codeSnippetID, gradData, bufferOffset, gatherer); break; case SkShaderBase::GradientType::kNone: default: SkDEBUGFAIL("Expected a gradient shader, but it wasn't one."); break; } builder->addBlock(codeSnippetID); } //-------------------------------------------------------------------------------------------------- namespace { void add_localmatrixshader_uniform_data(const ShaderCodeDictionary* dict, const SkM44& localMatrix, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kLocalMatrixShader) gatherer->write(localMatrix); } } // anonymous namespace void LocalMatrixShaderBlock::BeginBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const LMShaderData& lmShaderData) { add_localmatrixshader_uniform_data(keyContext.dict(), lmShaderData.fLocalMatrix, gatherer); builder->beginBlock(lmShaderData.fHasPerspective ? BuiltInCodeSnippetID::kLocalMatrixShaderPersp : BuiltInCodeSnippetID::kLocalMatrixShader); } //-------------------------------------------------------------------------------------------------- namespace { static constexpr int kColorSpaceXformFlagAlphaSwizzle = 0x20; void add_color_space_uniforms(const SkColorSpaceXformSteps& steps, ReadSwizzle readSwizzle, PipelineDataGatherer* gatherer) { // We have 7 source coefficients and 7 destination coefficients. We pass them via a 4x4 matrix; // the first two columns hold the source values, and the second two hold the destination. // (The final value of each 8-element group is ignored.) // In std140, this arrangement is much more efficient than a simple array of scalars. SkM44 coeffs; int colorXformFlags = SkTo(steps.flags.mask()); if (readSwizzle != ReadSwizzle::kRGBA) { // Ensure that we do the gamut step SkColorSpaceXformSteps gamutSteps; gamutSteps.flags.gamut_transform = true; colorXformFlags |= SkTo(gamutSteps.flags.mask()); if (readSwizzle != ReadSwizzle::kBGRA) { // TODO: Maybe add a fullMask() method to XformSteps? SkASSERT(colorXformFlags < kColorSpaceXformFlagAlphaSwizzle); colorXformFlags |= kColorSpaceXformFlagAlphaSwizzle; } } gatherer->write(colorXformFlags); if (steps.flags.linearize) { gatherer->write(SkTo(skcms_TransferFunction_getType(&steps.srcTF))); coeffs.setCol(0, {steps.srcTF.g, steps.srcTF.a, steps.srcTF.b, steps.srcTF.c}); coeffs.setCol(1, {steps.srcTF.d, steps.srcTF.e, steps.srcTF.f, 0.0f}); } else { gatherer->write(SkTo(skcms_TFType::skcms_TFType_Invalid)); } 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 // src_to_dst_matrix is column-major, SkMatrix is row-major. const float* m = steps.flags.gamut_transform ? steps.src_to_dst_matrix : 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.flags.gamut_transform) { gamutTransform.setAll(m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]); } gatherer->writeHalf(gamutTransform); if (steps.flags.encode) { gatherer->write(SkTo(skcms_TransferFunction_getType(&steps.dstTFInv))); coeffs.setCol(2, {steps.dstTFInv.g, steps.dstTFInv.a, steps.dstTFInv.b, steps.dstTFInv.c}); coeffs.setCol(3, {steps.dstTFInv.d, steps.dstTFInv.e, steps.dstTFInv.f, 0.0f}); } else { gatherer->write(SkTo(skcms_TFType::skcms_TFType_Invalid)); } // Pack alpha swizzle in the unused coeff entries. switch (readSwizzle) { case ReadSwizzle::k000R: coeffs.setRC(3, 1, 1.f); coeffs.setRC(3, 3, 0.f); break; case ReadSwizzle::kRGB1: case ReadSwizzle::kRRR1: coeffs.setRC(3, 1, 0.f); coeffs.setRC(3, 3, 1.f); break; default: coeffs.setRC(3, 1, 0.f); coeffs.setRC(3, 3, 0.f); break; } gatherer->writeHalf(coeffs); } void add_image_uniform_data(const ShaderCodeDictionary* dict, const ImageShaderBlock::ImageData& imgData, PipelineDataGatherer* gatherer) { SkASSERT(!imgData.fSampling.useCubic); BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kImageShader) gatherer->write(SkSize::Make(1.f/imgData.fImgSize.width(), 1.f/imgData.fImgSize.height())); gatherer->write(imgData.fSubset); gatherer->write(SkTo(imgData.fTileModes.first)); gatherer->write(SkTo(imgData.fTileModes.second)); gatherer->write(SkTo(imgData.fSampling.filter)); } void add_cubic_image_uniform_data(const ShaderCodeDictionary* dict, const ImageShaderBlock::ImageData& imgData, PipelineDataGatherer* gatherer) { SkASSERT(imgData.fSampling.useCubic); BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kCubicImageShader) gatherer->write(SkSize::Make(1.f/imgData.fImgSize.width(), 1.f/imgData.fImgSize.height())); gatherer->write(imgData.fSubset); gatherer->write(SkTo(imgData.fTileModes.first)); gatherer->write(SkTo(imgData.fTileModes.second)); const SkCubicResampler& cubic = imgData.fSampling.cubic; gatherer->writeHalf(SkImageShader::CubicResamplerMatrix(cubic.B, cubic.C)); } void add_hw_image_uniform_data(const ShaderCodeDictionary* dict, const ImageShaderBlock::ImageData& imgData, PipelineDataGatherer* gatherer) { SkASSERT(!imgData.fSampling.useCubic); BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kHWImageShader) gatherer->write(SkSize::Make(1.f/imgData.fImgSize.width(), 1.f/imgData.fImgSize.height())); } bool can_do_tiling_in_hw(const Caps* caps, const ImageShaderBlock::ImageData& imgData) { if (!caps->clampToBorderSupport() && (imgData.fTileModes.first == SkTileMode::kDecal || imgData.fTileModes.second == SkTileMode::kDecal)) { return false; } return imgData.fSubset.contains(SkRect::Make(imgData.fImgSize)); } void add_sampler_data_to_key(PaintParamsKeyBuilder* builder, const SamplerDesc& samplerDesc) { if (samplerDesc.isImmutable()) { builder->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. builder->addData({{}}); } } } // anonymous namespace ImageShaderBlock::ImageData::ImageData(const SkSamplingOptions& sampling, SkTileMode tileModeX, SkTileMode tileModeY, SkISize imgSize, SkRect subset) : fSampling(sampling) , fTileModes{tileModeX, tileModeY} , fImgSize(imgSize) , fSubset(subset) { } void ImageShaderBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const ImageData& imgData) { if (keyContext.recorder() && !imgData.fTextureProxy) { builder->addBlock(BuiltInCodeSnippetID::kError); return; } const Caps* caps = keyContext.caps(); const bool doTilingInHw = !imgData.fSampling.useCubic && can_do_tiling_in_hw(caps, imgData); if (doTilingInHw) { add_hw_image_uniform_data(keyContext.dict(), imgData, gatherer); builder->beginBlock(BuiltInCodeSnippetID::kHWImageShader); } else if (imgData.fSampling.useCubic) { add_cubic_image_uniform_data(keyContext.dict(), imgData, gatherer); builder->beginBlock(BuiltInCodeSnippetID::kCubicImageShader); } else { add_image_uniform_data(keyContext.dict(), imgData, gatherer); builder->beginBlock(BuiltInCodeSnippetID::kImageShader); } static constexpr std::pair kDefaultTileModes = {SkTileMode::kClamp, SkTileMode::kClamp}; // Image shaders must append immutable sampler data (or '0' in the more common case where // regular samplers are used). ImmutableSamplerInfo info = caps->getImmutableSamplerInfo(imgData.fTextureProxy.get()); SamplerDesc samplerDesc {imgData.fSampling, doTilingInHw ? imgData.fTileModes : kDefaultTileModes, info}; gatherer->add(imgData.fTextureProxy, samplerDesc); add_sampler_data_to_key(builder, samplerDesc); builder->endBlock(); } //-------------------------------------------------------------------------------------------------- // makes use of ImageShader functions, above namespace { void add_yuv_image_uniform_data(const ShaderCodeDictionary* dict, const YUVImageShaderBlock::ImageData& imgData, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kYUVImageShader) gatherer->write(SkSize::Make(1.f/imgData.fImgSize.width(), 1.f/imgData.fImgSize.height())); gatherer->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(), 1.f/imgData.fImgSizeUV.height())); gatherer->write(imgData.fSubset); gatherer->write(imgData.fLinearFilterUVInset); gatherer->write(SkTo(imgData.fTileModes.first)); gatherer->write(SkTo(imgData.fTileModes.second)); gatherer->write(SkTo(imgData.fSampling.filter)); gatherer->write(SkTo(imgData.fSamplingUV.filter)); for (int i = 0; i < 4; ++i) { gatherer->writeHalf(imgData.fChannelSelect[i]); } gatherer->writeHalf(imgData.fYUVtoRGBMatrix); gatherer->writeHalf(imgData.fYUVtoRGBTranslate); } void add_cubic_yuv_image_uniform_data(const ShaderCodeDictionary* dict, const YUVImageShaderBlock::ImageData& imgData, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kCubicYUVImageShader) gatherer->write(SkSize::Make(1.f/imgData.fImgSize.width(), 1.f/imgData.fImgSize.height())); gatherer->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(), 1.f/imgData.fImgSizeUV.height())); gatherer->write(imgData.fSubset); gatherer->write(SkTo(imgData.fTileModes.first)); gatherer->write(SkTo(imgData.fTileModes.second)); const SkCubicResampler& cubic = imgData.fSampling.cubic; gatherer->writeHalf(SkImageShader::CubicResamplerMatrix(cubic.B, cubic.C)); for (int i = 0; i < 4; ++i) { gatherer->writeHalf(imgData.fChannelSelect[i]); } gatherer->writeHalf(imgData.fYUVtoRGBMatrix); gatherer->writeHalf(imgData.fYUVtoRGBTranslate); } void add_hw_yuv_image_uniform_data(const ShaderCodeDictionary* dict, const YUVImageShaderBlock::ImageData& imgData, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kHWYUVImageShader) gatherer->write(SkSize::Make(1.f/imgData.fImgSize.width(), 1.f/imgData.fImgSize.height())); gatherer->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(), 1.f/imgData.fImgSizeUV.height())); for (int i = 0; i < 4; ++i) { gatherer->writeHalf(imgData.fChannelSelect[i]); } gatherer->writeHalf(imgData.fYUVtoRGBMatrix); gatherer->writeHalf(imgData.fYUVtoRGBTranslate); } void add_hw_yuv_no_swizzle_image_uniform_data(const ShaderCodeDictionary* dict, const YUVImageShaderBlock::ImageData& imgData, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kHWYUVNoSwizzleImageShader) gatherer->write(SkSize::Make(1.f/imgData.fImgSize.width(), 1.f/imgData.fImgSize.height())); gatherer->write(SkSize::Make(1.f/imgData.fImgSizeUV.width(), 1.f/imgData.fImgSizeUV.height())); gatherer->writeHalf(imgData.fYUVtoRGBMatrix); SkV4 yuvToRGBXlateAlphaParam = { imgData.fYUVtoRGBTranslate.fX, imgData.fYUVtoRGBTranslate.fY, imgData.fYUVtoRGBTranslate.fZ, imgData.fAlphaParam }; gatherer->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 Caps* caps, const YUVImageShaderBlock::ImageData& imgData) { if (!caps->clampToBorderSupport() && (imgData.fTileModes.first == SkTileMode::kDecal || imgData.fTileModes.second == SkTileMode::kDecal)) { return false; } // We depend on the subset code to handle cases where the UV dimensions times the // subsample factors are not equal to the Y dimensions. if (imgData.fImgSize != imgData.fImgSizeUV) { return false; } // For nearest filtering when the Y texture size is larger than the UV texture size, // we use linear filtering for the UV texture. In this case we also adjust pixel centers // which may affect dependent texture reads. if (imgData.fSampling.filter != imgData.fSamplingUV.filter) { return false; } return imgData.fSubset.contains(SkRect::Make(imgData.fImgSize)); } 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, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const ImageData& imgData) { if (keyContext.recorder() && (!imgData.fTextureProxies[0] || !imgData.fTextureProxies[1] || !imgData.fTextureProxies[2] || !imgData.fTextureProxies[3])) { builder->addBlock(BuiltInCodeSnippetID::kError); return; } const Caps* caps = keyContext.caps(); const bool doTilingInHw = !imgData.fSampling.useCubic && can_do_yuv_tiling_in_hw(caps, imgData); const bool noYUVSwizzle = no_yuv_swizzle(imgData); 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); gatherer->add(imgData.fTextureProxies[0], {imgData.fSampling, imgData.fTileModes}); gatherer->add(imgData.fTextureProxies[1], {imgData.fSamplingUV, uvTileModes}); gatherer->add(imgData.fTextureProxies[2], {imgData.fSamplingUV, uvTileModes}); gatherer->add(imgData.fTextureProxies[3], {imgData.fSampling, imgData.fTileModes}); if (doTilingInHw && noYUVSwizzle) { add_hw_yuv_no_swizzle_image_uniform_data(keyContext.dict(), imgData, gatherer); builder->addBlock(BuiltInCodeSnippetID::kHWYUVNoSwizzleImageShader); } else if (doTilingInHw) { add_hw_yuv_image_uniform_data(keyContext.dict(), imgData, gatherer); builder->addBlock(BuiltInCodeSnippetID::kHWYUVImageShader); } else if (imgData.fSampling.useCubic) { add_cubic_yuv_image_uniform_data(keyContext.dict(), imgData, gatherer); builder->addBlock(BuiltInCodeSnippetID::kCubicYUVImageShader); } else { add_yuv_image_uniform_data(keyContext.dict(), imgData, gatherer); builder->addBlock(BuiltInCodeSnippetID::kYUVImageShader); } } //-------------------------------------------------------------------------------------------------- namespace { void add_coordclamp_uniform_data(const ShaderCodeDictionary* dict, const CoordClampShaderBlock::CoordClampData& clampData, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kCoordClampShader) gatherer->write(clampData.fSubset); } } // anonymous namespace void CoordClampShaderBlock::BeginBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const CoordClampData& clampData) { add_coordclamp_uniform_data(keyContext.dict(), clampData, gatherer); builder->beginBlock(BuiltInCodeSnippetID::kCoordClampShader); } //-------------------------------------------------------------------------------------------------- namespace { void add_dither_uniform_data(const ShaderCodeDictionary* dict, const DitherShaderBlock::DitherData& ditherData, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kDitherShader) gatherer->writeHalf(ditherData.fRange); } } // anonymous namespace void DitherShaderBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const DitherData& data) { add_dither_uniform_data(keyContext.dict(), data, gatherer); SkASSERT(data.fLUTProxy || !keyContext.recorder()); gatherer->add(data.fLUTProxy, {SkFilterMode::kNearest, SkTileMode::kRepeat}); builder->addBlock(BuiltInCodeSnippetID::kDitherShader); } //-------------------------------------------------------------------------------------------------- namespace { void add_perlin_noise_uniform_data(const ShaderCodeDictionary* dict, const PerlinNoiseShaderBlock::PerlinNoiseData& noiseData, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kPerlinNoiseShader) gatherer->write(noiseData.fBaseFrequency); gatherer->write(noiseData.fStitchData); gatherer->write(static_cast(noiseData.fType)); gatherer->write(noiseData.fNumOctaves); gatherer->write(static_cast(noiseData.stitching())); static const std::pair 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, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const PerlinNoiseData& noiseData) { add_perlin_noise_uniform_data(keyContext.dict(), noiseData, gatherer); builder->addBlock(BuiltInCodeSnippetID::kPerlinNoiseShader); } //-------------------------------------------------------------------------------------------------- void BlendComposeBlock::BeginBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, keyContext.dict(), BuiltInCodeSnippetID::kBlendCompose) builder->beginBlock(BuiltInCodeSnippetID::kBlendCompose); } //-------------------------------------------------------------------------------------------------- void PorterDuffBlenderBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, SkSpan coeffs) { BEGIN_WRITE_UNIFORMS(gatherer, keyContext.dict(), BuiltInCodeSnippetID::kPorterDuffBlender) SkASSERT(coeffs.size() == 4); gatherer->writeHalf(SkV4{coeffs[0], coeffs[1], coeffs[2], coeffs[3]}); builder->addBlock(BuiltInCodeSnippetID::kPorterDuffBlender); } //-------------------------------------------------------------------------------------------------- void HSLCBlenderBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, SkSpan coeffs) { BEGIN_WRITE_UNIFORMS(gatherer, keyContext.dict(), BuiltInCodeSnippetID::kHSLCBlender) SkASSERT(coeffs.size() == 2); gatherer->writeHalf(SkV2{coeffs[0], coeffs[1]}); builder->addBlock(BuiltInCodeSnippetID::kHSLCBlender); } //-------------------------------------------------------------------------------------------------- void ComposeBlock::BeginBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer) { builder->beginBlock(BuiltInCodeSnippetID::kCompose); } //-------------------------------------------------------------------------------------------------- namespace { void add_matrix_colorfilter_uniform_data(const ShaderCodeDictionary* dict, const MatrixColorFilterBlock::MatrixColorFilterData& data, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kMatrixColorFilter) gatherer->write(data.fMatrix); gatherer->write(data.fTranslate); gatherer->write(static_cast(data.fInHSLA)); gatherer->write(static_cast(data.fClamp)); } } // anonymous namespace void MatrixColorFilterBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const MatrixColorFilterData& matrixCFData) { add_matrix_colorfilter_uniform_data(keyContext.dict(), matrixCFData, gatherer); builder->addBlock(BuiltInCodeSnippetID::kMatrixColorFilter); } //-------------------------------------------------------------------------------------------------- namespace { void add_table_colorfilter_uniform_data(const ShaderCodeDictionary* dict, const TableColorFilterBlock::TableColorFilterData& data, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kTableColorFilter) gatherer->add(data.fTextureProxy, {SkFilterMode::kNearest, SkTileMode::kClamp}); } } // anonymous namespace void TableColorFilterBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const TableColorFilterData& data) { SkASSERT(data.fTextureProxy || !keyContext.recorder()); add_table_colorfilter_uniform_data(keyContext.dict(), data, gatherer); builder->addBlock(BuiltInCodeSnippetID::kTableColorFilter); } //-------------------------------------------------------------------------------------------------- namespace { void add_color_space_xform_uniform_data( const ShaderCodeDictionary* dict, const ColorSpaceTransformBlock::ColorSpaceTransformData& data, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kColorSpaceXformColorFilter) add_color_space_uniforms(data.fSteps, data.fReadSwizzle, gatherer); } } // 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, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const ColorSpaceTransformData& data) { add_color_space_xform_uniform_data(keyContext.dict(), data, gatherer); builder->addBlock(BuiltInCodeSnippetID::kColorSpaceXformColorFilter); } //-------------------------------------------------------------------------------------------------- namespace { void add_circular_rrect_clip_data( const ShaderCodeDictionary* dict, const CircularRRectClipBlock::CircularRRectClipData& data, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kCircularRRectClip) gatherer->write(data.fRect); gatherer->write(data.fRadiusPlusHalf); gatherer->writeHalf(data.fEdgeSelect); } } // anonymous namespace void CircularRRectClipBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const CircularRRectClipData& data) { add_circular_rrect_clip_data(keyContext.dict(), data, gatherer); builder->addBlock(BuiltInCodeSnippetID::kCircularRRectClip); } //-------------------------------------------------------------------------------------------------- namespace { void add_primitive_color_uniform_data( const ShaderCodeDictionary* dict, const SkColorSpaceXformSteps& steps, PipelineDataGatherer* gatherer) { BEGIN_WRITE_UNIFORMS(gatherer, dict, BuiltInCodeSnippetID::kPrimitiveColor) add_color_space_uniforms(steps, ReadSwizzle::kRGBA, gatherer); } } // anonymous namespace void PrimitiveColorBlock::AddBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer) { SkColorSpaceXformSteps steps = SkColorSpaceXformSteps(SkColorSpace::MakeSRGB().get(), kPremul_SkAlphaType, keyContext.dstColorInfo().colorSpace(), keyContext.dstColorInfo().alphaType()); add_primitive_color_uniform_data(keyContext.dict(), steps, gatherer); builder->addBlock(BuiltInCodeSnippetID::kPrimitiveColor); } //-------------------------------------------------------------------------------------------------- void AddBlendModeColorFilter(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, SkBlendMode bm, const SkPMColor4f& srcColor) { Blend(keyContext, builder, gatherer, /* addBlendToKey= */ [&] () -> void { AddBlendMode(keyContext, builder, gatherer, bm); }, /* addSrcToKey= */ [&]() -> void { SolidColorShaderBlock::AddBlock(keyContext, builder, gatherer, srcColor); }, /* addDstToKey= */ [&]() -> void { builder->addBlock(BuiltInCodeSnippetID::kPriorOutput); }); } RuntimeEffectBlock::ShaderData::ShaderData(sk_sp effect) : fEffect(std::move(effect)) {} RuntimeEffectBlock::ShaderData::ShaderData(sk_sp effect, sk_sp 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 graphiteUniforms, const SkData* uniformData, PipelineDataGatherer* gatherer) { if (!uniformData) { return; // precompiling } SkDEBUGCODE(UniformExpectationsValidator uev(gatherer, graphiteUniforms);) SkSpan 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); } } } void RuntimeEffectBlock::BeginBlock(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const ShaderData& shaderData) { ShaderCodeDictionary* dict = keyContext.dict(); int codeSnippetID = dict->findOrCreateRuntimeEffectSnippet(shaderData.fEffect.get()); if (codeSnippetID >= SkKnownRuntimeEffects::kUnknownRuntimeEffectIDStart) { keyContext.rtEffectDict()->set(codeSnippetID, shaderData.fEffect); } const ShaderSnippet* entry = dict->getEntry(codeSnippetID); SkASSERT(entry); gather_runtime_effect_uniforms(keyContext, shaderData.fEffect.get(), entry->fUniforms, shaderData.fUniforms.get(), gatherer); builder->beginBlock(codeSnippetID); } // ================================================================== namespace { void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkBlendModeBlender* blender) { SkASSERT(blender); AddBlendMode(keyContext, builder, gatherer, blender->mode()); } // Be sure to keep this function in sync w/ the code in PrecompileRTEffect::addToKey void add_children_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, SkSpan children, const SkRuntimeEffect* effect) { SkSpan childInfo = effect->children(); SkASSERT(children.size() == childInfo.size()); using ChildType = SkRuntimeEffect::ChildType; KeyContextWithScope childContext(keyContext, KeyContext::Scope::kRuntimeEffect); for (size_t index = 0; index < children.size(); ++index) { const SkRuntimeEffect::ChildPtr& child = children[index]; std::optional type = child.type(); if (type == ChildType::kShader) { AddToKey(childContext, builder, gatherer, child.shader()); } else if (type == ChildType::kColorFilter) { AddToKey(childContext, builder, gatherer, child.colorFilter()); } else if (type == ChildType::kBlender) { AddToKey(childContext, builder, gatherer, 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, builder, gatherer, SK_PMColor4fTRANSPARENT); break; case ChildType::kColorFilter: // A "passthrough" color filter returns the input color as-is. builder->addBlock(BuiltInCodeSnippetID::kPriorOutput); break; case ChildType::kBlender: // A "passthrough" blender performs `blend_src_over(src, dest)`. AddFixedBlendMode(childContext, builder, gatherer, SkBlendMode::kSrcOver); break; } } } // 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(childContext, builder, gatherer, dstToLinear); ColorSpaceTransformBlock::AddBlock(childContext, builder, gatherer, linearToDst); } } void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkRuntimeBlender* blender) { SkASSERT(blender); sk_sp effect = blender->effect(); SkASSERT(effect); sk_sp uniforms = SkRuntimeEffectPriv::TransformUniforms( effect->uniforms(), blender->uniforms(), keyContext.dstColorInfo().colorSpace()); SkASSERT(uniforms); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, { effect, std::move(uniforms) }); add_children_to_key(keyContext, builder, gatherer, blender->children(), effect.get()); builder->endBlock(); } void notify_in_use(Recorder* recorder, DrawContext* drawContext, SkSpan children) { for (const auto& child : children) { if (child.type().has_value()) { switch (*child.type()) { case SkRuntimeEffect::ChildType::kShader: NotifyImagesInUse(recorder, drawContext, child.shader()); break; case SkRuntimeEffect::ChildType::kColorFilter: NotifyImagesInUse(recorder, drawContext, child.colorFilter()); break; case SkRuntimeEffect::ChildType::kBlender: NotifyImagesInUse(recorder, drawContext, child.blender()); break; } } // else a null child is a no-op, so cannot sample an image } } } // anonymous namespace void AddToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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, builder, gatherer, SkBlendMode::kSrcOver); return; } switch (as_BB(blender)->type()) { #define M(type) \ case SkBlenderBase::BlenderType::k##type: \ add_to_key(keyContext, \ builder, \ gatherer, \ static_cast(blender)); \ return; SK_ALL_BLENDERS(M) #undef M } SkUNREACHABLE; } void NotifyImagesInUse(Recorder* recorder, DrawContext* drawContext, const SkBlender* blender) { if (!blender) { return; } if (as_BB(blender)->type() == SkBlenderBase::BlenderType::kRuntime) { const auto* rbb = static_cast(blender); notify_in_use(recorder, drawContext, rbb->children()); } // else blend mode doesn't reference images } //-------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------- static SkPMColor4f map_color(const SkColor4f& c, SkColorSpace* src, SkColorSpace* dst) { SkPMColor4f color = {c.fR, c.fG, c.fB, c.fA}; SkColorSpaceXformSteps(src, kUnpremul_SkAlphaType, dst, kPremul_SkAlphaType).apply(color.vec()); return color; } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkBlendModeColorFilter* filter) { SkASSERT(filter); SkPMColor4f color = map_color(filter->color(), sk_srgb_singleton(), keyContext.dstColorInfo().colorSpace()); AddBlendModeColorFilter(keyContext, builder, gatherer, filter->mode(), color); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkColorSpaceXformColorFilter* filter) { SkASSERT(filter); constexpr SkAlphaType kAlphaType = kPremul_SkAlphaType; ColorSpaceTransformBlock::ColorSpaceTransformData csData(filter->src().get(), kAlphaType, filter->dst().get(), kAlphaType); ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, csData); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* keyBuilder, PipelineDataGatherer* gatherer, const SkComposeColorFilter* filter) { SkASSERT(filter); Compose(keyContext, keyBuilder, gatherer, /* addInnerToKey= */ [&]() -> void { AddToKey(keyContext, keyBuilder, gatherer, filter->inner().get()); }, /* addOuterToKey= */ [&]() -> void { AddToKey(keyContext, keyBuilder, gatherer, filter->outer().get()); }); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkGaussianColorFilter*) { builder->addBlock(BuiltInCodeSnippetID::kGaussianColorFilter); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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, builder, gatherer, matrixCFData); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkRuntimeColorFilter* filter) { SkASSERT(filter); sk_sp effect = filter->effect(); sk_sp uniforms = SkRuntimeEffectPriv::TransformUniforms( effect->uniforms(), filter->uniforms(), keyContext.dstColorInfo().colorSpace()); SkASSERT(uniforms); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, {effect, std::move(uniforms)}); add_children_to_key(keyContext, builder, gatherer, filter->children(), effect.get()); builder->endBlock(); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkTableColorFilter* filter) { SkASSERT(filter); sk_sp 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. builder->addBlock(BuiltInCodeSnippetID::kPriorOutput); return; } TableColorFilterBlock::TableColorFilterData data(std::move(proxy)); TableColorFilterBlock::AddBlock(keyContext, builder, gatherer, data); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkWorkingFormatColorFilter* filter) { SkASSERT(filter); const SkColorInfo& dstInfo = keyContext.dstColorInfo(); const SkAlphaType dstAT = dstInfo.alphaType(); sk_sp dstCS = dstInfo.refColorSpace(); if (!dstCS) { dstCS = SkColorSpace::MakeSRGB(); } SkAlphaType workingAT; sk_sp 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, builder, gatherer, /* addInnerToKey= */ [&]() -> void { // Inner compose Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { // Innermost (inner of inner compose) ColorSpaceTransformBlock::ColorSpaceTransformData data1( dstCS.get(), dstAT, workingCS.get(), workingAT); ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, data1); }, /* addOuterToKey= */ [&]() -> void { // Middle (outer of inner compose) AddToKey(workingContext, builder, gatherer, filter->child().get()); }); }, /* addOuterToKey= */ [&]() -> void { // Outermost (outer of outer compose) ColorSpaceTransformBlock::ColorSpaceTransformData data2( workingCS.get(), workingAT, dstCS.get(), dstAT); ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, data2); }); } void AddToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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); builder->addBlock(BuiltInCodeSnippetID::kPriorOutput); return; } switch (as_CFB(filter)->type()) { case SkColorFilterBase::Type::kNoop: // Return the input color as-is. builder->addBlock(BuiltInCodeSnippetID::kPriorOutput); return; #define M(type) \ case SkColorFilterBase::Type::k##type: \ add_to_key(keyContext, \ builder, \ gatherer, \ static_cast(filter)); \ return; SK_ALL_COLOR_FILTERS(M) #undef M } SkUNREACHABLE; } void NotifyImagesInUse(Recorder* recorder, DrawContext* drawContext, const SkColorFilter* filter) { if (!filter) { return; } if (as_CFB(filter)->type() == SkColorFilterBase::Type::kCompose) { // Recurse to two children const auto* cf = static_cast(filter); NotifyImagesInUse(recorder, drawContext, cf->inner().get()); NotifyImagesInUse(recorder, drawContext, cf->outer().get()); } else if (as_CFB(filter)->type() == SkColorFilterBase::Type::kWorkingFormat) { // Recurse to one child const auto* wfcf = static_cast(filter); NotifyImagesInUse(recorder, drawContext, wfcf->child().get()); } else if (as_CFB(filter)->type() == SkColorFilterBase::Type::kRuntime) { // Recurse to all children const auto* rcf = static_cast(filter); notify_in_use(recorder, drawContext, rcf->children()); } // else other color filters do not rely on SkImages } // ================================================================== static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkBlendShader* shader) { SkASSERT(shader); Blend(keyContext, builder, gatherer, /* addBlendToKey= */ [&] () -> void { AddBlendMode(keyContext, builder, gatherer, shader->mode()); }, /* addSrcToKey= */ [&]() -> void { AddToKey(keyContext, builder, gatherer, shader->src().get()); }, /* addDstToKey= */ [&]() -> void { AddToKey(keyContext, builder, gatherer, shader->dst().get()); }); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkBlendShader* shader) { // SkBlendShader uses a fixed blend mode, so there's no blender to recurse through NotifyImagesInUse(recorder, drawContext, shader->src().get()); NotifyImagesInUse(recorder, drawContext, 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, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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, builder, gatherer, lmShaderData); AddToKey(newContext, builder, gatherer, shader->proxyShader().get()); builder->endBlock(); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkCTMShader* shader) { NotifyImagesInUse(recorder, drawContext, shader->proxyShader().get()); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkColorShader* shader) { SkASSERT(shader); SolidColorShaderBlock::AddBlock(keyContext, builder, gatherer, SkColor4f::FromColor(shader->color()).premul()); } static void notify_in_use(Recorder*, DrawContext*, const SkColorShader*) { // No-op } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkColor4Shader* shader) { SkASSERT(shader); SkPMColor4f color = map_color(shader->color(), shader->colorSpace().get(), keyContext.dstColorInfo().colorSpace()); SolidColorShaderBlock::AddBlock(keyContext, builder, gatherer, color); } static void notify_in_use(Recorder*, DrawContext*, const SkColor4Shader*) { // No-op } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkColorFilterShader* shader) { SkASSERT(shader); Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { AddToKey(keyContext, builder, gatherer, shader->shader().get()); }, /* addOuterToKey= */ [&]() -> void { AddToKey(keyContext, builder, gatherer, shader->filter().get()); }); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkColorFilterShader* shader) { NotifyImagesInUse(recorder, drawContext, shader->shader().get()); NotifyImagesInUse(recorder, drawContext, shader->filter().get()); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkCoordClampShader* shader) { SkASSERT(shader); CoordClampShaderBlock::CoordClampData data(shader->subset()); KeyContextWithCoordClamp childContext(keyContext); CoordClampShaderBlock::BeginBlock(keyContext, builder, gatherer, data); AddToKey(childContext, builder, gatherer, shader->shader().get()); builder->endBlock(); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkCoordClampShader* shader) { NotifyImagesInUse(recorder, drawContext, shader->shader().get()); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkEmptyShader*) { builder->addBlock(BuiltInCodeSnippetID::kPriorOutput); } static void notify_in_use(Recorder*, DrawContext*, const SkEmptyShader*) { // No-op } static bool is_premul_alpha_only(const ColorSpaceTransformBlock::ColorSpaceTransformData& data) { // A mask value of 16 means premul only. if (SkTo(data.fSteps.flags.mask()) != 16) { return false; } // If read swizzle is RGBA or BGRA we don't need to do alpha swizzle return (data.fReadSwizzle == ReadSwizzle::kRGBA || data.fReadSwizzle == ReadSwizzle::kBGRA); } static void add_yuv_image_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkImageShader* origShader, sk_sp imageToDraw, SkSamplingOptions sampling) { SkASSERT(!imageToDraw->isAlphaOnly()); const Image_YUVA* yuvaImage = static_cast(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.flags.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); // We only do this for YUV images because this is the only case where we expect // a premul-only colorspace transformation to be common. Otherwise it's not // worth the combinatorial explosion in the precompile system. if (is_premul_alpha_only(data)) { Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { YUVImageShaderBlock::AddBlock(keyContext, builder, gatherer, imgData); }, /* addOuterToKey= */ [&]() -> void { builder->addBlock(BuiltInCodeSnippetID::kPremulAlphaColorFilter); }); } else { Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { YUVImageShaderBlock::AddBlock(keyContext, builder, gatherer, imgData); }, /* addOuterToKey= */ [&]() -> void { ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, data); }); } } static skgpu::graphite::ReadSwizzle swizzle_class_to_read_enum(const skgpu::Swizzle& swizzle) { if (swizzle == skgpu::Swizzle::RGBA()) { return skgpu::graphite::ReadSwizzle::kRGBA; } else if (swizzle == skgpu::Swizzle::RGB1()) { return skgpu::graphite::ReadSwizzle::kRGB1; } else if (swizzle == skgpu::Swizzle("rrr1")) { return skgpu::graphite::ReadSwizzle::kRRR1; } else if (swizzle == skgpu::Swizzle::BGRA()) { return skgpu::graphite::ReadSwizzle::kBGRA; } else if (swizzle == skgpu::Swizzle("000r")) { return skgpu::graphite::ReadSwizzle::k000R; } else { SKGPU_LOG_W("%s is an unsupported read swizzle. Defaulting to RGBA.\n", swizzle.asString().data()); return skgpu::graphite::ReadSwizzle::kRGBA; } } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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"); builder->addBlock(BuiltInCodeSnippetID::kError); return; } if (!as_IB(shader->image())->isGraphiteBacked()) { // GetGraphiteBacked() created a new image (or fetched a cached image) from the client // image provider. This image was not available when NotifyInUse() visited the shader tree, // so call notify again. These images shouldn't really be producing new tasks since it's // unlikely that a client will be fulfilling with a dynamic image that wraps a long-lived // SkSurface. However, the images can be linked to a surface that rendered the initial // content and not calling notifyInUse() prevents unlinking the image from the Device. // If the client image provider then holds on to many of these images, the leaked Device and // DrawContext memory can be surprisingly high. b/338453542. // TODO (b/330864257): Once paint keys are extracted at draw time, AddToKey() will be // fully responsible for notifyInUse() calls and then we can simply always call this on // `imageToDraw`. The DrawContext that samples the image will also be available to AddToKey // so we won't have to pass in nullptr. SkASSERT(as_IB(imageToDraw)->isGraphiteBacked()); static_cast(imageToDraw.get())->notifyInUse(keyContext.recorder(), /*drawContext=*/nullptr); } if (as_IB(imageToDraw)->isYUVA()) { return add_yuv_image_to_key(keyContext, builder, gatherer, 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.optimizeSampling() == KeyContext::OptimizeSampling::kYes && !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( swizzle_class_to_read_enum(readSwizzle)); if (!shader->isRaw()) { colorXformData.fSteps = SkColorSpaceXformSteps(imageToDraw->colorSpace(), imageToDraw->alphaType(), keyContext.dstColorInfo().colorSpace(), keyContext.dstColorInfo().alphaType()); if (imageToDraw->isAlphaOnly() && keyContext.scope() != KeyContext::Scope::kRuntimeEffect) { Blend(keyContext, builder, gatherer, /* addBlendToKey= */ [&] () -> void { AddFixedBlendMode(keyContext, builder, gatherer, SkBlendMode::kDstIn); }, /* addSrcToKey= */ [&] () -> void { Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { ImageShaderBlock::AddBlock(keyContext, builder, gatherer, imgData); }, /* addOuterToKey= */ [&]() -> void { ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, colorXformData); }); }, /* addDstToKey= */ [&]() -> void { RGBPaintColorBlock::AddBlock(keyContext, builder, gatherer); }); return; } } Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { ImageShaderBlock::AddBlock(keyContext, builder, gatherer, imgData); }, /* addOuterToKey= */ [&]() -> void { ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, colorXformData); }); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkImageShader* shader) { auto image = as_IB(shader->image()); if (!image->isGraphiteBacked()) { // If it's not graphite-backed, there's no pending graphite work. return; } static_cast(image)->notifyInUse(recorder, drawContext); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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(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(imgBase); SkASSERT(imgYUVA); matrix = matrix_invert_or_identity(imgYUVA->yuvaInfo().originMatrix()); } else { auto imgGraphite = static_cast(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(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(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 { SkAssertResult(SkConicalGradient::MapToUnitX(conicalShader->getStartCenter(), conicalShader->getEndCenter(), &conicalMatrix)); } 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, builder, gatherer, lmShaderData); AddToKey(newContext, builder, gatherer, wrappedShader); builder->endBlock(); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkLocalMatrixShader* shader) { NotifyImagesInUse(recorder, drawContext, shader->wrappedShader().get()); } // 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, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkPerlinNoiseShader* shader) { SkASSERT(shader); SkASSERT(shader->numOctaves()); std::unique_ptr paintingData = shader->getPaintingData(); paintingData->generateBitmaps(); sk_sp perm = RecorderPriv::CreateCachedProxy(keyContext.recorder(), paintingData->getPermutationsBitmap(), "PerlinNoisePermTable"); sk_sp noise = RecorderPriv::CreateCachedProxy(keyContext.recorder(), paintingData->getNoiseBitmap(), "PerlinNoiseNoiseTable"); if (!perm || !noise) { SKGPU_LOG_W("Couldn't create tables for PerlinNoiseShader"); builder->addBlock(BuiltInCodeSnippetID::kError); return; } PerlinNoiseShaderBlock::PerlinNoiseData perlinData( static_cast(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, builder, gatherer, perlinData); } static void notify_in_use(Recorder*, DrawContext*, const SkPerlinNoiseShader*) { // No-op, perlin noise has no children. } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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"); builder->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::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"); builder->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 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"); builder->addBlock(BuiltInCodeSnippetID::kError); return; } const auto shaderLM = SkMatrix::Scale(1.f/info.tileScale.width(), 1.f/info.tileScale.height()); sk_sp imgShader = img->makeShader(shader->tileModeX(), shader->tileModeY(), SkSamplingOptions(shader->filter()), &shaderLM); if (!imgShader) { SKGPU_LOG_W("Couldn't create SkImageShader for PictureShader"); builder->addBlock(BuiltInCodeSnippetID::kError); return; } AddToKey(keyContext, builder, gatherer, imgShader.get()); } static void notify_in_use(Recorder*, DrawContext*, const SkPictureShader*) { // While the SkPicture the shader points to, may have Graphite-backed shaders that need to be // notified, that will happen when the picture is rendered into an image in add_to_key } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkRuntimeShader* shader) { SkASSERT(shader); sk_sp effect = shader->effect(); sk_sp uniforms = SkRuntimeEffectPriv::TransformUniforms( effect->uniforms(), shader->uniformData(keyContext.dstColorInfo().colorSpace()), keyContext.dstColorInfo().colorSpace()); SkASSERT(uniforms); RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, {effect, std::move(uniforms)}); add_children_to_key(keyContext, builder, gatherer, shader->children(), effect.get()); builder->endBlock(); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkRuntimeShader* shader) { notify_in_use(recorder, drawContext, shader->children()); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkTransformShader* shader) { SKGPU_LOG_W("Raster-only SkShader (SkTransformShader) encountered"); builder->addBlock(BuiltInCodeSnippetID::kError); } static void notify_in_use(Recorder*, DrawContext*, const SkTransformShader*) { // no-op } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkTriColorShader* shader) { SKGPU_LOG_W("Raster-only SkShader (SkTriColorShader) encountered"); builder->addBlock(BuiltInCodeSnippetID::kError); } static void notify_in_use(Recorder*, DrawContext*, const SkTriColorShader*) { // no-op } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkWorkingColorSpaceShader* shader) { SkASSERT(shader); const SkColorInfo& dstInfo = keyContext.dstColorInfo(); const SkAlphaType dstAT = dstInfo.alphaType(); sk_sp dstCS = dstInfo.refColorSpace(); if (!dstCS) { dstCS = SkColorSpace::MakeSRGB(); } sk_sp workingCS = shader->workingSpace(); SkColorInfo workingInfo(dstInfo.colorType(), dstAT, workingCS); KeyContextWithColorInfo workingContext(keyContext, workingInfo); // Compose the inner shader (in the working space) with a (working->dst) transform: Compose(keyContext, builder, gatherer, /* addInnerToKey= */ [&]() -> void { AddToKey(workingContext, builder, gatherer, shader->shader().get()); }, /* addOuterToKey= */ [&]() -> void { ColorSpaceTransformBlock::ColorSpaceTransformData data( workingCS.get(), dstAT, dstCS.get(), dstAT); ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, data); }); } static void notify_in_use(Recorder* recorder, DrawContext* drawContext, const SkWorkingColorSpaceShader* shader) { NotifyImagesInUse(recorder, drawContext, shader->shader().get()); } 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, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const GradientShaderBlocks::GradientData& gradData, const SkGradientShader::Interpolation& interp, SkColorSpace* intermediateCS) { using ColorSpace = SkGradientShader::Interpolation::ColorSpace; bool inputPremul = static_cast(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, builder, gatherer, /* addInnerToKey= */ [&]() -> void { GradientShaderBlocks::AddBlock(keyContext, builder, gatherer, gradData); }, /* addOuterToKey= */ [&]() -> void { ColorSpaceTransformBlock::AddBlock(keyContext, builder, gatherer, data); }); } static void add_gradient_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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 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"); builder->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"); builder->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, builder, gatherer, data, shader->getInterpolation(), xformedColors.fIntermediateColorSpace.get()); } static void add_gradient_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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, builder, gatherer, shader, shader->getStartCenter(), shader->getEndCenter(), r0, r1, 0.0f, 0.0f); } static void add_gradient_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkLinearGradient* shader) { add_gradient_to_key(keyContext, builder, gatherer, shader, shader->start(), shader->end(), 0.0f, 0.0f, 0.0f, 0.0f); } static void add_gradient_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkRadialGradient* shader) { add_gradient_to_key(keyContext, builder, gatherer, shader, shader->center(), { 0.0f, 0.0f }, shader->radius(), 0.0f, 0.0f, 0.0f); } static void add_gradient_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkSweepGradient* shader) { add_gradient_to_key(keyContext, builder, gatherer, shader, shader->center(), { 0.0f, 0.0f }, 0.0f, 0.0f, shader->tBias(), shader->tScale()); } static void add_to_key(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, const SkGradientBaseShader* shader) { SkASSERT(shader); switch (shader->asGradient()) { #define M(type) \ case SkShaderBase::GradientType::k##type: \ add_gradient_to_key(keyContext, \ builder, \ gatherer, \ static_cast(shader)); \ return; SK_ALL_GRADIENTS(M) #undef M case SkShaderBase::GradientType::kNone: SkDEBUGFAIL("Gradient shader says its type is none"); return; } SkUNREACHABLE; } static void notify_in_use(Recorder*, DrawContext*, const SkGradientBaseShader*) { // Gradients do not have children, so no images to notify } void AddToKey(const KeyContext& keyContext, PaintParamsKeyBuilder* builder, PipelineDataGatherer* gatherer, 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, builder, gatherer, SK_PMColor4fTRANSPARENT); return; } switch (as_SB(shader)->type()) { #define M(type) \ case SkShaderBase::ShaderType::k##type: \ add_to_key(keyContext, \ builder, \ gatherer, \ static_cast(shader)); \ return; SK_ALL_SHADERS(M) #undef M } SkUNREACHABLE; } void NotifyImagesInUse(Recorder* recorder, DrawContext* drawContext, const SkShader* shader) { if (!shader) { return; } switch (as_SB(shader)->type()) { #define M(type) \ case SkShaderBase::ShaderType::k##type: \ notify_in_use(recorder, \ drawContext, \ static_cast(shader)); \ return; SK_ALL_SHADERS(M) #undef M } SkUNREACHABLE; } } // namespace skgpu::graphite