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