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