tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:&currentDisplayMask
    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 */