| /* |
| * Copyright 2021 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/Caps.h" |
| |
| #include "include/core/SkCapabilities.h" |
| #include "include/gpu/ShaderErrorHandler.h" |
| #include "include/gpu/graphite/ContextOptions.h" |
| #include "include/gpu/graphite/TextureInfo.h" |
| #include "include/private/base/SkTArray.h" |
| #include "include/private/base/SkTo.h" |
| #include "src/gpu/graphite/ContextOptionsPriv.h" |
| #include "src/gpu/graphite/RenderPassDesc.h" |
| #include "src/gpu/graphite/ResourceTypes.h" |
| #include "src/gpu/graphite/TextureInfoPriv.h" |
| #include "src/sksl/SkSLUtil.h" |
| |
| #include <algorithm> |
| |
| namespace skgpu::graphite { |
| |
| Caps::Caps() |
| : fShaderCaps(std::make_unique<SkSL::ShaderCaps>()) |
| , fCapabilities(new SkCapabilities()) {} |
| |
| Caps::~Caps() {} |
| |
| void Caps::finishInitialization(const ContextOptions& options) { |
| fCapabilities->initSkCaps(fShaderCaps.get()); |
| |
| fMaxInternalSampleCount = options.fInternalMultisampleCount; |
| |
| if (options.fShaderErrorHandler) { |
| fShaderErrorHandler = options.fShaderErrorHandler; |
| } else { |
| fShaderErrorHandler = DefaultShaderErrorHandler(); |
| } |
| |
| #if defined(GPU_TEST_UTILS) |
| if (options.fOptionsPriv) { |
| fMaxTextureSize = std::min(fMaxTextureSize, options.fOptionsPriv->fMaxTextureSizeOverride); |
| fRequestedPathRendererStrategy = options.fOptionsPriv->fPathRendererStrategy; |
| fDrawListLayer = options.fOptionsPriv->fDrawListLayer; |
| } |
| #endif |
| fGlyphCacheTextureMaximumBytes = options.fGlyphCacheTextureMaximumBytes; |
| fMinMSAAPathSize = options.fMinimumPathSizeForMSAA; |
| fMinDistanceFieldFontSize = options.fMinDistanceFieldFontSize; |
| fGlyphsAsPathsFontSize = options.fGlyphsAsPathsFontSize; |
| fMaxPathAtlasTextureSize = options.fMaxPathAtlasTextureSize; |
| fAllowMultipleAtlasTextures = options.fAllowMultipleAtlasTextures; |
| fSupportBilerpFromGlyphAtlas = options.fSupportBilerpFromGlyphAtlas; |
| fRequireOrderedRecordings = options.fRequireOrderedRecordings; |
| fSetBackendLabels = options.fSetBackendLabels; |
| } |
| |
| sk_sp<SkCapabilities> Caps::capabilities() const { return fCapabilities; } |
| |
| bool Caps::isSampleCountSupported(TextureFormat format, SampleCount sampleCount) const { |
| // Assume optimal tiling |
| auto [formatSupport, sampleCounts] = this->getTextureSupport(format, Tiling::kOptimal); |
| return SkToBool(formatSupport & TextureUsage::kRender) && SkToBool(sampleCounts & sampleCount); |
| } |
| |
| TextureFormat Caps::getDepthStencilFormat(SkEnumBitMask<DepthStencilFlags> dssFlags) const { |
| auto canUse = [this](TextureFormat format) { |
| auto [formatSupport, sampleCounts] = this->getTextureSupport(format, Tiling::kOptimal); |
| // Check that the format can be rendered into and that it supports single-sampled rendering, |
| // and if we aren't avoiding MSAA, that it also has some additional sample count. |
| return SkToBool(formatSupport & TextureUsage::kRender) && |
| SkToBool(sampleCounts & SampleCount::k1) && |
| (this->avoidMSAA() || sampleCounts != SampleCount::k1); |
| }; |
| |
| if (dssFlags == DepthStencilFlags::kDepth) { |
| // Prefer D16, but fallback to D32F or lastly a combined DS format if needed |
| if (canUse(TextureFormat::kD16)) { |
| return TextureFormat::kD16; |
| } else if (canUse(TextureFormat::kD32F)) { |
| return TextureFormat::kD32F; |
| } else { |
| return this->getDepthStencilFormat(DepthStencilFlags::kDepthStencil); |
| } |
| } else if (dssFlags == DepthStencilFlags::kStencil) { |
| // Prefer S8, but fallback to a combined DS format if needed |
| if (canUse(TextureFormat::kS8)) { |
| return TextureFormat::kS8; |
| } else { |
| return this->getDepthStencilFormat(DepthStencilFlags::kDepthStencil); |
| } |
| } else if (dssFlags == DepthStencilFlags::kDepthStencil) { |
| // Prefer D24_S8 over D32F_S8 for memory savings if it is available |
| if (canUse(TextureFormat::kD24_S8)) { |
| return TextureFormat::kD24_S8; |
| } else { |
| return TextureFormat::kD32F_S8; |
| } |
| } |
| |
| return TextureFormat::kUnsupported; // i.e. no attachment needed |
| } |
| |
| bool Caps::isSupported(const TextureInfo& info, |
| SkEnumBitMask<TextureUsage> test, |
| bool allowMSAA, |
| bool allowExternal, |
| bool allowCompressed, |
| bool allowProtected) const { |
| const TextureFormat format = TextureInfoPriv::ViewFormat(info); |
| if (format == TextureFormat::kUnsupported) { |
| return false; |
| } |
| SkASSERT(info.isValid()); |
| |
| auto [textureUsage, tiling] = this->getTextureUsage(info); |
| auto [formatSupport, supportedSampleCounts] = this->getTextureSupport(format, tiling); |
| |
| if (!allowMSAA) { |
| // Remove everything but 1x if the operation requires non-MSAA |
| supportedSampleCounts &= SampleCount::k1; |
| } |
| |
| // Intersect what the format and the texture can do to see if `test` is available, and make |
| // sure that the texture's sample count is supported. |
| if ((formatSupport & textureUsage & test) == test && |
| SkToBool(supportedSampleCounts & info.sampleCount())) { |
| // Basic rules that should be reflected in the supported operations bit masks |
| SkASSERT((allowProtected || info.isProtected() == Protected::kNo) && |
| (allowMSAA || info.sampleCount() == SampleCount::k1) && |
| (allowCompressed || TextureFormatCompressionType(format) == |
| SkTextureCompressionType::kNone) && |
| (allowExternal || format != TextureFormat::kExternal)); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| bool Caps::isTexturable(const TextureInfo& info, bool allowMSAA) const { |
| return this->isSupported(info, TextureUsage::kSample, |
| allowMSAA, |
| /*allowExternal=*/true, |
| /*allowCompressed=*/true, |
| /*allowProtected=*/true); |
| } |
| |
| bool Caps::isRenderable(const TextureInfo& info) const { |
| return this->isSupported(info, TextureUsage::kRender, |
| /*allowMSAA=*/true, |
| /*allowExternal=*/true, |
| /*allowCompressed=*/false, |
| /*allowProtected=*/true); |
| } |
| |
| bool Caps::isCopyableSrc(const TextureInfo& info) const { |
| return this->isSupported(info, TextureUsage::kCopySrc, |
| /*allowMSAA=*/false, |
| /*allowExternal=*/false, |
| /*allowCompressed=*/false, |
| /*allowProtected=*/false); |
| } |
| |
| bool Caps::isCopyableDst(const TextureInfo& info) const { |
| return this->isSupported(info, TextureUsage::kCopyDst, |
| /*allowMSAA=*/false, |
| /*allowExternal=*/false, |
| /*allowCompressed=*/true, |
| /*allowProtected=*/true); |
| } |
| |
| bool Caps::isStorage(const TextureInfo& info) const { |
| return this->isSupported(info, TextureUsage::kStorage, |
| /*allowMSAA=*/false, |
| /*allowExternal=*/false, |
| /*allowCompressed=*/false, |
| /*allowProtected=*/false); |
| } |
| |
| bool Caps::isRenderableWithMSRTSS(const TextureInfo& info) const { |
| return this->isSupported(info, TextureUsage::kMSRTSS | TextureUsage::kRender, |
| /*allowMSAA=*/true, |
| /*allowExternal=*/true, |
| /*allowCompressed=*/false, |
| /*allowProtected=*/true); |
| } |
| |
| TextureInfo Caps::getDefaultTextureInfo(SkEnumBitMask<TextureUsage> usage, |
| SkSpan<const TextureFormat> formats, |
| SampleCount sampleCount, |
| Mipmapped mipmapped, |
| Protected isProtected, |
| Discardable discardable) const { |
| // Assert we're only requesting Discardable::kYes when the requested usages make sense for it. |
| [[maybe_unused]] static constexpr SkEnumBitMask<TextureUsage> kDiscardableAllowed = |
| TextureUsage::kRender | TextureUsage::kMSRTSS; |
| SkASSERT(discardable == Discardable::kNo || (usage & kDiscardableAllowed) == usage); |
| |
| if (isProtected == Protected::kYes && !this->protectedSupport()) { |
| return {}; // Cannot handle protected content on this Context |
| } |
| |
| for (TextureFormat format : formats) { |
| auto [supportedUsage, supportedSampleCounts] = |
| this->getTextureSupport(format, Tiling::kOptimal); |
| if ((supportedUsage & usage) != usage || !SkToBool(supportedSampleCounts & sampleCount)) { |
| continue; // unsupported, move on to the next possible format |
| } |
| |
| if (SkToBool(usage & TextureUsage::kRender) && |
| SkToBool(supportedUsage & TextureUsage::kMSRTSS) && |
| sampleCount == SampleCount::k1) { |
| // Proactivately prepare a single-sampled image for use with MSRTSS if it's supported by |
| // the format and kRender is requested. This flag is expected to be harmless (if not, |
| // it's a driver bug). |
| usage |= TextureUsage::kMSRTSS; |
| } |
| |
| if (SkToBool(usage & TextureUsage::kCopyDst) && |
| SkToBool(supportedUsage & TextureUsage::kHostCopy) && |
| !SkToBool(usage & TextureUsage::kRender) && |
| isProtected == Protected::kNo) { |
| // Proactively enable kHostCopy when supported by the format and kCopyDst is requested, |
| // so long as it's not going to be protected or rendered into. On every known driver |
| // where VK_EXT_host_image_copy is used by Skia, it's known that using the |
| // host-image-copy flag reduces the performance of renderable images. |
| usage |= TextureUsage::kHostCopy; |
| } |
| return this->onGetDefaultTextureInfo(usage, format, sampleCount, mipmapped, |
| isProtected, discardable); |
| } |
| |
| // None of the possible formats were supported |
| return {}; |
| } |
| |
| TextureInfo Caps::getDefaultAttachmentTextureInfo(AttachmentDesc desc, |
| Protected isProtected, |
| Discardable discardable) const { |
| return this->getDefaultTextureInfo(TextureUsage::kRender, |
| SkSpan(&desc.fFormat, 1), |
| desc.fSampleCount, |
| Mipmapped::kNo, |
| isProtected, |
| discardable); |
| } |
| |
| // Graphite by default requires copy-src and copy-dst for sampled textures. |
| static constexpr SkEnumBitMask<TextureUsage> kDefaultSampledUsage = |
| TextureUsage::kSample | TextureUsage::kCopySrc | TextureUsage::kCopyDst; |
| |
| TextureInfo Caps::getDefaultSampledTextureInfo(SkColorType colorType, |
| Mipmapped mipmapped, |
| Protected isProtected, |
| Renderable renderable) const { |
| SkEnumBitMask<TextureUsage> usage = kDefaultSampledUsage; |
| SkSpan<const TextureFormat> formats = PreferredTextureFormats(colorType); |
| skia_private::STArray<3, TextureFormat> validFormats; |
| if (renderable == Renderable::kYes) { |
| usage |= TextureUsage::kRender; |
| // Any possible preferred format must also have a valid write swizzle for the requested |
| // color type. |
| for (TextureFormat f : formats) { |
| if (WriteSwizzleForColorType(colorType, f).has_value()) { |
| validFormats.push_back(f); |
| } |
| } |
| formats = validFormats; |
| } |
| |
| return this->getDefaultTextureInfo(usage, |
| formats, |
| SampleCount::k1, |
| mipmapped, |
| isProtected, |
| Discardable::kNo); |
| } |
| |
| TextureInfo Caps::getTextureInfoForSampledCopy(const TextureInfo& info, Mipmapped mipmapped) const { |
| const TextureFormat format = TextureInfoPriv::ViewFormat(info); |
| return this->getDefaultTextureInfo(kDefaultSampledUsage, |
| SkSpan(&format, 1), |
| SampleCount::k1, |
| mipmapped, |
| info.isProtected(), |
| Discardable::kNo); |
| } |
| |
| TextureInfo Caps::getDefaultCompressedTextureInfo(SkTextureCompressionType compressionType, |
| Mipmapped mipmapped, |
| Protected isProtected) const { |
| // Remove CopySrc for compressed textures |
| const TextureFormat format = CompressionTypeToTextureFormat(compressionType); |
| return this->getDefaultTextureInfo(kDefaultSampledUsage & ~TextureUsage::kCopySrc, |
| SkSpan(&format, 1), |
| SampleCount::k1, |
| mipmapped, |
| isProtected, |
| Discardable::kNo); |
| } |
| |
| TextureInfo Caps::getDefaultStorageTextureInfo(SkColorType colorType) const { |
| // Storage textures are currently always assumed to be sampleable from a shader and can be |
| // copied out of (for unit tests). |
| return this->getDefaultTextureInfo(TextureUsage::kStorage | |
| TextureUsage::kSample | |
| TextureUsage::kCopySrc, |
| PreferredTextureFormats(colorType), |
| SampleCount::k1, |
| Mipmapped::kNo, |
| Protected::kNo, |
| Discardable::kNo); |
| } |
| |
| bool Caps::areColorTypeAndTextureInfoCompatible(SkColorType ct, const TextureInfo& info) const { |
| // TODO: add SkTextureCompressionType handling |
| // (can be handled by setting up the colorTypeInfo instead?) |
| |
| return SkToBool(this->getColorTypeInfo(ct, info)); |
| } |
| |
| const Caps::ColorTypeInfo* Caps::getColorTypeInfo(SkColorType ct, const TextureInfo& info) const { |
| if (!info.isValid()) { |
| return nullptr; |
| } |
| |
| for (const ColorTypeInfo& colorInfo : this->getColorTypeInfos(info)) { |
| if (colorInfo.fColorType == ct) { |
| return &colorInfo; |
| } |
| } |
| return nullptr; |
| } |
| |
| SkColorType Caps::getDefaultColorType(const TextureInfo& info) const { |
| if (!info.isValid()) { |
| return kUnknown_SkColorType; |
| } |
| |
| const bool isRenderable = this->isRenderable(info); |
| for (const ColorTypeInfo& colorInfo : this->getColorTypeInfos(info)) { |
| if (!isRenderable || (colorInfo.fFlags & ColorTypeInfo::kRenderable_Flag)) { |
| return colorInfo.fColorType; |
| } |
| } |
| return kUnknown_SkColorType; |
| } |
| |
| SampleCount Caps::getCompatibleMSAASampleCount(const TextureInfo& info) const { |
| if (info.sampleCount() > SampleCount::k1) { |
| // Use the inherent sample count since it's already MSAA |
| return info.sampleCount(); |
| } else if (!this->avoidMSAA()) { |
| // The max internal sample count may be higher than what is universally supported for |
| // every renderable TextureFormat, but unless avoidMSAA() was true, this should bottom out |
| // at SampleCount::k4. |
| TextureFormat format = TextureInfoPriv::ViewFormat(info); |
| for (SampleCount s = fMaxInternalSampleCount; |
| s > SampleCount::k1; |
| s = static_cast<SampleCount>((uint8_t)s >> 1)) { |
| if (this->isSampleCountSupported(format, s)) { |
| return s; |
| } |
| } |
| } |
| |
| // If we got here, MSAA has been disabled somehow (by ContextOption, driver workaround, or |
| // no support for a particular TextureFormat). |
| return SampleCount::k1; |
| } |
| |
| std::pair<SkColorType, bool /*isRGBFormat*/> Caps::supportedTransferColorType( |
| SkColorType colorType, |
| const TextureInfo& textureInfo) const { |
| // NOTE: Compressed textures can't be read back, and external format textures can't be read or |
| // written to. However, this is not checked here. Instead that is expected to be handled by |
| // supports[Read|Write]Pixels(). |
| const ColorTypeInfo* colorInfo = this->getColorTypeInfo(colorType, textureInfo); |
| if (colorInfo) { |
| const TextureFormat format = TextureInfoPriv::ViewFormat(textureInfo); |
| const bool rgbRequiresIntervention = |
| TextureFormatChannelMask(format) == kRGB_SkColorChannelFlags && |
| colorInfo->fTransferColorType != kRGB_565_SkColorType; |
| return {colorInfo->fTransferColorType, rgbRequiresIntervention}; |
| } else { |
| return {kUnknown_SkColorType, false}; |
| } |
| } |
| |
| DstReadStrategy Caps::getDstReadStrategy() const { |
| // TODO(b/238757201; b/383769988): Dst reads are currently only supported by FB fetch and |
| // texture copy. |
| if (this->shaderCaps()->fFBFetchSupport) { |
| return DstReadStrategy::kFramebufferFetch; |
| } else { |
| return DstReadStrategy::kTextureCopy; |
| } |
| } |
| |
| sktext::gpu::SubRunControl Caps::getSubRunControl(bool useSDFTForSmallText) const { |
| #if !defined(SK_DISABLE_SDF_TEXT) |
| return sktext::gpu::SubRunControl{ |
| this->shaderCaps()->supportsDistanceFieldText(), |
| useSDFTForSmallText, |
| true, /*ableToUsePerspectiveSDFT*/ |
| this->minDistanceFieldFontSize(), |
| this->glyphsAsPathsFontSize(), |
| true /*forcePathAA*/}; |
| #else |
| return sktext::gpu::SubRunControl{/*forcePathAA=*/true}; |
| #endif |
| } |
| |
| } // namespace skgpu::graphite |