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