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