// // 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. // // SurfaceMtl.mm: // Implements the class methods for SurfaceMtl. // #include "libANGLE/renderer/metal/SurfaceMtl.h" #include #include "libANGLE/Display.h" #include "libANGLE/Surface.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h" #include "libANGLE/renderer/metal/FrameBufferMtl.h" #include "libANGLE/renderer/metal/mtl_format_utils.h" #include "libANGLE/renderer/metal/mtl_utils.h" // Compiler can turn on programmatical frame capture in release build by defining // ANGLE_METAL_FRAME_CAPTURE flag. #if defined(NDEBUG) && !defined(ANGLE_METAL_FRAME_CAPTURE) # define ANGLE_METAL_FRAME_CAPTURE_ENABLED 0 #else # define ANGLE_METAL_FRAME_CAPTURE_ENABLED ANGLE_WITH_MODERN_METAL_API #endif namespace rx { namespace { constexpr angle::FormatID kDefaultFrameBufferDepthFormatId = angle::FormatID::D32_FLOAT; constexpr angle::FormatID kDefaultFrameBufferStencilFormatId = angle::FormatID::S8_UINT; constexpr angle::FormatID kDefaultFrameBufferDepthStencilFormatId = angle::FormatID::D24_UNORM_S8_UINT; angle::Result CreateOrResizeTexture(const gl::Context *context, const mtl::Format &format, uint32_t width, uint32_t height, uint32_t samples, bool renderTargetOnly, mtl::TextureRef *textureOut) { ContextMtl *contextMtl = mtl::GetImpl(context); bool allowFormatView = format.hasDepthAndStencilBits(); if (*textureOut) { ANGLE_TRY((*textureOut)->resize(contextMtl, width, height)); } else if (samples > 1) { ANGLE_TRY(mtl::Texture::Make2DMSTexture(contextMtl, format, width, height, samples, /** renderTargetOnly */ renderTargetOnly, /** allowFormatView */ allowFormatView, textureOut)); } else { ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, format, width, height, 1, /** renderTargetOnly */ renderTargetOnly, /** allowFormatView */ allowFormatView, textureOut)); } return angle::Result::Continue; } } // anonymous namespace // SurfaceMtl implementation SurfaceMtl::SurfaceMtl(DisplayMtl *display, const egl::SurfaceState &state, const egl::AttributeMap &attribs) : SurfaceImpl(state) { mRobustResourceInit = attribs.get(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, EGL_FALSE) == EGL_TRUE; mColorFormat = display->getPixelFormat(angle::FormatID::B8G8R8A8_UNORM); mSamples = state.config->samples; int depthBits = 0; int stencilBits = 0; if (state.config) { depthBits = state.config->depthSize; stencilBits = state.config->stencilSize; } if (depthBits && stencilBits) { if (display->getFeatures().allowSeparatedDepthStencilBuffers.enabled) { mDepthFormat = display->getPixelFormat(kDefaultFrameBufferDepthFormatId); mStencilFormat = display->getPixelFormat(kDefaultFrameBufferStencilFormatId); } else { // We must use packed depth stencil mUsePackedDepthStencil = true; mDepthFormat = display->getPixelFormat(kDefaultFrameBufferDepthStencilFormatId); mStencilFormat = mDepthFormat; } } else if (depthBits) { mDepthFormat = display->getPixelFormat(kDefaultFrameBufferDepthFormatId); } else if (stencilBits) { mStencilFormat = display->getPixelFormat(kDefaultFrameBufferStencilFormatId); } } SurfaceMtl::~SurfaceMtl() {} void SurfaceMtl::destroy(const egl::Display *display) { mColorTexture = nullptr; mDepthTexture = nullptr; mStencilTexture = nullptr; mMSColorTexture = nullptr; mColorRenderTarget.reset(); mColorManualResolveRenderTarget.reset(); mDepthRenderTarget.reset(); mStencilRenderTarget.reset(); } egl::Error SurfaceMtl::initialize(const egl::Display *display) { return egl::NoError(); } FramebufferImpl *SurfaceMtl::createDefaultFramebuffer(const gl::Context *context, const gl::FramebufferState &state) { auto fbo = new FramebufferMtl(state, /* flipY */ false, /* backbuffer */ nullptr); return fbo; } egl::Error SurfaceMtl::makeCurrent(const gl::Context *context) { ContextMtl *contextMtl = mtl::GetImpl(context); StartFrameCapture(contextMtl); return egl::NoError(); } egl::Error SurfaceMtl::unMakeCurrent(const gl::Context *context) { ContextMtl *contextMtl = mtl::GetImpl(context); contextMtl->flushCommandBufer(); StopFrameCapture(); return egl::NoError(); } egl::Error SurfaceMtl::swap(const gl::Context *context) { return egl::NoError(); } egl::Error SurfaceMtl::postSubBuffer(const gl::Context *context, EGLint x, EGLint y, EGLint width, EGLint height) { UNIMPLEMENTED(); return egl::EglBadAccess(); } egl::Error SurfaceMtl::querySurfacePointerANGLE(EGLint attribute, void **value) { UNIMPLEMENTED(); return egl::EglBadAccess(); } egl::Error SurfaceMtl::bindTexImage(const gl::Context *context, gl::Texture *texture, EGLint buffer) { UNIMPLEMENTED(); return egl::EglBadAccess(); } egl::Error SurfaceMtl::releaseTexImage(const gl::Context *context, EGLint buffer) { UNIMPLEMENTED(); return egl::EglBadAccess(); } egl::Error SurfaceMtl::getSyncValues(EGLuint64KHR *ust, EGLuint64KHR *msc, EGLuint64KHR *sbc) { UNIMPLEMENTED(); return egl::EglBadAccess(); } egl::Error SurfaceMtl::getMscRate(EGLint *numerator, EGLint *denominator) { UNIMPLEMENTED(); return egl::EglBadAccess(); } void SurfaceMtl::setSwapInterval(EGLint interval) {} void SurfaceMtl::setFixedWidth(EGLint width) { UNIMPLEMENTED(); } void SurfaceMtl::setFixedHeight(EGLint height) { UNIMPLEMENTED(); } EGLint SurfaceMtl::getWidth() const { if (mColorTexture) { return static_cast(mColorTexture->widthAt0()); } return 0; } EGLint SurfaceMtl::getHeight() const { if (mColorTexture) { return static_cast(mColorTexture->heightAt0()); } return 0; } EGLint SurfaceMtl::isPostSubBufferSupported() const { return EGL_FALSE; } EGLint SurfaceMtl::getSwapBehavior() const { return EGL_BUFFER_PRESERVED; } angle::Result SurfaceMtl::initializeContents(const gl::Context *context, const gl::ImageIndex &imageIndex) { ASSERT(mColorTexture); ContextMtl *contextMtl = mtl::GetImpl(context); // Use loadAction=clear mtl::RenderPassDesc rpDesc; rpDesc.sampleCount = mColorTexture->samples(); rpDesc.numColorAttachments = 1; mColorRenderTarget.toRenderPassAttachmentDesc(&rpDesc.colorAttachments[0]); rpDesc.colorAttachments[0].loadAction = MTLLoadActionClear; MTLClearColor black = {}; rpDesc.colorAttachments[0].clearColor = mtl::EmulatedAlphaClearColor(black, mColorTexture->getColorWritableMask()); if (mDepthTexture) { mDepthRenderTarget.toRenderPassAttachmentDesc(&rpDesc.depthAttachment); rpDesc.depthAttachment.loadAction = MTLLoadActionClear; } if (mStencilTexture) { mStencilRenderTarget.toRenderPassAttachmentDesc(&rpDesc.stencilAttachment); rpDesc.stencilAttachment.loadAction = MTLLoadActionClear; } mtl::RenderCommandEncoder *encoder = contextMtl->getRenderPassCommandEncoder(rpDesc); encoder->setStoreAction(MTLStoreActionStore); return angle::Result::Continue; } angle::Result SurfaceMtl::getAttachmentRenderTarget(const gl::Context *context, GLenum binding, const gl::ImageIndex &imageIndex, GLsizei samples, FramebufferAttachmentRenderTarget **rtOut) { ASSERT(mColorTexture); switch (binding) { case GL_BACK: *rtOut = &mColorRenderTarget; break; case GL_DEPTH: *rtOut = mDepthFormat.valid() ? &mDepthRenderTarget : nullptr; break; case GL_STENCIL: *rtOut = mStencilFormat.valid() ? &mStencilRenderTarget : nullptr; break; case GL_DEPTH_STENCIL: // NOTE(hqle): ES 3.0 feature UNREACHABLE(); break; } return angle::Result::Continue; } angle::Result SurfaceMtl::ensureCompanionTexturesSizeCorrect(const gl::Context *context, const gl::Extents &size) { ContextMtl *contextMtl = mtl::GetImpl(context); ASSERT(mColorTexture); if (mSamples > 1 && (!mMSColorTexture || mMSColorTexture->sizeAt0() != size)) { mAutoResolveMSColorTexture = contextMtl->getDisplay()->getFeatures().allowMultisampleStoreAndResolve.enabled; ANGLE_TRY(CreateOrResizeTexture(context, mColorFormat, size.width, size.height, mSamples, /** renderTargetOnly */ mAutoResolveMSColorTexture, &mMSColorTexture)); if (mAutoResolveMSColorTexture) { // Use auto MSAA resolve at the end of render pass. mColorRenderTarget.setImplicitMSTexture(mMSColorTexture); } else { mColorRenderTarget.setTexture(mMSColorTexture); } } if (mDepthFormat.valid() && (!mDepthTexture || mDepthTexture->sizeAt0() != size)) { ANGLE_TRY(CreateOrResizeTexture(context, mDepthFormat, size.width, size.height, mSamples, /** renderTargetOnly */ false, &mDepthTexture)); mDepthRenderTarget.set(mDepthTexture, mtl::kZeroNativeMipLevel, 0, mDepthFormat); } if (mStencilFormat.valid() && (!mStencilTexture || mStencilTexture->sizeAt0() != size)) { if (mUsePackedDepthStencil) { mStencilTexture = mDepthTexture; } else { ANGLE_TRY(CreateOrResizeTexture(context, mStencilFormat, size.width, size.height, mSamples, /** renderTargetOnly */ false, &mStencilTexture)); } mStencilRenderTarget.set(mStencilTexture, mtl::kZeroNativeMipLevel, 0, mStencilFormat); } return angle::Result::Continue; } angle::Result SurfaceMtl::resolveColorTextureIfNeeded(const gl::Context *context) { ASSERT(mMSColorTexture); if (!mAutoResolveMSColorTexture) { // Manually resolve texture ContextMtl *contextMtl = mtl::GetImpl(context); mColorManualResolveRenderTarget.set(mColorTexture, mtl::kZeroNativeMipLevel, 0, mColorFormat); mtl::RenderCommandEncoder *encoder = contextMtl->getRenderTargetCommandEncoder(mColorManualResolveRenderTarget); ANGLE_TRY(contextMtl->getDisplay()->getUtils().blitColorWithDraw( context, encoder, mColorFormat.actualAngleFormat(), mMSColorTexture)); contextMtl->endEncoding(true); mColorManualResolveRenderTarget.reset(); } return angle::Result::Continue; } // WindowSurfaceMtl implementation. WindowSurfaceMtl::WindowSurfaceMtl(DisplayMtl *display, const egl::SurfaceState &state, EGLNativeWindowType window, const egl::AttributeMap &attribs) : SurfaceMtl(display, state, attribs), mLayer((__bridge CALayer *)(window)) { // NOTE(hqle): Width and height attributes is ignored for now. mCurrentKnownDrawableSize = CGSizeMake(0, 0); } WindowSurfaceMtl::~WindowSurfaceMtl() {} void WindowSurfaceMtl::destroy(const egl::Display *display) { SurfaceMtl::destroy(display); mCurrentDrawable = nil; if (mMetalLayer && mMetalLayer.get() != mLayer) { // If we created metal layer in WindowSurfaceMtl::initialize(), // we need to detach it from super layer now. [mMetalLayer.get() removeFromSuperlayer]; } mMetalLayer = nil; } egl::Error WindowSurfaceMtl::initialize(const egl::Display *display) { egl::Error re = SurfaceMtl::initialize(display); if (re.isError()) { return re; } DisplayMtl *displayMtl = mtl::GetImpl(display); id metalDevice = displayMtl->getMetalDevice(); StartFrameCapture(metalDevice, displayMtl->cmdQueue().get()); ANGLE_MTL_OBJC_SCOPE { if ([mLayer isKindOfClass:CAMetalLayer.class]) { mMetalLayer.retainAssign(static_cast(mLayer)); } else { mMetalLayer = [[[CAMetalLayer alloc] init] ANGLE_MTL_AUTORELEASE]; mMetalLayer.get().frame = mLayer.frame; } mMetalLayer.get().device = metalDevice; mMetalLayer.get().pixelFormat = mColorFormat.metalFormat; mMetalLayer.get().framebufferOnly = NO; // Support blitting and glReadPixels #if TARGET_OS_OSX || TARGET_OS_MACCATALYST // Autoresize with parent layer. mMetalLayer.get().autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; #endif if (mMetalLayer.get() != mLayer) { mMetalLayer.get().contentsScale = mLayer.contentsScale; [mLayer addSublayer:mMetalLayer.get()]; } // ensure drawableSize is set to correct value: mMetalLayer.get().drawableSize = mCurrentKnownDrawableSize = calcExpectedDrawableSize(); } return egl::NoError(); } FramebufferImpl *WindowSurfaceMtl::createDefaultFramebuffer(const gl::Context *context, const gl::FramebufferState &state) { auto fbo = new FramebufferMtl(state, /* flipY */ true, /* backbuffer */ this); return fbo; } egl::Error WindowSurfaceMtl::swap(const gl::Context *context) { ANGLE_TO_EGL_TRY(swapImpl(context)); return egl::NoError(); } void WindowSurfaceMtl::setSwapInterval(EGLint interval) { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST mMetalLayer.get().displaySyncEnabled = interval != 0; #endif } // width and height can change with client window resizing EGLint WindowSurfaceMtl::getWidth() const { return static_cast(mCurrentKnownDrawableSize.width); } EGLint WindowSurfaceMtl::getHeight() const { return static_cast(mCurrentKnownDrawableSize.height); } EGLint WindowSurfaceMtl::getSwapBehavior() const { return EGL_BUFFER_DESTROYED; } angle::Result WindowSurfaceMtl::initializeContents(const gl::Context *context, const gl::ImageIndex &imageIndex) { bool newDrawable; ANGLE_TRY(ensureCurrentDrawableObtained(context, &newDrawable)); if (!newDrawable) { return angle::Result::Continue; } return SurfaceMtl::initializeContents(context, imageIndex); } angle::Result WindowSurfaceMtl::getAttachmentRenderTarget(const gl::Context *context, GLenum binding, const gl::ImageIndex &imageIndex, GLsizei samples, FramebufferAttachmentRenderTarget **rtOut) { ANGLE_TRY(ensureCurrentDrawableObtained(context, nullptr)); ANGLE_TRY(ensureCompanionTexturesSizeCorrect(context)); return SurfaceMtl::getAttachmentRenderTarget(context, binding, imageIndex, samples, rtOut); } angle::Result WindowSurfaceMtl::ensureCurrentDrawableObtained(const gl::Context *context) { return ensureCurrentDrawableObtained(context, nullptr); } angle::Result WindowSurfaceMtl::ensureCurrentDrawableObtained(const gl::Context *context, bool *newDrawableOut) { if (newDrawableOut) { *newDrawableOut = !mCurrentDrawable; } if (!mCurrentDrawable) { ANGLE_TRY(obtainNextDrawable(context)); } return angle::Result::Continue; } angle::Result WindowSurfaceMtl::ensureCompanionTexturesSizeCorrect(const gl::Context *context) { ASSERT(mMetalLayer); gl::Extents size(static_cast(mMetalLayer.get().drawableSize.width), static_cast(mMetalLayer.get().drawableSize.height), 1); ANGLE_TRY(SurfaceMtl::ensureCompanionTexturesSizeCorrect(context, size)); return angle::Result::Continue; } angle::Result WindowSurfaceMtl::ensureColorTextureReadyForReadPixels(const gl::Context *context) { ANGLE_TRY(ensureCurrentDrawableObtained(context, nullptr)); if (mMSColorTexture) { if (mMSColorTexture->isCPUReadMemNeedSync()) { ANGLE_TRY(resolveColorTextureIfNeeded(context)); mMSColorTexture->resetCPUReadMemNeedSync(); } } return angle::Result::Continue; } CGSize WindowSurfaceMtl::calcExpectedDrawableSize() const { CGSize currentLayerSize = mMetalLayer.get().bounds.size; CGFloat currentLayerContentsScale = mMetalLayer.get().contentsScale; CGSize expectedDrawableSize = CGSizeMake(currentLayerSize.width * currentLayerContentsScale, currentLayerSize.height * currentLayerContentsScale); return expectedDrawableSize; } bool WindowSurfaceMtl::checkIfLayerResized(const gl::Context *context) { if (mMetalLayer.get() != mLayer && mMetalLayer.get().contentsScale != mLayer.contentsScale) { // Parent layer's content scale has changed, update Metal layer's scale factor. mMetalLayer.get().contentsScale = mLayer.contentsScale; } CGSize currentLayerDrawableSize = mMetalLayer.get().drawableSize; CGSize expectedDrawableSize = calcExpectedDrawableSize(); // NOTE(hqle): We need to compare the size against mCurrentKnownDrawableSize also. // That is because metal framework might internally change the drawableSize property of // metal layer, and it might become equal to expectedDrawableSize. If that happens, we cannot // know whether the layer has been resized or not. if (currentLayerDrawableSize.width != expectedDrawableSize.width || currentLayerDrawableSize.height != expectedDrawableSize.height || mCurrentKnownDrawableSize.width != expectedDrawableSize.width || mCurrentKnownDrawableSize.height != expectedDrawableSize.height) { // Resize the internal drawable texture. mMetalLayer.get().drawableSize = mCurrentKnownDrawableSize = expectedDrawableSize; return true; } return false; } angle::Result WindowSurfaceMtl::obtainNextDrawable(const gl::Context *context) { ANGLE_MTL_OBJC_SCOPE { ContextMtl *contextMtl = mtl::GetImpl(context); ANGLE_MTL_TRY(contextMtl, mMetalLayer); // Check if layer was resized if (checkIfLayerResized(context)) { contextMtl->onBackbufferResized(context, this); } mCurrentDrawable.retainAssign([mMetalLayer nextDrawable]); if (!mCurrentDrawable) { // The GPU might be taking too long finishing its rendering to the previous frame. // Try again, indefinitely wait until the previous frame render finishes. // TODO: this may wait forever here mMetalLayer.get().allowsNextDrawableTimeout = NO; mCurrentDrawable.retainAssign([mMetalLayer nextDrawable]); mMetalLayer.get().allowsNextDrawableTimeout = YES; } if (!mColorTexture) { mColorTexture = mtl::Texture::MakeFromMetal(mCurrentDrawable.get().texture); ASSERT(!mColorRenderTarget.getTexture()); mColorRenderTarget.setWithImplicitMSTexture(mColorTexture, mMSColorTexture, mtl::kZeroNativeMipLevel, 0, mColorFormat); } else { mColorTexture->set(mCurrentDrawable.get().texture); } ANGLE_MTL_LOG("Current metal drawable size=%d,%d", mColorTexture->width(), mColorTexture->height()); // Now we have to resize depth stencil buffers if required. ANGLE_TRY(ensureCompanionTexturesSizeCorrect(context)); return angle::Result::Continue; } } angle::Result WindowSurfaceMtl::swapImpl(const gl::Context *context) { if (mCurrentDrawable) { ASSERT(mColorTexture); ContextMtl *contextMtl = mtl::GetImpl(context); if (mMSColorTexture) { ANGLE_TRY(resolveColorTextureIfNeeded(context)); } contextMtl->present(context, mCurrentDrawable); StopFrameCapture(); StartFrameCapture(contextMtl); // Invalidate current drawable mColorTexture->set(nil); mCurrentDrawable = nil; } return angle::Result::Continue; } // OffscreenSurfaceMtl implementation OffscreenSurfaceMtl::OffscreenSurfaceMtl(DisplayMtl *display, const egl::SurfaceState &state, const egl::AttributeMap &attribs) : SurfaceMtl(display, state, attribs) { mSize = gl::Extents(attribs.getAsInt(EGL_WIDTH, 1), attribs.getAsInt(EGL_HEIGHT, 1), 1); } OffscreenSurfaceMtl::~OffscreenSurfaceMtl() {} void OffscreenSurfaceMtl::destroy(const egl::Display *display) { SurfaceMtl::destroy(display); } egl::Error OffscreenSurfaceMtl::swap(const gl::Context *context) { // Check for surface resize. ANGLE_TO_EGL_TRY(ensureTexturesSizeCorrect(context)); return egl::NoError(); } egl::Error OffscreenSurfaceMtl::bindTexImage(const gl::Context *context, gl::Texture *texture, EGLint buffer) { ContextMtl *contextMtl = mtl::GetImpl(context); contextMtl->flushCommandBufer(); // Initialize offscreen textures if needed: ANGLE_TO_EGL_TRY(ensureTexturesSizeCorrect(context)); return egl::NoError(); } egl::Error OffscreenSurfaceMtl::releaseTexImage(const gl::Context *context, EGLint buffer) { ContextMtl *contextMtl = mtl::GetImpl(context); if (mMSColorTexture) { ANGLE_TO_EGL_TRY(resolveColorTextureIfNeeded(context)); } // NOTE(hqle): Should we finishCommandBuffer or flush is enough? contextMtl->flushCommandBufer(); return egl::NoError(); } angle::Result OffscreenSurfaceMtl::getAttachmentRenderTarget( const gl::Context *context, GLenum binding, const gl::ImageIndex &imageIndex, GLsizei samples, FramebufferAttachmentRenderTarget **rtOut) { // Initialize offscreen textures if needed: ANGLE_TRY(ensureTexturesSizeCorrect(context)); return SurfaceMtl::getAttachmentRenderTarget(context, binding, imageIndex, samples, rtOut); } angle::Result OffscreenSurfaceMtl::ensureTexturesSizeCorrect(const gl::Context *context) { if (!mColorTexture || mColorTexture->sizeAt0() != mSize) { ANGLE_TRY(CreateOrResizeTexture(context, mColorFormat, mSize.width, mSize.height, 1, /** renderTargetOnly */ false, &mColorTexture)); mColorRenderTarget.set(mColorTexture, mtl::kZeroNativeMipLevel, 0, mColorFormat); } return ensureCompanionTexturesSizeCorrect(context, mSize); } // PBufferSurfaceMtl implementation PBufferSurfaceMtl::PBufferSurfaceMtl(DisplayMtl *display, const egl::SurfaceState &state, const egl::AttributeMap &attribs) : OffscreenSurfaceMtl(display, state, attribs) {} void PBufferSurfaceMtl::setFixedWidth(EGLint width) { mSize.width = width; } void PBufferSurfaceMtl::setFixedHeight(EGLint height) { mSize.height = height; } }