| /* | |
| * Copyright 2024 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/gpu/graphite/precompile/PaintOptions.h" | |
| #include "include/gpu/graphite/precompile/PrecompileBlender.h" | |
| #include "include/gpu/graphite/precompile/PrecompileColorFilter.h" | |
| #include "include/gpu/graphite/precompile/PrecompileImageFilter.h" | |
| #include "include/gpu/graphite/precompile/PrecompileMaskFilter.h" | |
| #include "include/gpu/graphite/precompile/PrecompileShader.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/ContextUtils.h" | |
| #include "src/gpu/graphite/KeyContext.h" | |
| #include "src/gpu/graphite/PaintParamsKey.h" | |
| #include "src/gpu/graphite/PipelineData.h" | |
| #include "src/gpu/graphite/PrecompileInternal.h" | |
| #include "src/gpu/graphite/RenderPassDesc.h" | |
| #include "src/gpu/graphite/Renderer.h" | |
| #include "src/gpu/graphite/ShaderCodeDictionary.h" | |
| #include "src/gpu/graphite/precompile/PaintOption.h" | |
| #include "src/gpu/graphite/precompile/PaintOptionsPriv.h" | |
| #include "src/gpu/graphite/precompile/PrecompileBaseComplete.h" | |
| #include "src/gpu/graphite/precompile/PrecompileBasePriv.h" | |
| #include "src/gpu/graphite/precompile/PrecompileBlenderPriv.h" | |
| #include "src/gpu/graphite/precompile/PrecompileShaderPriv.h" | |
| #include "src/gpu/graphite/precompile/PrecompileShadersPriv.h" | |
| namespace skgpu::graphite { | |
| PaintOptions::PaintOptions() = default; | |
| PaintOptions::PaintOptions(const PaintOptions&) = default; | |
| PaintOptions::~PaintOptions() = default; | |
| PaintOptions& PaintOptions::operator=(const PaintOptions&) = default; | |
| void PaintOptions::setShaders(SkSpan<const sk_sp<PrecompileShader>> shaders) { | |
| fShaderOptions.clear(); | |
| fShaderOptions.push_back_n(shaders.size(), shaders.data()); | |
| } | |
| void PaintOptions::setImageFilters(SkSpan<const sk_sp<PrecompileImageFilter>> imageFilters) { | |
| fImageFilterOptions.clear(); | |
| fImageFilterOptions.push_back_n(imageFilters.size(), imageFilters.data()); | |
| } | |
| void PaintOptions::setMaskFilters(SkSpan<const sk_sp<PrecompileMaskFilter>> maskFilters) { | |
| fMaskFilterOptions.clear(); | |
| fMaskFilterOptions.push_back_n(maskFilters.size(), maskFilters.data()); | |
| } | |
| void PaintOptions::setColorFilters(SkSpan<const sk_sp<PrecompileColorFilter>> colorFilters) { | |
| fColorFilterOptions.clear(); | |
| fColorFilterOptions.push_back_n(colorFilters.size(), colorFilters.data()); | |
| } | |
| void PaintOptions::addColorFilter(sk_sp<PrecompileColorFilter> cf) { | |
| fColorFilterOptions.push_back(std::move(cf)); | |
| } | |
| void PaintOptions::setBlendModes(SkSpan<const SkBlendMode> blendModes) { | |
| fBlendModeOptions.clear(); | |
| fBlendModeOptions.push_back_n(blendModes.size(), blendModes.data()); | |
| } | |
| void PaintOptions::setBlenders(SkSpan<const sk_sp<PrecompileBlender>> blenders) { | |
| for (const sk_sp<PrecompileBlender>& b: blenders) { | |
| if (b->priv().asBlendMode().has_value()) { | |
| fBlendModeOptions.push_back(b->priv().asBlendMode().value()); | |
| } else { | |
| fBlenderOptions.push_back(b); | |
| } | |
| } | |
| } | |
| 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::numBlendCombinations() const { | |
| int numBlendCombos = fBlendModeOptions.size(); | |
| for (const sk_sp<PrecompileBlender>& b: fBlenderOptions) { | |
| SkASSERT(!b->priv().asBlendMode().has_value()); | |
| numBlendCombos += b->priv().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->priv().numChildCombinations(); | |
| } else { | |
| ++numClipShaderCombos; | |
| } | |
| } | |
| // If no clipShader options are specified we will just have the unclipped options | |
| return numClipShaderCombos ? numClipShaderCombos : 1; | |
| } | |
| int PaintOptions::numCombinations() const { | |
| return this->numShaderCombinations() * | |
| this->numColorFilterCombinations() * | |
| this->numBlendCombinations() * | |
| this->numClipShaderCombinations(); | |
| } | |
| void PaintOptions::createKey(const KeyContext& keyContext, | |
| TextureFormat targetFormat, | |
| int desiredCombination, | |
| bool addPrimitiveBlender, | |
| bool addAnalyticClip, | |
| Coverage coverage) const { | |
| SkASSERT(desiredCombination < this->numCombinations()); | |
| const int numClipShaderCombos = this->numClipShaderCombinations(); | |
| const int numBlendModeCombos = this->numBlendCombinations(); | |
| 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()); | |
| auto clipShader = PrecompileBase::SelectOption(SkSpan(fClipShaderOptions), | |
| desiredClipShaderCombination); | |
| std::pair<sk_sp<PrecompileBlender>, int> finalBlender; | |
| if (desiredBlendCombination < fBlendModeOptions.size()) { | |
| finalBlender = { PrecompileBlenders::Mode(fBlendModeOptions[desiredBlendCombination]), 0 }; | |
| } else { | |
| finalBlender = PrecompileBase::SelectOption( | |
| SkSpan(fBlenderOptions), | |
| desiredBlendCombination - fBlendModeOptions.size()); | |
| } | |
| if (!finalBlender.first) { | |
| finalBlender = { PrecompileBlenders::Mode(SkBlendMode::kSrcOver), 0 }; | |
| } | |
| PaintOption option(fPaintColorIsOpaque, | |
| finalBlender, | |
| PrecompileBase::SelectOption(SkSpan(fShaderOptions), | |
| desiredShaderCombination), | |
| PrecompileBase::SelectOption(SkSpan(fColorFilterOptions), | |
| desiredColorFilterCombination), | |
| addPrimitiveBlender, | |
| fPrimitiveBlendMode, | |
| fSkipColorXform, | |
| clipShader, | |
| coverage, | |
| targetFormat, | |
| fDither, | |
| addAnalyticClip); | |
| option.toKey(keyContext); | |
| } | |
| namespace { | |
| void create_image_drawing_pipelines(const KeyContext& keyContext, | |
| const PaintOptions& orig, | |
| const RenderPassDesc& renderPassDesc, | |
| const PaintOptionsPriv::ProcessCombination& processCombination) { | |
| PaintOptions imagePaintOptions; | |
| // For imagefilters we know we don't have alpha-only textures and don't need cubic filtering. | |
| sk_sp<PrecompileShader> imageShader = PrecompileShaders::Image( | |
| PrecompileShaders::ImageShaderFlags::kNoAlphaNoCubic); | |
| imagePaintOptions.setShaders({{ imageShader }}); | |
| imagePaintOptions.setBlendModes(orig.getBlendModes()); | |
| imagePaintOptions.setBlenders(orig.getBlenders()); | |
| imagePaintOptions.setColorFilters(orig.getColorFilters()); | |
| imagePaintOptions.priv().addColorFilter(nullptr); | |
| imagePaintOptions.priv().buildCombinations(keyContext, | |
| DrawTypeFlags::kSimpleShape, | |
| /* withPrimitiveBlender= */ false, | |
| Coverage::kSingleChannel, | |
| renderPassDesc, | |
| processCombination); | |
| } | |
| } // anonymous namespace | |
| void PaintOptions::buildCombinations( | |
| const KeyContext& keyContext, | |
| DrawTypeFlags drawTypes, | |
| bool withPrimitiveBlender, | |
| Coverage coverage, | |
| const RenderPassDesc& renderPassDesc, | |
| const ProcessCombination& processCombination) const { | |
| 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, drawTypes, withPrimitiveBlender, coverage, renderPassDesc, | |
| processCombination); | |
| create_image_drawing_pipelines(keyContext, *this, renderPassDesc, processCombination); | |
| for (const sk_sp<PrecompileImageFilter>& o : fImageFilterOptions) { | |
| o->createPipelines(keyContext, renderPassDesc, processCombination); | |
| } | |
| for (const sk_sp<PrecompileMaskFilter>& o : fMaskFilterOptions) { | |
| o->createPipelines(keyContext, *this, renderPassDesc, processCombination); | |
| } | |
| } else { | |
| int numCombinations = this->numCombinations(); | |
| // This matches the logic in Device::drawGeometry() that optimizes inner-fill capable and | |
| // non-AA draws to disable HW blending when possible. | |
| KeyContext finalContext = keyContext; | |
| if (drawTypes & kSimpleShape || coverage == Coverage::kNone) { | |
| finalContext = keyContext.withExtraFlags(KeyGenFlags::kPreferFixedSrcBlend); | |
| } | |
| 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 | |
| keyContext.pipelineDataGatherer()->resetForDraw(); | |
| keyContext.paintParamsKeyBuilder()->resetForDraw(); | |
| this->createKey(finalContext, renderPassDesc.fColorAttachment.fFormat, | |
| i, withPrimitiveBlender, | |
| SkToBool(drawTypes & DrawTypeFlags::kAnalyticClip), coverage); | |
| // Reset the builder after we get the paintID, we don't need the key anymore | |
| // for precompilation. | |
| UniquePaintParamsID paintID = finalContext.dict()->findOrCreate( | |
| finalContext.paintParamsKeyBuilder()); | |
| processCombination(paintID, drawTypes, withPrimitiveBlender, coverage, renderPassDesc); | |
| } | |
| } | |
| } | |
| void PaintOptionsPriv::addColorFilter(sk_sp<PrecompileColorFilter> cf) { | |
| fPaintOptions->addColorFilter(std::move(cf)); | |
| } | |
| } // namespace skgpu::graphite |