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