| /* |
| * 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/DitherUtils.h" |
| #include "src/gpu/graphite/Caps.h" |
| #include "src/gpu/graphite/ContextUtils.h" |
| #include "src/gpu/graphite/FactoryFunctions.h" |
| #include "src/gpu/graphite/FactoryFunctionsPriv.h" |
| #include "src/gpu/graphite/KeyContext.h" |
| #include "src/gpu/graphite/KeyHelpers.h" |
| #include "src/gpu/graphite/PaintOptionsPriv.h" |
| #include "src/gpu/graphite/PaintParams.h" |
| #include "src/gpu/graphite/PaintParamsKey.h" |
| #include "src/gpu/graphite/Precompile.h" |
| #include "src/gpu/graphite/PrecompileBasePriv.h" |
| #include "src/gpu/graphite/Renderer.h" |
| #include "src/gpu/graphite/ShaderCodeDictionary.h" |
| |
| namespace skgpu::graphite { |
| |
| //-------------------------------------------------------------------------------------------------- |
| sk_sp<PrecompileShader> PrecompileShader::makeWithLocalMatrix() { |
| if (this->priv().isALocalMatrixShader()) { |
| // SkShader::makeWithLocalMatrix collapses chains of localMatrix shaders so we need to |
| // follow suit here |
| return sk_ref_sp(this); |
| } |
| |
| return PrecompileShaders::LocalMatrix({ sk_ref_sp(this) }); |
| } |
| |
| sk_sp<PrecompileShader> PrecompileShader::makeWithColorFilter(sk_sp<PrecompileColorFilter> cf) { |
| if (!cf) { |
| return sk_ref_sp(this); |
| } |
| |
| return PrecompileShaders::ColorFilter({ sk_ref_sp(this) }, { std::move(cf) }); |
| } |
| |
| sk_sp<PrecompileShader> PrecompileShader::makeWithWorkingColorSpace(sk_sp<SkColorSpace> cs) { |
| if (!cs) { |
| return sk_ref_sp(this); |
| } |
| |
| return PrecompileShaders::WorkingColorSpace({ sk_ref_sp(this) }, { std::move(cs) }); |
| } |
| |
| sk_sp<PrecompileColorFilter> PrecompileColorFilter::makeComposed( |
| sk_sp<PrecompileColorFilter> inner) const { |
| if (!inner) { |
| return sk_ref_sp(this); |
| } |
| |
| return PrecompileColorFilters::Compose({ sk_ref_sp(this) }, { std::move(inner) }); |
| } |
| |
| //-------------------------------------------------------------------------------------------------- |
| void PaintOptions::setClipShaders(SkSpan<const sk_sp<PrecompileShader>> clipShaders) { |
| // In the normal API this modification happens in SkDevice::clipShader() |
| fClipShaderOptions.reserve(2 * clipShaders.size()); |
| for (const sk_sp<PrecompileShader>& cs : clipShaders) { |
| // All clipShaders get wrapped in a CTMShader ... |
| sk_sp<PrecompileShader> withCTM = cs ? PrecompileShadersPriv::CTM({ cs }) : nullptr; |
| // and, if it is a SkClipOp::kDifference clip, an additional ColorFilterShader |
| sk_sp<PrecompileShader> inverted = |
| withCTM ? withCTM->makeWithColorFilter(PrecompileColorFilters::Blend()) |
| : nullptr; |
| |
| fClipShaderOptions.emplace_back(std::move(withCTM)); |
| fClipShaderOptions.emplace_back(std::move(inverted)); |
| } |
| } |
| |
| int PaintOptions::numShaderCombinations() const { |
| int numShaderCombinations = 0; |
| for (const sk_sp<PrecompileShader>& s : fShaderOptions) { |
| numShaderCombinations += s->numCombinations(); |
| } |
| |
| // If no shader option is specified we will add a solid color shader option |
| return numShaderCombinations ? numShaderCombinations : 1; |
| } |
| |
| int PaintOptions::numColorFilterCombinations() const { |
| int numColorFilterCombinations = 0; |
| for (const sk_sp<PrecompileColorFilter>& cf : fColorFilterOptions) { |
| if (!cf) { |
| ++numColorFilterCombinations; |
| } else { |
| numColorFilterCombinations += cf->numCombinations(); |
| } |
| } |
| |
| // If no color filter options are specified we will use the unmodified result color |
| return numColorFilterCombinations ? numColorFilterCombinations : 1; |
| } |
| |
| int PaintOptions::numBlendModeCombinations() const { |
| int numBlendCombos = fBlendModeOptions.size(); |
| for (const sk_sp<PrecompileBlender>& b: fBlenderOptions) { |
| SkASSERT(!b->asBlendMode().has_value()); |
| numBlendCombos += b->numChildCombinations(); |
| } |
| |
| if (!numBlendCombos) { |
| // If the user didn't specify a blender we will fall back to kSrcOver blending |
| numBlendCombos = 1; |
| } |
| |
| return numBlendCombos; |
| } |
| |
| int PaintOptions::numClipShaderCombinations() const { |
| int numClipShaderCombos = 0; |
| for (const sk_sp<PrecompileShader>& cs: fClipShaderOptions) { |
| if (cs) { |
| numClipShaderCombos += cs->numChildCombinations(); |
| } else { |
| ++numClipShaderCombos; |
| } |
| } |
| |
| // If no clipShader options are specified we will just have the unclipped options |
| return numClipShaderCombos ? numClipShaderCombos : 1; |
| } |
| |
| |
| int PaintOptions::numCombinations() const { |
| // TODO: we need to handle ImageFilters separately |
| return this->numShaderCombinations() * |
| this->numColorFilterCombinations() * |
| this->numBlendModeCombinations() * |
| this->numClipShaderCombinations(); |
| } |
| |
| DstReadRequirement get_dst_read_req(const Caps* caps, |
| Coverage coverage, |
| PrecompileBlender* blender) { |
| if (blender) { |
| return GetDstReadRequirement(caps, blender->asBlendMode(), coverage); |
| } |
| return GetDstReadRequirement(caps, SkBlendMode::kSrcOver, coverage); |
| } |
| |
| class PaintOption { |
| public: |
| PaintOption(bool opaquePaintColor, |
| const std::pair<sk_sp<PrecompileBlender>, int>& finalBlender, |
| const std::pair<sk_sp<PrecompileShader>, int>& shader, |
| const std::pair<sk_sp<PrecompileColorFilter>, int>& colorFilter, |
| bool hasPrimitiveBlender, |
| const std::pair<sk_sp<PrecompileShader>, int>& clipShader, |
| DstReadRequirement dstReadReq, |
| bool dither) |
| : fOpaquePaintColor(opaquePaintColor) |
| , fFinalBlender(finalBlender) |
| , fShader(shader) |
| , fColorFilter(colorFilter) |
| , fHasPrimitiveBlender(hasPrimitiveBlender) |
| , fClipShader(clipShader) |
| , fDstReadReq(dstReadReq) |
| , fDither(dither) { |
| } |
| |
| const PrecompileBlender* finalBlender() const { return fFinalBlender.first.get(); } |
| |
| void toKey(const KeyContext&, PaintParamsKeyBuilder*, PipelineDataGatherer*) const; |
| |
| private: |
| void addPaintColorToKey(const KeyContext&, PaintParamsKeyBuilder*, PipelineDataGatherer*) const; |
| void handlePrimitiveColor(const KeyContext&, |
| PaintParamsKeyBuilder*, |
| PipelineDataGatherer*) const; |
| void handlePaintAlpha(const KeyContext&, PaintParamsKeyBuilder*, PipelineDataGatherer*) const; |
| void handleColorFilter(const KeyContext&, PaintParamsKeyBuilder*, PipelineDataGatherer*) const; |
| void handleDithering(const KeyContext&, PaintParamsKeyBuilder*, PipelineDataGatherer*) const; |
| void handleDstRead(const KeyContext&, PaintParamsKeyBuilder*, PipelineDataGatherer*) const; |
| |
| bool shouldDither(SkColorType dstCT) const; |
| |
| bool fOpaquePaintColor; |
| std::pair<sk_sp<PrecompileBlender>, int> fFinalBlender; |
| std::pair<sk_sp<PrecompileShader>, int> fShader; |
| std::pair<sk_sp<PrecompileColorFilter>, int> fColorFilter; |
| bool fHasPrimitiveBlender; |
| std::pair<sk_sp<PrecompileShader>, int> fClipShader; |
| DstReadRequirement fDstReadReq; |
| bool fDither; |
| }; |
| |
| |
| void PaintOption::addPaintColorToKey(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* builder, |
| PipelineDataGatherer* gatherer) const { |
| if (fShader.first) { |
| fShader.first->priv().addToKey(keyContext, builder, gatherer, fShader.second); |
| } else { |
| RGBPaintColorBlock::AddBlock(keyContext, builder, gatherer); |
| } |
| } |
| |
| void PaintOption::handlePrimitiveColor(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* keyBuilder, |
| PipelineDataGatherer* gatherer) const { |
| if (fHasPrimitiveBlender) { |
| Blend(keyContext, keyBuilder, gatherer, |
| /* addBlendToKey= */ [&] () -> void { |
| // TODO: Support runtime blenders for primitive blending in the precompile API. |
| // In the meantime, assume for now that we're using kSrcOver here. |
| AddToKey(keyContext, keyBuilder, gatherer, |
| SkBlender::Mode(SkBlendMode::kSrcOver).get()); |
| }, |
| /* addSrcToKey= */ [&]() -> void { |
| this->addPaintColorToKey(keyContext, keyBuilder, gatherer); |
| }, |
| /* addDstToKey= */ [&]() -> void { |
| keyBuilder->addBlock(BuiltInCodeSnippetID::kPrimitiveColor); |
| }); |
| } else { |
| this->addPaintColorToKey(keyContext, keyBuilder, gatherer); |
| } |
| } |
| |
| void PaintOption::handlePaintAlpha(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* keyBuilder, |
| PipelineDataGatherer* gatherer) const { |
| |
| if (!fShader.first && !fHasPrimitiveBlender) { |
| // If there is no shader and no primitive blending the input to the colorFilter stage |
| // is just the premultiplied paint color. |
| SolidColorShaderBlock::AddBlock(keyContext, keyBuilder, gatherer, SK_PMColor4fWHITE); |
| return; |
| } |
| |
| if (!fOpaquePaintColor) { |
| Blend(keyContext, keyBuilder, gatherer, |
| /* addBlendToKey= */ [&] () -> void { |
| AddKnownModeBlend(keyContext, keyBuilder, gatherer, SkBlendMode::kSrcIn); |
| }, |
| /* addSrcToKey= */ [&]() -> void { |
| this->handlePrimitiveColor(keyContext, keyBuilder, gatherer); |
| }, |
| /* addDstToKey= */ [&]() -> void { |
| AlphaOnlyPaintColorBlock::AddBlock(keyContext, keyBuilder, gatherer); |
| }); |
| } else { |
| this->handlePrimitiveColor(keyContext, keyBuilder, gatherer); |
| } |
| } |
| |
| void PaintOption::handleColorFilter(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* builder, |
| PipelineDataGatherer* gatherer) const { |
| if (fColorFilter.first) { |
| Compose(keyContext, builder, gatherer, |
| /* addInnerToKey= */ [&]() -> void { |
| this->handlePaintAlpha(keyContext, builder, gatherer); |
| }, |
| /* addOuterToKey= */ [&]() -> void { |
| fColorFilter.first->priv().addToKey(keyContext, builder, gatherer, |
| fColorFilter.second); |
| }); |
| } else { |
| this->handlePaintAlpha(keyContext, builder, gatherer); |
| } |
| } |
| |
| // This should be kept in sync w/ SkPaintPriv::ShouldDither and PaintParams::should_dither |
| bool PaintOption::shouldDither(SkColorType dstCT) const { |
| // The paint dither flag can veto. |
| if (!fDither) { |
| return false; |
| } |
| |
| if (dstCT == kUnknown_SkColorType) { |
| return false; |
| } |
| |
| // We always dither 565 or 4444 when requested. |
| if (dstCT == kRGB_565_SkColorType || dstCT == kARGB_4444_SkColorType) { |
| return true; |
| } |
| |
| // Otherwise, dither is only needed for non-const paints. |
| return fShader.first && !fShader.first->isConstant(fShader.second); |
| } |
| |
| void PaintOption::handleDithering(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* builder, |
| PipelineDataGatherer* gatherer) const { |
| |
| #ifndef SK_IGNORE_GPU_DITHER |
| SkColorType ct = keyContext.dstColorInfo().colorType(); |
| if (this->shouldDither(ct)) { |
| Compose(keyContext, builder, gatherer, |
| /* addInnerToKey= */ [&]() -> void { |
| this->handleColorFilter(keyContext, builder, gatherer); |
| }, |
| /* addOuterToKey= */ [&]() -> void { |
| AddDitherBlock(keyContext, builder, gatherer, ct); |
| }); |
| } else |
| #endif |
| { |
| this->handleColorFilter(keyContext, builder, gatherer); |
| } |
| } |
| |
| void PaintOption::handleDstRead(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* builder, |
| PipelineDataGatherer* gatherer) const { |
| if (fDstReadReq != DstReadRequirement::kNone) { |
| Blend(keyContext, builder, gatherer, |
| /* addBlendToKey= */ [&] () -> void { |
| if (fFinalBlender.first) { |
| fFinalBlender.first->priv().addToKey(keyContext, builder, gatherer, |
| fFinalBlender.second); |
| } else { |
| AddKnownModeBlend(keyContext, builder, gatherer, SkBlendMode::kSrcOver); |
| } |
| }, |
| /* addSrcToKey= */ [&]() -> void { |
| this->handleDithering(keyContext, builder, gatherer); |
| }, |
| /* addDstToKey= */ [&]() -> void { |
| AddDstReadBlock(keyContext, builder, gatherer, fDstReadReq); |
| }); |
| } else { |
| this->handleDithering(keyContext, builder, gatherer); |
| } |
| } |
| |
| void PaintOption::toKey(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* keyBuilder, |
| PipelineDataGatherer* gatherer) const { |
| this->handleDstRead(keyContext, keyBuilder, gatherer); |
| |
| std::optional<SkBlendMode> finalBlendMode = this->finalBlender() |
| ? this->finalBlender()->asBlendMode() |
| : SkBlendMode::kSrcOver; |
| if (fDstReadReq != DstReadRequirement::kNone) { |
| // In this case the blend will have been handled by shader-based blending with the dstRead. |
| finalBlendMode = SkBlendMode::kSrc; |
| } |
| |
| if (fClipShader.first) { |
| ClipShaderBlock::BeginBlock(keyContext, keyBuilder, gatherer); |
| fClipShader.first->priv().addToKey(keyContext, keyBuilder, gatherer, |
| fClipShader.second); |
| keyBuilder->endBlock(); |
| } |
| |
| // Set the hardware blend mode. |
| SkASSERT(finalBlendMode); |
| BuiltInCodeSnippetID fixedFuncBlendModeID = static_cast<BuiltInCodeSnippetID>( |
| kFixedFunctionBlendModeIDOffset + static_cast<int>(*finalBlendMode)); |
| |
| keyBuilder->addBlock(fixedFuncBlendModeID); |
| } |
| |
| void PaintOptions::createKey(const KeyContext& keyContext, |
| PaintParamsKeyBuilder* keyBuilder, |
| PipelineDataGatherer* gatherer, |
| int desiredCombination, |
| bool addPrimitiveBlender, |
| Coverage coverage) const { |
| SkDEBUGCODE(keyBuilder->checkReset();) |
| SkASSERT(desiredCombination < this->numCombinations()); |
| |
| const int numClipShaderCombos = this->numClipShaderCombinations(); |
| const int numBlendModeCombos = this->numBlendModeCombinations(); |
| const int numColorFilterCombinations = this->numColorFilterCombinations(); |
| |
| const int desiredClipShaderCombination = desiredCombination % numClipShaderCombos; |
| int remainingCombinations = desiredCombination / numClipShaderCombos; |
| |
| const int desiredBlendCombination = remainingCombinations % numBlendModeCombos; |
| remainingCombinations /= numBlendModeCombos; |
| |
| const int desiredColorFilterCombination = remainingCombinations % numColorFilterCombinations; |
| remainingCombinations /= numColorFilterCombinations; |
| |
| const int desiredShaderCombination = remainingCombinations; |
| SkASSERT(desiredShaderCombination < this->numShaderCombinations()); |
| |
| // TODO: this probably needs to be passed in just like addPrimitiveBlender |
| const bool kOpaquePaintColor = true; |
| |
| auto clipShader = PrecompileBase::SelectOption(SkSpan(fClipShaderOptions), |
| desiredClipShaderCombination); |
| |
| std::pair<sk_sp<PrecompileBlender>, int> finalBlender; |
| if (desiredBlendCombination < fBlendModeOptions.size()) { |
| finalBlender = { PrecompileBlender::Mode(fBlendModeOptions[desiredBlendCombination]), 0 }; |
| } else { |
| finalBlender = PrecompileBase::SelectOption( |
| SkSpan(fBlenderOptions), |
| desiredBlendCombination - fBlendModeOptions.size()); |
| } |
| if (!finalBlender.first) { |
| finalBlender = { PrecompileBlender::Mode(SkBlendMode::kSrcOver), 0 }; |
| } |
| DstReadRequirement dstReadReq = get_dst_read_req(keyContext.caps(), coverage, |
| finalBlender.first.get()); |
| |
| PaintOption option(kOpaquePaintColor, |
| finalBlender, |
| PrecompileBase::SelectOption(SkSpan(fShaderOptions), |
| desiredShaderCombination), |
| PrecompileBase::SelectOption(SkSpan(fColorFilterOptions), |
| desiredColorFilterCombination), |
| addPrimitiveBlender, |
| clipShader, |
| dstReadReq, |
| fDither); |
| |
| option.toKey(keyContext, keyBuilder, gatherer); |
| } |
| |
| namespace { |
| |
| void create_image_drawing_pipelines(const KeyContext& keyContext, |
| PipelineDataGatherer* gatherer, |
| const PaintOptions::ProcessCombination& processCombination, |
| const PaintOptions& orig) { |
| PaintOptions imagePaintOptions; |
| |
| // For imagefilters we know we don't have alpha-only textures and don't need cubic filtering. |
| sk_sp<PrecompileShader> imageShader = PrecompileShadersPriv::Image( |
| PrecompileImageShaderFlags::kExcludeAlpha | PrecompileImageShaderFlags::kExcludeCubic); |
| |
| imagePaintOptions.setShaders({ imageShader }); |
| imagePaintOptions.setBlendModes(orig.blendModes()); |
| imagePaintOptions.setBlenders(orig.blenders()); |
| imagePaintOptions.setColorFilters(orig.colorFilters()); |
| imagePaintOptions.addColorFilter(nullptr); |
| |
| imagePaintOptions.priv().buildCombinations(keyContext, |
| gatherer, |
| DrawTypeFlags::kSimpleShape, |
| /* withPrimitiveBlender= */ false, |
| Coverage::kSingleChannel, |
| processCombination); |
| } |
| |
| } // anonymous namespace |
| |
| void PaintOptions::buildCombinations( |
| const KeyContext& keyContext, |
| PipelineDataGatherer* gatherer, |
| DrawTypeFlags drawTypes, |
| bool withPrimitiveBlender, |
| Coverage coverage, |
| const ProcessCombination& processCombination) const { |
| |
| PaintParamsKeyBuilder builder(keyContext.dict()); |
| |
| if (!fImageFilterOptions.empty() || !fMaskFilterOptions.empty()) { |
| // TODO: split this out into a create_restore_draw_pipelines method |
| PaintOptions tmp = *this; |
| |
| // When image filtering, the original blend mode is taken over by the restore paint |
| tmp.setImageFilters({}); |
| tmp.setMaskFilters({}); |
| tmp.addBlendMode(SkBlendMode::kSrcOver); |
| |
| if (!fImageFilterOptions.empty()) { |
| std::vector<sk_sp<PrecompileColorFilter>> newCFs(tmp.fColorFilterOptions.begin(), |
| tmp.fColorFilterOptions.end()); |
| if (newCFs.empty()) { |
| // TODO: I (robertphillips) believe this is unnecessary and is just a result of the |
| // base SkPaint generated in the PaintParamsKeyTest not correctly taking CFIFs into |
| // account. |
| newCFs.push_back(nullptr); |
| } |
| |
| // As in SkCanvasPriv::ImageToColorFilter, we fuse CFIFs into the base draw's CFs. |
| // TODO: in SkCanvasPriv::ImageToColorFilter this fusing of CFIFs and CFs is skipped |
| // when there is a maskfilter. For now we over-generate. |
| for (const sk_sp<PrecompileImageFilter>& o : fImageFilterOptions) { |
| sk_sp<PrecompileColorFilter> imageFiltersCF = o ? o->asAColorFilter() : nullptr; |
| if (imageFiltersCF) { |
| if (!tmp.fColorFilterOptions.empty()) { |
| for (const sk_sp<PrecompileColorFilter>& cf : tmp.fColorFilterOptions) { |
| // TODO: if a CFIF was fully handled here it should be removed from the |
| // later loop over fImageFilterOptions. For now we over-generate. |
| sk_sp<PrecompileColorFilter> newCF = imageFiltersCF->makeComposed(cf); |
| newCFs.push_back(std::move(newCF)); |
| } |
| } else { |
| newCFs.push_back(imageFiltersCF); |
| } |
| } |
| } |
| |
| tmp.setColorFilters(newCFs); |
| } |
| |
| tmp.buildCombinations(keyContext, gatherer, drawTypes, withPrimitiveBlender, coverage, |
| processCombination); |
| |
| create_image_drawing_pipelines(keyContext, gatherer, processCombination, *this); |
| |
| for (const sk_sp<PrecompileImageFilter>& o : fImageFilterOptions) { |
| o->createPipelines(keyContext, gatherer, processCombination); |
| } |
| for (const sk_sp<PrecompileMaskFilter>& o : fMaskFilterOptions) { |
| o->createPipelines(keyContext, gatherer, processCombination); |
| } |
| } else { |
| int numCombinations = this->numCombinations(); |
| for (int i = 0; i < numCombinations; ++i) { |
| // Since the precompilation path's uniforms aren't used and don't change the key, |
| // the exact layout doesn't matter |
| gatherer->resetWithNewLayout(Layout::kMetal); |
| |
| this->createKey(keyContext, &builder, gatherer, i, withPrimitiveBlender, coverage); |
| |
| // The 'findOrCreate' calls lockAsKey on builder and then destroys the returned |
| // PaintParamsKey. This serves to reset the builder. |
| UniquePaintParamsID paintID = keyContext.dict()->findOrCreate(&builder); |
| |
| processCombination(paintID, drawTypes, withPrimitiveBlender, coverage); |
| } |
| } |
| } |
| |
| } // namespace skgpu::graphite |