1//
2// Copyright 2015 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
7// DisplayCGL.mm: CGL implementation of egl::Display
8
9#include "common/platform.h"
10
11#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)
12
13#    include "libANGLE/renderer/gl/cgl/DisplayCGL.h"
14
15#    import <Cocoa/Cocoa.h>
16#    include <EGL/eglext.h>
17#    include <dlfcn.h>
18
19#    include "common/debug.h"
20#    include "common/gl/cgl/FunctionsCGL.h"
21#    include "gpu_info_util/SystemInfo.h"
22#    include "libANGLE/Display.h"
23#    include "libANGLE/Error.h"
24#    include "libANGLE/renderer/gl/cgl/ContextCGL.h"
25#    include "libANGLE/renderer/gl/cgl/DeviceCGL.h"
26#    include "libANGLE/renderer/gl/cgl/IOSurfaceSurfaceCGL.h"
27#    include "libANGLE/renderer/gl/cgl/PbufferSurfaceCGL.h"
28#    include "libANGLE/renderer/gl/cgl/RendererCGL.h"
29#    include "libANGLE/renderer/gl/cgl/WindowSurfaceCGL.h"
30#    include "platform/PlatformMethods.h"
31
32namespace
33{
34
35const char *kDefaultOpenGLDylibName =
36    "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib";
37const char *kFallbackOpenGLDylibName = "GL";
38
39}
40
41namespace rx
42{
43
44namespace
45{
46
47// Global IOKit I/O registryID that can match a GPU across process boundaries.
48using IORegistryGPUID = uint64_t;
49
50// Code from WebKit to set an OpenGL context to use a particular GPU by ID.
51// https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/graphics/cocoa/GraphicsContextGLOpenGLCocoa.mm
52// Used with permission.
53static void SetGPUByRegistryID(CGLContextObj contextObj,
54                               CGLPixelFormatObj pixelFormatObj,
55                               IORegistryGPUID preferredGPUID)
56{
57    if (@available(macOS 10.13, *))
58    {
59        // When a process does not have access to the WindowServer (as with Chromium's GPU process
60        // and WebKit's WebProcess), there is no way for OpenGL to tell which GPU is connected to a
61        // display. On 10.13+, find the virtual screen that corresponds to the preferred GPU by its
62        // registryID. CGLSetVirtualScreen can then be used to tell OpenGL which GPU it should be
63        // using.
64
65        if (!contextObj || !preferredGPUID)
66            return;
67
68        GLint virtualScreenCount = 0;
69        CGLError error = CGLDescribePixelFormat(pixelFormatObj, 0, kCGLPFAVirtualScreenCount,
70                                                &virtualScreenCount);
71        ASSERT(error == kCGLNoError);
72
73        GLint firstAcceleratedScreen = -1;
74
75        for (GLint virtualScreen = 0; virtualScreen < virtualScreenCount; ++virtualScreen)
76        {
77            GLint displayMask = 0;
78            error = CGLDescribePixelFormat(pixelFormatObj, virtualScreen, kCGLPFADisplayMask,
79                                           &displayMask);
80            ASSERT(error == kCGLNoError);
81
82            auto gpuID = angle::GetGpuIDFromOpenGLDisplayMask(displayMask);
83
84            if (gpuID == preferredGPUID)
85            {
86                error = CGLSetVirtualScreen(contextObj, virtualScreen);
87                ASSERT(error == kCGLNoError);
88                fprintf(stderr, "Context (%p) set to GPU with ID: (%lld).", contextObj, gpuID);
89                return;
90            }
91
92            if (firstAcceleratedScreen < 0)
93            {
94                GLint isAccelerated = 0;
95                error = CGLDescribePixelFormat(pixelFormatObj, virtualScreen, kCGLPFAAccelerated,
96                                               &isAccelerated);
97                ASSERT(error == kCGLNoError);
98                if (isAccelerated)
99                    firstAcceleratedScreen = virtualScreen;
100            }
101        }
102
103        // No registryID match found; set to first hardware-accelerated virtual screen.
104        if (firstAcceleratedScreen >= 0)
105        {
106            error = CGLSetVirtualScreen(contextObj, firstAcceleratedScreen);
107            ASSERT(error == kCGLNoError);
108        }
109    }
110}
111
112}  // anonymous namespace
113
114EnsureCGLContextIsCurrent::EnsureCGLContextIsCurrent(CGLContextObj context)
115    : mOldContext(CGLGetCurrentContext()), mResetContext(mOldContext != context)
116{
117    if (mResetContext)
118    {
119        CGLSetCurrentContext(context);
120    }
121}
122
123EnsureCGLContextIsCurrent::~EnsureCGLContextIsCurrent()
124{
125    if (mResetContext)
126    {
127        CGLSetCurrentContext(mOldContext);
128    }
129}
130
131class FunctionsGLCGL : public FunctionsGL
132{
133  public:
134    FunctionsGLCGL(void *dylibHandle) : mDylibHandle(dylibHandle) {}
135
136    ~FunctionsGLCGL() override { dlclose(mDylibHandle); }
137
138  private:
139    void *loadProcAddress(const std::string &function) const override
140    {
141        return dlsym(mDylibHandle, function.c_str());
142    }
143
144    void *mDylibHandle;
145};
146
147DisplayCGL::DisplayCGL(const egl::DisplayState &state)
148    : DisplayGL(state),
149      mEGLDisplay(nullptr),
150      mContext(nullptr),
151      mThreadsWithCurrentContext(),
152      mPixelFormat(nullptr),
153      mSupportsGPUSwitching(false),
154      mCurrentGPUID(0),
155      mDiscreteGPUPixelFormat(nullptr),
156      mDiscreteGPURefs(0),
157      mLastDiscreteGPUUnrefTime(0.0)
158{}
159
160DisplayCGL::~DisplayCGL() {}
161
162egl::Error DisplayCGL::initialize(egl::Display *display)
163{
164    mEGLDisplay = display;
165
166    angle::SystemInfo info;
167    // It's legal for GetSystemInfo to return false and thereby
168    // contain incomplete information.
169    (void)angle::GetSystemInfo(&info);
170
171    // This code implements the effect of the
172    // disableGPUSwitchingSupport workaround in FeaturesGL.
173    mSupportsGPUSwitching = info.isMacSwitchable && !info.hasNVIDIAGPU();
174
175    {
176        // TODO(cwallez) investigate which pixel format we want
177        std::vector<CGLPixelFormatAttribute> attribs;
178        attribs.push_back(kCGLPFAOpenGLProfile);
179        attribs.push_back(static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_3_2_Core));
180        attribs.push_back(kCGLPFAAllowOfflineRenderers);
181        attribs.push_back(static_cast<CGLPixelFormatAttribute>(0));
182        GLint nVirtualScreens = 0;
183        CGLChoosePixelFormat(attribs.data(), &mPixelFormat, &nVirtualScreens);
184
185        if (mPixelFormat == nullptr)
186        {
187            return egl::EglNotInitialized() << "Could not create the context's pixel format.";
188        }
189    }
190
191    CGLCreateContext(mPixelFormat, nullptr, &mContext);
192    if (mContext == nullptr)
193    {
194        return egl::EglNotInitialized() << "Could not create the CGL context.";
195    }
196
197    if (mSupportsGPUSwitching)
198    {
199        // Determine the currently active GPU on the system.
200        mCurrentGPUID = angle::GetGpuIDFromDisplayID(kCGDirectMainDisplay);
201    }
202
203    if (CGLSetCurrentContext(mContext) != kCGLNoError)
204    {
205        return egl::EglNotInitialized() << "Could not make the CGL context current.";
206    }
207    mThreadsWithCurrentContext.insert(std::this_thread::get_id());
208
209    // There is no equivalent getProcAddress in CGL so we open the dylib directly
210    void *handle = dlopen(kDefaultOpenGLDylibName, RTLD_NOW);
211    if (!handle)
212    {
213        handle = dlopen(kFallbackOpenGLDylibName, RTLD_NOW);
214    }
215    if (!handle)
216    {
217        return egl::EglNotInitialized() << "Could not open the OpenGL Framework.";
218    }
219
220    std::unique_ptr<FunctionsGL> functionsGL(new FunctionsGLCGL(handle));
221    functionsGL->initialize(display->getAttributeMap());
222
223    mRenderer.reset(new RendererCGL(std::move(functionsGL), display->getAttributeMap(), this));
224
225    const gl::Version &maxVersion = mRenderer->getMaxSupportedESVersion();
226    if (maxVersion < gl::Version(2, 0))
227    {
228        return egl::EglNotInitialized() << "OpenGL ES 2.0 is not supportable.";
229    }
230
231    auto &attributes = display->getAttributeMap();
232    mDeviceContextIsVolatile =
233        attributes.get(EGL_PLATFORM_ANGLE_DEVICE_CONTEXT_VOLATILE_CGL_ANGLE, GL_FALSE);
234
235    return DisplayGL::initialize(display);
236}
237
238void DisplayCGL::terminate()
239{
240    DisplayGL::terminate();
241
242    mRenderer.reset();
243    if (mPixelFormat != nullptr)
244    {
245        CGLDestroyPixelFormat(mPixelFormat);
246        mPixelFormat = nullptr;
247    }
248    if (mContext != nullptr)
249    {
250        CGLSetCurrentContext(nullptr);
251        CGLDestroyContext(mContext);
252        mContext = nullptr;
253        mThreadsWithCurrentContext.clear();
254    }
255    if (mDiscreteGPUPixelFormat != nullptr)
256    {
257        CGLDestroyPixelFormat(mDiscreteGPUPixelFormat);
258        mDiscreteGPUPixelFormat   = nullptr;
259        mLastDiscreteGPUUnrefTime = 0.0;
260    }
261}
262
263egl::Error DisplayCGL::prepareForCall()
264{
265    if (!mContext)
266    {
267        return egl::EglNotInitialized() << "Context not allocated.";
268    }
269    auto threadId = std::this_thread::get_id();
270    if (mDeviceContextIsVolatile ||
271        mThreadsWithCurrentContext.find(threadId) == mThreadsWithCurrentContext.end())
272    {
273        if (CGLSetCurrentContext(mContext) != kCGLNoError)
274        {
275            return egl::EglBadAlloc() << "Could not make device CGL context current.";
276        }
277        mThreadsWithCurrentContext.insert(threadId);
278    }
279    return egl::NoError();
280}
281
282egl::Error DisplayCGL::releaseThread()
283{
284    ASSERT(mContext);
285    auto threadId = std::this_thread::get_id();
286    if (mThreadsWithCurrentContext.find(threadId) != mThreadsWithCurrentContext.end())
287    {
288        if (CGLSetCurrentContext(nullptr) != kCGLNoError)
289        {
290            return egl::EglBadAlloc() << "Could not release device CGL context.";
291        }
292        mThreadsWithCurrentContext.erase(threadId);
293    }
294    return egl::NoError();
295}
296
297egl::Error DisplayCGL::makeCurrent(egl::Display *display,
298                                   egl::Surface *drawSurface,
299                                   egl::Surface *readSurface,
300                                   gl::Context *context)
301{
302    checkDiscreteGPUStatus();
303    return DisplayGL::makeCurrent(display, drawSurface, readSurface, context);
304}
305
306SurfaceImpl *DisplayCGL::createWindowSurface(const egl::SurfaceState &state,
307                                             EGLNativeWindowType window,
308                                             const egl::AttributeMap &attribs)
309{
310    return new WindowSurfaceCGL(state, mRenderer.get(), window, mContext);
311}
312
313SurfaceImpl *DisplayCGL::createPbufferSurface(const egl::SurfaceState &state,
314                                              const egl::AttributeMap &attribs)
315{
316    EGLint width  = static_cast<EGLint>(attribs.get(EGL_WIDTH, 0));
317    EGLint height = static_cast<EGLint>(attribs.get(EGL_HEIGHT, 0));
318    return new PbufferSurfaceCGL(state, mRenderer.get(), width, height);
319}
320
321SurfaceImpl *DisplayCGL::createPbufferFromClientBuffer(const egl::SurfaceState &state,
322                                                       EGLenum buftype,
323                                                       EGLClientBuffer clientBuffer,
324                                                       const egl::AttributeMap &attribs)
325{
326    ASSERT(buftype == EGL_IOSURFACE_ANGLE);
327
328    return new IOSurfaceSurfaceCGL(state, mContext, clientBuffer, attribs);
329}
330
331SurfaceImpl *DisplayCGL::createPixmapSurface(const egl::SurfaceState &state,
332                                             NativePixmapType nativePixmap,
333                                             const egl::AttributeMap &attribs)
334{
335    UNIMPLEMENTED();
336    return nullptr;
337}
338
339ContextImpl *DisplayCGL::createContext(const gl::State &state,
340                                       gl::ErrorSet *errorSet,
341                                       const egl::Config *configuration,
342                                       const gl::Context *shareContext,
343                                       const egl::AttributeMap &attribs)
344{
345    bool usesDiscreteGPU = false;
346
347    if (attribs.get(EGL_POWER_PREFERENCE_ANGLE, EGL_LOW_POWER_ANGLE) == EGL_HIGH_POWER_ANGLE)
348    {
349        // Should have been rejected by validation if not supported.
350        ASSERT(mSupportsGPUSwitching);
351        usesDiscreteGPU = true;
352    }
353
354    return new ContextCGL(this, state, errorSet, mRenderer, usesDiscreteGPU);
355}
356
357DeviceImpl *DisplayCGL::createDevice()
358{
359    return new DeviceCGL();
360}
361
362egl::ConfigSet DisplayCGL::generateConfigs()
363{
364    // TODO(cwallez): generate more config permutations
365    egl::ConfigSet configs;
366
367    const gl::Version &maxVersion = getMaxSupportedESVersion();
368    ASSERT(maxVersion >= gl::Version(2, 0));
369    bool supportsES3 = maxVersion >= gl::Version(3, 0);
370
371    egl::Config config;
372
373    // Native stuff
374    config.nativeVisualID   = 0;
375    config.nativeVisualType = 0;
376    config.nativeRenderable = EGL_TRUE;
377
378    // Buffer sizes
379    config.redSize     = 8;
380    config.greenSize   = 8;
381    config.blueSize    = 8;
382    config.alphaSize   = 8;
383    config.depthSize   = 24;
384    config.stencilSize = 8;
385
386    config.colorBufferType = EGL_RGB_BUFFER;
387    config.luminanceSize   = 0;
388    config.alphaMaskSize   = 0;
389
390    config.bufferSize = config.redSize + config.greenSize + config.blueSize + config.alphaSize;
391
392    config.transparentType = EGL_NONE;
393
394    // Pbuffer
395    config.maxPBufferWidth  = 4096;
396    config.maxPBufferHeight = 4096;
397    config.maxPBufferPixels = 4096 * 4096;
398
399    // Caveat
400    config.configCaveat = EGL_NONE;
401
402    // Misc
403    config.sampleBuffers     = 0;
404    config.samples           = 0;
405    config.level             = 0;
406    config.bindToTextureRGB  = EGL_FALSE;
407    config.bindToTextureRGBA = EGL_FALSE;
408
409    config.bindToTextureTarget = EGL_TEXTURE_RECTANGLE_ANGLE;
410
411    config.surfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT;
412
413    config.minSwapInterval = 1;
414    config.maxSwapInterval = 1;
415
416    config.renderTargetFormat = GL_RGBA8;
417    config.depthStencilFormat = GL_DEPTH24_STENCIL8;
418
419    config.conformant     = EGL_OPENGL_ES2_BIT | (supportsES3 ? EGL_OPENGL_ES3_BIT_KHR : 0);
420    config.renderableType = config.conformant;
421
422    config.matchNativePixmap = EGL_NONE;
423
424    config.colorComponentType = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT;
425
426    configs.add(config);
427    return configs;
428}
429
430bool DisplayCGL::testDeviceLost()
431{
432    // TODO(cwallez) investigate implementing this
433    return false;
434}
435
436egl::Error DisplayCGL::restoreLostDevice(const egl::Display *display)
437{
438    UNIMPLEMENTED();
439    return egl::EglBadDisplay();
440}
441
442bool DisplayCGL::isValidNativeWindow(EGLNativeWindowType window) const
443{
444    NSObject *layer = reinterpret_cast<NSObject *>(window);
445    return [layer isKindOfClass:[CALayer class]];
446}
447
448egl::Error DisplayCGL::validateClientBuffer(const egl::Config *configuration,
449                                            EGLenum buftype,
450                                            EGLClientBuffer clientBuffer,
451                                            const egl::AttributeMap &attribs) const
452{
453    ASSERT(buftype == EGL_IOSURFACE_ANGLE);
454
455    if (!IOSurfaceSurfaceCGL::validateAttributes(clientBuffer, attribs))
456    {
457        return egl::EglBadAttribute();
458    }
459
460    return egl::NoError();
461}
462
463CGLContextObj DisplayCGL::getCGLContext() const
464{
465    return mContext;
466}
467
468CGLPixelFormatObj DisplayCGL::getCGLPixelFormat() const
469{
470    return mPixelFormat;
471}
472
473void DisplayCGL::generateExtensions(egl::DisplayExtensions *outExtensions) const
474{
475    outExtensions->iosurfaceClientBuffer = true;
476    outExtensions->surfacelessContext    = true;
477
478    // Contexts are virtualized so textures and semaphores can be shared globally
479    outExtensions->displayTextureShareGroup   = true;
480    outExtensions->displaySemaphoreShareGroup = true;
481
482    if (mSupportsGPUSwitching)
483    {
484        outExtensions->powerPreference = true;
485    }
486
487    DisplayGL::generateExtensions(outExtensions);
488}
489
490void DisplayCGL::generateCaps(egl::Caps *outCaps) const
491{
492    outCaps->textureNPOT = true;
493}
494
495egl::Error DisplayCGL::waitClient(const gl::Context *context)
496{
497    // TODO(cwallez) UNIMPLEMENTED()
498    return egl::NoError();
499}
500
501egl::Error DisplayCGL::waitNative(const gl::Context *context, EGLint engine)
502{
503    // TODO(cwallez) UNIMPLEMENTED()
504    return egl::NoError();
505}
506
507gl::Version DisplayCGL::getMaxSupportedESVersion() const
508{
509    return mRenderer->getMaxSupportedESVersion();
510}
511
512egl::Error DisplayCGL::makeCurrentSurfaceless(gl::Context *context)
513{
514    // We have nothing to do as mContext is always current, and that CGL is surfaceless by
515    // default.
516    return egl::NoError();
517}
518
519class WorkerContextCGL final : public WorkerContext
520{
521  public:
522    WorkerContextCGL(CGLContextObj context);
523    ~WorkerContextCGL() override;
524
525    bool makeCurrent() override;
526    void unmakeCurrent() override;
527
528  private:
529    CGLContextObj mContext;
530};
531
532WorkerContextCGL::WorkerContextCGL(CGLContextObj context) : mContext(context) {}
533
534WorkerContextCGL::~WorkerContextCGL()
535{
536    CGLSetCurrentContext(nullptr);
537    CGLReleaseContext(mContext);
538    mContext = nullptr;
539}
540
541bool WorkerContextCGL::makeCurrent()
542{
543    CGLError error = CGLSetCurrentContext(mContext);
544    if (error != kCGLNoError)
545    {
546        ERR() << "Unable to make gl context current.\n";
547        return false;
548    }
549    return true;
550}
551
552void WorkerContextCGL::unmakeCurrent()
553{
554    CGLSetCurrentContext(nullptr);
555}
556
557WorkerContext *DisplayCGL::createWorkerContext(std::string *infoLog)
558{
559    CGLContextObj context = nullptr;
560    CGLCreateContext(mPixelFormat, mContext, &context);
561    if (context == nullptr)
562    {
563        *infoLog += "Could not create the CGL context.";
564        return nullptr;
565    }
566
567    return new WorkerContextCGL(context);
568}
569
570void DisplayCGL::initializeFrontendFeatures(angle::FrontendFeatures *features) const
571{
572    mRenderer->initializeFrontendFeatures(features);
573}
574
575void DisplayCGL::populateFeatureList(angle::FeatureList *features)
576{
577    mRenderer->getFeatures().populateFeatureList(features);
578}
579
580RendererGL *DisplayCGL::getRenderer() const
581{
582    return mRenderer.get();
583}
584
585egl::Error DisplayCGL::referenceDiscreteGPU()
586{
587    // Should have been rejected by validation if not supported.
588    ASSERT(mSupportsGPUSwitching);
589    // Create discrete pixel format if necessary.
590    if (mDiscreteGPUPixelFormat)
591    {
592        // Clear this out if necessary.
593        mLastDiscreteGPUUnrefTime = 0.0;
594    }
595    else
596    {
597        ASSERT(mLastDiscreteGPUUnrefTime == 0.0);
598        CGLPixelFormatAttribute discreteAttribs[] = {static_cast<CGLPixelFormatAttribute>(0)};
599        GLint numPixelFormats                     = 0;
600        if (CGLChoosePixelFormat(discreteAttribs, &mDiscreteGPUPixelFormat, &numPixelFormats) !=
601            kCGLNoError)
602        {
603            return egl::EglBadAlloc() << "Error choosing discrete pixel format.";
604        }
605    }
606    ++mDiscreteGPURefs;
607
608    return egl::NoError();
609}
610
611egl::Error DisplayCGL::unreferenceDiscreteGPU()
612{
613    // Should have been rejected by validation if not supported.
614    ASSERT(mSupportsGPUSwitching);
615    ASSERT(mDiscreteGPURefs > 0);
616    if (--mDiscreteGPURefs == 0)
617    {
618        auto *platform            = ANGLEPlatformCurrent();
619        mLastDiscreteGPUUnrefTime = platform->monotonicallyIncreasingTime(platform);
620    }
621
622    return egl::NoError();
623}
624
625void DisplayCGL::checkDiscreteGPUStatus()
626{
627    const double kDiscreteGPUTimeoutInSeconds = 10.0;
628
629    if (mLastDiscreteGPUUnrefTime != 0.0)
630    {
631        ASSERT(mSupportsGPUSwitching);
632        // A non-zero value implies that the timer is ticking on deleting the discrete GPU pixel
633        // format.
634        auto *platform = ANGLEPlatformCurrent();
635        ASSERT(platform);
636        double currentTime = platform->monotonicallyIncreasingTime(platform);
637        if (currentTime > mLastDiscreteGPUUnrefTime + kDiscreteGPUTimeoutInSeconds)
638        {
639            CGLDestroyPixelFormat(mDiscreteGPUPixelFormat);
640            mDiscreteGPUPixelFormat   = nullptr;
641            mLastDiscreteGPUUnrefTime = 0.0;
642        }
643    }
644}
645
646egl::Error DisplayCGL::handleGPUSwitch()
647{
648    if (mSupportsGPUSwitching)
649    {
650        uint64_t gpuID = angle::GetGpuIDFromDisplayID(kCGDirectMainDisplay);
651        if (gpuID != mCurrentGPUID)
652        {
653            SetGPUByRegistryID(mContext, mPixelFormat, gpuID);
654            // Performing the above operation seems to need a call to CGLSetCurrentContext to make
655            // the context work properly again. Failing to do this returns null strings for
656            // GL_VENDOR and GL_RENDERER.
657            CGLUpdateContext(mContext);
658            CGLSetCurrentContext(mContext);
659            onStateChange(angle::SubjectMessage::SubjectChanged);
660            mCurrentGPUID = gpuID;
661
662            mRenderer->handleGPUSwitch();
663        }
664    }
665
666    return egl::NoError();
667}
668
669}  // namespace rx
670
671#endif  // defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)
672