blob: 0c9372ff6f22eddcea43eb692759c701de2fede4 [file] [log] [blame]
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrMtlCaps.h"
#include "GrBackendSurface.h"
#include "GrMtlUtil.h"
#include "GrRenderTarget.h"
#include "GrRenderTargetProxy.h"
#include "GrShaderCaps.h"
#include "GrSurfaceProxy.h"
#include "SkRect.h"
GrMtlCaps::GrMtlCaps(const GrContextOptions& contextOptions, const id<MTLDevice> device,
MTLFeatureSet featureSet)
: INHERITED(contextOptions) {
fShaderCaps.reset(new GrShaderCaps(contextOptions));
this->initFeatureSet(featureSet);
this->initGrCaps(device);
this->initShaderCaps();
this->initConfigTable();
this->initStencilFormat(device);
this->applyOptionsOverrides(contextOptions);
fShaderCaps->applyOptionsOverrides(contextOptions);
// The following are disabled due to the unfinished Metal backend, not because Metal itself
// doesn't support it.
fFenceSyncSupport = false; // Fences are not implemented yet
fMultisampleDisableSupport = true; // MSAA and resolving not implemented yet
fDiscardRenderTargetSupport = false; // GrMtlGpuCommandBuffer::discard() not implemented
fCrossContextTextureSupport = false; // GrMtlGpu::prepareTextureForCrossContextUsage() not impl
}
void GrMtlCaps::initFeatureSet(MTLFeatureSet featureSet) {
// Mac OSX
#ifdef SK_BUILD_FOR_MAC
if (MTLFeatureSet_OSX_GPUFamily1_v2 == featureSet) {
fPlatform = Platform::kMac;
fFamilyGroup = 1;
fVersion = 2;
return;
}
if (MTLFeatureSet_OSX_GPUFamily1_v1 == featureSet) {
fPlatform = Platform::kMac;
fFamilyGroup = 1;
fVersion = 1;
return;
}
#endif
// iOS Family group 3
#ifdef SK_BUILD_FOR_IOS
if (MTLFeatureSet_iOS_GPUFamily3_v2 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 3;
fVersion = 2;
return;
}
if (MTLFeatureSet_iOS_GPUFamily3_v1 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 3;
fVersion = 1;
return;
}
// iOS Family group 2
if (MTLFeatureSet_iOS_GPUFamily2_v3 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 2;
fVersion = 3;
return;
}
if (MTLFeatureSet_iOS_GPUFamily2_v2 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 2;
fVersion = 2;
return;
}
if (MTLFeatureSet_iOS_GPUFamily2_v1 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 2;
fVersion = 1;
return;
}
// iOS Family group 1
if (MTLFeatureSet_iOS_GPUFamily1_v3 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 1;
fVersion = 3;
return;
}
if (MTLFeatureSet_iOS_GPUFamily1_v2 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 1;
fVersion = 2;
return;
}
if (MTLFeatureSet_iOS_GPUFamily1_v1 == featureSet) {
fPlatform = Platform::kIOS;
fFamilyGroup = 1;
fVersion = 1;
return;
}
#endif
// No supported feature sets were found
SK_ABORT("Requested an unsupported feature set");
}
bool GrMtlCaps::canCopyAsBlit(GrPixelConfig dstConfig, int dstSampleCount,
GrSurfaceOrigin dstOrigin,
GrPixelConfig srcConfig, int srcSampleCount,
GrSurfaceOrigin srcOrigin,
const SkIRect& srcRect, const SkIPoint& dstPoint,
bool areDstSrcSameObj) const {
if (dstConfig != srcConfig) {
return false;
}
if ((dstSampleCount > 1 || srcSampleCount > 1) && (dstSampleCount != srcSampleCount)) {
return false;
}
if (dstOrigin != srcOrigin) {
return false;
}
if (areDstSrcSameObj) {
SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.x(), dstPoint.y(),
srcRect.width(), srcRect.height());
if (dstRect.intersect(srcRect)) {
return false;
}
}
return true;
}
bool GrMtlCaps::canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable,
GrPixelConfig srcConfig, bool srcIsTextureable) const {
// TODO: Make copySurfaceAsDraw handle the swizzle
if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
this->shaderCaps()->configOutputSwizzle(dstConfig)) {
return false;
}
if (!dstIsRenderable || !srcIsTextureable) {
return false;
}
return true;
}
bool GrMtlCaps::canCopyAsDrawThenBlit(GrPixelConfig dstConfig, GrPixelConfig srcConfig,
bool srcIsTextureable) const {
// TODO: Make copySurfaceAsDraw handle the swizzle
if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
this->shaderCaps()->configOutputSwizzle(dstConfig)) {
return false;
}
if (!srcIsTextureable) {
return false;
}
return true;
}
bool GrMtlCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
const SkIRect& srcRect, const SkIPoint& dstPoint) const {
GrSurfaceOrigin dstOrigin = dst->origin();
GrSurfaceOrigin srcOrigin = src->origin();
int dstSampleCnt = 0;
int srcSampleCnt = 0;
if (const GrRenderTargetProxy* rtProxy = dst->asRenderTargetProxy()) {
dstSampleCnt = rtProxy->numColorSamples();
}
if (const GrRenderTargetProxy* rtProxy = src->asRenderTargetProxy()) {
srcSampleCnt = rtProxy->numColorSamples();
}
SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy()));
SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTargetProxy()));
return this->canCopyAsBlit(dst->config(), dstSampleCnt, dstOrigin,
src->config(), srcSampleCnt, srcOrigin,
srcRect, dstPoint, dst == src) ||
this->canCopyAsDraw(dst->config(), SkToBool(dst->asRenderTargetProxy()),
src->config(), SkToBool(src->asTextureProxy())) ||
this->canCopyAsDrawThenBlit(dst->config(), src->config(),
SkToBool(src->asTextureProxy()));
}
void GrMtlCaps::initGrCaps(const id<MTLDevice> device) {
// Max vertex attribs is the same on all devices
fMaxVertexAttributes = 31;
// Metal does not support scissor + clear
fPerformPartialClearsAsDraws = true;
// RenderTarget and Texture size
if (this->isMac()) {
fMaxRenderTargetSize = 16384;
} else {
if (3 == fFamilyGroup) {
fMaxRenderTargetSize = 16384;
} else {
// Family group 1 and 2 support 8192 for version 2 and above, 4096 for v1
if (1 == fVersion) {
fMaxRenderTargetSize = 4096;
} else {
fMaxRenderTargetSize = 8192;
}
}
}
fMaxPreferredRenderTargetSize = fMaxRenderTargetSize;
fMaxTextureSize = fMaxRenderTargetSize;
// Init sample counts. All devices support 1 (i.e. 0 in skia).
fSampleCounts.push_back(1);
for (auto sampleCnt : {2, 4, 8}) {
if ([device supportsTextureSampleCount:sampleCnt]) {
fSampleCounts.push_back(sampleCnt);
}
}
// Clamp to border is supported on Mac 10.12 and higher (gpu family.version >= 1.2). It is not
// supported on iOS.
if (this->isMac()) {
if (fFamilyGroup == 1 && fVersion < 2) {
fClampToBorderSupport = false;
}
} else {
fClampToBorderSupport = false;
}
// Starting with the assumption that there isn't a reason to not map small buffers.
fBufferMapThreshold = 0;
// Buffers are always fully mapped.
fMapBufferFlags = kCanMap_MapFlag;
fOversizedStencilSupport = true;
fSRGBSupport = true; // always available in Metal
fSRGBWriteControl = false;
fMipMapSupport = true; // always available in Metal
fNPOTTextureTileSupport = true; // always available in Metal
fDiscardRenderTargetSupport = true;
fReuseScratchTextures = true; // Assuming this okay
fTextureBarrierSupport = false; // Need to figure out if we can do this
fSampleLocationsSupport = false;
fMultisampleDisableSupport = false;
if (this->isMac() || 3 == fFamilyGroup) {
fInstanceAttribSupport = true;
}
fUsesMixedSamples = false;
fGpuTracingSupport = false;
fFenceSyncSupport = true; // always available in Metal
fCrossContextTextureSupport = false;
fHalfFloatVertexAttributeSupport = true;
}
int GrMtlCaps::maxRenderTargetSampleCount(GrPixelConfig config) const {
if (fConfigTable[config].fFlags & ConfigInfo::kMSAA_Flag) {
return fSampleCounts[fSampleCounts.count() - 1];
} else if (fConfigTable[config].fFlags & ConfigInfo::kRenderable_Flag) {
return 1;
}
return 0;
}
int GrMtlCaps::getRenderTargetSampleCount(int requestedCount, GrPixelConfig config) const {
requestedCount = SkTMax(requestedCount, 1);
if (fConfigTable[config].fFlags & ConfigInfo::kMSAA_Flag) {
int count = fSampleCounts.count();
for (int i = 0; i < count; ++i) {
if (fSampleCounts[i] >= requestedCount) {
return fSampleCounts[i];
}
}
} else if (fConfigTable[config].fFlags & ConfigInfo::kRenderable_Flag) {
return 1 == requestedCount ? 1 : 0;
}
return 0;
}
void GrMtlCaps::initShaderCaps() {
GrShaderCaps* shaderCaps = fShaderCaps.get();
// fConfigOutputSwizzle will default to RGBA so we only need to set it for alpha only config.
for (int i = 0; i < kGrPixelConfigCnt; ++i) {
GrPixelConfig config = static_cast<GrPixelConfig>(i);
if (GrPixelConfigIsAlphaOnly(config)) {
shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRR();
shaderCaps->fConfigOutputSwizzle[i] = GrSwizzle::AAAA();
} else {
if (kGray_8_GrPixelConfig == config) {
shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRA();
} else if (kRGB_888X_GrPixelConfig == config || kRGB_888_GrPixelConfig == config ) {
shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGB1();
} else {
shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGBA();
}
}
}
// 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;
// We haven't yet tested that using flat attributes perform well.
shaderCaps->fPreferFlatInterpolation = true;
shaderCaps->fShaderDerivativeSupport = true;
shaderCaps->fGeometryShaderSupport = false;
if ((this->isMac() && fVersion >= 2) ||
(this->isIOS() && ((1 == fFamilyGroup && 4 == fVersion) ||
(2 == fFamilyGroup && 4 == fVersion) ||
(3 == fFamilyGroup && 3 == fVersion)))) {
shaderCaps->fDualSourceBlendingSupport = true;
}
// TODO: Re-enable this once skbug:8720 is fixed. Will also need to remove asserts in
// GrMtlPipelineStateBuilder which assert we aren't using this feature.
#if 0
if (this->isIOS()) {
shaderCaps->fFBFetchSupport = true;
shaderCaps->fFBFetchNeedsCustomOutput = true; // ??
shaderCaps->fFBFetchColorName = ""; // Somehow add [[color(0)]] to arguments to frag shader
}
#endif
shaderCaps->fDstReadInShaderSupport = shaderCaps->fFBFetchSupport;
shaderCaps->fIntegerSupport = true;
shaderCaps->fVertexIDSupport = false;
shaderCaps->fImageLoadStoreSupport = false;
// Metal uses IEEE float and half floats so assuming those values here.
shaderCaps->fFloatIs32Bits = true;
shaderCaps->fHalfIs32Bits = false;
// Metal supports unsigned integers.
shaderCaps->fUnsignedSupport = true;
shaderCaps->fMaxFragmentSamplers = 16;
}
void GrMtlCaps::initConfigTable() {
ConfigInfo* info;
// Alpha_8 uses R8Unorm
info = &fConfigTable[kAlpha_8_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// Alpha_8_as_Red uses R8Unorm
info = &fConfigTable[kAlpha_8_as_Red_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// Gray_8 uses R8Unorm
info = &fConfigTable[kGray_8_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// Gray_8_as_Red uses R8Unorm
info = &fConfigTable[kGray_8_as_Red_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// RGB_565 uses B5G6R5Unorm, even though written opposite this format packs how we want
info = &fConfigTable[kRGB_565_GrPixelConfig];
if (this->isMac()) {
info->fFlags = 0;
} else {
info->fFlags = ConfigInfo::kAllFlags;
}
// RGBA_4444 uses ABGR4Unorm
info = &fConfigTable[kRGBA_4444_GrPixelConfig];
if (this->isMac()) {
info->fFlags = 0;
} else {
info->fFlags = ConfigInfo::kAllFlags;
}
// RGBA_8888 uses RGBA8Unorm
info = &fConfigTable[kRGBA_8888_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// RGB_888X uses RGBA8Unorm and we will swizzle the 1
info = &fConfigTable[kRGB_888X_GrPixelConfig];
info->fFlags = ConfigInfo::kTextureable_Flag;
// RGB_888 uses RGBA8Unorm and we will swizzle the 1
info = &fConfigTable[kRGB_888_GrPixelConfig];
info->fFlags = ConfigInfo::kTextureable_Flag;
// RG_88 uses RG8Unorm
info = &fConfigTable[kRG_88_GrPixelConfig];
info->fFlags = ConfigInfo::kTextureable_Flag;
// BGRA_8888 uses BGRA8Unorm
info = &fConfigTable[kBGRA_8888_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// SRGBA_8888 uses RGBA8Unorm_sRGB
info = &fConfigTable[kSRGBA_8888_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// SBGRA_8888 uses BGRA8Unorm_sRGB
info = &fConfigTable[kSBGRA_8888_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// RGBA_float uses RGBA32Float
info = &fConfigTable[kRGBA_float_GrPixelConfig];
if (this->isMac()) {
info->fFlags = ConfigInfo::kAllFlags;
} else {
info->fFlags = 0;
}
// RG_float uses RG32Float
info = &fConfigTable[kRG_float_GrPixelConfig];
if (this->isMac()) {
info->fFlags = ConfigInfo::kAllFlags;
} else {
info->fFlags = ConfigInfo::kTextureable_Flag | ConfigInfo::kRenderable_Flag;
}
// Alpha_half uses R16Float
info = &fConfigTable[kAlpha_half_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
// RGBA_half uses RGBA16Float
info = &fConfigTable[kRGBA_half_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
info = &fConfigTable[kRGBA_half_Clamped_GrPixelConfig];
info->fFlags = ConfigInfo::kAllFlags;
}
void GrMtlCaps::initStencilFormat(id<MTLDevice> physDev) {
fPreferredStencilFormat = StencilFormat{ MTLPixelFormatStencil8, 8, 8, true };
}
bool GrMtlCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
if (auto rt = surface->asRenderTarget()) {
return rt->numColorSamples() <= 1 && SkToBool(surface->asTexture());
}
return true;
}
GrPixelConfig validate_sized_format(GrMTLPixelFormat grFormat, SkColorType ct) {
MTLPixelFormat format = static_cast<MTLPixelFormat>(grFormat);
switch (ct) {
case kUnknown_SkColorType:
return kUnknown_GrPixelConfig;
case kAlpha_8_SkColorType:
if (MTLPixelFormatA8Unorm == format) {
return kAlpha_8_as_Alpha_GrPixelConfig;
} else if (MTLPixelFormatR8Unorm == format) {
return kAlpha_8_as_Red_GrPixelConfig;
}
break;
#ifdef SK_BUILD_FOR_MAC
case kRGB_565_SkColorType:
case kARGB_4444_SkColorType:
return kUnknown_GrPixelConfig;
break;
#else
case kRGB_565_SkColorType:
if (MTLPixelFormatB5G6R5Unorm == format) {
return kRGB_565_GrPixelConfig;
}
break;
case kARGB_4444_SkColorType:
if (MTLPixelFormatABGR4Unorm == format) {
return kRGBA_4444_GrPixelConfig;
}
break;
#endif
case kRGBA_8888_SkColorType:
if (MTLPixelFormatRGBA8Unorm == format) {
return kRGBA_8888_GrPixelConfig;
} else if (MTLPixelFormatRGBA8Unorm_sRGB == format) {
return kSRGBA_8888_GrPixelConfig;
}
break;
case kRGB_888x_SkColorType:
if (MTLPixelFormatRGBA8Unorm == format) {
return kRGB_888X_GrPixelConfig;
}
break;
case kBGRA_8888_SkColorType:
if (MTLPixelFormatBGRA8Unorm == format) {
return kBGRA_8888_GrPixelConfig;
} else if (MTLPixelFormatBGRA8Unorm_sRGB == format) {
return kSBGRA_8888_GrPixelConfig;
}
break;
case kRGBA_1010102_SkColorType:
if (MTLPixelFormatRGB10A2Unorm == format) {
return kRGBA_1010102_GrPixelConfig;
}
break;
case kRGB_101010x_SkColorType:
break;
case kGray_8_SkColorType:
if (MTLPixelFormatR8Unorm == format) {
return kGray_8_as_Red_GrPixelConfig;
}
break;
case kRGBA_F16Norm_SkColorType:
if (MTLPixelFormatRGBA16Float == format) {
return kRGBA_half_Clamped_GrPixelConfig;
}
break;
case kRGBA_F16_SkColorType:
if (MTLPixelFormatRGBA16Float == format) {
return kRGBA_half_GrPixelConfig;
}
break;
case kRGBA_F32_SkColorType:
if (MTLPixelFormatR32Float == format) {
return kRGBA_float_GrPixelConfig;
}
break;
}
return kUnknown_GrPixelConfig;
}
GrPixelConfig GrMtlCaps::validateBackendRenderTarget(const GrBackendRenderTarget& rt,
SkColorType ct) const {
GrMtlTextureInfo fbInfo;
if (!rt.getMtlTextureInfo(&fbInfo)) {
return kUnknown_GrPixelConfig;
}
id<MTLTexture> texture = (__bridge id<MTLTexture>)fbInfo.fTexture;
return validate_sized_format(texture.pixelFormat, ct);
}
GrPixelConfig GrMtlCaps::getConfigFromBackendFormat(const GrBackendFormat& format,
SkColorType ct) const {
const GrMTLPixelFormat* mtlFormat = format.getMtlFormat();
if (!mtlFormat) {
return kUnknown_GrPixelConfig;
}
return validate_sized_format(*mtlFormat, ct);
}
static GrPixelConfig get_yuva_config(GrMTLPixelFormat grFormat) {
MTLPixelFormat format = static_cast<MTLPixelFormat>(grFormat);
switch (format) {
case MTLPixelFormatA8Unorm:
return kAlpha_8_as_Alpha_GrPixelConfig;
break;
case MTLPixelFormatR8Unorm:
return kAlpha_8_as_Red_GrPixelConfig;
break;
case MTLPixelFormatRG8Unorm:
return kRG_88_GrPixelConfig;
break;
case MTLPixelFormatRGBA8Unorm:
return kRGBA_8888_GrPixelConfig;
break;
case MTLPixelFormatBGRA8Unorm:
return kBGRA_8888_GrPixelConfig;
break;
default:
return kUnknown_GrPixelConfig;
break;
}
}
GrPixelConfig GrMtlCaps::getYUVAConfigFromBackendFormat(const GrBackendFormat& format) const {
const GrMTLPixelFormat* mtlFormat = format.getMtlFormat();
if (!mtlFormat) {
return kUnknown_GrPixelConfig;
}
return get_yuva_config(*mtlFormat);
}
GrBackendFormat GrMtlCaps::getBackendFormatFromGrColorType(GrColorType ct,
GrSRGBEncoded srgbEncoded) const {
GrPixelConfig config = GrColorTypeToPixelConfig(ct, srgbEncoded);
if (config == kUnknown_GrPixelConfig) {
return GrBackendFormat();
}
MTLPixelFormat format;
if (!GrPixelConfigToMTLFormat(config, &format)) {
return GrBackendFormat();
}
return GrBackendFormat::MakeMtl(format);
}