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