| /* | |
| * 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/SkTArray.h" | |
| #include "include/private/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()) { | |
| this->setDefaultShaderCaps(); | |
| } | |
| Caps::~Caps() {} | |
| void Caps::setDefaultShaderCaps() { | |
| fShaderCaps->fFlatInterpolationSupport = true; | |
| fShaderCaps->fShaderDerivativeSupport = true; | |
| fShaderCaps->fExplicitTextureLodSupport = true; | |
| fShaderCaps->fSampleMaskSupport = true; | |
| fShaderCaps->fInfinitySupport = true; | |
| fShaderCaps->fIntegerSupport = true; | |
| fShaderCaps->fNonsquareMatrixSupport = true; | |
| fShaderCaps->fInverseHyperbolicSupport = true; | |
| fShaderCaps->fFloatIs32Bits = true; | |
| } | |
| 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; | |
| fAvoidDepthMode = options.fAvoidDepthMode; | |
| // Enable setting this flag from either the private or public context options. | |
| fDrawListLayer |= options.fUseDrawListLayer; | |
| fResourceBindingReqs.fMaxFallbackTextureSize = | |
| std::max(std::min(ResourceBindingRequirements::kDefaultMaxFallbackTextureSize, | |
| fMaxTextureSize), | |
| 1); | |
| constexpr int kBytesPerTexel = 16; // 4 floats (RGBA32F) per fallback texel | |
| fResourceBindingReqs.fMaxFallbackTextureBytes = | |
| fResourceBindingReqs.fMaxFallbackTextureSize * | |
| fResourceBindingReqs.fMaxFallbackTextureSize * | |
| kBytesPerTexel; | |
| } | |
| 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::isReadable(const TextureInfo& info, bool allowMSAA) const { | |
| return this->isSupported(info, TextureUsage::kRead, | |
| 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::getDefaultReadableTextureInfo(TextureFormat format, Protected isProtected) const { | |
| return this->getDefaultTextureInfo(TextureUsage::kRead | | |
| TextureUsage::kCopySrc | | |
| TextureUsage::kCopyDst, | |
| SkSpan(&format, 1), | |
| SampleCount::k1, | |
| Mipmapped::kNo, | |
| 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::getTextureInfoForReadableCopy(const TextureInfo& info) const { | |
| return this->getDefaultReadableTextureInfo(TextureInfoPriv::ViewFormat(info), | |
| info.isProtected()); | |
| } | |
| 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); | |
| } | |
| TextureInfo Caps::getDefaultReadableStorageTextureInfo(TextureFormat format, | |
| Protected isProtected) const { | |
| return this->getDefaultTextureInfo(TextureUsage::kStorage | | |
| TextureUsage::kRead | | |
| TextureUsage::kCopySrc | | |
| TextureUsage::kCopyDst, | |
| SkSpan(&format, 1), | |
| SampleCount::k1, | |
| Mipmapped::kNo, | |
| isProtected, | |
| Discardable::kNo); | |
| } | |
| 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; | |
| } | |
| 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*/ | |
| this->supportBilerpFromGlyphAtlas()}; | |
| #else | |
| return sktext::gpu::SubRunControl{/*forcePathAA=*/true, this->supportBilerpFromGlyphAtlas()}; | |
| #endif | |
| } | |
| } // namespace skgpu::graphite |