blob: d526fbf3223c31443a98f2fef7a5b5908d97dc2b [file] [log] [blame]
/*
* 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