1// 2// Copyright 2019 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// SurfaceMtl.mm: 7// Implements the class methods for SurfaceMtl. 8// 9 10#include "libANGLE/renderer/metal/SurfaceMtl.h" 11 12#include <TargetConditionals.h> 13 14#include "libANGLE/Display.h" 15#include "libANGLE/Surface.h" 16#include "libANGLE/renderer/metal/ContextMtl.h" 17#include "libANGLE/renderer/metal/DisplayMtl.h" 18#include "libANGLE/renderer/metal/FrameBufferMtl.h" 19#include "libANGLE/renderer/metal/mtl_format_utils.h" 20 21// Compiler can turn on programmatical frame capture in release build by defining 22// ANGLE_METAL_FRAME_CAPTURE flag. 23#if defined(NDEBUG) && !defined(ANGLE_METAL_FRAME_CAPTURE) 24# define ANGLE_METAL_FRAME_CAPTURE_ENABLED 0 25#else 26# define ANGLE_METAL_FRAME_CAPTURE_ENABLED 1 27#endif 28 29namespace rx 30{ 31 32namespace 33{ 34constexpr angle::FormatID kDefaultFrameBufferDepthFormatId = angle::FormatID::D32_FLOAT; 35constexpr angle::FormatID kDefaultFrameBufferStencilFormatId = angle::FormatID::S8_UINT; 36constexpr angle::FormatID kDefaultFrameBufferDepthStencilFormatId = 37 angle::FormatID::D24_UNORM_S8_UINT; 38 39ANGLE_MTL_UNUSED 40bool IsFrameCaptureEnabled() 41{ 42#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED 43 return false; 44#else 45 // We only support frame capture programmatically if the ANGLE_METAL_FRAME_CAPTURE 46 // environment flag is set. Otherwise, it will slow down the rendering. This allows user to 47 // finely control whether he wants to capture the frame for particular application or not. 48 auto var = std::getenv("ANGLE_METAL_FRAME_CAPTURE"); 49 static const bool enabled = var ? (strcmp(var, "1") == 0) : false; 50 51 return enabled; 52#endif 53} 54 55ANGLE_MTL_UNUSED 56size_t MaxAllowedFrameCapture() 57{ 58#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED 59 return 0; 60#else 61 auto var = std::getenv("ANGLE_METAL_FRAME_CAPTURE_MAX"); 62 static const size_t maxFrames = var ? std::atoi(var) : 100; 63 64 return maxFrames; 65#endif 66} 67 68ANGLE_MTL_UNUSED 69size_t MinAllowedFrameCapture() 70{ 71#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED 72 return 0; 73#else 74 auto var = std::getenv("ANGLE_METAL_FRAME_CAPTURE_MIN"); 75 static const size_t minFrame = var ? std::atoi(var) : 0; 76 77 return minFrame; 78#endif 79} 80 81ANGLE_MTL_UNUSED 82bool FrameCaptureDeviceScope() 83{ 84#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED 85 return false; 86#else 87 auto var = std::getenv("ANGLE_METAL_FRAME_CAPTURE_SCOPE"); 88 static const bool scopeDevice = var ? (strcmp(var, "device") == 0) : false; 89 90 return scopeDevice; 91#endif 92} 93 94ANGLE_MTL_UNUSED 95std::atomic<size_t> gFrameCaptured(0); 96 97ANGLE_MTL_UNUSED 98void StartFrameCapture(id<MTLDevice> metalDevice, id<MTLCommandQueue> metalCmdQueue) 99{ 100#if ANGLE_METAL_FRAME_CAPTURE_ENABLED 101 if (!IsFrameCaptureEnabled()) 102 { 103 return; 104 } 105 106 if (gFrameCaptured >= MaxAllowedFrameCapture()) 107 { 108 return; 109 } 110 111 MTLCaptureManager *captureManager = [MTLCaptureManager sharedCaptureManager]; 112 if (captureManager.isCapturing) 113 { 114 return; 115 } 116 117 gFrameCaptured++; 118 119 if (gFrameCaptured < MinAllowedFrameCapture()) 120 { 121 return; 122 } 123 124# ifdef __MAC_10_15 125 if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.0, 13)) 126 { 127 MTLCaptureDescriptor *captureDescriptor = [[MTLCaptureDescriptor alloc] init]; 128 captureDescriptor.captureObject = metalDevice; 129 130 NSError *error; 131 if (![captureManager startCaptureWithDescriptor:captureDescriptor error:&error]) 132 { 133 NSLog(@"Failed to start capture, error %@", error); 134 } 135 } 136 else 137# endif // __MAC_10_15 138 { 139 if (FrameCaptureDeviceScope()) 140 { 141 [captureManager startCaptureWithDevice:metalDevice]; 142 } 143 else 144 { 145 [captureManager startCaptureWithCommandQueue:metalCmdQueue]; 146 } 147 } 148#endif // ANGLE_METAL_FRAME_CAPTURE_ENABLED 149} 150 151void StartFrameCapture(ContextMtl *context) 152{ 153 StartFrameCapture(context->getMetalDevice(), context->cmdQueue().get()); 154} 155 156void StopFrameCapture() 157{ 158#if ANGLE_METAL_FRAME_CAPTURE_ENABLED 159 if (!IsFrameCaptureEnabled()) 160 { 161 return; 162 } 163 MTLCaptureManager *captureManager = [MTLCaptureManager sharedCaptureManager]; 164 if (captureManager.isCapturing) 165 { 166 [captureManager stopCapture]; 167 } 168#endif 169} 170} 171 172SurfaceMtl::SurfaceMtl(DisplayMtl *display, 173 const egl::SurfaceState &state, 174 EGLNativeWindowType window, 175 const egl::AttributeMap &attribs) 176 : SurfaceImpl(state), mLayer((__bridge CALayer *)(window)) 177{ 178 // NOTE(hqle): Width and height attributes is ignored for now. 179 180 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf says that BGRA8Unorm is 181 // only supported if depth24Stencil8PixelFormatSupported capabilitiy is YES. Yet 182 // CAMetalLayer can be created with pixelFormat MTLPixelFormatBGRA8Unorm. So the mtl::Format 183 // used for SurfaceMtl is initialized a bit differently from normal TextureMtl's mtl::Format. 184 // It won't use format table, instead we initialize its values here to use BGRA8Unorm directly: 185 mColorFormat.intendedFormatId = mColorFormat.actualFormatId = angle::FormatID::B8G8R8A8_UNORM; 186 mColorFormat.metalFormat = MTLPixelFormatBGRA8Unorm; 187 188 int depthBits = 0; 189 int stencilBits = 0; 190 if (state.config) 191 { 192 depthBits = state.config->depthSize; 193 stencilBits = state.config->stencilSize; 194 } 195 196 if (depthBits && stencilBits) 197 { 198 if (display->getFeatures().allowSeparatedDepthStencilBuffers.enabled) 199 { 200 mDepthFormat = display->getPixelFormat(kDefaultFrameBufferDepthFormatId); 201 mStencilFormat = display->getPixelFormat(kDefaultFrameBufferStencilFormatId); 202 } 203 else 204 { 205 // We must use packed depth stencil 206 mUsePackedDepthStencil = true; 207 mDepthFormat = display->getPixelFormat(kDefaultFrameBufferDepthStencilFormatId); 208 mStencilFormat = mDepthFormat; 209 } 210 } 211 else if (depthBits) 212 { 213 mDepthFormat = display->getPixelFormat(kDefaultFrameBufferDepthFormatId); 214 } 215 else if (stencilBits) 216 { 217 mStencilFormat = display->getPixelFormat(kDefaultFrameBufferStencilFormatId); 218 } 219} 220 221SurfaceMtl::~SurfaceMtl() {} 222 223void SurfaceMtl::destroy(const egl::Display *display) 224{ 225 mDrawableTexture = nullptr; 226 mDepthTexture = nullptr; 227 mStencilTexture = nullptr; 228 mCurrentDrawable = nil; 229 if (mMetalLayer && mMetalLayer.get() != mLayer) 230 { 231 // If we created metal layer in SurfaceMtl::initialize(), 232 // we need to detach it from super layer now. 233 [mMetalLayer.get() removeFromSuperlayer]; 234 } 235 mMetalLayer = nil; 236} 237 238egl::Error SurfaceMtl::initialize(const egl::Display *display) 239{ 240 DisplayMtl *displayMtl = mtl::GetImpl(display); 241 id<MTLDevice> metalDevice = displayMtl->getMetalDevice(); 242 243 StartFrameCapture(metalDevice, displayMtl->cmdQueue().get()); 244 245 ANGLE_MTL_OBJC_SCOPE 246 { 247 if ([mLayer isKindOfClass:CAMetalLayer.class]) 248 { 249 mMetalLayer.retainAssign(static_cast<CAMetalLayer *>(mLayer)); 250 } 251 else 252 { 253 mMetalLayer = [[[CAMetalLayer alloc] init] ANGLE_MTL_AUTORELEASE]; 254 mMetalLayer.get().frame = mLayer.frame; 255 } 256 257 mMetalLayer.get().device = metalDevice; 258 mMetalLayer.get().pixelFormat = mColorFormat.metalFormat; 259 mMetalLayer.get().framebufferOnly = NO; // This to allow readPixels 260 261#if TARGET_OS_OSX || TARGET_OS_MACCATALYST 262 // Autoresize with parent layer. 263 mMetalLayer.get().autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; 264#endif 265 266 if (mMetalLayer.get() != mLayer) 267 { 268 mMetalLayer.get().contentsScale = mLayer.contentsScale; 269 270 [mLayer addSublayer:mMetalLayer.get()]; 271 } 272 273 // ensure drawableSize is set to correct value: 274 checkIfLayerResized(); 275 } 276 277 return egl::NoError(); 278} 279 280FramebufferImpl *SurfaceMtl::createDefaultFramebuffer(const gl::Context *context, 281 const gl::FramebufferState &state) 282{ 283 auto fbo = new FramebufferMtl(state, /* flipY */ true); 284 285 return fbo; 286} 287 288egl::Error SurfaceMtl::makeCurrent(const gl::Context *context) 289{ 290 angle::Result result = obtainNextDrawable(context); 291 if (result != angle::Result::Continue) 292 { 293 return egl::EglBadCurrentSurface(); 294 } 295 return egl::NoError(); 296} 297 298egl::Error SurfaceMtl::unMakeCurrent(const gl::Context *context) 299{ 300 return egl::NoError(); 301} 302 303egl::Error SurfaceMtl::swap(const gl::Context *context) 304{ 305 angle::Result result = swapImpl(context); 306 307 if (result != angle::Result::Continue) 308 { 309 return egl::EglBadSurface(); 310 } 311 312 return egl::NoError(); 313} 314 315egl::Error SurfaceMtl::postSubBuffer(const gl::Context *context, 316 EGLint x, 317 EGLint y, 318 EGLint width, 319 EGLint height) 320{ 321 UNIMPLEMENTED(); 322 return egl::EglBadAccess(); 323} 324 325egl::Error SurfaceMtl::querySurfacePointerANGLE(EGLint attribute, void **value) 326{ 327 UNIMPLEMENTED(); 328 return egl::EglBadAccess(); 329} 330 331egl::Error SurfaceMtl::bindTexImage(const gl::Context *context, gl::Texture *texture, EGLint buffer) 332{ 333 UNIMPLEMENTED(); 334 return egl::EglBadAccess(); 335} 336 337egl::Error SurfaceMtl::releaseTexImage(const gl::Context *context, EGLint buffer) 338{ 339 UNIMPLEMENTED(); 340 return egl::EglBadAccess(); 341} 342 343egl::Error SurfaceMtl::getSyncValues(EGLuint64KHR *ust, EGLuint64KHR *msc, EGLuint64KHR *sbc) 344{ 345 UNIMPLEMENTED(); 346 return egl::EglBadAccess(); 347} 348 349egl::Error SurfaceMtl::getMscRate(EGLint *numerator, EGLint *denominator) 350{ 351 UNIMPLEMENTED(); 352 return egl::EglBadAccess(); 353} 354 355void SurfaceMtl::setSwapInterval(EGLint interval) {} 356 357void SurfaceMtl::setFixedWidth(EGLint width) 358{ 359 UNIMPLEMENTED(); 360} 361 362void SurfaceMtl::setFixedHeight(EGLint height) 363{ 364 UNIMPLEMENTED(); 365} 366 367// width and height can change with client window resizing 368EGLint SurfaceMtl::getWidth() const 369{ 370 if (mDrawableTexture) 371 { 372 return static_cast<EGLint>(mDrawableTexture->width()); 373 } 374 375 if (mMetalLayer) 376 { 377 return static_cast<EGLint>(mMetalLayer.get().drawableSize.width); 378 } 379 return 0; 380} 381 382EGLint SurfaceMtl::getHeight() const 383{ 384 if (mDrawableTexture) 385 { 386 return static_cast<EGLint>(mDrawableTexture->height()); 387 } 388 389 if (mMetalLayer) 390 { 391 return static_cast<EGLint>(mMetalLayer.get().drawableSize.height); 392 } 393 return 0; 394} 395 396EGLint SurfaceMtl::isPostSubBufferSupported() const 397{ 398 return EGL_FALSE; 399} 400 401EGLint SurfaceMtl::getSwapBehavior() const 402{ 403 return EGL_BUFFER_DESTROYED; 404} 405 406angle::Result SurfaceMtl::getAttachmentRenderTarget(const gl::Context *context, 407 GLenum binding, 408 const gl::ImageIndex &imageIndex, 409 GLsizei samples, 410 FramebufferAttachmentRenderTarget **rtOut) 411{ 412 // NOTE(hqle): Support MSAA. 413 ANGLE_TRY(ensureRenderTargetsCreated(context)); 414 415 switch (binding) 416 { 417 case GL_BACK: 418 *rtOut = &mColorRenderTarget; 419 break; 420 case GL_DEPTH: 421 *rtOut = mDepthFormat.valid() ? &mDepthRenderTarget : nullptr; 422 break; 423 case GL_STENCIL: 424 *rtOut = mStencilFormat.valid() ? &mStencilRenderTarget : nullptr; 425 break; 426 case GL_DEPTH_STENCIL: 427 // NOTE(hqle): ES 3.0 feature 428 UNREACHABLE(); 429 break; 430 } 431 432 return angle::Result::Continue; 433} 434 435angle::Result SurfaceMtl::ensureRenderTargetsCreated(const gl::Context *context) 436{ 437 if (!mDrawableTexture) 438 { 439 ANGLE_TRY(obtainNextDrawable(context)); 440 } 441 442 return angle::Result::Continue; 443} 444 445angle::Result SurfaceMtl::ensureDepthStencilSizeCorrect(const gl::Context *context, 446 gl::Framebuffer::DirtyBits *fboDirtyBits) 447{ 448 ASSERT(mDrawableTexture && mDrawableTexture->get()); 449 450 ContextMtl *contextMtl = mtl::GetImpl(context); 451 auto size = mDrawableTexture->size(); 452 453 if (mDepthFormat.valid() && (!mDepthTexture || mDepthTexture->size() != size)) 454 { 455 ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mDepthFormat, size.width, size.height, 1, 456 true, false, &mDepthTexture)); 457 458 mDepthRenderTarget.set(mDepthTexture, 0, 0, mDepthFormat); 459 fboDirtyBits->set(gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT); 460 } 461 462 if (mStencilFormat.valid() && (!mStencilTexture || mStencilTexture->size() != size)) 463 { 464 if (mUsePackedDepthStencil) 465 { 466 mStencilTexture = mDepthTexture; 467 } 468 else 469 { 470 ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mStencilFormat, size.width, 471 size.height, 1, true, false, &mStencilTexture)); 472 } 473 474 mStencilRenderTarget.set(mStencilTexture, 0, 0, mStencilFormat); 475 fboDirtyBits->set(gl::Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT); 476 } 477 478 return angle::Result::Continue; 479} 480 481void SurfaceMtl::checkIfLayerResized() 482{ 483 CGSize currentDrawableSize = mMetalLayer.get().drawableSize; 484 CGSize currentLayerSize = mMetalLayer.get().bounds.size; 485 CGFloat currentLayerContentsScale = mMetalLayer.get().contentsScale; 486 CGSize expectedDrawableSize = CGSizeMake(currentLayerSize.width * currentLayerContentsScale, 487 currentLayerSize.height * currentLayerContentsScale); 488 if (currentDrawableSize.width != expectedDrawableSize.width || 489 currentDrawableSize.height != expectedDrawableSize.height) 490 { 491 // Resize the internal drawable texture. 492 mMetalLayer.get().drawableSize = expectedDrawableSize; 493 } 494} 495 496angle::Result SurfaceMtl::obtainNextDrawable(const gl::Context *context) 497{ 498 checkIfLayerResized(); 499 500 ANGLE_MTL_OBJC_SCOPE 501 { 502 ContextMtl *contextMtl = mtl::GetImpl(context); 503 504 StartFrameCapture(contextMtl); 505 506 ANGLE_MTL_TRY(contextMtl, mMetalLayer); 507 508 if (mDrawableTexture) 509 { 510 mDrawableTexture->set(nil); 511 } 512 513 mCurrentDrawable = nil; 514 mCurrentDrawable.retainAssign([mMetalLayer nextDrawable]); 515 if (!mCurrentDrawable) 516 { 517 // The GPU might be taking too long finishing its rendering to the previous frame. 518 // Try again, indefinitely wait until the previous frame render finishes. 519 // TODO: this may wait forever here 520 mMetalLayer.get().allowsNextDrawableTimeout = NO; 521 mCurrentDrawable.retainAssign([mMetalLayer nextDrawable]); 522 mMetalLayer.get().allowsNextDrawableTimeout = YES; 523 } 524 525 if (!mDrawableTexture) 526 { 527 mDrawableTexture = mtl::Texture::MakeFromMetal(mCurrentDrawable.get().texture); 528 mColorRenderTarget.set(mDrawableTexture, 0, 0, mColorFormat); 529 } 530 else 531 { 532 mDrawableTexture->set(mCurrentDrawable.get().texture); 533 } 534 535 ANGLE_MTL_LOG("Current metal drawable size=%d,%d", mDrawableTexture->width(), 536 mDrawableTexture->height()); 537 538 gl::Framebuffer::DirtyBits fboDirtyBits; 539 fboDirtyBits.set(gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0); 540 541 // Now we have to resize depth stencil buffers if necessary. 542 ANGLE_TRY(ensureDepthStencilSizeCorrect(context, &fboDirtyBits)); 543 544 // Need to notify default framebuffer to invalidate its render targets. 545 // Since a new drawable texture has been obtained, also, the depth stencil 546 // buffers might have been resized. 547 gl::Framebuffer *defaultFbo = 548 context->getFramebuffer(gl::Framebuffer::kDefaultDrawFramebufferHandle); 549 if (defaultFbo) 550 { 551 FramebufferMtl *framebufferMtl = mtl::GetImpl(defaultFbo); 552 ANGLE_TRY(framebufferMtl->syncState(context, GL_FRAMEBUFFER, fboDirtyBits)); 553 } 554 555 return angle::Result::Continue; 556 } 557} 558 559angle::Result SurfaceMtl::swapImpl(const gl::Context *context) 560{ 561 ANGLE_TRY(ensureRenderTargetsCreated(context)); 562 563 ContextMtl *contextMtl = mtl::GetImpl(context); 564 565 contextMtl->present(context, mCurrentDrawable); 566 567 StopFrameCapture(); 568 569 ANGLE_TRY(obtainNextDrawable(context)); 570 571 return angle::Result::Continue; 572} 573} 574