blob: a7145b4be761b21f21d45fc002a732bc06d1b7aa [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 "experimental/graphite/src/mtl/MtlCaps.h"
#include "experimental/graphite/include/TextureInfo.h"
#include "experimental/graphite/include/mtl/MtlTypes.h"
#include "experimental/graphite/src/CommandBuffer.h"
#include "experimental/graphite/src/GraphicsPipelineDesc.h"
#include "experimental/graphite/src/GraphiteResourceKey.h"
#include "experimental/graphite/src/mtl/MtlUtils.h"
#include "src/sksl/SkSLUtil.h"
namespace skgpu::mtl {
Caps::Caps(const id<MTLDevice> device)
: skgpu::Caps() {
fShaderCaps = std::make_unique<SkSL::ShaderCaps>();
this->initGPUFamily(device);
this->initCaps(device);
this->initShaderCaps();
this->initFormatTable();
// Metal-specific caps
}
// translates from older MTLFeatureSet interface to MTLGPUFamily interface
bool Caps::GetGPUFamilyFromFeatureSet(id<MTLDevice> device, GPUFamily* gpuFamily, int* group) {
#if defined(SK_BUILD_FOR_MAC)
// Apple Silicon is only available in later OSes
*gpuFamily = GPUFamily::kMac;
// Mac OSX 14
if (@available(macOS 10.14, *)) {
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) {
*group = 2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v4]) {
*group = 1;
return true;
}
}
// Mac OSX 13
if (@available(macOS 10.13, *)) {
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v3]) {
*group = 1;
return true;
}
}
// Mac OSX 12
if (@available(macOS 10.12, *)) {
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v2]) {
*group = 1;
return true;
}
}
// Mac OSX 11
if (@available(macOS 10.11, *)) {
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1]) {
*group = 1;
return true;
}
}
#elif defined(SK_BUILD_FOR_IOS)
// TODO: support tvOS
*gpuFamily = GPUFamily::kApple;
// iOS 12
if (@available(iOS 12.0, *)) {
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily5_v1]) {
*group = 5;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v2]) {
*group = 4;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v4]) {
*group = 3;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v5]) {
*group = 2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v5]) {
*group = 1;
return true;
}
}
// iOS 11
if (@available(iOS 11.0, *)) {
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
*group = 4;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v3]) {
*group = 3;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v4]) {
*group = 2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v4]) {
*group = 1;
return true;
}
}
// iOS 10
if (@available(iOS 10.0, *)) {
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v2]) {
*group = 3;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v3]) {
*group = 2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v3]) {
*group = 1;
return true;
}
}
// We don't support earlier OSes
#endif
// No supported GPU families were found
return false;
}
bool Caps::GetGPUFamily(id<MTLDevice> device, GPUFamily* gpuFamily, int* group) {
#if GR_METAL_SDK_VERSION >= 220
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
// Apple Silicon
#if GR_METAL_SDK_VERSION >= 230
if ([device supportsFamily:MTLGPUFamilyApple7]) {
*gpuFamily = GPUFamily::kApple;
*group = 7;
return true;
}
#endif
#ifdef SK_BUILD_FOR_IOS
if ([device supportsFamily:MTLGPUFamilyApple6]) {
*gpuFamily = GPUFamily::kApple;
*group = 6;
return true;
}
if ([device supportsFamily:MTLGPUFamilyApple5]) {
*gpuFamily = GPUFamily::kApple;
*group = 5;
return true;
}
if ([device supportsFamily:MTLGPUFamilyApple4]) {
*gpuFamily = GPUFamily::kApple;
*group = 4;
return true;
}
if ([device supportsFamily:MTLGPUFamilyApple3]) {
*gpuFamily = GPUFamily::kApple;
*group = 3;
return true;
}
if ([device supportsFamily:MTLGPUFamilyApple2]) {
*gpuFamily = GPUFamily::kApple;
*group = 2;
return true;
}
if ([device supportsFamily:MTLGPUFamilyApple1]) {
*gpuFamily = GPUFamily::kApple;
*group = 1;
return true;
}
#endif
// Older Macs
// At the moment MacCatalyst families have the same features as Mac,
// so we treat them the same
if ([device supportsFamily:MTLGPUFamilyMac2] ||
[device supportsFamily:MTLGPUFamilyMacCatalyst2]) {
*gpuFamily = GPUFamily::kMac;
*group = 2;
return true;
}
if ([device supportsFamily:MTLGPUFamilyMac1] ||
[device supportsFamily:MTLGPUFamilyMacCatalyst1]) {
*gpuFamily = GPUFamily::kMac;
*group = 1;
return true;
}
}
#endif
// No supported GPU families were found
return false;
}
void Caps::initGPUFamily(id<MTLDevice> device) {
if (!GetGPUFamily(device, &fGPUFamily, &fFamilyGroup) &&
!GetGPUFamilyFromFeatureSet(device, &fGPUFamily, &fFamilyGroup)) {
// We don't know what this is, fall back to minimum defaults
#ifdef SK_BUILD_FOR_MAC
fGPUFamily = GPUFamily::kMac;
fFamilyGroup = 1;
#else
fGPUFamily = GPUFamily::kApple;
fFamilyGroup = 1;
#endif
}
}
void Caps::initCaps(const id<MTLDevice> device) {
if (this->isMac() || fFamilyGroup >= 3) {
fMaxTextureSize = 16384;
} else {
fMaxTextureSize = 8192;
}
// We use constant address space for our uniform buffers which has various alignment
// requirements for the offset when binding the buffer. On MacOS the offset must align to 256.
// On iOS we must align to the max of the data type consumed by the vertex function or 4 bytes.
// We can ignore the data type and just always use 16 bytes on iOS.
if (this->isMac()) {
fRequiredUniformBufferAlignment = 256;
} else {
fRequiredUniformBufferAlignment = 16;
}
if (@available(macOS 10.12, ios 14.0, *)) {
fClampToBorderSupport = (this->isMac() || fFamilyGroup >= 7);
} else {
fClampToBorderSupport = false;
}
}
void Caps::initShaderCaps() {
SkSL::ShaderCaps* shaderCaps = fShaderCaps.get();
// Setting this true with the assumption that this cap will eventually mean we support varying
// precisions and not just via modifiers.
shaderCaps->fUsesPrecisionModifiers = true;
shaderCaps->fFlatInterpolationSupport = true;
shaderCaps->fShaderDerivativeSupport = true;
// TODO(skia:8270): Re-enable this once bug 8270 is fixed
#if 0
if (this->isApple()) {
shaderCaps->fFBFetchSupport = true;
shaderCaps->fFBFetchNeedsCustomOutput = true; // ??
shaderCaps->fFBFetchColorName = ""; // Somehow add [[color(0)]] to arguments to frag shader
}
#endif
shaderCaps->fIntegerSupport = true;
shaderCaps->fNonsquareMatrixSupport = true;
shaderCaps->fInverseHyperbolicSupport = true;
// Metal uses IEEE floats so assuming those values here.
// TODO: add fHalfIs32Bits?
shaderCaps->fFloatIs32Bits = true;
}
void Caps::initFormatTable() {
// TODO
}
skgpu::TextureInfo Caps::getDefaultSampledTextureInfo(SkColorType colorType,
uint32_t levelCount,
Protected,
Renderable renderable) const {
MTLTextureUsage usage = MTLTextureUsageShaderRead;
if (renderable == Renderable::kYes) {
usage |= MTLTextureUsageRenderTarget;
}
TextureInfo info;
info.fSampleCount = 1;
info.fLevelCount = levelCount;
info.fFormat = SkColorTypeToFormat(colorType);
info.fUsage = usage;
info.fStorageMode = MTLStorageModePrivate;
info.fFramebufferOnly = false;
return info;
}
skgpu::TextureInfo Caps::getDefaultMSAATextureInfo(SkColorType colorType,
uint32_t sampleCount,
Protected) const {
MTLTextureUsage usage = MTLTextureUsageRenderTarget;
TextureInfo info;
info.fSampleCount = sampleCount;
info.fLevelCount = 1;
info.fFormat = SkColorTypeToFormat(colorType);
info.fUsage = usage;
info.fStorageMode = MTLStorageModePrivate;
info.fFramebufferOnly = false;
return info;
}
skgpu::TextureInfo Caps::getDefaultDepthStencilTextureInfo(Mask<DepthStencilFlags> depthStencilType,
uint32_t sampleCount,
Protected) const {
TextureInfo info;
info.fSampleCount = sampleCount;
info.fLevelCount = 1;
info.fFormat = DepthStencilFlagsToFormat(depthStencilType);
info.fUsage = MTLTextureUsageRenderTarget;
info.fStorageMode = MTLStorageModePrivate;
info.fFramebufferOnly = false;
return info;
}
UniqueKey Caps::makeGraphicsPipelineKey(const GraphicsPipelineDesc& pipelineDesc,
const RenderPassDesc& renderPassDesc) const {
UniqueKey pipelineKey;
{
static const skgpu::UniqueKey::Domain kGraphicsPipelineDomain = UniqueKey::GenerateDomain();
SkSpan<const uint32_t> pipelineDescKey = pipelineDesc.asKey();
UniqueKey::Builder builder(&pipelineKey, kGraphicsPipelineDomain,
pipelineDescKey.size() + 1, "GraphicsPipeline");
// add graphicspipelinedesc key
for (unsigned int i = 0; i < pipelineDescKey.size(); ++i) {
builder[i] = pipelineDescKey[i];
}
// add renderpassdesc key
mtl::TextureInfo colorInfo, depthStencilInfo;
renderPassDesc.fColorAttachment.fTextureInfo.getMtlTextureInfo(&colorInfo);
renderPassDesc.fDepthStencilAttachment.fTextureInfo.getMtlTextureInfo(&depthStencilInfo);
SkASSERT(colorInfo.fFormat < 65535 && depthStencilInfo.fFormat < 65535);
uint32_t renderPassKey = colorInfo.fFormat << 16 | depthStencilInfo.fFormat;
builder[pipelineDescKey.size()] = renderPassKey;
builder.finish();
}
return pipelineKey;
}
bool Caps::onIsTexturable(const skgpu::TextureInfo& info) const {
if (!(info.mtlTextureSpec().fUsage & MTLTextureUsageShaderRead)) {
return false;
}
if (info.mtlTextureSpec().fFramebufferOnly) {
return false;
}
return this->isTexturable((MTLPixelFormat)info.mtlTextureSpec().fFormat);
}
bool Caps::isTexturable(MTLPixelFormat format) const {
// TODO: Fill out format table so that we can query all formats. For now we only support RGBA8
// and BGRA8 which is supported everywhere.
return format == MTLPixelFormatRGBA8Unorm || format == MTLPixelFormatBGRA8Unorm;
}
bool Caps::isRenderable(const skgpu::TextureInfo& info) const {
return info.mtlTextureSpec().fUsage & MTLTextureUsageRenderTarget &&
this->isRenderable((MTLPixelFormat)info.mtlTextureSpec().fFormat, info.numSamples());
}
bool Caps::isRenderable(MTLPixelFormat format, uint32_t numSamples) const {
// TODO: Fill out format table so that we can query all formats. For now we only support RGBA8
// and BGRA8 with a sampleCount of 1 which is supported everywhere.
if ((format != MTLPixelFormatRGBA8Unorm && format != MTLPixelFormatBGRA8Unorm) ||
numSamples != 1) {
return false;
}
return true;
}
bool Caps::onAreColorTypeAndTextureInfoCompatible(SkColorType type,
const skgpu::TextureInfo& info) const {
// TODO: Fill out format table so that we can query all formats. For now we only support RGBA8
// or BGRA8 for both the color type and format.
return (type == kRGBA_8888_SkColorType &&
info.mtlTextureSpec().fFormat == MTLPixelFormatRGBA8Unorm) ||
(type == kBGRA_8888_SkColorType &&
info.mtlTextureSpec().fFormat == MTLPixelFormatBGRA8Unorm);
}
size_t Caps::getTransferBufferAlignment(size_t bytesPerPixel) const {
return std::max(bytesPerPixel, getMinBufferAlignment());
}
// There are only a few possible valid sample counts (1, 2, 4, 8, 16). So we can key on those 5
// options instead of the actual sample value.
uint32_t samples_to_key(uint32_t numSamples) {
switch (numSamples) {
case 1:
return 0;
case 2:
return 1;
case 4:
return 2;
case 8:
return 3;
case 16:
return 4;
default:
SkUNREACHABLE;
}
}
void Caps::buildKeyForTexture(SkISize dimensions,
const skgpu::TextureInfo& info,
ResourceType type,
Shareable shareable,
GraphiteResourceKey* key) const {
const TextureSpec& mtlSpec = info.mtlTextureSpec();
SkASSERT(!dimensions.isEmpty());
// A MTLPixelFormat is an NSUInteger type which is documented to be 32 bits in 32 bit
// applications and 64 bits in 64 bit applications. So it should fit in an uint64_t, but adding
// the assert heere to make sure.
static_assert(sizeof(MTLPixelFormat) <= sizeof(uint64_t));
SkASSERT(mtlSpec.fFormat != MTLPixelFormatInvalid);
uint64_t formatKey = static_cast<uint64_t>(mtlSpec.fFormat);
uint32_t samplesKey = samples_to_key(info.numSamples());
// We don't have to key the number of mip levels because it is inherit in the combination of
// isMipped and dimensions.
bool isMipped = info.numMipLevels() > 1;
Protected isProtected = info.isProtected();
bool isFBOnly = mtlSpec.fFramebufferOnly;
// Confirm all the below parts of the key can fit in a single uint32_t. The sum of the shift
// amounts in the asserts must be less than or equal to 32.
SkASSERT(samplesKey < (1u << 3));
SkASSERT(static_cast<uint32_t>(isMipped) < (1u << 1));
SkASSERT(static_cast<uint32_t>(isProtected) < (1u << 1));
SkASSERT(mtlSpec.fUsage < (1u << 5));
SkASSERT(mtlSpec.fStorageMode < (1u << 2));
SkASSERT(static_cast<uint32_t>(isFBOnly) < (1u << 1));
// We need two uint32_ts for dimensions, 2 for format, and 1 for the rest of the key;
static int kNum32DataCnt = 2 + 2 + 1;
GraphiteResourceKey::Builder builder(key, type, kNum32DataCnt, shareable);
builder[0] = dimensions.width();
builder[1] = dimensions.height();
builder[2] = formatKey & 0xFFFFFFFF;
builder[3] = (formatKey >> 32) & 0xFFFFFFFF;
builder[4] = (samplesKey << 0) |
(static_cast<uint32_t>(isMipped) << 3) |
(static_cast<uint32_t>(isProtected) << 4) |
(static_cast<uint32_t>(mtlSpec.fUsage) << 5) |
(static_cast<uint32_t>(mtlSpec.fStorageMode) << 10)|
(static_cast<uint32_t>(isFBOnly) << 12);
}
} // namespace skgpu::mtl