GLContextProviderCGL.mm (12538B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "GLContextProvider.h" 7 #include "GLContextCGL.h" 8 #include "GLLibraryLoader.h" 9 #include "nsDebug.h" 10 #include "nsIWidget.h" 11 #include <OpenGL/gl.h> 12 #include "gfxFailure.h" 13 #include "mozilla/IntegerRange.h" 14 #include "mozilla/StaticPrefs_gfx.h" 15 #include "mozilla/StaticPrefs_gl.h" 16 #include "mozilla/StaticPrefs_layout.h" 17 #include "prenv.h" 18 #include "prlink.h" 19 #include "mozilla/ProfilerLabels.h" 20 #include "MozFramebuffer.h" 21 #include "mozilla/layers/CompositorOptions.h" 22 #include "mozilla/widget/CompositorWidget.h" 23 #include "ScopedGLHelpers.h" 24 25 #include <OpenGL/OpenGL.h> 26 27 namespace mozilla { 28 namespace gl { 29 30 using namespace mozilla::gfx; 31 using namespace mozilla::widget; 32 33 class CGLLibrary { 34 public: 35 bool EnsureInitialized() { 36 if (mInitialized) { 37 return true; 38 } 39 if (!mOGLLibrary) { 40 mOGLLibrary = 41 PR_LoadLibrary("/System/Library/Frameworks/OpenGL.framework/OpenGL"); 42 if (!mOGLLibrary) { 43 NS_WARNING("Couldn't load OpenGL Framework."); 44 return false; 45 } 46 } 47 48 mInitialized = true; 49 return true; 50 } 51 52 const auto& Library() const { return mOGLLibrary; } 53 54 private: 55 bool mInitialized = false; 56 PRLibrary* mOGLLibrary = nullptr; 57 }; 58 59 CGLLibrary sCGLLibrary; 60 61 GLContextCGL::GLContextCGL(const GLContextDesc& desc, NSOpenGLContext* context) 62 : GLContext(desc), mContext(context) { 63 CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback, 64 this); 65 } 66 67 GLContextCGL::~GLContextCGL() { 68 MarkDestroyed(); 69 70 CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, this); 71 72 if (mContext) { 73 if ([NSOpenGLContext currentContext] == mContext) { 74 // Clear the current context before releasing. If we don't do 75 // this, the next time we call [NSOpenGLContext currentContext], 76 // "invalid context" will be printed to the console. 77 [NSOpenGLContext clearCurrentContext]; 78 } 79 [mContext release]; 80 } 81 } 82 83 CGLContextObj GLContextCGL::GetCGLContext() const { 84 return static_cast<CGLContextObj>([mContext CGLContextObj]); 85 } 86 87 bool GLContextCGL::MakeCurrentImpl() const { 88 if (mContext) { 89 GLContext::ResetTLSCurrentContext(); 90 91 [mContext makeCurrentContext]; 92 MOZ_ASSERT(IsCurrentImpl()); 93 // Use non-blocking swap in "ASAP mode". 94 // ASAP mode means that rendering is iterated as fast as possible. 95 // ASAP mode is entered when layout.frame_rate=0 (requires restart). 96 // If swapInt is 1, then glSwapBuffers will block and wait for a vblank 97 // signal. When we're iterating as fast as possible, however, we want a 98 // non-blocking glSwapBuffers, which will happen when swapInt==0. 99 GLint swapInt = StaticPrefs::layout_frame_rate() == 0 ? 0 : 1; 100 [mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; 101 } 102 return true; 103 } 104 105 bool GLContextCGL::IsCurrentImpl() const { 106 return [NSOpenGLContext currentContext] == mContext; 107 } 108 109 /* static */ void GLContextCGL::DisplayReconfigurationCallback( 110 CGDirectDisplayID aDisplay, CGDisplayChangeSummaryFlags aFlags, 111 void* aUserInfo) { 112 if (aFlags & kCGDisplaySetModeFlag) { 113 static_cast<GLContextCGL*>(aUserInfo)->mActiveGPUSwitchMayHaveOccurred = 114 true; 115 } 116 } 117 118 static NSOpenGLContext* CreateWithFormat( 119 const NSOpenGLPixelFormatAttribute* attribs) { 120 NSOpenGLPixelFormat* format = 121 [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; 122 if (!format) { 123 NS_WARNING("Failed to create NSOpenGLPixelFormat."); 124 return nullptr; 125 } 126 127 NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:format 128 shareContext:nullptr]; 129 130 [format release]; 131 132 return context; 133 } 134 135 // Get the "OpenGL display mask" for a fresh context. The return value of this 136 // function depends on the time at which this function is called. 137 // In practice, on a Macbook Pro with an integrated and a discrete GPU, this 138 // function returns the display mask for the GPU that currently drives the 139 // internal display. 140 // 141 // Quick reference of the concepts involved in the code below: 142 // GPU switch: On Mac devices with an integrated and a discrete GPU, a GPU 143 // switch changes which 144 // GPU drives the internal display. Both GPUs are still usable at all times. 145 // (When the integrated GPU is driving the internal display, using the 146 // discrete GPU can incur a longer warm-up cost.) 147 // Virtual screen: A CGL concept. A "virtual screen" corresponds to a GL 148 // renderer. There's one 149 // for the integrated GPU, one for each discrete GPU, and one for the Apple 150 // software renderer. The list of virtual screens is 151 // per-NSOpenGLPixelFormat; it is filtered down to only the renderers that 152 // support the requirements from the pixel format attributes. Indexes into 153 // this list (such as currentVirtualScreen) cannot be used interchangably 154 // across different NSOpenGLPixelFormat instances. 155 // Display mask: A bitset per GL renderer. Different renderers have disjoint 156 // display masks. The 157 // Apple software renderer has all bits zeroed. For each CGDirectDisplayID, 158 // CGDisplayIDToOpenGLDisplayMask(displayID) returns a single bit in the 159 // display mask. 160 // CGDirectDisplayID: An ID for each (physical screen, GPU which can drive 161 // this screen) pair. The 162 // current CGDirectDisplayID for an NSScreen object can be obtained using 163 // [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] 164 // unsignedIntValue]; it changes depending on which GPU is currently driving 165 // the screen. 166 static CGOpenGLDisplayMask GetFreshContextDisplayMask() { 167 NSOpenGLPixelFormatAttribute attribs[] = {NSOpenGLPFAAllowOfflineRenderers, 168 0}; 169 NSOpenGLPixelFormat* pixelFormat = 170 [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; 171 MOZ_RELEASE_ASSERT(pixelFormat); 172 NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat 173 shareContext:nullptr]; 174 GLint displayMask = 0; 175 [pixelFormat getValues:&displayMask 176 forAttribute:NSOpenGLPFAScreenMask 177 forVirtualScreen:[context currentVirtualScreen]]; 178 [pixelFormat release]; 179 [context release]; 180 return static_cast<CGOpenGLDisplayMask>(displayMask); 181 } 182 183 static bool IsSameGPU(CGOpenGLDisplayMask mask1, CGOpenGLDisplayMask mask2) { 184 if ((mask1 & mask2) != 0) { 185 return true; 186 } 187 // Both masks can be zero, when using the Apple software renderer. 188 return !mask1 && !mask2; 189 } 190 191 void GLContextCGL::MigrateToActiveGPU() { 192 if (!mActiveGPUSwitchMayHaveOccurred.compareExchange(true, false)) { 193 return; 194 } 195 196 CGOpenGLDisplayMask newPreferredDisplayMask = GetFreshContextDisplayMask(); 197 NSOpenGLPixelFormat* pixelFormat = [mContext pixelFormat]; 198 GLint currentVirtualScreen = [mContext currentVirtualScreen]; 199 GLint currentDisplayMask = 0; 200 [pixelFormat getValues:¤tDisplayMask 201 forAttribute:NSOpenGLPFAScreenMask 202 forVirtualScreen:currentVirtualScreen]; 203 if (IsSameGPU(currentDisplayMask, newPreferredDisplayMask)) { 204 // No "virtual screen" change needed. 205 return; 206 } 207 208 // Find the "virtual screen" with a display mask that matches 209 // newPreferredDisplayMask, if available, and switch the context over to it. 210 // This code was inspired by equivalent functionality in -[NSOpenGLContext 211 // update] which only kicks in for contexts that present via a CAOpenGLLayer. 212 for (const auto i : IntegerRange([pixelFormat numberOfVirtualScreens])) { 213 GLint displayMask = 0; 214 [pixelFormat getValues:&displayMask 215 forAttribute:NSOpenGLPFAScreenMask 216 forVirtualScreen:i]; 217 if (IsSameGPU(displayMask, newPreferredDisplayMask)) { 218 CGLSetVirtualScreen([mContext CGLContextObj], i); 219 return; 220 } 221 } 222 } 223 224 GLenum GLContextCGL::GetPreferredARGB32Format() const { return LOCAL_GL_BGRA; } 225 226 bool GLContextCGL::SwapBuffers() { 227 AUTO_PROFILER_LABEL("GLContextCGL::SwapBuffers", GRAPHICS); 228 229 // We do not have a default framebuffer. Just do a flush. 230 // Flushing is necessary if we want our IOSurfaces to have the correct 231 // content once they're picked up by the WindowServer from our CALayers. 232 fFlush(); 233 234 return true; 235 } 236 237 void GLContextCGL::GetWSIInfo(nsCString* const out) const { 238 out->AppendLiteral("CGL"); 239 } 240 241 Maybe<SymbolLoader> GLContextCGL::GetSymbolLoader() const { 242 const auto& lib = sCGLLibrary.Library(); 243 return Some(SymbolLoader(*lib)); 244 } 245 246 already_AddRefed<GLContext> GLContextProviderCGL::CreateForCompositorWidget( 247 CompositorWidget* aCompositorWidget, bool aHardwareWebRender, 248 bool aForceAccelerated) { 249 CreateContextFlags flags = CreateContextFlags::ALLOW_OFFLINE_RENDERER; 250 if (aForceAccelerated) { 251 flags |= CreateContextFlags::FORBID_SOFTWARE; 252 } 253 if (!aHardwareWebRender) { 254 flags |= CreateContextFlags::REQUIRE_COMPAT_PROFILE; 255 } 256 nsCString failureUnused; 257 return CreateHeadless({flags}, &failureUnused); 258 } 259 260 static RefPtr<GLContextCGL> CreateOffscreenFBOContext( 261 GLContextCreateDesc desc) { 262 if (!sCGLLibrary.EnsureInitialized()) { 263 return nullptr; 264 } 265 266 NSOpenGLContext* context = nullptr; 267 268 std::vector<NSOpenGLPixelFormatAttribute> attribs; 269 auto& flags = desc.flags; 270 271 if (!StaticPrefs::gl_allow_high_power()) { 272 flags &= ~CreateContextFlags::HIGH_POWER; 273 } 274 if (flags & CreateContextFlags::ALLOW_OFFLINE_RENDERER || 275 !(flags & CreateContextFlags::HIGH_POWER)) { 276 // This is really poorly named on Apple's part, but "AllowOfflineRenderers" 277 // means that we want to allow running on the iGPU instead of requiring the 278 // dGPU. 279 attribs.push_back(NSOpenGLPFAAllowOfflineRenderers); 280 } 281 282 if (flags & CreateContextFlags::FORBID_SOFTWARE) { 283 if (flags & CreateContextFlags::FORBID_HARDWARE) { 284 NS_WARNING("Both !hardware and !software."); 285 return nullptr; 286 } 287 attribs.push_back(NSOpenGLPFAAccelerated); 288 } 289 if (flags & CreateContextFlags::FORBID_HARDWARE) { 290 /* NSOpenGLPFARendererID: 291 * > OpenGL renderers that match the specified ID are preferred. 292 * > Constants to select specific renderers are provided in the 293 * > GLRenderers.h header of the OpenGL framework. 294 * > Of note is kCGLRendererGenericID which selects the Apple software 295 * > renderer. 296 * > The other constants select renderers for specific hardware vendors. 297 */ 298 attribs.push_back(NSOpenGLPFARendererID); 299 attribs.push_back(kCGLRendererGenericID); 300 } 301 302 if (!(flags & CreateContextFlags::REQUIRE_COMPAT_PROFILE)) { 303 auto coreAttribs = attribs; 304 coreAttribs.push_back(NSOpenGLPFAOpenGLProfile); 305 coreAttribs.push_back(NSOpenGLProfileVersion3_2Core); 306 coreAttribs.push_back(0); 307 context = CreateWithFormat(coreAttribs.data()); 308 } 309 310 if (!context) { 311 attribs.push_back(0); 312 context = CreateWithFormat(attribs.data()); 313 } 314 315 if (!context) { 316 NS_WARNING("Failed to create NSOpenGLContext."); 317 return nullptr; 318 } 319 320 RefPtr<GLContextCGL> glContext = new GLContextCGL({desc, true}, context); 321 322 if (flags & CreateContextFlags::PREFER_MULTITHREADED) { 323 CGLEnable(glContext->GetCGLContext(), kCGLCEMPEngine); 324 } 325 return glContext; 326 } 327 328 already_AddRefed<GLContext> GLContextProviderCGL::CreateHeadless( 329 const GLContextCreateDesc& desc, nsACString* const out_failureId) { 330 auto gl = CreateOffscreenFBOContext(desc); 331 if (!gl) { 332 *out_failureId = "FEATURE_FAILURE_CGL_FBO"_ns; 333 return nullptr; 334 } 335 336 if (!gl->Init()) { 337 *out_failureId = "FEATURE_FAILURE_CGL_INIT"_ns; 338 NS_WARNING("Failed during Init."); 339 return nullptr; 340 } 341 342 return gl.forget(); 343 } 344 345 constinit static RefPtr<GLContext> gGlobalContext; 346 347 GLContext* GLContextProviderCGL::GetGlobalContext() { 348 static bool triedToCreateContext = false; 349 if (!triedToCreateContext) { 350 triedToCreateContext = true; 351 352 MOZ_RELEASE_ASSERT(!gGlobalContext); 353 nsCString discardFailureId; 354 RefPtr<GLContext> temp = CreateHeadless({}, &discardFailureId); 355 gGlobalContext = temp; 356 357 if (!gGlobalContext) { 358 NS_WARNING("Couldn't init gGlobalContext."); 359 } 360 } 361 362 return gGlobalContext; 363 } 364 365 void GLContextProviderCGL::Shutdown() { gGlobalContext = nullptr; } 366 367 } /* namespace gl */ 368 } /* namespace mozilla */