summaryrefslogtreecommitdiffstats
path: root/gfx/gl/GLContextProviderCGL.mm
blob: bcb39c2f684c018d2e14007f11e1caa2827c5c2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "GLContextProvider.h"
#include "GLContextCGL.h"
#include "GLLibraryLoader.h"
#include "nsDebug.h"
#include "nsIWidget.h"
#include <OpenGL/gl.h>
#include "gfxFailure.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_gl.h"
#include "mozilla/StaticPrefs_layout.h"
#include "prenv.h"
#include "prlink.h"
#include "mozilla/ProfilerLabels.h"
#include "MozFramebuffer.h"
#include "mozilla/layers/CompositorOptions.h"
#include "mozilla/widget/CompositorWidget.h"
#include "ScopedGLHelpers.h"

#include <OpenGL/OpenGL.h>

namespace mozilla {
namespace gl {

using namespace mozilla::gfx;
using namespace mozilla::widget;

class CGLLibrary {
 public:
  bool EnsureInitialized() {
    if (mInitialized) {
      return true;
    }
    if (!mOGLLibrary) {
      mOGLLibrary = PR_LoadLibrary("/System/Library/Frameworks/OpenGL.framework/OpenGL");
      if (!mOGLLibrary) {
        NS_WARNING("Couldn't load OpenGL Framework.");
        return false;
      }
    }

    mInitialized = true;
    return true;
  }

  const auto& Library() const { return mOGLLibrary; }

 private:
  bool mInitialized = false;
  PRLibrary* mOGLLibrary = nullptr;
};

CGLLibrary sCGLLibrary;

GLContextCGL::GLContextCGL(const GLContextDesc& desc, NSOpenGLContext* context)
    : GLContext(desc), mContext(context) {
  CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback, this);
}

GLContextCGL::~GLContextCGL() {
  MarkDestroyed();

  CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, this);

  if (mContext) {
    if ([NSOpenGLContext currentContext] == mContext) {
      // Clear the current context before releasing. If we don't do
      // this, the next time we call [NSOpenGLContext currentContext],
      // "invalid context" will be printed to the console.
      [NSOpenGLContext clearCurrentContext];
    }
    [mContext release];
  }
}

CGLContextObj GLContextCGL::GetCGLContext() const {
  return static_cast<CGLContextObj>([mContext CGLContextObj]);
}

bool GLContextCGL::MakeCurrentImpl() const {
  if (mContext) {
    [mContext makeCurrentContext];
    MOZ_ASSERT(IsCurrentImpl());
    // Use non-blocking swap in "ASAP mode".
    // ASAP mode means that rendering is iterated as fast as possible.
    // ASAP mode is entered when layout.frame_rate=0 (requires restart).
    // If swapInt is 1, then glSwapBuffers will block and wait for a vblank signal.
    // When we're iterating as fast as possible, however, we want a non-blocking
    // glSwapBuffers, which will happen when swapInt==0.
    GLint swapInt = StaticPrefs::layout_frame_rate() == 0 ? 0 : 1;
    [mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
  }
  return true;
}

bool GLContextCGL::IsCurrentImpl() const { return [NSOpenGLContext currentContext] == mContext; }

/* static */ void GLContextCGL::DisplayReconfigurationCallback(CGDirectDisplayID aDisplay,
                                                               CGDisplayChangeSummaryFlags aFlags,
                                                               void* aUserInfo) {
  if (aFlags & kCGDisplaySetModeFlag) {
    static_cast<GLContextCGL*>(aUserInfo)->mActiveGPUSwitchMayHaveOccurred = true;
  }
}

static NSOpenGLContext* CreateWithFormat(const NSOpenGLPixelFormatAttribute* attribs) {
  NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
  if (!format) {
    NS_WARNING("Failed to create NSOpenGLPixelFormat.");
    return nullptr;
  }

  NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nullptr];

  [format release];

  return context;
}

