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 // Resolve color channel bits from GL, rather than NSOpenGLPFAColorSize,
269 // as the latter is not specific enough (combines all channels).
270 GLint redBits, greenBits, blueBits;
271 glGetIntegerv(GL_RED_BITS, &redBits);
272 glGetIntegerv(GL_GREEN_BITS, &greenBits);
273 glGetIntegerv(GL_BLUE_BITS, &blueBits);
274 m_format.setRedBufferSize(redBits);
275 m_format.setGreenBufferSize(greenBits);
276 m_format.setBlueBufferSize(blueBits);
277
278 // Surfaces on macOS always have an alpha channel, but unless the user requested
279 // one via setAlphaBufferSize(), which triggered setting NSOpenGLCPSurfaceOpacity
280 // to make the surface non-opaque, we don't want to report back the actual alpha
281 // size, as that will make the user believe the alpha channel can be used for
282 // something useful, when in reality it can't, due to the surface being opaque.
283 if (m_format.alphaBufferSize() > 0) {
284 GLint alphaBits;
285 glGetIntegerv(GL_ALPHA_BITS, &alphaBits);
286 m_format.setAlphaBufferSize(alphaBits);
287 }
288
289 m_format.setDepthBufferSize(pixelFormatAttribute(NSOpenGLPFADepthSize));
290 m_format.setStencilBufferSize(pixelFormatAttribute(NSOpenGLPFAStencilSize));
291 m_format.setSamples(pixelFormatAttribute(NSOpenGLPFASamples));
292
293 if (pixelFormatAttribute(NSOpenGLPFATripleBuffer))
294 m_format.setSwapBehavior(QSurfaceFormat::TripleBuffer);
295 else if (pixelFormatAttribute(NSOpenGLPFADoubleBuffer))
296 m_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
297 else
298 m_format.setSwapBehavior(QSurfaceFormat::SingleBuffer);
299
300 m_isSoftwareContext = (pixelFormatAttribute(NSOpenGLPFARendererID)
301 & kCGLRendererIDMatchingMask) == kCGLRendererGenericFloatID;
302
303 // ------------------- Query the context -------------------
304
305 auto glContextParameter = [&](NSOpenGLContextParameter parameter) {
306 int value = 0;
307 [m_context getValues:&value forParameter:parameter];
308 return value;
309 };
310
311 m_format.setSwapInterval(glContextParameter(NSOpenGLContextParameterSwapInterval));
312
313 if (oldContext)
314 [oldContext makeCurrentContext];
315 else
316 [NSOpenGLContext clearCurrentContext];
317}
318
320{
321 [m_context release];
322}
323
324bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface)
325{
326 QMacAutoReleasePool pool;
327
328 qCDebug(lcQpaOpenGLContext) << "Making" << this << "current"
329 << "in" << QThread::currentThread() << "for" << surface;
330
331 Q_ASSERT(surface->surface()->supportsOpenGL());
332
333 if (!setDrawable(surface))
334 return false;
335
336 [m_context makeCurrentContext];
337
338 if (surface->surface()->surfaceClass() == QSurface::Window) {
339 if (m_needsUpdate.fetchAndStoreRelaxed(false))
340 update();
341 }
342
343 return true;
344}
345
347{
348 QMacAutoReleasePool pool;
349
350 Q_ASSERT(context() && context()->surface());
351 auto *surface = context()->surface()->surfaceHandle();
352 Q_ASSERT(surface);
353
354 qCDebug(lcQpaOpenGLContext) << "Beginning frame for" << this
355 << "in" << QThread::currentThread() << "for" << surface;
356
357 Q_ASSERT(surface->surface()->supportsOpenGL());
358
359 if (surface->surface()->surfaceClass() == QSurface::Window) {
360 if (m_needsUpdate.fetchAndStoreRelaxed(false))
361 update();
362 }
363}
364
365/*!
366 Sets the drawable object of the NSOpenGLContext, which is the
367 frame buffer that is the target of OpenGL drawing operations.
368*/
369bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
370{
371 // Make sure any surfaces released during this process are deallocated
372 // straight away, otherwise we may run out of surfaces when spinning a
373 // render-loop that doesn't return to one of the outer pools.
374 QMacAutoReleasePool pool;
375
376 if (!surface || surface->surface()->surfaceClass() == QSurface::Offscreen) {
377 // Clear the current drawable and reset the active window, so that GL
378 // commands that don't target a specific FBO will not end up stomping
379 // on the previously set drawable.
380 qCDebug(lcQpaOpenGLContext) << "Clearing current drawable" << QT_IGNORE_DEPRECATIONS(m_context.view) << "for" << m_context;
381 [m_context clearDrawable];
382 return true;
383 }
384
385 Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window);
386 auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
387 QNSView *view = qnsview_cast(cocoaWindow->view());
388
389 if (view == QT_IGNORE_DEPRECATIONS(m_context.view))
390 return true;
391
392 // We generally want high-DPI GL surfaces, unless the user has explicitly disabled them.
393 // According to the documentation, layer-backed views ignore wantsBestResolutionOpenGLSurface
394 // and configure their own backing surface at an appropriate resolution, but in some cases
395 // we've seen this fail (plugin views embedded in surface-backed hosts), so we do it anyways.
396 QT_IGNORE_DEPRECATIONS(view.wantsBestResolutionOpenGLSurface) = qt_mac_resolveOption(YES,
397 cocoaWindow->window(), "_q_mac_wantsBestResolutionOpenGLSurface",
398 "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE");
399
400 // Setting the drawable may happen on a separate thread as a result of
401 // a call to makeCurrent, so we need to set up the observers before we
402 // associate the view with the context. That way we will guarantee that
403 // as long as the view is the drawable of the context we will know about
404 // any updates to the view that require surface invalidation.
405
406 auto updateCallback = [this, view]() {
407 Q_ASSERT(QThread::currentThread() == qApp->thread());
408 if (QT_IGNORE_DEPRECATIONS(m_context.view) != view)
409 return;
410 m_needsUpdate = true;
411 };
412
413 m_updateObservers.clear();
414
415 m_updateObservers.append(QMacNotificationObserver(view, NSViewFrameDidChangeNotification, updateCallback));
416 m_updateObservers.append(QMacNotificationObserver(view.window, NSWindowDidChangeScreenNotification, updateCallback));
417
418 m_updateObservers.append(QMacNotificationObserver([NSApplication sharedApplication],
419 NSApplicationDidChangeScreenParametersNotification, updateCallback));
420
421 m_updateObservers.append(QMacNotificationObserver(view,
422 QCocoaWindowWillReleaseQNSViewNotification, [this, view] {
423 if (QT_IGNORE_DEPRECATIONS(m_context.view) != view)
424 return;
425 qCDebug(lcQpaOpenGLContext) << view << "about to be released."
426 << "Clearing current drawable for" << m_context;
427 [m_context clearDrawable];
428 }));
429
430 // If any of the observers fire at this point it's fine. We check the
431 // view association (atomically) in the update callback, and skip the
432 // update if we haven't associated yet. Setting the drawable below will
433 // have the same effect as an update.
434
435 // Now we are ready to associate the view with the context
436 QT_IGNORE_DEPRECATIONS(m_context.view) = view;
437 if (QT_IGNORE_DEPRECATIONS(m_context.view) != view) {
438 qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context;
439 m_updateObservers.clear();
440 return false;
441 }
442
443 qCInfo(lcQpaOpenGLContext) << "Set drawable for" << m_context << "to" << QT_IGNORE_DEPRECATIONS(m_context.view);
444 return true;
445}
446
447// NSOpenGLContext is not re-entrant. Even when using separate contexts per thread,
448// view, and window, calls into the API will still deadlock. For more information
449// see https://openradar.appspot.com/37064579
450Q_CONSTINIT static QMutex s_reentrancyMutex;
451
453{
454 // Make sure any surfaces released during this process are deallocated
455 // straight away, otherwise we may run out of surfaces when spinning a
456 // render-loop that doesn't return to one of the outer pools.
457 QMacAutoReleasePool pool;
458
459 QMutexLocker locker(&s_reentrancyMutex);
460 qCInfo(lcQpaOpenGLContext) << "Updating" << m_context << "for" << QT_IGNORE_DEPRECATIONS(m_context.view);
461 [m_context update];
462}
463
464void QCocoaGLContext::swapBuffers(QPlatformSurface *surface)
465{
466 QMacAutoReleasePool pool;
467
468 qCDebug(lcQpaOpenGLContext) << "Swapping" << m_context
469 << "in" << QThread::currentThread() << "to" << surface;
470
471 if (surface->surface()->surfaceClass() == QSurface::Offscreen)
472 return; // Nothing to do
473
474 if (!setDrawable(surface)) {
475 qCWarning(lcQpaOpenGLContext) << "Can't flush" << m_context
476 << "without" << surface << "as drawable";
477 return;
478 }
479
480 // Flushing an NSOpenGLContext will hit the screen immediately, ignoring
481 // any Core Animation transactions in place. This may result in major
482 // visual artifacts if the flush happens out of sync with the size
483 // of the layer, view, and window reflected by other parts of the UI,
484 // e.g. if the application flushes in the resize event or a timer during
485 // window resizing, instead of in the expose event.
486 auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
487 if (cocoaWindow->geometry().size() != cocoaWindow->m_exposedRect.size()) {
488 qCInfo(lcQpaOpenGLContext) << "Window exposed size does not match geometry (yet)."
489 << "Skipping flush to avoid visual artifacts.";
490 return;
491 }
492
493 QMutexLocker locker(&s_reentrancyMutex);
494 [m_context flushBuffer];
495}
496
498{
499 QMacAutoReleasePool pool;
500
501 qCDebug(lcQpaOpenGLContext) << "Clearing current context"
502 << [NSOpenGLContext currentContext] << "in" << QThread::currentThread();
503
504 // Note: We do not need to clear the current drawable here.
505 // As long as there is no current context, GL calls will
506 // do nothing.
507
508 [NSOpenGLContext clearCurrentContext];
509}
510
512{
513 return m_format;
514}
515
517{
518 return m_context != nil;
519}
520
522{
523 return m_shareContext != nil;
524}
525
527{
528 return m_isSoftwareContext;
529}
530
532{
533 return m_context;
534}
535
537{
538 return (QFunctionPointer)dlsym(RTLD_NEXT, procName);
539}
540
541#ifndef QT_NO_DEBUG_STREAM
542QDebug operator<<(QDebug debug, const QCocoaGLContext *context)
543{
544 QDebugStateSaver saver(debug);
545 debug.nospace();
546 debug << "QCocoaGLContext(" << (const void *)context;
547 if (context) {
548 if (debug.verbosity() > QDebug::DefaultVerbosity)
549 debug << ", " << context->format();
550 debug << ", " << context->nativeContext();
551 }
552 debug << ')';
553 return debug;
554}
555#endif // !QT_NO_DEBUG_STREAM
556
557QT_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")