| // |
| // 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 |