• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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