| /* |
| * 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 "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. |
| fBlacklistCoverageCounting = true; // CCPR shaders have some incompatabilities with SkSLC |
| fFenceSyncSupport = false; // Fences are not implemented yet |
| fMipMapSupport = false; // GrMtlGpu::onRegenerateMipMapLevels() not implemented |
| 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; |
| |
| // 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 { |
| 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; |
| |
| // Gray_8 uses R8Unorm |
| info = &fConfigTable[kGray_8_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; |
| |
| // 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::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; |
| } |
| |
| void GrMtlCaps::initStencilFormat(id<MTLDevice> physDev) { |
| fPreferredStencilFormat = StencilFormat{ MTLPixelFormatStencil8, 8, 8, true }; |
| } |
| |
| GrBackendFormat GrMtlCaps::onCreateFormatFromBackendTexture( |
| const GrBackendTexture& backendTex) const { |
| GrMtlTextureInfo mtlInfo; |
| SkAssertResult(backendTex.getMtlTextureInfo(&mtlInfo)); |
| id<MTLTexture> mtlTexture = GrGetMTLTexture(mtlInfo.fTexture, |
| GrWrapOwnership::kBorrow_GrWrapOwnership); |
| return GrBackendFormat::MakeMtl(mtlTexture.pixelFormat); |
| } |
| |
| 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); |
| } |
| |