// Get the "OpenGL display mask" for a fresh context. The return value of this
// function depends on the time at which this function is called.
// In practice, on a Macbook Pro with an integrated and a discrete GPU, this function returns the
// display mask for the GPU that currently drives the internal display.
//
// Quick reference of the concepts involved in the code below:
//   GPU switch: On Mac devices with an integrated and a discrete GPU, a GPU switch changes which
//     GPU drives the internal display. Both GPUs are still usable at all times. (When the
//     integrated GPU is driving the internal display, using the discrete GPU can incur a longer
//     warm-up cost.)
//   Virtual screen: A CGL concept. A "virtual screen" corresponds to a GL renderer. There's one
//     for the integrated GPU, one for each discrete GPU, and one for the Apple software renderer.
//     The list of virtual screens is per-NSOpenGLPixelFormat; it is filtered down to only the
//     renderers that support the requirements from the pixel format attributes. Indexes into this
//     list (such as currentVirtualScreen) cannot be used interchangably across different
//     NSOpenGLPixelFormat instances.
//   Display mask: A bitset per GL renderer. Different renderers have disjoint display masks. The
//     Apple software renderer has all bits zeroed. For each CGDirectDisplayID,
//     CGDisplayIDToOpenGLDisplayMask(displayID) returns a single bit in the display mask.
//   CGDirectDisplayID: An ID for each (physical screen, GPU which can drive this screen) pair. The
//     current CGDirectDisplayID for an NSScreen object can be obtained using [[[screen
//     deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue]; it changes depending on
//     which GPU is currently driving the screen.
static CGOpenGLDisplayMask GetFreshContextDisplayMask() {
  NSOpenGLPixelFormatAttribute attribs[] = {NSOpenGLPFAAllowOfflineRenderers, 0};
  NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
  MOZ_RELEASE_ASSERT(pixelFormat);
  NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
                                                        shareContext:nullptr];
  GLint displayMask = 0;
  [pixelFormat getValues:&displayMask
            forAttribute:NSOpenGLPFAScreenMask
        forVirtualScreen:[context currentVirtualScreen]];
  [pixelFormat release];
  [context release];
  return static_cast<CGOpenGLDisplayMask>(displayMask);
}

static bool IsSameGPU(CGOpenGLDisplayMask mask1, CGOpenGLDisplayMask mask2) {
  if ((mask1 & mask2) != 0) {
    return true;
  }
  // Both masks can be zero, when using the Apple software renderer.
  return !mask1 && !mask2;
}

void GLContextCGL::MigrateToActiveGPU() {
  if (!mActiveGPUSwitchMayHaveOccurred.compareExchange(true, false)) {
    return;
  }

  CGOpenGLDisplayMask newPreferredDisplayMask = GetFreshContextDisplayMask();
  NSOpenGLPixelFormat* pixelFormat = [mContext pixelFormat];
  GLint currentVirtualScreen = [mContext currentVirtualScreen];
  GLint currentDisplayMask = 0;
  [pixelFormat getValues:&currentDisplayMask
            forAttribute:NSOpenGLPFAScreenMask
        forVirtualScreen:currentVirtualScreen];
  if (IsSameGPU(currentDisplayMask, newPreferredDisplayMask)) {
    // No "virtual screen" change needed.
    return;
  }

  // Find the "virtual screen" with a display mask that matches newPreferredDisplayMask, if
  // available, and switch the context over to it.
  // This code was inspired by equivalent functionality in -[NSOpenGLContext update] which only
  // kicks in for contexts that present via a CAOpenGLLayer.
  for (const auto i : IntegerRange([pixelFormat numberOfVirtualScreens])) {
    GLint displayMask = 0;
    [pixelFormat getValues:&displayMask forAttribute:NSOpenGLPFAScreenMask forVirtualScreen:i];
    if (IsSameGPU(displayMask, newPreferredDisplayMask)) {
      CGLSetVirtualScreen([mContext CGLContextObj], i);
      return;
    }
  }
}

GLenum GLContextCGL::GetPreferredARGB32Format() const { return LOCAL_GL_BGRA; }

bool GLContextCGL::SwapBuffers() {
  AUTO_PROFILER_LABEL("GLContextCGL::SwapBuffers", GRAPHICS);

  // We do not have a default framebuffer. Just do a flush.
  // Flushing is necessary if we want our IOSurfaces to have the correct
  // content once they're picked up by the WindowServer from our CALayers.
  fFlush();

  return true;
}

