blob: a860b6bbc28db080dd65108a830968b8fd97751c [file] [log] [blame]
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// DisplayMtl.mm: Metal implementation of DisplayImpl
#include "libANGLE/renderer/metal/DisplayMtl.h"
#include <sys/param.h>
#include "common/apple_platform_utils.h"
#include "common/system_utils.h"
#include "gpu_info_util/SystemInfo.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/Surface.h"
#include "libANGLE/renderer/driver_utils.h"
#include "libANGLE/renderer/metal/CompilerMtl.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/DeviceMtl.h"
#include "libANGLE/renderer/metal/IOSurfaceSurfaceMtl.h"
#include "libANGLE/renderer/metal/ImageMtl.h"
#include "libANGLE/renderer/metal/SurfaceMtl.h"
#include "libANGLE/renderer/metal/SyncMtl.h"
#include "libANGLE/renderer/metal/mtl_common.h"
#include "libANGLE/trace.h"
#include "mtl_command_buffer.h"
#include "platform/PlatformMethods.h"
#if ANGLE_METAL_XCODE_BUILDS_SHADERS
# include "libANGLE/renderer/metal/shaders/mtl_internal_shaders_metallib.h"
#elif ANGLE_METAL_HAS_PREBUILT_INTERNAL_SHADERS
# include "mtl_internal_shaders_metallib.h"
#else
# include "libANGLE/renderer/metal/shaders/mtl_internal_shaders_src_autogen.h"
#endif
#include "EGL/eglext.h"
namespace rx
{
static EGLint GetDepthSize(GLint internalformat)
{
switch (internalformat)
{
case GL_STENCIL_INDEX8:
return 0;
case GL_DEPTH_COMPONENT16:
return 16;
case GL_DEPTH_COMPONENT24:
return 24;
case GL_DEPTH_COMPONENT32_OES:
return 32;
case GL_DEPTH_COMPONENT32F:
return 32;
case GL_DEPTH24_STENCIL8:
return 24;
case GL_DEPTH32F_STENCIL8:
return 32;
default:
// UNREACHABLE(internalformat);
return 0;
}
}
static EGLint GetStencilSize(GLint internalformat)
{
switch (internalformat)
{
case GL_STENCIL_INDEX8:
return 8;
case GL_DEPTH_COMPONENT16:
return 0;
case GL_DEPTH_COMPONENT24:
return 0;
case GL_DEPTH_COMPONENT32_OES:
return 0;
case GL_DEPTH_COMPONENT32F:
return 0;
case GL_DEPTH24_STENCIL8:
return 8;
case GL_DEPTH32F_STENCIL8:
return 8;
default:
// UNREACHABLE(internalformat);
return 0;
}
}
bool IsMetalDisplayAvailable()
{
return angle::IsMetalRendererAvailable();
}
DisplayImpl *CreateMetalDisplay(const egl::DisplayState &state)
{
return new DisplayMtl(state);
}
DisplayMtl::DisplayMtl(const egl::DisplayState &state)
: DisplayImpl(state), mDisplay(nullptr), mStateCache(mFeatures)
{}
DisplayMtl::~DisplayMtl() {}
egl::Error DisplayMtl::initialize(egl::Display *display)
{
ASSERT(IsMetalDisplayAvailable());
angle::Result result = initializeImpl(display);
if (result != angle::Result::Continue)
{
terminate();
return egl::EglNotInitialized();
}
return egl::NoError();
}
angle::Result DisplayMtl::initializeImpl(egl::Display *display)
{
ANGLE_MTL_OBJC_SCOPE
{
mDisplay = display;
mMetalDevice = getMetalDeviceMatchingAttribute(display->getAttributeMap());
// If we can't create a device, fail initialization.
if (!mMetalDevice.get())
{
return angle::Result::Stop;
}
mMetalDeviceVendorId = mtl::GetDeviceVendorId(mMetalDevice);
mCapsInitialized = false;
initializeFeatures();
if (mFeatures.requireGpuFamily2.enabled && !supportsEitherGPUFamily(1, 2))
{
ANGLE_MTL_LOG("Could not initialize: Metal device does not support Mac GPU family 2.");
return angle::Result::Stop;
}
if (mFeatures.requireMsl21.enabled && !supportsMetal2_1())
{
ANGLE_MTL_LOG("Could not initialize: MSL 2.1 is not available.");
return angle::Result::Stop;
}
if (mFeatures.disableMetalOnNvidia.enabled && isNVIDIA())
{
ANGLE_MTL_LOG("Could not initialize: Metal not supported on NVIDIA GPUs.");
return angle::Result::Stop;
}
mCmdQueue.set([[mMetalDevice newCommandQueue] ANGLE_MTL_AUTORELEASE]);
ANGLE_TRY(mFormatTable.initialize(this));
ANGLE_TRY(initializeShaderLibrary());
mUtils = std::make_unique<mtl::RenderUtils>(this);
return angle::Result::Continue;
}
}
void DisplayMtl::terminate()
{
mUtils = nullptr;
mCmdQueue.reset();
mDefaultShaders = nil;
mMetalDevice = nil;
#if ANGLE_MTL_EVENT_AVAILABLE
mSharedEventListener = nil;
#endif
mCapsInitialized = false;
mMetalDeviceVendorId = 0;
mComputedAMDBronze = false;
mIsAMDBronze = false;
}
bool DisplayMtl::testDeviceLost()
{
return false;
}
egl::Error DisplayMtl::restoreLostDevice(const egl::Display *display)
{
return egl::NoError();
}
std::string DisplayMtl::getRendererDescription()
{
ANGLE_MTL_OBJC_SCOPE
{
std::string desc = "ANGLE Metal Renderer";
if (mMetalDevice)
{
desc += ": ";
desc += mMetalDevice.get().name.UTF8String;
}
return desc;
}
}
std::string DisplayMtl::getVendorString()
{
return GetVendorString(mMetalDeviceVendorId);
}
std::string DisplayMtl::getVersionString(bool includeFullVersion)
{
if (!includeFullVersion)
{
// For WebGL contexts it's inappropriate to include any
// additional version information, but Chrome requires
// something to be present here.
return "Unspecified Version";
}
ANGLE_MTL_OBJC_SCOPE
{
NSProcessInfo *procInfo = [NSProcessInfo processInfo];
return procInfo.operatingSystemVersionString.UTF8String;
}
}
DeviceImpl *DisplayMtl::createDevice()
{
return new DeviceMtl();
}
mtl::AutoObjCPtr<id<MTLDevice>> DisplayMtl::getMetalDeviceMatchingAttribute(
const egl::AttributeMap &attribs)
{
#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)
auto deviceList = mtl::adoptObjCObj(MTLCopyAllDevices());
EGLAttrib high = attribs.get(EGL_PLATFORM_ANGLE_DEVICE_ID_HIGH_ANGLE, 0);
EGLAttrib low = attribs.get(EGL_PLATFORM_ANGLE_DEVICE_ID_LOW_ANGLE, 0);
uint64_t deviceId =
angle::GetSystemDeviceIdFromParts(static_cast<uint32_t>(high), static_cast<uint32_t>(low));
// Check EGL_ANGLE_platform_angle_device_id to see if a device was specified.
if (deviceId != 0)
{
for (id<MTLDevice> device in deviceList.get())
{
if ([device registryID] == deviceId)
{
return device;
}
}
}
auto externalGPUs =
mtl::adoptObjCObj<NSMutableArray<id<MTLDevice>>>([[NSMutableArray alloc] init]);
auto integratedGPUs =
mtl::adoptObjCObj<NSMutableArray<id<MTLDevice>>>([[NSMutableArray alloc] init]);
auto discreteGPUs =
mtl::adoptObjCObj<NSMutableArray<id<MTLDevice>>>([[NSMutableArray alloc] init]);
for (id<MTLDevice> device in deviceList.get())
{
if (device.removable)
{
[externalGPUs addObject:device];
}
else if (device.lowPower)
{
[integratedGPUs addObject:device];
}
else
{
[discreteGPUs addObject:device];
}
}
// TODO(kpiddington: External GPU support. Do we prefer high power / low bandwidth for general
// WebGL applications?
// Can we support hot-swapping in GPU's?
if (attribs.get(EGL_POWER_PREFERENCE_ANGLE, 0) == EGL_HIGH_POWER_ANGLE)
{
// Search for a discrete GPU first.
for (id<MTLDevice> device in discreteGPUs.get())
{
if (![device isHeadless])
return device;
}
}
else if (attribs.get(EGL_POWER_PREFERENCE_ANGLE, 0) == EGL_LOW_POWER_ANGLE)
{
// If we've selected a low power device, look through integrated devices.
for (id<MTLDevice> device in integratedGPUs.get())
{
if (![device isHeadless])
return device;
}
}
const std::string preferredDeviceString = angle::GetPreferredDeviceString();
if (!preferredDeviceString.empty())
{
for (id<MTLDevice> device in deviceList.get())
{
if ([device.name.lowercaseString
containsString:[NSString stringWithUTF8String:preferredDeviceString.c_str()]])
{
NSLog(@"Using Metal Device: %@", [device name]);
return device;
}
}
}
#endif
// If we can't find anything, or are on a platform that doesn't support power options, create a
// default device.
return mtl::adoptObjCObj(MTLCreateSystemDefaultDevice());
}
egl::Error DisplayMtl::waitClient(const gl::Context *context)
{
auto contextMtl = GetImplAs<ContextMtl>(context);
angle::Result result = contextMtl->finishCommandBuffer();
if (result != angle::Result::Continue)
{
return egl::EglBadAccess();
}
return egl::NoError();
}
egl::Error DisplayMtl::waitNative(const gl::Context *context, EGLint engine)
{
UNIMPLEMENTED();
return egl::NoError();
}
egl::Error DisplayMtl::waitUntilWorkScheduled()
{
for (auto context : mState.contextMap)
{
auto contextMtl = GetImplAs<ContextMtl>(context.second);
contextMtl->flushCommandBuffer(mtl::WaitUntilScheduled);
}
return egl::NoError();
}
SurfaceImpl *DisplayMtl::createWindowSurface(const egl::SurfaceState &state,
EGLNativeWindowType window,
const egl::AttributeMap &attribs)
{
return new WindowSurfaceMtl(this, state, window, attribs);
}
SurfaceImpl *DisplayMtl::createPbufferSurface(const egl::SurfaceState &state,
const egl::AttributeMap &attribs)
{
return new PBufferSurfaceMtl(this, state, attribs);
}
SurfaceImpl *DisplayMtl::createPbufferFromClientBuffer(const egl::SurfaceState &state,
EGLenum buftype,
EGLClientBuffer clientBuffer,
const egl::AttributeMap &attribs)
{
switch (buftype)
{
case EGL_IOSURFACE_ANGLE:
return new IOSurfaceSurfaceMtl(this, state, clientBuffer, attribs);
default:
UNREACHABLE();
}
return nullptr;
}
SurfaceImpl *DisplayMtl::createPixmapSurface(const egl::SurfaceState &state,
NativePixmapType nativePixmap,
const egl::AttributeMap &attribs)
{
UNIMPLEMENTED();
return static_cast<SurfaceImpl *>(0);
}
ImageImpl *DisplayMtl::createImage(const egl::ImageState &state,
const gl::Context *context,
EGLenum target,
const egl::AttributeMap &attribs)
{
return new ImageMtl(state, context);
}
rx::ContextImpl *DisplayMtl::createContext(const gl::State &state,
gl::ErrorSet *errorSet,
const egl::Config *configuration,
const gl::Context *shareContext,
const egl::AttributeMap &attribs)
{
return new ContextMtl(state, errorSet, attribs, this);
}
StreamProducerImpl *DisplayMtl::createStreamProducerD3DTexture(
egl::Stream::ConsumerType consumerType,
const egl::AttributeMap &attribs)
{
UNIMPLEMENTED();
return nullptr;
}
ShareGroupImpl *DisplayMtl::createShareGroup(const egl::ShareGroupState &state)
{
return new ShareGroupMtl(state);
}
ExternalImageSiblingImpl *DisplayMtl::createExternalImageSibling(const gl::Context *context,
EGLenum target,
EGLClientBuffer buffer,
const egl::AttributeMap &attribs)
{
switch (target)
{
case EGL_METAL_TEXTURE_ANGLE:
return new TextureImageSiblingMtl(buffer);
default:
UNREACHABLE();
return nullptr;
}
}
gl::Version DisplayMtl::getMaxSupportedESVersion() const
{
#if TARGET_OS_SIMULATOR
// Simulator should be able to support ES3, despite not supporting iOS GPU
// Family 3 in its entirety.
// FIXME: None of the feature conditions are checked for simulator support.
return gl::Version(3, 0);
#else
if (supportsEitherGPUFamily(3, 1))
{
return mtl::kMaxSupportedGLVersion;
}
return gl::Version(2, 0);
#endif
}
gl::Version DisplayMtl::getMaxConformantESVersion() const
{
return std::min(getMaxSupportedESVersion(), gl::Version(3, 0));
}
Optional<gl::Version> DisplayMtl::getMaxSupportedDesktopVersion() const
{
return Optional<gl::Version>::Invalid();
}
EGLSyncImpl *DisplayMtl::createSync(const egl::AttributeMap &attribs)
{
return new EGLSyncMtl(attribs);
}
egl::Error DisplayMtl::makeCurrent(egl::Display *display,
egl::Surface *drawSurface,
egl::Surface *readSurface,
gl::Context *context)
{
if (!context)
{
return egl::NoError();
}
return egl::NoError();
}
void DisplayMtl::generateExtensions(egl::DisplayExtensions *outExtensions) const
{
outExtensions->iosurfaceClientBuffer = true;
outExtensions->surfacelessContext = true;
outExtensions->noConfigContext = true;
outExtensions->displayTextureShareGroup = true;
outExtensions->displaySemaphoreShareGroup = true;
outExtensions->mtlTextureClientBuffer = true;
outExtensions->waitUntilWorkScheduled = true;
if (mFeatures.hasEvents.enabled)
{
// MTLSharedEvent is only available since Metal 2.1
outExtensions->fenceSync = true;
outExtensions->waitSync = true;
}
// Note that robust resource initialization is not yet implemented. We only expose
// this extension so that ANGLE can be initialized in Chrome. WebGL will fail to use
// this extension (anglebug.com/4929)
outExtensions->robustResourceInitializationANGLE = true;
// EGL_KHR_image
outExtensions->image = true;
outExtensions->imageBase = true;
// EGL_ANGLE_metal_create_context_ownership_identity
outExtensions->metalCreateContextOwnershipIdentityANGLE = true;
// EGL_ANGLE_metal_sync_shared_event
outExtensions->mtlSyncSharedEventANGLE = true;
}
void DisplayMtl::generateCaps(egl::Caps *outCaps) const
{
outCaps->textureNPOT = true;
}
void DisplayMtl::initializeFrontendFeatures(angle::FrontendFeatures *features) const
{
// The Metal backend's handling of compile is thread-safe
ANGLE_FEATURE_CONDITION(features, compileJobIsThreadSafe, true);
// The link job in this backend references gl::Context and ContextMtl, and thread-safety is not
// guaranteed. The link subtasks are safe however, they are still parallelized.
//
// Once the link jobs are made thread-safe and using mtl::Context, this feature can be removed.
ANGLE_FEATURE_CONDITION(features, linkJobIsThreadSafe, false);
}
void DisplayMtl::populateFeatureList(angle::FeatureList *features)
{
mFeatures.populateFeatureList(features);
}
EGLenum DisplayMtl::EGLDrawingBufferTextureTarget()
{
// TODO(anglebug.com/6395): Apple's implementation conditionalized this on
// MacCatalyst and whether it was running on ARM64 or X64, preferring
// EGL_TEXTURE_RECTANGLE_ANGLE. Metal can bind IOSurfaces to regular 2D
// textures, and rectangular textures don't work in the SPIR-V Metal
// backend, so for the time being use EGL_TEXTURE_2D on all platforms.
return EGL_TEXTURE_2D;
}
egl::ConfigSet DisplayMtl::generateConfigs()
{
// NOTE(hqle): generate more config permutations
egl::ConfigSet configs;
const gl::Version &maxVersion = getMaxSupportedESVersion();
ASSERT(maxVersion >= gl::Version(2, 0));
bool supportsES3 = maxVersion >= gl::Version(3, 0);
egl::Config config;
// Native stuff
config.nativeVisualID = 0;
config.nativeVisualType = 0;
config.nativeRenderable = EGL_TRUE;
config.colorBufferType = EGL_RGB_BUFFER;
config.luminanceSize = 0;
config.alphaMaskSize = 0;
config.transparentType = EGL_NONE;
// Pbuffer
config.bindToTextureTarget = EGLDrawingBufferTextureTarget();
config.maxPBufferWidth = 4096;
config.maxPBufferHeight = 4096;
config.maxPBufferPixels = 4096 * 4096;
// Caveat
config.configCaveat = EGL_NONE;
// Misc
config.sampleBuffers = 0;
config.samples = 0;
config.level = 0;
config.bindToTextureRGB = EGL_FALSE;
config.bindToTextureRGBA = EGL_TRUE;
config.surfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
config.minSwapInterval = 0;
config.maxSwapInterval = 1;
#else
config.minSwapInterval = 1;
config.maxSwapInterval = 1;
#endif
config.renderTargetFormat = GL_RGBA8;
config.conformant = EGL_OPENGL_ES2_BIT | (supportsES3 ? EGL_OPENGL_ES3_BIT_KHR : 0);
config.renderableType = config.conformant;
config.matchNativePixmap = EGL_NONE;
config.colorComponentType = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT;
constexpr int samplesSupported[] = {0, 4};
for (int samples : samplesSupported)
{
config.samples = samples;
config.sampleBuffers = (samples == 0) ? 0 : 1;
// Buffer sizes
config.redSize = 8;
config.greenSize = 8;
config.blueSize = 8;
config.alphaSize = 8;
config.bufferSize = config.redSize + config.greenSize + config.blueSize + config.alphaSize;
// With DS
config.depthSize = 24;
config.stencilSize = 8;
config.depthStencilFormat = GL_DEPTH24_STENCIL8;
configs.add(config);
// With D
config.depthSize = 24;
config.stencilSize = 0;
config.depthStencilFormat = GL_DEPTH_COMPONENT24;
configs.add(config);
// With S
config.depthSize = 0;
config.stencilSize = 8;
config.depthStencilFormat = GL_STENCIL_INDEX8;
configs.add(config);
// No DS
config.depthSize = 0;
config.stencilSize = 0;
config.depthStencilFormat = GL_NONE;
configs.add(config);
// Tests like dEQP-GLES2.functional.depth_range.* assume EGL_DEPTH_SIZE is properly set even
// if renderConfig attributes are set to glu::RenderConfig::DONT_CARE
config.depthSize = GetDepthSize(config.depthStencilFormat);
config.stencilSize = GetStencilSize(config.depthStencilFormat);
configs.add(config);
}
return configs;
}
bool DisplayMtl::isValidNativeWindow(EGLNativeWindowType window) const
{
ANGLE_MTL_OBJC_SCOPE
{
NSObject *layer = (__bridge NSObject *)(window);
return [layer isKindOfClass:[CALayer class]];
}
}
egl::Error DisplayMtl::validateClientBuffer(const egl::Config *configuration,
EGLenum buftype,
EGLClientBuffer clientBuffer,
const egl::AttributeMap &attribs) const
{
switch (buftype)
{
case EGL_IOSURFACE_ANGLE:
if (!IOSurfaceSurfaceMtl::ValidateAttributes(clientBuffer, attribs))
{
return egl::EglBadAttribute();
}
break;
default:
UNREACHABLE();
return egl::EglBadAttribute();
}
return egl::NoError();
}
egl::Error DisplayMtl::validateImageClientBuffer(const gl::Context *context,
EGLenum target,
EGLClientBuffer clientBuffer,
const egl::AttributeMap &attribs) const
{
switch (target)
{
case EGL_METAL_TEXTURE_ANGLE:
if (!TextureImageSiblingMtl::ValidateClientBuffer(this, clientBuffer))
{
return egl::EglBadAttribute();
}
break;
default:
UNREACHABLE();
return egl::EglBadAttribute();
}
return egl::NoError();
}
gl::Caps DisplayMtl::getNativeCaps() const
{
ensureCapsInitialized();
return mNativeCaps;
}
const gl::TextureCapsMap &DisplayMtl::getNativeTextureCaps() const
{
ensureCapsInitialized();
return mNativeTextureCaps;
}
const gl::Extensions &DisplayMtl::getNativeExtensions() const
{
ensureCapsInitialized();
return mNativeExtensions;
}
const gl::Limitations &DisplayMtl::getNativeLimitations() const
{
ensureCapsInitialized();
return mNativeLimitations;
}
const ShPixelLocalStorageOptions &DisplayMtl::getNativePixelLocalStorageOptions() const
{
ensureCapsInitialized();
return mNativePLSOptions;
}
void DisplayMtl::ensureCapsInitialized() const
{
if (mCapsInitialized)
{
return;
}
mCapsInitialized = true;
// Reset
mNativeCaps = gl::Caps();
// Fill extension and texture caps
initializeExtensions();
initializeTextureCaps();
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
mNativeCaps.maxElementIndex = std::numeric_limits<GLuint>::max() - 1;
mNativeCaps.max3DTextureSize = 2048;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
mNativeCaps.max2DTextureSize = 16384;
// On macOS exclude [[position]] from maxVaryingVectors.
mNativeCaps.maxVaryingVectors = 31 - 1;
mNativeCaps.maxVertexOutputComponents = mNativeCaps.maxFragmentInputComponents = 124 - 4;
#else
if (supportsAppleGPUFamily(3))
{
mNativeCaps.max2DTextureSize = 16384;
mNativeCaps.maxVertexOutputComponents = mNativeCaps.maxFragmentInputComponents = 124;
mNativeCaps.maxVaryingVectors = mNativeCaps.maxVertexOutputComponents / 4;
}
else
{
mNativeCaps.max2DTextureSize = 8192;
mNativeCaps.maxVertexOutputComponents = mNativeCaps.maxFragmentInputComponents = 60;
mNativeCaps.maxVaryingVectors = mNativeCaps.maxVertexOutputComponents / 4;
}
#endif
mNativeCaps.maxArrayTextureLayers = 2048;
mNativeCaps.maxLODBias = std::log2(mNativeCaps.max2DTextureSize) + 1;
mNativeCaps.maxCubeMapTextureSize = mNativeCaps.max2DTextureSize;
mNativeCaps.maxRenderbufferSize = mNativeCaps.max2DTextureSize;
mNativeCaps.minAliasedPointSize = 1;
// NOTE(hqle): Metal has some problems drawing big point size even though
// Metal-Feature-Set-Tables.pdf says that max supported point size is 511. We limit it to 64
// for now. http://anglebug.com/4816
// NOTE(kpiddington): This seems to be fixed in macOS Monterey
if (ANGLE_APPLE_AVAILABLE_XCI(12.0, 15.0, 15.0))
{
mNativeCaps.maxAliasedPointSize = 511;
}
else
{
mNativeCaps.maxAliasedPointSize = 64;
}
mNativeCaps.minAliasedLineWidth = 1.0f;
mNativeCaps.maxAliasedLineWidth = 1.0f;
if (supportsEitherGPUFamily(2, 1) && !mFeatures.limitMaxDrawBuffersForTesting.enabled)
{
mNativeCaps.maxDrawBuffers = mtl::kMaxRenderTargets;
mNativeCaps.maxColorAttachments = mtl::kMaxRenderTargets;
}
else
{
mNativeCaps.maxDrawBuffers = mtl::kMaxRenderTargetsOlderGPUFamilies;
mNativeCaps.maxColorAttachments = mtl::kMaxRenderTargetsOlderGPUFamilies;
}
ASSERT(static_cast<uint32_t>(mNativeCaps.maxDrawBuffers) <= mtl::kMaxRenderTargets);
ASSERT(static_cast<uint32_t>(mNativeCaps.maxColorAttachments) <= mtl::kMaxRenderTargets);
mNativeCaps.maxFramebufferWidth = mNativeCaps.max2DTextureSize;
mNativeCaps.maxFramebufferHeight = mNativeCaps.max2DTextureSize;
mNativeCaps.maxViewportWidth = mNativeCaps.max2DTextureSize;
mNativeCaps.maxViewportHeight = mNativeCaps.max2DTextureSize;
bool isCatalyst = TARGET_OS_MACCATALYST;
mMaxColorTargetBits = mtl::kMaxColorTargetBitsApple1To3;
if (supportsMacGPUFamily(1) || isCatalyst)
{
mMaxColorTargetBits = mtl::kMaxColorTargetBitsMacAndCatalyst;
}
else if (supportsAppleGPUFamily(4))
{
mMaxColorTargetBits = mtl::kMaxColorTargetBitsApple4Plus;
}
if (mFeatures.limitMaxColorTargetBitsForTesting.enabled)
{
// Set so we have enough for RGBA8 on every attachment
// but not enough for RGBA32UI.
mMaxColorTargetBits = mNativeCaps.maxColorAttachments * 32;
}
// MSAA
mNativeCaps.maxSamples = mFormatTable.getMaxSamples();
mNativeCaps.maxSampleMaskWords = 0;
mNativeCaps.maxColorTextureSamples = mNativeCaps.maxSamples;
mNativeCaps.maxDepthTextureSamples = mNativeCaps.maxSamples;
mNativeCaps.maxIntegerSamples = 1;
mNativeCaps.maxVertexAttributes = mtl::kMaxVertexAttribs;
mNativeCaps.maxVertexAttribBindings = mtl::kMaxVertexAttribs;
mNativeCaps.maxVertexAttribRelativeOffset = std::numeric_limits<GLint>::max();
mNativeCaps.maxVertexAttribStride = std::numeric_limits<GLint>::max();
// glGet() use signed integer as parameter so we have to use GLint's max here, not GLuint.
mNativeCaps.maxElementsIndices = std::numeric_limits<GLint>::max();
mNativeCaps.maxElementsVertices = std::numeric_limits<GLint>::max();
// Looks like all floats are IEEE according to the docs here:
mNativeCaps.vertexHighpFloat.setIEEEFloat();
mNativeCaps.vertexMediumpFloat.setIEEEFloat();
mNativeCaps.vertexLowpFloat.setIEEEFloat();
mNativeCaps.fragmentHighpFloat.setIEEEFloat();
mNativeCaps.fragmentMediumpFloat.setIEEEFloat();
mNativeCaps.fragmentLowpFloat.setIEEEFloat();
mNativeCaps.vertexHighpInt.setTwosComplementInt(32);
mNativeCaps.vertexMediumpInt.setTwosComplementInt(32);
mNativeCaps.vertexLowpInt.setTwosComplementInt(32);
mNativeCaps.fragmentHighpInt.setTwosComplementInt(32);
mNativeCaps.fragmentMediumpInt.setTwosComplementInt(32);
mNativeCaps.fragmentLowpInt.setTwosComplementInt(32);
GLuint maxDefaultUniformVectors = mtl::kDefaultUniformsMaxSize / (sizeof(GLfloat) * 4);
const GLuint maxDefaultUniformComponents = maxDefaultUniformVectors * 4;
// Uniforms are implemented using a uniform buffer, so the max number of uniforms we can
// support is the max buffer range divided by the size of a single uniform (4X float).
mNativeCaps.maxVertexUniformVectors = maxDefaultUniformVectors;
mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Vertex] = maxDefaultUniformComponents;
mNativeCaps.maxFragmentUniformVectors = maxDefaultUniformVectors;
mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Fragment] = maxDefaultUniformComponents;
mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Vertex] = mtl::kMaxShaderUBOs;
mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Fragment] = mtl::kMaxShaderUBOs;
mNativeCaps.maxCombinedUniformBlocks = mtl::kMaxGLUBOBindings;
// Note that we currently implement textures as combined image+samplers, so the limit is
// the minimum of supported samplers and sampled images.
mNativeCaps.maxCombinedTextureImageUnits = mtl::kMaxGLSamplerBindings;
mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Fragment] = mtl::kMaxShaderSamplers;
mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Vertex] = mtl::kMaxShaderSamplers;
// No info from Metal given, use default GLES3 spec values:
mNativeCaps.minProgramTexelOffset = -8;
mNativeCaps.maxProgramTexelOffset = 7;
// NOTE(hqle): support storage buffer.
const uint32_t maxPerStageStorageBuffers = 0;
mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Vertex] = maxPerStageStorageBuffers;
mNativeCaps.maxShaderStorageBlocks[gl::ShaderType::Fragment] = maxPerStageStorageBuffers;
mNativeCaps.maxCombinedShaderStorageBlocks = maxPerStageStorageBuffers;
// Fill in additional limits for UBOs and SSBOs.
mNativeCaps.maxUniformBufferBindings = mNativeCaps.maxCombinedUniformBlocks;
mNativeCaps.maxUniformBlockSize = mtl::kMaxUBOSize; // Default according to GLES 3.0 spec.
if (supportsAppleGPUFamily(1))
{
mNativeCaps.uniformBufferOffsetAlignment =
16; // on Apple based GPU's We can ignore data types when setting constant buffer
// alignment at 16.
}
else
{
mNativeCaps.uniformBufferOffsetAlignment =
256; // constant buffers on all other GPUs must be aligned to 256.
}
mNativeCaps.maxShaderStorageBufferBindings = 0;
mNativeCaps.maxShaderStorageBlockSize = 0;
mNativeCaps.shaderStorageBufferOffsetAlignment = 0;
// UBO plus default uniform limits
const uint32_t maxCombinedUniformComponents =
maxDefaultUniformComponents + mtl::kMaxUBOSize * mtl::kMaxShaderUBOs / 4;
for (gl::ShaderType shaderType : gl::kAllGraphicsShaderTypes)
{
mNativeCaps.maxCombinedShaderUniformComponents[shaderType] = maxCombinedUniformComponents;
}
mNativeCaps.maxCombinedShaderOutputResources = 0;
mNativeCaps.maxTransformFeedbackInterleavedComponents =
gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS;
mNativeCaps.maxTransformFeedbackSeparateAttributes =
gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS;
mNativeCaps.maxTransformFeedbackSeparateComponents =
gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS;
// GL_OES_get_program_binary
mNativeCaps.programBinaryFormats.push_back(GL_PROGRAM_BINARY_ANGLE);
// GL_APPLE_clip_distance / GL_ANGLE_clip_cull_distance
mNativeCaps.maxClipDistances = 8;
// Metal doesn't support GL_TEXTURE_COMPARE_MODE=GL_NONE for shadow samplers
mNativeLimitations.noShadowSamplerCompareModeNone = true;
// Apple platforms require PVRTC1 textures to be squares.
mNativeLimitations.squarePvrtc1 = true;
// Older Metal does not support compressed formats for TEXTURE_3D target.
if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13.0))
{
mNativeLimitations.noCompressedTexture3D = !supportsEitherGPUFamily(3, 1);
}
else
{
mNativeLimitations.noCompressedTexture3D = true;
}
}
void DisplayMtl::initializeExtensions() const
{
// Reset
mNativeExtensions = gl::Extensions();
// Enable this for simple buffer readback testing, but some functionality is missing.
// NOTE(hqle): Support full mapBufferRangeEXT extension.
mNativeExtensions.mapbufferOES = true;
mNativeExtensions.mapBufferRangeEXT = true;
mNativeExtensions.textureStorageEXT = true;
mNativeExtensions.clipControlEXT = true;
mNativeExtensions.drawBuffersEXT = true;
mNativeExtensions.drawBuffersIndexedEXT = true;
mNativeExtensions.drawBuffersIndexedOES = true;
mNativeExtensions.fboRenderMipmapOES = true;
mNativeExtensions.fragDepthEXT = true;
mNativeExtensions.conservativeDepthEXT = true;
mNativeExtensions.framebufferBlitANGLE = true;
mNativeExtensions.framebufferBlitNV = true;
mNativeExtensions.framebufferMultisampleANGLE = true;
mNativeExtensions.polygonModeANGLE = true;
mNativeExtensions.polygonOffsetClampEXT = true;
mNativeExtensions.stencilTexturingANGLE = true;
mNativeExtensions.copyTextureCHROMIUM = true;
mNativeExtensions.copyCompressedTextureCHROMIUM = false;
#if !ANGLE_PLATFORM_WATCHOS
if (@available(iOS 14.0, macOS 10.11, macCatalyst 14.0, tvOS 16.0, *))
{
mNativeExtensions.textureMirrorClampToEdgeEXT = true;
}
#endif
if (ANGLE_APPLE_AVAILABLE_XCI(10.11, 13.1, 11.0))
{
mNativeExtensions.depthClampEXT = true;
}
// EXT_debug_marker is not implemented yet, but the entry points must be exposed for the
// Metal backend to be used in Chrome (http://anglebug.com/4946)
mNativeExtensions.debugMarkerEXT = true;
mNativeExtensions.robustnessEXT = true;
mNativeExtensions.textureBorderClampOES = false; // not implemented yet
mNativeExtensions.multiDrawIndirectEXT = true;
mNativeExtensions.translatedShaderSourceANGLE = true;
mNativeExtensions.discardFramebufferEXT = true;
// TODO(anglebug.com/6395): Apple's implementation exposed
// mNativeExtensions.textureRectangle = true here and
// EGL_TEXTURE_RECTANGLE_ANGLE as the eglBindTexImage texture target on
// macOS. This no longer seems necessary as IOSurfaces can be bound to
// regular 2D textures with Metal, and causes other problems such as
// breaking the SPIR-V Metal compiler.
// TODO(anglebug.com/6395): figure out why WebGL drawing buffer
// creation fails on macOS when the Metal backend advertises the
// EXT_multisampled_render_to_texture extension.
// TODO(anglebug.com/3107): Metal doesn't implement render to texture
// correctly. A texture (if used as a color attachment for a framebuffer)
// is always created with sample count == 1, which results in creation of a
// render pipeline with the same value. Moreover, if there is a more
// sophisticated case and a framebuffer also has a stencil/depth attachment,
// it will result in creation of a render pipeline with those attachment's
// sample count, but the texture that was used as a color attachment, will
// still remain with sample count 1. That results in Metal validation error
// if enabled.
mNativeExtensions.multisampledRenderToTextureEXT = false;
// Enable EXT_blend_minmax
mNativeExtensions.blendMinmaxEXT = true;
mNativeExtensions.EGLImageOES = true;
mNativeExtensions.EGLImageExternalOES = false;
// NOTE(hqle): Support GL_OES_EGL_image_external_essl3.
mNativeExtensions.EGLImageExternalEssl3OES = false;
mNativeExtensions.memoryObjectEXT = false;
mNativeExtensions.memoryObjectFdEXT = false;
mNativeExtensions.semaphoreEXT = false;
mNativeExtensions.semaphoreFdEXT = false;
mNativeExtensions.instancedArraysANGLE = true;
mNativeExtensions.instancedArraysEXT = mNativeExtensions.instancedArraysANGLE;
mNativeExtensions.robustBufferAccessBehaviorKHR = false;
mNativeExtensions.EGLSyncOES = false;
mNativeExtensions.occlusionQueryBooleanEXT = true;
mNativeExtensions.disjointTimerQueryEXT = true;
mNativeCaps.queryCounterBitsTimeElapsed = 64;
mNativeCaps.queryCounterBitsTimestamp = 0;
mNativeExtensions.textureFilterAnisotropicEXT = true;
mNativeCaps.maxTextureAnisotropy = 16;
mNativeExtensions.textureNpotOES = true;
mNativeExtensions.texture3DOES = true;
mNativeExtensions.sampleVariablesOES = true;
if (ANGLE_APPLE_AVAILABLE_XCI(11.0, 14.0, 14.0))
{
mNativeExtensions.shaderMultisampleInterpolationOES =
[mMetalDevice supportsPullModelInterpolation];
if (mNativeExtensions.shaderMultisampleInterpolationOES)
{
mNativeCaps.subPixelInterpolationOffsetBits = 4;
if (supportsAppleGPUFamily(1))
{
mNativeCaps.minInterpolationOffset = -0.5f;
mNativeCaps.maxInterpolationOffset = +0.5f;
}
else
{
// On non-Apple GPUs, the actual range is usually
// [-0.5, +0.4375] but due to framebuffer Y-flip
// the effective range for the Y direction will be
// [-0.4375, +0.5] when the default FBO is bound.
mNativeCaps.minInterpolationOffset = -0.4375f; // -0.5 + (2 ^ -4)
mNativeCaps.maxInterpolationOffset = +0.4375f; // +0.5 - (2 ^ -4)
}
}
}
mNativeExtensions.shaderNoperspectiveInterpolationNV = true;
mNativeExtensions.shaderTextureLodEXT = true;
mNativeExtensions.standardDerivativesOES = true;
mNativeExtensions.elementIndexUintOES = true;
// GL_OES_get_program_binary
mNativeExtensions.getProgramBinaryOES = true;
// GL_APPLE_clip_distance
mNativeExtensions.clipDistanceAPPLE = true;
// GL_ANGLE_clip_cull_distance
mNativeExtensions.clipCullDistanceANGLE = true;
// GL_NV_pixel_buffer_object
mNativeExtensions.pixelBufferObjectNV = true;
if (mFeatures.hasEvents.enabled)
{
// MTLSharedEvent is only available since Metal 2.1
// GL_NV_fence
mNativeExtensions.fenceNV = true;
// GL_OES_EGL_sync
mNativeExtensions.EGLSyncOES = true;
// GL_ARB_sync
mNativeExtensions.syncARB = true;
}
// GL_KHR_parallel_shader_compile
mNativeExtensions.parallelShaderCompileKHR = true;
mNativeExtensions.baseInstanceEXT = mFeatures.hasBaseVertexInstancedDraw.enabled;
mNativeExtensions.baseVertexBaseInstanceANGLE = mFeatures.hasBaseVertexInstancedDraw.enabled;
mNativeExtensions.baseVertexBaseInstanceShaderBuiltinANGLE =
mFeatures.hasBaseVertexInstancedDraw.enabled;
// Metal uses the opposite provoking vertex as GLES so emulation is required to use the GLES
// behaviour. Allow users to change the provoking vertex for improved performance.
mNativeExtensions.provokingVertexANGLE = true;
// GL_EXT_blend_func_extended
if (ANGLE_APPLE_AVAILABLE_XCI(10.12, 13.1, 11.0))
{
mNativeExtensions.blendFuncExtendedEXT = true;
mNativeCaps.maxDualSourceDrawBuffers = 1;
}
// GL_ANGLE_shader_pixel_local_storage.
if (!mFeatures.disableProgrammableBlending.enabled && supportsAppleGPUFamily(1))
{
// Programmable blending is supported on all Apple GPU families, and is always coherent.
mNativePLSOptions.type = ShPixelLocalStorageType::FramebufferFetch;
// Raster order groups are NOT required to make framebuffer fetch coherent, however, they
// may improve performance by allowing finer grained synchronization (e.g., by assigning
// attachments to different raster order groups when they don't depend on each other).
bool rasterOrderGroupsSupported =
!mFeatures.disableRasterOrderGroups.enabled && supportsAppleGPUFamily(4);
mNativePLSOptions.fragmentSyncType =
rasterOrderGroupsSupported ? ShFragmentSynchronizationType::RasterOrderGroups_Metal
: ShFragmentSynchronizationType::Automatic;
mNativeExtensions.shaderPixelLocalStorageANGLE = true;
mNativeExtensions.shaderPixelLocalStorageCoherentANGLE = true;
}
else
{
MTLReadWriteTextureTier readWriteTextureTier = [mMetalDevice readWriteTextureSupport];
if (readWriteTextureTier != MTLReadWriteTextureTierNone)
{
mNativePLSOptions.type = ShPixelLocalStorageType::ImageLoadStore;
// Raster order groups are required to make PLS coherent when using read_write textures.
bool rasterOrderGroupsSupported = !mFeatures.disableRasterOrderGroups.enabled &&
[mMetalDevice areRasterOrderGroupsSupported];
mNativePLSOptions.fragmentSyncType =
rasterOrderGroupsSupported ? ShFragmentSynchronizationType::RasterOrderGroups_Metal
: ShFragmentSynchronizationType::NotSupported;
mNativePLSOptions.supportsNativeRGBA8ImageFormats =
!mFeatures.disableRWTextureTier2Support.enabled &&
readWriteTextureTier == MTLReadWriteTextureTier2;
if (isAMD())
{
// anglebug.com/7792 -- [[raster_order_group()]] does not work for read_write
// textures on AMD when the render pass doesn't have a color attachment on slot 0.
// To work around this we attach one of the PLS textures to GL_COLOR_ATTACHMENT0, if
// there isn't one already.
mNativePLSOptions.renderPassNeedsAMDRasterOrderGroupsWorkaround = true;
}
mNativeExtensions.shaderPixelLocalStorageANGLE = true;
mNativeExtensions.shaderPixelLocalStorageCoherentANGLE = rasterOrderGroupsSupported;
// Set up PLS caps here because the higher level context won't have enough info to set
// them up itself. Shader images and other ES3.1 caps aren't fully exposed yet.
static_assert(mtl::kMaxShaderImages >=
gl::IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES);
mNativeCaps.maxPixelLocalStoragePlanes =
gl::IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES;
mNativeCaps.maxColorAttachmentsWithActivePixelLocalStorage =
gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
mNativeCaps.maxCombinedDrawBuffersAndPixelLocalStoragePlanes =
gl::IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES +
gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
mNativeCaps.maxShaderImageUniforms[gl::ShaderType::Fragment] =
gl::IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES;
mNativeCaps.maxImageUnits = gl::IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES;
}
}
// "The GPUs in Apple3 through Apple8 families only support memory barriers for compute command
// encoders, and for vertex-to-vertex and vertex-to-fragment stages of render command encoders."
mHasFragmentMemoryBarriers = !supportsAppleGPUFamily(3);
}
void DisplayMtl::initializeTextureCaps() const
{
mNativeTextureCaps.clear();
mFormatTable.generateTextureCaps(this, &mNativeTextureCaps);
// Re-verify texture extensions.
mNativeExtensions.setTextureExtensionSupport(mNativeTextureCaps);
// When ETC2/EAC formats are natively supported, enable ANGLE-specific extension string to
// expose them to WebGL. In other case, mark potentially-available ETC1 extension as
// emulated.
if (supportsAppleGPUFamily(1) && gl::DetermineCompressedTextureETCSupport(mNativeTextureCaps))
{
mNativeExtensions.compressedTextureEtcANGLE = true;
}
else
{
mNativeLimitations.emulatedEtc1 = true;
}
// Enable EXT_compressed_ETC1_RGB8_sub_texture if ETC1 is supported.
mNativeExtensions.compressedETC1RGB8SubTextureEXT =
mNativeExtensions.compressedETC1RGB8TextureOES;
// Enable ASTC sliced 3D, requires MTLGPUFamilyApple3
if (supportsAppleGPUFamily(3) && mNativeExtensions.textureCompressionAstcLdrKHR)
{
mNativeExtensions.textureCompressionAstcSliced3dKHR = true;
}
// Enable ASTC HDR, requires MTLGPUFamilyApple6
if (supportsAppleGPUFamily(6) && mNativeExtensions.textureCompressionAstcLdrKHR)
{
mNativeExtensions.textureCompressionAstcHdrKHR = true;
}
// Disable all depth buffer and stencil buffer readback extensions until we need them
mNativeExtensions.readDepthNV = false;
mNativeExtensions.readStencilNV = false;
mNativeExtensions.depthBufferFloat2NV = false;
}
void DisplayMtl::initializeLimitations()
{
mNativeLimitations.noVertexAttributeAliasing = true;
}
void DisplayMtl::initializeFeatures()
{
bool isOSX = TARGET_OS_OSX;
bool isCatalyst = TARGET_OS_MACCATALYST;
bool isSimulator = TARGET_OS_SIMULATOR;
bool isARM = ANGLE_APPLE_IS_ARM;
ApplyFeatureOverrides(&mFeatures, getState());
if (mState.featuresAllDisabled)
{
return;
}
ANGLE_FEATURE_CONDITION((&mFeatures), allowGenMultipleMipsPerPass, true);
ANGLE_FEATURE_CONDITION((&mFeatures), forceBufferGPUStorage, false);
ANGLE_FEATURE_CONDITION((&mFeatures), hasExplicitMemBarrier,
supportsMetal2_1() && (isOSX || isCatalyst) && !isARM);
ANGLE_FEATURE_CONDITION((&mFeatures), hasDepthAutoResolve, supportsEitherGPUFamily(3, 2));
ANGLE_FEATURE_CONDITION((&mFeatures), hasStencilAutoResolve, supportsEitherGPUFamily(5, 2));
ANGLE_FEATURE_CONDITION((&mFeatures), allowMultisampleStoreAndResolve,
supportsEitherGPUFamily(3, 1));
ANGLE_FEATURE_CONDITION((&mFeatures), allowRuntimeSamplerCompareMode,
supportsEitherGPUFamily(3, 1));
// AMD does not support sample_compare_grad
ANGLE_FEATURE_CONDITION((&mFeatures), allowSamplerCompareGradient,
supportsEitherGPUFamily(3, 1) && !isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), allowSamplerCompareLod, supportsEitherGPUFamily(3, 1));
// http://anglebug.com/4919
// Stencil blit shader is not compiled on Intel & NVIDIA, need investigation.
ANGLE_FEATURE_CONDITION((&mFeatures), hasShaderStencilOutput,
supportsMetal2_1() && !isIntel() && !isNVIDIA());
ANGLE_FEATURE_CONDITION((&mFeatures), hasTextureSwizzle,
supportsMetal2_2() && supportsEitherGPUFamily(3, 2) && !isSimulator);
ANGLE_FEATURE_CONDITION((&mFeatures), avoidStencilTextureSwizzle, isIntel());
// http://crbug.com/1136673
// Fence sync is flaky on Nvidia
ANGLE_FEATURE_CONDITION((&mFeatures), hasEvents, supportsMetal2_1() && !isNVIDIA());
ANGLE_FEATURE_CONDITION((&mFeatures), hasCheapRenderPass, (isOSX || isCatalyst) && !isARM);
// http://anglebug.com/5235
// D24S8 is unreliable on AMD.
ANGLE_FEATURE_CONDITION((&mFeatures), forceD24S8AsUnsupported, isAMD());
// Base Vertex drawing is only supported since GPU family 3.
ANGLE_FEATURE_CONDITION((&mFeatures), hasBaseVertexInstancedDraw,
isOSX || isCatalyst || supportsAppleGPUFamily(3));
ANGLE_FEATURE_CONDITION((&mFeatures), hasNonUniformDispatch,
isOSX || isCatalyst || supportsAppleGPUFamily(4));
ANGLE_FEATURE_CONDITION((&mFeatures), allowSeparateDepthStencilBuffers,
!isOSX && !isCatalyst && !isSimulator);
ANGLE_FEATURE_CONDITION((&mFeatures), emulateTransformFeedback, true);
ANGLE_FEATURE_CONDITION((&mFeatures), intelExplicitBoolCastWorkaround,
isIntel() && GetMacOSVersion() < OSVersion(11, 0, 0));
ANGLE_FEATURE_CONDITION((&mFeatures), intelDisableFastMath,
isIntel() && GetMacOSVersion() < OSVersion(12, 0, 0));
ANGLE_FEATURE_CONDITION((&mFeatures), emulateAlphaToCoverage,
isSimulator || !supportsAppleGPUFamily(1));
ANGLE_FEATURE_CONDITION((&mFeatures), multisampleColorFormatShaderReadWorkaround, isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), copyIOSurfaceToNonIOSurfaceForReadOptimization,
isIntel() || isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), copyTextureToBufferForReadOptimization, isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), forceNonCSBaseMipmapGeneration, isIntel());
ANGLE_FEATURE_CONDITION((&mFeatures), preemptivelyStartProvokingVertexCommandBuffer, isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), alwaysUseStagedBufferUpdates, isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), alwaysUseManagedStorageModeForBuffers, isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), alwaysUseSharedStorageModeForBuffers, isIntel());
ANGLE_FEATURE_CONDITION((&mFeatures), useShadowBuffersWhenAppropriate, isIntel());
// At least one of these must not be set.
ASSERT(!mFeatures.alwaysUseManagedStorageModeForBuffers.enabled ||
!mFeatures.alwaysUseSharedStorageModeForBuffers.enabled);
ANGLE_FEATURE_CONDITION((&mFeatures), uploadDataToIosurfacesWithStagingBuffers, isAMD());
// Render passes can be rendered without attachments on Apple4 , mac2 hardware.
ANGLE_FEATURE_CONDITION(&(mFeatures), allowRenderpassWithoutAttachment,
supportsEitherGPUFamily(4, 2));
ANGLE_FEATURE_CONDITION((&mFeatures), enableInMemoryMtlLibraryCache, true);
ANGLE_FEATURE_CONDITION((&mFeatures), enableParallelMtlLibraryCompilation, true);
// Uploading texture data via staging buffers improves performance on all tested systems.
// http://anglebug.com/8092: Disabled on intel due to some texture formats uploading incorrectly
// with staging buffers
ANGLE_FEATURE_CONDITION(&mFeatures, alwaysPreferStagedTextureUploads, true);
ANGLE_FEATURE_CONDITION(&mFeatures, disableStagedInitializationOfPackedTextureFormats,
isIntel() || isAMD());
ANGLE_FEATURE_CONDITION((&mFeatures), generateShareableShaders, true);
// http://anglebug.com/8170: NVIDIA GPUs are unsupported due to scarcity of the hardware.
ANGLE_FEATURE_CONDITION((&mFeatures), disableMetalOnNvidia, true);
// The AMDMTLBronzeDriver seems to have bugs flushing vertex data to the GPU during some kinds
// of buffer uploads which require a flush to work around.
ANGLE_FEATURE_CONDITION((&mFeatures), flushAfterStreamVertexData, isAMDBronzeDriver());
// TODO(anglebug.com/7952): GPUs that don't support Mac GPU family 2 or greater are
// unsupported by the Metal backend.
ANGLE_FEATURE_CONDITION((&mFeatures), requireGpuFamily2, true);
// anglebug.com/8258 Builtin shaders currently require MSL 2.1
ANGLE_FEATURE_CONDITION((&mFeatures), requireMsl21, true);
// http://anglebug.com/8311: Rescope global variables which are only used in one function to be
// function local. Disabled on AMD FirePro devices: http://anglebug.com/8317
ANGLE_FEATURE_CONDITION((&mFeatures), rescopeGlobalVariables, !isAMDFireProDevice());
// On tile-based GPUs, always resolving MSAA render buffers to single-sampled
// is preferred. Because it would save bandwidth by avoiding the cost of storing the MSAA
// textures to memory. Traditional desktop GPUs almost always store MSAA textures to memory
// anyway, so this feature would have no benefit besides adding additional resolve step and
// memory overhead of the hidden single-sampled textures.
ANGLE_FEATURE_CONDITION((&mFeatures), alwaysResolveMultisampleRenderBuffers, isARM);
}
angle::Result DisplayMtl::initializeShaderLibrary()
{
mtl::AutoObjCPtr<NSError *> err = nil;
#if ANGLE_METAL_XCODE_BUILDS_SHADERS || ANGLE_METAL_HAS_PREBUILT_INTERNAL_SHADERS
mDefaultShaders = mtl::CreateShaderLibraryFromBinary(getMetalDevice(), gDefaultMetallib,
std::size(gDefaultMetallib), &err);
#else
mDefaultShaders = mtl::CreateShaderLibrary(getMetalDevice(), gDefaultMetallibSrc,
std::size(gDefaultMetallibSrc), &err);
#endif
if (err)
{
ERR() << "Internal error: " << err.get().localizedDescription.UTF8String;
return angle::Result::Stop;
}
return angle::Result::Continue;
}
id<MTLLibrary> DisplayMtl::getDefaultShadersLib()
{
return mDefaultShaders;
}
bool DisplayMtl::supportsAppleGPUFamily(uint8_t iOSFamily) const
{
return mtl::SupportsAppleGPUFamily(getMetalDevice(), iOSFamily);
}
bool DisplayMtl::supportsMacGPUFamily(uint8_t macFamily) const
{
return mtl::SupportsMacGPUFamily(getMetalDevice(), macFamily);
}
bool DisplayMtl::supportsEitherGPUFamily(uint8_t iOSFamily, uint8_t macFamily) const
{
return supportsAppleGPUFamily(iOSFamily) || supportsMacGPUFamily(macFamily);
}
bool DisplayMtl::supportsMetal2_1() const
{
if (ANGLE_APPLE_AVAILABLE_XCI(10.14, 13.1, 12.0))
{
return true;
}
else
{
return false;
}
}
bool DisplayMtl::supportsMetal2_2() const
{
if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13.0))
{
return true;
}
else
{
return false;
}
}
bool DisplayMtl::supports32BitFloatFiltering() const
{
#if (defined(__MAC_11_0) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_11_0) || \
(defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_14_0) || \
(defined(__TVOS_14_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_14_0)
if (@available(ios 14.0, macOS 11.0, *))
{
return [mMetalDevice supports32BitFloatFiltering];
}
else
#endif
{
return supportsMacGPUFamily(1);
}
}
bool DisplayMtl::supportsDepth24Stencil8PixelFormat() const
{
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
return [mMetalDevice isDepth24Stencil8PixelFormatSupported];
#else
return false;
#endif
}
bool DisplayMtl::isAMD() const
{
return angle::IsAMD(mMetalDeviceVendorId);
}
bool DisplayMtl::isAMDBronzeDriver() const
{
if (!isAMD())
{
return false;
}
if (mComputedAMDBronze)
{
return mIsAMDBronze;
}
// All devices known to be covered by AMDMTlBronzeDriver.
//
// Note that we can not compare substrings because some devices
// (AMD Radeon Pro 560) are substrings of ones supported by a
// later driver (AMD Radeon Pro 5600M).
NSString *kMTLBronzeDeviceNames[22] = {
@"FirePro D300", @"FirePro D500", @"FirePro D700", @"Radeon R9 M290",
@"Radeon R9 M290X", @"Radeon R9 M370X", @"Radeon R9 M380", @"Radeon R9 M390",
@"Radeon R9 M395", @"Radeon Pro 450", @"Radeon Pro 455", @"Radeon Pro 460",
@"Radeon Pro 555", @"Radeon Pro 555X", @"Radeon Pro 560", @"Radeon Pro 560X",
@"Radeon Pro 570", @"Radeon Pro 570X", @"Radeon Pro 575", @"Radeon Pro 575X",
@"Radeon Pro 580", @"Radeon Pro 580X"};
for (size_t i = 0; i < ArraySize(kMTLBronzeDeviceNames); ++i)
{
if ([[mMetalDevice name] hasSuffix:kMTLBronzeDeviceNames[i]])
{
mIsAMDBronze = true;
break;
}
}
mComputedAMDBronze = true;
return mIsAMDBronze;
}
bool DisplayMtl::isAMDFireProDevice() const
{
if (!isAMD())
{
return false;
}
return [[mMetalDevice name] containsString:@"FirePro"];
}
bool DisplayMtl::isIntel() const
{
return angle::IsIntel(mMetalDeviceVendorId);
}
bool DisplayMtl::isNVIDIA() const
{
return angle::IsNVIDIA(mMetalDeviceVendorId);
}
bool DisplayMtl::isSimulator() const
{
return TARGET_OS_SIMULATOR;
}
#if ANGLE_MTL_EVENT_AVAILABLE
mtl::AutoObjCObj<MTLSharedEventListener> DisplayMtl::getOrCreateSharedEventListener()
{
if (!mSharedEventListener)
{
ANGLE_MTL_OBJC_SCOPE
{
mSharedEventListener = [[[MTLSharedEventListener alloc] init] ANGLE_MTL_AUTORELEASE];
ASSERT(mSharedEventListener); // Failure here most probably means a sandbox issue.
}
}
return mSharedEventListener;
}
#endif
} // namespace rx