Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcocoaglcontext.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
8#include "qcocoawindow.h"
10#include "qcocoascreen.h"
11
12#include <QtCore/private/qcore_mac_p.h>
13
14#include <qdebug.h>
15#include <dlfcn.h>
16
17static inline QByteArray getGlString(GLenum param)
18{
19 if (const GLubyte *s = glGetString(param))
20 return QByteArray(reinterpret_cast<const char*>(s));
21 return QByteArray();
22}
23
25
26Q_LOGGING_CATEGORY(lcQpaOpenGLContext, "qt.qpa.openglcontext", QtWarningMsg);
27
28QCocoaGLContext::QCocoaGLContext(QOpenGLContext *context)
31{
32}
33
34QCocoaGLContext::QCocoaGLContext(NSOpenGLContext *nativeContext)
36{
37 m_context = [nativeContext retain];
38}
39
41{
42 if (m_context) {
43 // Note: We have no way of knowing whether the NSOpenGLContext was created with the
44 // share context as reported by the QOpenGLContext, but we just have to trust that
45 // it was. It's okey, as the only thing we're using it for is to report isShared().
46 if (QPlatformOpenGLContext *shareContext = context()->shareHandle())
47 m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext();
48
49 updateSurfaceFormat();
50 return;
51 }
52
53 // ----------- Default case, we own the NSOpenGLContext -----------
54
55 // We only support OpenGL contexts under Cocoa
56 if (m_format.renderableType() == QSurfaceFormat::DefaultRenderableType)
57 m_format.setRenderableType(QSurfaceFormat::OpenGL);
58 if (m_format.renderableType() != QSurfaceFormat::OpenGL)
59 return;
60
61 if (QPlatformOpenGLContext *shareContext = context()->shareHandle()) {
62 m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext();
63
64 // Allow sharing between 3.2 Core and 4.1 Core profile versions in
65 // cases where NSOpenGLContext creates a 4.1 context where a 3.2
66 // context was requested. Due to the semantics of QSurfaceFormat
67 // this 4.1 version can find its way onto the format for the new
68 // context, even though it was at no point requested by the user.
69 GLint shareContextRequestedProfile;
70 [m_shareContext.pixelFormat getValues:&shareContextRequestedProfile
71 forAttribute:NSOpenGLPFAOpenGLProfile forVirtualScreen:0];
72 auto shareContextActualProfile = shareContext->format().version();
73
74 if (shareContextRequestedProfile == NSOpenGLProfileVersion3_2Core
75 && shareContextActualProfile >= qMakePair(4, 1)) {
76 // There is a mismatch. Downgrade requested format to make the
77 // NSOpenGLPFAOpenGLProfile attributes match. (NSOpenGLContext
78 // will fail to create a new context if there is a mismatch).
79 if (m_format.version() >= qMakePair(4, 1))
80 m_format.setVersion(3, 2);
81 }
82 }
83
84 // ------------------------- Create NSOpenGLContext -------------------------
85
86 NSOpenGLPixelFormat *pixelFormat = [pixelFormatForSurfaceFormat(m_format) autorelease];
87 m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:m_shareContext];
88
89 if (!m_context && m_shareContext) {
90 qCWarning(lcQpaOpenGLContext, "Could not create NSOpenGLContext with shared context, "
91 "falling back to unshared context.");
92 m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
93 m_shareContext = nil;
94 }
95
96 if (!m_context) {
97 qCWarning(lcQpaOpenGLContext, "Failed to create NSOpenGLContext");
98 return;
99 }
100
101 // --------------------- Set NSOpenGLContext properties ---------------------
102
103 const GLint interval = m_format.swapInterval() >= 0 ? m_format.swapInterval() : 1;
104 [m_context setValues:&interval forParameter:NSOpenGLContextParameterSwapInterval];
105
106 if (m_format.alphaBufferSize() > 0) {
107 int zeroOpacity = 0;
108 [m_context setValues:&zeroOpacity forParameter:NSOpenGLContextParameterSurfaceOpacity];
109 }
110
111 // OpenGL surfaces can be ordered either above(default) or below the NSWindow
112 // FIXME: Promote to QSurfaceFormat option or property
113 const GLint order = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER");
114 [m_context setValues:&order forParameter:NSOpenGLContextParameterSurfaceOrder];
115
116 updateSurfaceFormat();
117
118 qCDebug(lcQpaOpenGLContext).verbosity(3) << "Created" << this << "based on requested" << context()->format();
119}
120
121NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurfaceFormat &format)
122{
123 QVector<NSOpenGLPixelFormatAttribute> attrs;
124
125 attrs << NSOpenGLPFAOpenGLProfile;
126 if (format.profile() == QSurfaceFormat::CoreProfile) {
127 if (format.version() >= qMakePair(4, 1))
128 attrs << NSOpenGLProfileVersion4_1Core;
129 else if (format.version() >= qMakePair(3, 2))
130 attrs << NSOpenGLProfileVersion3_2Core;
131 else
132 attrs << NSOpenGLProfileVersionLegacy;
133 } else {
134 attrs << NSOpenGLProfileVersionLegacy;
135 }
136
137 switch (format.swapBehavior()) {
138 case QSurfaceFormat::SingleBuffer:
139 break; // The NSOpenGLPixelFormat default, no attribute to set
140 case QSurfaceFormat::DefaultSwapBehavior:
141 // Technically this should be single-buffered, but we force double-buffered
142 // FIXME: Why do we force double-buffered?
143 Q_FALLTHROUGH();
144 case QSurfaceFormat::DoubleBuffer:
145 attrs.append(NSOpenGLPFADoubleBuffer);
146 break;
147 case QSurfaceFormat::TripleBuffer:
148 attrs.append(NSOpenGLPFATripleBuffer);
149 break;
150 }
151
152 if (format.depthBufferSize() > 0)
153 attrs << NSOpenGLPFADepthSize << format.depthBufferSize();
154 if (format.stencilBufferSize() > 0)
155 attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize();
156 if (format.alphaBufferSize() > 0)
157 attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize();
158
159 auto rbz = format.redBufferSize();
160 auto gbz = format.greenBufferSize();
161 auto bbz = format.blueBufferSize();
162 if (rbz > 0 || gbz > 0 || bbz > 0) {
163 auto fallbackSize = qMax(rbz, qMax(gbz, bbz));
164 auto colorSize = (rbz > 0 ? rbz : fallbackSize)
165 + (gbz > 0 ? gbz : fallbackSize)
166 + (bbz > 0 ? bbz : fallbackSize);
167 attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy;
168 }
169
170 if (format.samples() > 0) {
171 attrs << NSOpenGLPFAMultisample
172 << NSOpenGLPFASampleBuffers << NSOpenGLPixelFormatAttribute(1)
173 << NSOpenGLPFASamples << NSOpenGLPixelFormatAttribute(format.samples());
174 }
175
176 //Workaround for problems with Chromium and offline renderers on the lat 2013 MacPros.
177 //FIXME: Think if this could be solved via QSurfaceFormat in the future.
178 static bool offlineRenderersAllowed = qEnvironmentVariableIsEmpty("QT_MAC_PRO_WEBENGINE_WORKAROUND");
179 if (offlineRenderersAllowed) {
180 // Allow rendering on GPUs without a connected display
181 attrs << NSOpenGLPFAAllowOfflineRenderers;
182 }
183
184 if (qGuiApp->testAttribute(Qt::AA_UseSoftwareOpenGL)) {
185 // kCGLRendererGenericFloatID is the modern software renderer on macOS,
186 // as opposed to kCGLRendererGenericID, which is deprecated.
187 attrs << NSOpenGLPFARendererID << kCGLRendererGenericFloatID;
188 }
189
190 attrs << 0; // 0-terminate array
191 return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()];
192}
193
194/*!
195 Updates the surface format of this context based on properties of
196 the native context and GL state, so that the result of creating
197 the context is reflected back in QOpenGLContext.
198*/
199void QCocoaGLContext::updateSurfaceFormat()
200{
201 NSOpenGLContext *oldContext = [NSOpenGLContext currentContext];
202 [m_context makeCurrentContext];
203
204 // --------------------- Query GL state ---------------------
205
206 int major = 0, minor = 0;
207 QByteArray versionString(getGlString(GL_VERSION));
208 if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) {
209 m_format.setMajorVersion(major);
210 m_format.setMinorVersion(minor);
211 }
212
213 m_format.setProfile(QSurfaceFormat::NoProfile);
214 if (m_format.version() >= qMakePair(3, 2)) {
215 GLint value = 0;
216 glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value);
217 if (value & GL_CONTEXT_CORE_PROFILE_BIT)
218 m_format.setProfile(QSurfaceFormat::CoreProfile);
219 else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
220 m_format.setProfile(QSurfaceFormat::CompatibilityProfile);
221 }
222
223 m_format.setOption(QSurfaceFormat::DeprecatedFunctions, [&]() {
224 if (m_format.version() < qMakePair(3, 0)) {
225 return true;
226 } else {
227 GLint value = 0;
228 glGetIntegerv(GL_CONTEXT_FLAGS, &value);
229 return !(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT);
230 }
231 }());
232
233 // Debug contexts not supported on macOS
234 m_format.setOption(QSurfaceFormat::DebugContext, false);
235
236 // Nor are stereo buffers (deprecated in macOS 10.12)
237 m_format.setOption(QSurfaceFormat::StereoBuffers, false);
238
239 // ------------------ Query the pixel format ------------------
240
241 NSOpenGLPixelFormat *pixelFormat = m_context.pixelFormat;
242
243 GLint virtualScreen = [&, this]() {
244 auto *platformScreen = static_cast<QCocoaScreen*>(context()->screen()->handle());
245 auto displayId = platformScreen->nativeScreen().qt_displayId;
246 auto requestedDisplay = CGDisplayIDToOpenGLDisplayMask(displayId);
247 for (int i = 0; i < pixelFormat.numberOfVirtualScreens; ++i) {
248 GLint supportedDisplays;
249 [pixelFormat getValues:&supportedDisplays forAttribute:NSOpenGLPFAScreenMask forVirtualScreen:i];
250 // Note: The mask returned for NSOpenGLPFAScreenMask is a bit mask of
251 // physical displays that the renderer can drive, while the one returned
252 // from CGDisplayIDToOpenGLDisplayMask has a single bit set, representing
253 // that particular display.
254 if (requestedDisplay & supportedDisplays)
255 return i;
256 }
257 qCWarning(lcQpaOpenGLContext) << "Could not find virtual screen for"
258 << platformScreen << "with displayId" << displayId;
259 return 0;
260 }();
261
262 auto pixelFormatAttribute = [&](NSOpenGLPixelFormatAttribute attribute) {
263 int value = 0;
264 [pixelFormat getValues:&value forAttribute:attribute forVirtualScreen:virtualScreen];
265 return value;
266 };
267
268 int colorSize = pixelFormatAttribute(NSOpenGLPFAColorSize);
269 colorSize /= 4; // The attribute includes the alpha component
270 m_format.setRedBufferSize(colorSize);
271 m_format.setGreenBufferSize(colorSize);
272 m_format.setBlueBufferSize(colorSize);
273
274 // Surfaces on macOS always have an alpha channel, but unless the user requested
275 // one via setAlphaBufferSize(), which triggered setting NSOpenGLCPSurfaceOpacity
276 // to make the surface non-opaque, we don't want to report back the actual alpha
277 // size, as that will make the user believe the alpha channel can be used for
278 // something useful, when in reality it can't, due to the surface being opaque.
279 if (m_format.alphaBufferSize() > 0)
280 m_format.setAlphaBufferSize(pixelFormatAttribute(NSOpenGLPFAAlphaSize));
281
282 m_format.setDepthBufferSize(pixelFormatAttribute(NSOpenGLPFADepthSize));
283 m_format.setStencilBufferSize(pixelFormatAttribute(NSOpenGLPFAStencilSize));
284 m_format.setSamples(pixelFormatAttribute(NSOpenGLPFASamples));
285
286 if (pixelFormatAttribute(NSOpenGLPFATripleBuffer))
287 m_format.setSwapBehavior(QSurfaceFormat::TripleBuffer);
288 else if (pixelFormatAttribute(NSOpenGLPFADoubleBuffer))
289 m_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
290 else
291 m_format.setSwapBehavior(QSurfaceFormat::SingleBuffer);
292
293 m_isSoftwareContext = (pixelFormatAttribute(NSOpenGLPFARendererID)
294 & kCGLRendererIDMatchingMask) == kCGLRendererGenericFloatID;
295
296 // ------------------- Query the context -------------------
297
298 auto glContextParameter = [&](NSOpenGLContextParameter parameter) {
299 int value = 0;
300 [m_context getValues:&value forParameter:parameter];
301 return value;
302 };
303
304 m_format.setSwapInterval(glContextParameter(NSOpenGLContextParameterSwapInterval));
305
306 if (oldContext)
307 [oldContext makeCurrentContext];
308 else
309 [NSOpenGLContext clearCurrentContext];
310}
311
313{
314 [m_context release];
315}
316
317bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface)
318{
319 QMacAutoReleasePool pool;
320
321 qCDebug(lcQpaOpenGLContext) << "Making" << this << "current"
322 << "in" << QThread::currentThread() << "for" << surface;
323
324 Q_ASSERT(surface->surface()->supportsOpenGL());
325
326 if (!setDrawable(surface))
327 return false;
328
329 [m_context makeCurrentContext];
330
331 if (surface->surface()->surfaceClass() == QSurface::Window) {
332 if (m_needsUpdate.fetchAndStoreRelaxed(false))
333 update();
334 }
335
336 return true;
337}
338
340{
341 QMacAutoReleasePool pool;
342
343 Q_ASSERT(context() && context()->surface());
344 auto *surface = context()->surface()->surfaceHandle();
345 Q_ASSERT(surface);
346
347 qCDebug(lcQpaOpenGLContext) << "Beginning frame for" << this
348 << "in" << QThread::currentThread() << "for" << surface;
349
350 Q_ASSERT(surface->surface()->supportsOpenGL());
351
352 if (surface->surface()->surfaceClass() == QSurface::Window) {
353 if (m_needsUpdate.fetchAndStoreRelaxed(false))
354 update();
355 }
356}
357
358/*!
359 Sets the drawable object of the NSOpenGLContext, which is the
360 frame buffer that is the target of OpenGL drawing operations.
361*/
362bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
363{
364 // Make sure any surfaces released during this process are deallocated
365 // straight away, otherwise we may run out of surfaces when spinning a
366 // render-loop that doesn't return to one of the outer pools.
367 QMacAutoReleasePool pool;
368
369 if (!surface || surface->surface()->surfaceClass() == QSurface::Offscreen) {
370 // Clear the current drawable and reset the active window, so that GL
371 // commands that don't target a specific FBO will not end up stomping
372 // on the previously set drawable.
373 qCDebug(lcQpaOpenGLContext) << "Clearing current drawable" << QT_IGNORE_DEPRECATIONS(m_context.view) << "for" << m_context;
374 [m_context clearDrawable];
375 return true;
376 }
377
378 Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window);
379 auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
380 QNSView *view = qnsview_cast(cocoaWindow->view());
381
382 if (view == QT_IGNORE_DEPRECATIONS(m_context.view))
383 return true;
384
385 // We generally want high-DPI GL surfaces, unless the user has explicitly disabled them.
386 // According to the documentation, layer-backed views ignore wantsBestResolutionOpenGLSurface
387 // and configure their own backing surface at an appropriate resolution, but in some cases
388 // we've seen this fail (plugin views embedded in surface-backed hosts), so we do it anyways.
389 QT_IGNORE_DEPRECATIONS(view.wantsBestResolutionOpenGLSurface) = qt_mac_resolveOption(YES,
390 cocoaWindow->window(), "_q_mac_wantsBestResolutionOpenGLSurface",
391 "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE");
392
393 // Setting the drawable may happen on a separate thread as a result of
394 // a call to makeCurrent, so we need to set up the observers before we
395 // associate the view with the context. That way we will guarantee that
396 // as long as the view is the drawable of the context we will know about
397 // any updates to the view that require surface invalidation.
398
399 auto updateCallback = [this, view]() {
400 Q_ASSERT(QThread::currentThread() == qApp->thread());
401 if (QT_IGNORE_DEPRECATIONS(m_context.view) != view)
402 return;
403 m_needsUpdate = true;
404 };
405
406 m_updateObservers.clear();
407
408 m_updateObservers.append(QMacNotificationObserver(view, NSViewFrameDidChangeNotification, updateCallback));
409 m_updateObservers.append(QMacNotificationObserver(view.window, NSWindowDidChangeScreenNotification, updateCallback));
410
411 m_updateObservers.append(QMacNotificationObserver([NSApplication sharedApplication],
412 NSApplicationDidChangeScreenParametersNotification, updateCallback));
413
414 m_updateObservers.append(QMacNotificationObserver(view,
415 QCocoaWindowWillReleaseQNSViewNotification, [this, view] {
416 if (QT_IGNORE_DEPRECATIONS(m_context.view) != view)
417 return;
418 qCDebug(lcQpaOpenGLContext) << view << "about to be released."
419 << "Clearing current drawable for" << m_context;
420 [m_context clearDrawable];
421 }));
422
423 // If any of the observers fire at this point it's fine. We check the
424 // view association (atomically) in the update callback, and skip the
425 // update if we haven't associated yet. Setting the drawable below will
426 // have the same effect as an update.
427
428 // Now we are ready to associate the view with the context
429 QT_IGNORE_DEPRECATIONS(m_context.view) = view;
430 if (QT_IGNORE_DEPRECATIONS(m_context.view) != view) {
431 qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context;
432 m_updateObservers.clear();
433 return false;
434 }
435
436 qCInfo(lcQpaOpenGLContext) << "Set drawable for" << m_context << "to" << QT_IGNORE_DEPRECATIONS(m_context.view);
437 return true;
438}
439
440// NSOpenGLContext is not re-entrant. Even when using separate contexts per thread,
441// view, and window, calls into the API will still deadlock. For more information
442// see https://openradar.appspot.com/37064579
443Q_CONSTINIT static QMutex s_reentrancyMutex;
444
446{
447 // Make sure any surfaces released during this process are deallocated
448 // straight away, otherwise we may run out of surfaces when spinning a
449 // render-loop that doesn't return to one of the outer pools.
450 QMacAutoReleasePool pool;
451
452 QMutexLocker locker(&s_reentrancyMutex);
453 qCInfo(lcQpaOpenGLContext) << "Updating" << m_context << "for" << QT_IGNORE_DEPRECATIONS(m_context.view);
454 [m_context update];
455}
456
457void QCocoaGLContext::swapBuffers(QPlatformSurface *surface)
458{
459 QMacAutoReleasePool pool;
460
461 qCDebug(lcQpaOpenGLContext) << "Swapping" << m_context
462 << "in" << QThread::currentThread() << "to" << surface;
463
464 if (surface->surface()->surfaceClass() == QSurface::Offscreen)
465 return; // Nothing to do
466
467 if (!setDrawable(surface)) {
468 qCWarning(lcQpaOpenGLContext) << "Can't flush" << m_context
469 << "without" << surface << "as drawable";
470 return;
471 }
472
473 // Flushing an NSOpenGLContext will hit the screen immediately, ignoring
474 // any Core Animation transactions in place. This may result in major
475 // visual artifacts if the flush happens out of sync with the size
476 // of the layer, view, and window reflected by other parts of the UI,
477 // e.g. if the application flushes in the resize event or a timer during
478 // window resizing, instead of in the expose event.
479 auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
480 if (cocoaWindow->geometry().size() != cocoaWindow->m_exposedRect.size()) {
481 qCInfo(lcQpaOpenGLContext) << "Window exposed size does not match geometry (yet)."
482 << "Skipping flush to avoid visual artifacts.";
483 return;
484 }
485
486 QMutexLocker locker(&s_reentrancyMutex);
487 [m_context flushBuffer];
488}
489
491{
492 QMacAutoReleasePool pool;
493
494 qCDebug(lcQpaOpenGLContext) << "Clearing current context"
495 << [NSOpenGLContext currentContext] << "in" << QThread::currentThread();
496
497 // Note: We do not need to clear the current drawable here.
498 // As long as there is no current context, GL calls will
499 // do nothing.
500
501 [NSOpenGLContext clearCurrentContext];
502}
503
505{
506 return m_format;
507}
508
510{
511 return m_context != nil;
512}
513
515{
516 return m_shareContext != nil;
517}
518
520{
521 return m_isSoftwareContext;
522}
523
525{
526 return m_context;
527}
528
530{
531 return (QFunctionPointer)dlsym(RTLD_NEXT, procName);
532}
533
534#ifndef QT_NO_DEBUG_STREAM
535QDebug operator<<(QDebug debug, const QCocoaGLContext *context)
536{
537 QDebugStateSaver saver(debug);
538 debug.nospace();
539 debug << "QCocoaGLContext(" << (const void *)context;
540 if (context) {
541 if (debug.verbosity() > QDebug::DefaultVerbosity)
542 debug << ", " << context->format();
543 debug << ", " << context->nativeContext();
544 }
545 debug << ')';
546 return debug;
547}
548#endif // !QT_NO_DEBUG_STREAM
549
550QT_END_NAMESPACE
NSOpenGLContext * nativeContext() const override
void swapBuffers(QPlatformSurface *surface) override
Reimplement in subclass to native swap buffers calls.
void doneCurrent() override
QSurfaceFormat format() const override
bool isSoftwareContext() const
bool isSharing() const override
void initialize() override
Called after a new instance is constructed.
void beginFrame() override
Called when the RHI begins rendering a new frame in the context.
QFunctionPointer getProcAddress(const char *procName) override
Reimplement in subclass to allow dynamic querying of OpenGL symbols.
QCocoaGLContext(QOpenGLContext *context)
bool makeCurrent(QPlatformSurface *surface) override
bool isValid() const override
static QByteArray getGlString(GLenum param)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")