blob: f40a3f82880f45a5213a0029e7e22d9c8298829d [file] [log] [blame]
//
// Copyright (c) 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 "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/Surface.h"
#include "libANGLE/renderer/glslang_wrapper_utils.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/SurfaceMtl.h"
#include "libANGLE/renderer/metal/mtl_common.h"
#include "platform/Platform.h"
#include "EGL/eglext.h"
namespace rx
{
bool IsMetalDisplayAvailable()
{
// We only support macos 10.13+ and 11 for now. Since they are requirements for Metal 2.0.
if (ANGLE_APPLE_AVAILABLE_XCI(10.13, 13.0, 11))
{
return true;
}
return false;
}
DisplayImpl *CreateMetalDisplay(const egl::DisplayState &state)
{
return new DisplayMtl(state);
}
DisplayMtl::DisplayMtl(const egl::DisplayState &state) : DisplayImpl(state), mUtils(this) {}
DisplayMtl::~DisplayMtl() {}
egl::Error DisplayMtl::initialize(egl::Display *display)
{
ASSERT(IsMetalDisplayAvailable());
angle::Result result = initializeImpl(display);
if (result != angle::Result::Continue)
{
return egl::EglNotInitialized();
}
return egl::NoError();
}
angle::Result DisplayMtl::initializeImpl(egl::Display *display)
{
ANGLE_MTL_OBJC_SCOPE
{
mMetalDevice = [MTLCreateSystemDefaultDevice() ANGLE_MTL_AUTORELEASE];
if (!mMetalDevice)
{
return angle::Result::Stop;
}
mCmdQueue.set([[mMetalDevice.get() newCommandQueue] ANGLE_MTL_AUTORELEASE]);
mCapsInitialized = false;
GlslangInitialize();
if (!mState.featuresAllDisabled)
{
initializeFeatures();
}
ANGLE_TRY(mFormatTable.initialize(this));
return mUtils.initialize();
}
}
void DisplayMtl::terminate()
{
for (mtl::TextureRef &nullTex : mNullTextures)
{
nullTex.reset();
}
mUtils.onDestroy();
mCmdQueue.reset();
mMetalDevice = nil;
mCapsInitialized = false;
GlslangRelease();
}
bool DisplayMtl::testDeviceLost()
{
return false;
}
egl::Error DisplayMtl::restoreLostDevice(const egl::Display *display)
{
return egl::NoError();
}
std::string DisplayMtl::getVendorString() const
{
ANGLE_MTL_OBJC_SCOPE
{
std::string vendorString = "Google Inc.";
if (mMetalDevice)
{
vendorString += " Metal Renderer: ";
vendorString += mMetalDevice.get().name.UTF8String;
}
return vendorString;
}
}
DeviceImpl *DisplayMtl::createDevice()
{
UNIMPLEMENTED();
return nullptr;
}
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::EglBadAccess();
}
SurfaceImpl *DisplayMtl::createWindowSurface(const egl::SurfaceState &state,
EGLNativeWindowType window,
const egl::AttributeMap &attribs)
{
return new SurfaceMtl(this, state, window, attribs);
}
SurfaceImpl *DisplayMtl::createPbufferSurface(const egl::SurfaceState &state,
const egl::AttributeMap &attribs)
{
UNIMPLEMENTED();
return static_cast<SurfaceImpl *>(0);
}
SurfaceImpl *DisplayMtl::createPbufferFromClientBuffer(const egl::SurfaceState &state,
EGLenum buftype,
EGLClientBuffer clientBuffer,
const egl::AttributeMap &attribs)
{
UNIMPLEMENTED();
return static_cast<SurfaceImpl *>(0);
}
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)
{
UNIMPLEMENTED();
return nullptr;
}
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, this);
}
StreamProducerImpl *DisplayMtl::createStreamProducerD3DTexture(
egl::Stream::ConsumerType consumerType,
const egl::AttributeMap &attribs)
{
UNIMPLEMENTED();
return nullptr;
}
gl::Version DisplayMtl::getMaxSupportedESVersion() const
{
return mtl::kMaxSupportedGLVersion;
}
gl::Version DisplayMtl::getMaxConformantESVersion() const
{
return std::min(getMaxSupportedESVersion(), gl::Version(2, 0));
}
EGLSyncImpl *DisplayMtl::createSync(const egl::AttributeMap &attribs)
{
UNIMPLEMENTED();
return nullptr;
}
egl::Error DisplayMtl::makeCurrent(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->flexibleSurfaceCompatibility = true;
}
void DisplayMtl::generateCaps(egl::Caps *outCaps) const {}
void DisplayMtl::populateFeatureList(angle::FeatureList *features) {}
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.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_FALSE;
config.surfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT;
config.minSwapInterval = 1;
config.maxSwapInterval = 1;
config.renderTargetFormat = GL_RGBA8;
config.depthStencilFormat = GL_DEPTH24_STENCIL8;
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;
// 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;
configs.add(config);
// With D
config.depthSize = 24;
config.stencilSize = 0;
configs.add(config);
// With S
config.depthSize = 0;
config.stencilSize = 8;
configs.add(config);
// No DS
config.depthSize = 0;
config.stencilSize = 0;
configs.add(config);
return configs;
}
bool DisplayMtl::isValidNativeWindow(EGLNativeWindowType window) const
{
NSObject *layer = (__bridge NSObject *)(window);
return [layer isKindOfClass:[CALayer class]];
}
std::string DisplayMtl::getRendererDescription() const
{
ANGLE_MTL_OBJC_SCOPE
{
std::string desc = "Metal Renderer";
if (mMetalDevice)
{
desc += ": ";
desc += mMetalDevice.get().name.UTF8String;
}
return desc;
}
}
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 mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl::TextureType type)
{
// TODO(hqle): Use rx::IncompleteTextureSet.
ContextMtl *contextMtl = mtl::GetImpl(context);
if (!mNullTextures[type])
{
// initialize content with zeros
MTLRegion region = MTLRegionMake2D(0, 0, 1, 1);
const uint8_t zeroPixel[4] = {0, 0, 0, 255};
const auto &rgbaFormat = getPixelFormat(angle::FormatID::R8G8B8A8_UNORM);
switch (type)
{
case gl::TextureType::_2D:
(void)(mtl::Texture::Make2DTexture(contextMtl, rgbaFormat, 1, 1, 1, false, false,
&mNullTextures[type]));
mNullTextures[type]->replaceRegion(contextMtl, region, 0, 0, zeroPixel,
sizeof(zeroPixel));
break;
case gl::TextureType::CubeMap:
(void)(mtl::Texture::MakeCubeTexture(contextMtl, rgbaFormat, 1, 1, false, false,
&mNullTextures[type]));
for (int f = 0; f < 6; ++f)
{
mNullTextures[type]->replaceRegion(contextMtl, region, 0, f, zeroPixel,
sizeof(zeroPixel));
}
break;
default:
UNREACHABLE();
// NOTE(hqle): Support more texture types.
}
ASSERT(mNullTextures[type]);
}
return mNullTextures[type];
}
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;
mNativeCaps.maxVaryingVectors = 31;
mNativeCaps.maxVertexOutputComponents = 124;
#else
if ([getMetalDevice() supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1])
{
mNativeCaps.max2DTextureSize = 16384;
mNativeCaps.maxVertexOutputComponents = 124;
mNativeCaps.maxVaryingVectors = mNativeCaps.maxVertexOutputComponents / 4;
}
else
{
mNativeCaps.max2DTextureSize = 8192;
mNativeCaps.maxVertexOutputComponents = 60;
mNativeCaps.maxVaryingVectors = mNativeCaps.maxVertexOutputComponents / 4;
}
#endif
mNativeCaps.maxArrayTextureLayers = 2048;
mNativeCaps.maxLODBias = 0;
mNativeCaps.maxCubeMapTextureSize = mNativeCaps.max2DTextureSize;
mNativeCaps.maxRenderbufferSize = mNativeCaps.max2DTextureSize;
mNativeCaps.minAliasedPointSize = 1;
mNativeCaps.maxAliasedPointSize = 511;
mNativeCaps.minAliasedLineWidth = 1.0f;
mNativeCaps.maxAliasedLineWidth = 1.0f;
mNativeCaps.maxDrawBuffers = mtl::kMaxRenderTargets;
mNativeCaps.maxFramebufferWidth = mNativeCaps.max2DTextureSize;
mNativeCaps.maxFramebufferHeight = mNativeCaps.max2DTextureSize;
mNativeCaps.maxColorAttachments = mtl::kMaxRenderTargets;
mNativeCaps.maxViewportWidth = mNativeCaps.max2DTextureSize;
mNativeCaps.maxViewportHeight = mNativeCaps.max2DTextureSize;
// NOTE(hqle): MSAA
mNativeCaps.maxSampleMaskWords = 0;
mNativeCaps.maxColorTextureSamples = 1;
mNativeCaps.maxDepthTextureSamples = 1;
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();
mNativeCaps.maxElementsIndices = std::numeric_limits<GLuint>::max();
mNativeCaps.maxElementsVertices = std::numeric_limits<GLuint>::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 maxUniformVectors = mtl::kDefaultUniformsMaxSize / (sizeof(GLfloat) * 4);
const GLuint maxUniformComponents = maxUniformVectors * 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 = maxUniformVectors;
mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Vertex] = maxUniformComponents;
mNativeCaps.maxFragmentUniformVectors = maxUniformVectors;
mNativeCaps.maxShaderUniformComponents[gl::ShaderType::Fragment] = maxUniformComponents;
// NOTE(hqle): support UBO (ES 3.0 feature)
mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Vertex] = 0;
mNativeCaps.maxShaderUniformBlocks[gl::ShaderType::Fragment] = 0;
mNativeCaps.maxCombinedUniformBlocks = 0;
// 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::kMaxShaderSamplers;
mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Fragment] = mtl::kMaxShaderSamplers;
mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Vertex] = mtl::kMaxShaderSamplers;
// 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 = 0;
mNativeCaps.maxUniformBlockSize = 0;
mNativeCaps.uniformBufferOffsetAlignment = 0;
mNativeCaps.maxShaderStorageBufferBindings = 0;
mNativeCaps.maxShaderStorageBlockSize = 0;
mNativeCaps.shaderStorageBufferOffsetAlignment = 0;
// NOTE(hqle): support UBO
for (gl::ShaderType shaderType : gl::kAllGraphicsShaderTypes)
{
mNativeCaps.maxCombinedShaderUniformComponents[shaderType] = maxUniformComponents;
}
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;
// NOTE(hqle): support MSAA.
mNativeCaps.maxSamples = 1;
}
void DisplayMtl::initializeExtensions() const
{
// Reset
mNativeExtensions = gl::Extensions();
// Enable this for simple buffer readback testing, but some functionality is missing.
// NOTE(hqle): Support full mapBufferRange extension.
mNativeExtensions.mapBuffer = true;
mNativeExtensions.mapBufferRange = false;
mNativeExtensions.textureStorage = true;
mNativeExtensions.drawBuffers = false;
mNativeExtensions.fragDepth = true;
mNativeExtensions.framebufferBlit = false;
mNativeExtensions.framebufferMultisample = false;
mNativeExtensions.copyTexture = false;
mNativeExtensions.copyCompressedTexture = false;
mNativeExtensions.debugMarker = false;
mNativeExtensions.robustness = true;
mNativeExtensions.textureBorderClamp = false; // not implemented yet
mNativeExtensions.translatedShaderSource = true;
mNativeExtensions.discardFramebuffer = true;
// Enable EXT_blend_minmax
mNativeExtensions.blendMinMax = true;
mNativeExtensions.eglImage = false;
mNativeExtensions.eglImageExternal = false;
// NOTE(hqle): Support GL_OES_EGL_image_external_essl3.
mNativeExtensions.eglImageExternalEssl3 = false;
mNativeExtensions.memoryObject = false;
mNativeExtensions.memoryObjectFd = false;
mNativeExtensions.semaphore = false;
mNativeExtensions.semaphoreFd = false;
// TODO: Enable this always and emulate instanced draws if any divisor exceeds the maximum
// supported. http://anglebug.com/2672
mNativeExtensions.instancedArraysANGLE = false;
mNativeExtensions.robustBufferAccessBehavior = false;
mNativeExtensions.eglSync = false;
// NOTE(hqle): support occlusion query
mNativeExtensions.occlusionQueryBoolean = false;
mNativeExtensions.disjointTimerQuery = false;
mNativeExtensions.queryCounterBitsTimeElapsed = false;
mNativeExtensions.queryCounterBitsTimestamp = false;
mNativeExtensions.textureFilterAnisotropic = true;
mNativeExtensions.maxTextureAnisotropy = 16;
// NOTE(hqle): Support true NPOT textures.
mNativeExtensions.textureNPOT = false;
mNativeExtensions.texture3DOES = false;
mNativeExtensions.standardDerivatives = true;
mNativeExtensions.elementIndexUint = true;
}
void DisplayMtl::initializeTextureCaps() const
{
mNativeTextureCaps.clear();
mFormatTable.generateTextureCaps(this, &mNativeTextureCaps,
&mNativeCaps.compressedTextureFormats);
// Re-verify texture extensions.
mNativeExtensions.setTextureExtensionSupport(mNativeTextureCaps);
}
void DisplayMtl::initializeFeatures()
{
// default values:
mFeatures.hasBaseVertexInstancedDraw.enabled = true;
mFeatures.hasNonUniformDispatch.enabled = true;
mFeatures.hasTextureSwizzle.enabled = false;
mFeatures.allowSeparatedDepthStencilBuffers.enabled = false;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
// Texture swizzle is only supported if macos sdk 10.15 is present
# if defined(__MAC_10_15)
if (ANGLE_APPLE_AVAILABLE_XC(10.15, 13.0))
{
// The runtime OS must be MacOS 10.15+ or Mac Catalyst for this to be supported:
ANGLE_FEATURE_CONDITION((&mFeatures), hasTextureSwizzle,
[getMetalDevice() supportsFamily:MTLGPUFamilyMac2]);
}
# endif
#elif TARGET_OS_IOS
// Base Vertex drawing is only supported since GPU family 3.
ANGLE_FEATURE_CONDITION((&mFeatures), hasBaseVertexInstancedDraw,
[getMetalDevice() supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]);
ANGLE_FEATURE_CONDITION((&mFeatures), hasNonUniformDispatch,
[getMetalDevice() supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]);
# if !TARGET_OS_SIMULATOR
mFeatures.allowSeparatedDepthStencilBuffers.enabled = true;
# endif
#endif
angle::PlatformMethods *platform = ANGLEPlatformCurrent();
platform->overrideFeaturesMtl(platform, &mFeatures);
}
} // namespace rx