void GLContextCGL::GetWSIInfo(nsCString* const out) const { out->AppendLiteral("CGL"); }

Maybe<SymbolLoader> GLContextCGL::GetSymbolLoader() const {
  const auto& lib = sCGLLibrary.Library();
  return Some(SymbolLoader(*lib));
}

already_AddRefed<GLContext> GLContextProviderCGL::CreateForCompositorWidget(
    CompositorWidget* aCompositorWidget, bool aHardwareWebRender, bool aForceAccelerated) {
  CreateContextFlags flags = CreateContextFlags::ALLOW_OFFLINE_RENDERER;
  if (aForceAccelerated) {
    flags |= CreateContextFlags::FORCE_ENABLE_HARDWARE;
  }
  if (!aHardwareWebRender) {
    flags |= CreateContextFlags::REQUIRE_COMPAT_PROFILE;
  }
  nsCString failureUnused;
  return CreateHeadless({flags}, &failureUnused);
}

static RefPtr<GLContextCGL> CreateOffscreenFBOContext(GLContextCreateDesc desc) {
  if (!sCGLLibrary.EnsureInitialized()) {
    return nullptr;
  }

  NSOpenGLContext* context = nullptr;

  std::vector<NSOpenGLPixelFormatAttribute> attribs;
  auto& flags = desc.flags;

  if (!StaticPrefs::gl_allow_high_power()) {
    flags &= ~CreateContextFlags::HIGH_POWER;
  }
  if (flags & CreateContextFlags::ALLOW_OFFLINE_RENDERER ||
      !(flags & CreateContextFlags::HIGH_POWER)) {
    // This is really poorly named on Apple's part, but "AllowOfflineRenderers" means
    // that we want to allow running on the iGPU instead of requiring the dGPU.
    attribs.push_back(NSOpenGLPFAAllowOfflineRenderers);
  }

  if (flags & CreateContextFlags::FORCE_ENABLE_HARDWARE) {
    attribs.push_back(NSOpenGLPFAAccelerated);
  }

  if (!(flags & CreateContextFlags::REQUIRE_COMPAT_PROFILE)) {
    auto coreAttribs = attribs;
    coreAttribs.push_back(NSOpenGLPFAOpenGLProfile);
    coreAttribs.push_back(NSOpenGLProfileVersion3_2Core);
    coreAttribs.push_back(0);
    context = CreateWithFormat(coreAttribs.data());
  }

  if (!context) {
    attribs.push_back(0);
    context = CreateWithFormat(attribs.data());
  }

  if (!context) {
    NS_WARNING("Failed to create NSOpenGLContext.");
    return nullptr;
  }

  RefPtr<GLContextCGL> glContext = new GLContextCGL({desc, true}, context);

  if (flags & CreateContextFlags::PREFER_MULTITHREADED) {
    CGLEnable(glContext->GetCGLContext(), kCGLCEMPEngine);
  }
  return glContext;
}

already_AddRefed<GLContext> GLContextProviderCGL::CreateHeadless(const GLContextCreateDesc& desc,
                                                                 nsACString* const out_failureId) {
  auto gl = CreateOffscreenFBOContext(desc);
  if (!gl) {
    *out_failureId = "FEATURE_FAILURE_CGL_FBO"_ns;
    return nullptr;
  }

  if (!gl->Init()) {
    *out_failureId = "FEATURE_FAILURE_CGL_INIT"_ns;
    NS_WARNING("Failed during Init.");
    return nullptr;
  }

  return gl.forget();
}

static RefPtr<GLContext> gGlobalContext;

GLContext* GLContextProviderCGL::GetGlobalContext() {
  static bool triedToCreateContext = false;
  if (!triedToCreateContext) {
    triedToCreateContext = true;

    MOZ_RELEASE_ASSERT(!gGlobalContext);
    nsCString discardFailureId;
    RefPtr<GLContext> temp = CreateHeadless({}, &discardFailureId);
    gGlobalContext = temp;

    if (!gGlobalContext) {
      NS_WARNING("Couldn't init gGlobalContext.");
    }
  }

  return gGlobalContext;
}

void GLContextProviderCGL::Shutdown() { gGlobalContext = nullptr; }

} /* namespace gl */
} /* namespace mozilla */