summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoaglcontext.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoaglcontext.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoaglcontext.mm620
1 files changed, 329 insertions, 291 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm
index 7ffe0003d3..cba9e90a78 100644
--- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm
+++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm
@@ -41,14 +41,14 @@
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
#include <qdebug.h>
+#include <QtCore/qscopedvaluerollback.h>
+#include <QtCore/qatomic.h>
#include <QtCore/private/qcore_mac_p.h>
#include <QtPlatformHeaders/qcocoanativecontext.h>
#include <dlfcn.h>
#import <AppKit/AppKit.h>
-QT_BEGIN_NAMESPACE
-
static inline QByteArray getGlString(GLenum param)
{
if (const GLubyte *s = glGetString(param))
@@ -56,109 +56,66 @@ static inline QByteArray getGlString(GLenum param)
return QByteArray();
}
-#if !defined(GL_CONTEXT_FLAGS)
-#define GL_CONTEXT_FLAGS 0x821E
-#endif
-
-#if !defined(GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT)
-#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001
-#endif
-
-#if !defined(GL_CONTEXT_PROFILE_MASK)
-#define GL_CONTEXT_PROFILE_MASK 0x9126
-#endif
-
-#if !defined(GL_CONTEXT_CORE_PROFILE_BIT)
-#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001
-#endif
-
-#if !defined(GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
-#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
-#endif
-
-static void updateFormatFromContext(QSurfaceFormat *format)
+@implementation NSOpenGLPixelFormat (QtHelpers)
+- (GLint)qt_getAttribute:(NSOpenGLPixelFormatAttribute)attribute
{
- Q_ASSERT(format);
-
- // Update the version, profile, and context bit of the format
- int major = 0, minor = 0;
- QByteArray versionString(getGlString(GL_VERSION));
- if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) {
- format->setMajorVersion(major);
- format->setMinorVersion(minor);
- }
-
- format->setProfile(QSurfaceFormat::NoProfile);
-
- Q_ASSERT(format->renderableType() == QSurfaceFormat::OpenGL);
- if (format->version() < qMakePair(3, 0)) {
- format->setOption(QSurfaceFormat::DeprecatedFunctions);
- return;
- }
-
- // Version 3.0 onwards - check if it includes deprecated functionality
- GLint value = 0;
- glGetIntegerv(GL_CONTEXT_FLAGS, &value);
- if (!(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT))
- format->setOption(QSurfaceFormat::DeprecatedFunctions);
-
- // Debug context option not supported on OS X
-
- if (format->version() < qMakePair(3, 2))
- return;
-
- // Version 3.2 and newer have a profile
- value = 0;
- glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value);
+ int value = 0;
+ [self getValues:&value forAttribute:attribute forVirtualScreen:0];
+ return value;
+}
+@end
- if (value & GL_CONTEXT_CORE_PROFILE_BIT)
- format->setProfile(QSurfaceFormat::CoreProfile);
- else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
- format->setProfile(QSurfaceFormat::CompatibilityProfile);
+@implementation NSOpenGLContext (QtHelpers)
+- (GLint)qt_getParameter:(NSOpenGLContextParameter)parameter
+{
+ int value = 0;
+ [self getValues:&value forParameter:parameter];
+ return value;
}
+@end
- // NSOpenGLContext is not re-entrant (https://openradar.appspot.com/37064579)
-static QMutex s_contextMutex;
+QT_BEGIN_NAMESPACE
-QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share,
- const QVariant &nativeHandle)
- : m_context(nil),
- m_shareContext(nil),
- m_format(format),
- m_didCheckForSoftwareContext(false)
+Q_LOGGING_CATEGORY(lcQpaOpenGLContext, "qt.qpa.openglcontext", QtWarningMsg);
+
+QCocoaGLContext::QCocoaGLContext(QOpenGLContext *context)
+ : QPlatformOpenGLContext(), m_format(context->format())
{
+ QVariant nativeHandle = context->nativeHandle();
if (!nativeHandle.isNull()) {
if (!nativeHandle.canConvert<QCocoaNativeContext>()) {
- qWarning("QCocoaGLContext: Requires a QCocoaNativeContext");
+ qCWarning(lcQpaOpenGLContext, "QOpenGLContext native handle must be a QCocoaNativeContext");
return;
}
- QCocoaNativeContext handle = nativeHandle.value<QCocoaNativeContext>();
- NSOpenGLContext *context = handle.context();
- if (!context) {
- qWarning("QCocoaGLContext: No NSOpenGLContext given");
+ m_context = nativeHandle.value<QCocoaNativeContext>().context();
+ if (!m_context) {
+ qCWarning(lcQpaOpenGLContext, "QCocoaNativeContext's NSOpenGLContext can not be null");
return;
}
- m_context = context;
+
[m_context retain];
- m_shareContext = share ? static_cast<QCocoaGLContext *>(share)->nsOpenGLContext() : nil;
- // OpenGL surfaces can be ordered either above(default) or below the NSWindow.
- const GLint order = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER");
- [m_context setValues:&order forParameter:NSOpenGLCPSurfaceOrder];
+
+ // Note: We have no way of knowing whether the NSOpenGLContext was created with the
+ // share context as reported by the QOpenGLContext, but we just have to trust that
+ // it was. It's okey, as the only thing we're using it for is to report isShared().
+ if (QPlatformOpenGLContext *shareContext = context->shareHandle())
+ m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext();
+
updateSurfaceFormat();
return;
}
- // we only support OpenGL contexts under Cocoa
+ // ----------- Default case, we own the NSOpenGLContext -----------
+
+ // We only support OpenGL contexts under Cocoa
if (m_format.renderableType() == QSurfaceFormat::DefaultRenderableType)
m_format.setRenderableType(QSurfaceFormat::OpenGL);
if (m_format.renderableType() != QSurfaceFormat::OpenGL)
return;
- QMacAutoReleasePool pool; // For the SG Canvas render thread
+ if (QPlatformOpenGLContext *shareContext = context->shareHandle()) {
+ m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext();
- m_shareContext = share ? static_cast<QCocoaGLContext *>(share)->nsOpenGLContext() : nil;
-
- if (m_shareContext) {
// Allow sharing between 3.2 Core and 4.1 Core profile versions in
// cases where NSOpenGLContext creates a 4.1 context where a 3.2
// context was requested. Due to the semantics of QSurfaceFormat
@@ -167,106 +124,237 @@ QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLCo
GLint shareContextRequestedProfile;
[m_shareContext.pixelFormat getValues:&shareContextRequestedProfile
forAttribute:NSOpenGLPFAOpenGLProfile forVirtualScreen:0];
- auto shareContextActualProfile = share->format().version();
-
- if (shareContextRequestedProfile == NSOpenGLProfileVersion3_2Core &&
- shareContextActualProfile >= qMakePair(4, 1)) {
+ auto shareContextActualProfile = shareContext->format().version();
- // There is a mismatch, downgrade requested format to make the
- // NSOpenGLPFAOpenGLProfile attributes match. (NSOpenGLContext will
- // fail to create a new context if there is a mismatch).
+ if (shareContextRequestedProfile == NSOpenGLProfileVersion3_2Core
+ && shareContextActualProfile >= qMakePair(4, 1)) {
+ // There is a mismatch. Downgrade requested format to make the
+ // NSOpenGLPFAOpenGLProfile attributes match. (NSOpenGLContext
+ // will fail to create a new context if there is a mismatch).
if (m_format.version() >= qMakePair(4, 1))
m_format.setVersion(3, 2);
}
}
- // create native context for the requested pixel format and share
- NSOpenGLPixelFormat *pixelFormat = createNSOpenGLPixelFormat(m_format);
+ // ------------------------- Create NSOpenGLContext -------------------------
+
+ NSOpenGLPixelFormat *pixelFormat = [pixelFormatForSurfaceFormat(m_format) autorelease];
m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:m_shareContext];
- // retry without sharing on context creation failure.
if (!m_context && m_shareContext) {
- m_shareContext = nil;
+ qCWarning(lcQpaOpenGLContext, "Could not create NSOpenGLContext with shared context, "
+ "falling back to unshared context.");
m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
- if (m_context)
- qWarning("QCocoaGLContext: Falling back to unshared context.");
+ m_shareContext = nil;
}
- // give up if we still did not get a native context
- [pixelFormat release];
if (!m_context) {
- qWarning("QCocoaGLContext: Failed to create context.");
+ qCWarning(lcQpaOpenGLContext, "Failed to create NSOpenGLContext");
return;
}
- const GLint interval = format.swapInterval() >= 0 ? format.swapInterval() : 1;
+ // --------------------- Set NSOpenGLContext properties ---------------------
+
+ const GLint interval = m_format.swapInterval() >= 0 ? m_format.swapInterval() : 1;
[m_context setValues:&interval forParameter:NSOpenGLCPSwapInterval];
- if (format.alphaBufferSize() > 0) {
+ if (m_format.alphaBufferSize() > 0) {
int zeroOpacity = 0;
[m_context setValues:&zeroOpacity forParameter:NSOpenGLCPSurfaceOpacity];
}
-
- // OpenGL surfaces can be ordered either above(default) or below the NSWindow.
+ // OpenGL surfaces can be ordered either above(default) or below the NSWindow
+ // FIXME: Promote to QSurfaceFormat option or property
const GLint order = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER");
[m_context setValues:&order forParameter:NSOpenGLCPSurfaceOrder];
updateSurfaceFormat();
}
-QCocoaGLContext::~QCocoaGLContext()
+NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurfaceFormat &format)
{
- if (m_currentWindow && m_currentWindow.data()->handle())
- static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0);
+ QVector<NSOpenGLPixelFormatAttribute> attrs;
- [m_context release];
-}
+ attrs << NSOpenGLPFAOpenGLProfile;
+ if (format.profile() == QSurfaceFormat::CoreProfile) {
+ if (format.version() >= qMakePair(4, 1))
+ attrs << NSOpenGLProfileVersion4_1Core;
+ else if (format.version() >= qMakePair(3, 2))
+ attrs << NSOpenGLProfileVersion3_2Core;
+ else
+ attrs << NSOpenGLProfileVersionLegacy;
+ } else {
+ attrs << NSOpenGLProfileVersionLegacy;
+ }
-QVariant QCocoaGLContext::nativeHandle() const
-{
- return QVariant::fromValue<QCocoaNativeContext>(QCocoaNativeContext(m_context));
-}
+ switch (format.swapBehavior()) {
+ case QSurfaceFormat::SingleBuffer:
+ break; // The NSOpenGLPixelFormat default, no attribute to set
+ case QSurfaceFormat::DefaultSwapBehavior:
+ // Technically this should be single-buffered, but we force double-buffered
+ // FIXME: Why do we force double-buffered?
+ Q_FALLTHROUGH();
+ case QSurfaceFormat::DoubleBuffer:
+ attrs.append(NSOpenGLPFADoubleBuffer);
+ break;
+ case QSurfaceFormat::TripleBuffer:
+ attrs.append(NSOpenGLPFATripleBuffer);
+ break;
+ }
-QSurfaceFormat QCocoaGLContext::format() const
-{
- return m_format;
-}
+ if (format.depthBufferSize() > 0)
+ attrs << NSOpenGLPFADepthSize << format.depthBufferSize();
+ if (format.stencilBufferSize() > 0)
+ attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize();
+ if (format.alphaBufferSize() > 0)
+ attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize();
+ if (format.redBufferSize() > 0 && format.greenBufferSize() > 0 && format.blueBufferSize() > 0) {
+ const int colorSize = format.redBufferSize() + format.greenBufferSize() + format.blueBufferSize();
+ attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy;
+ }
-void QCocoaGLContext::windowWasHidden()
-{
- // If the window is hidden, we need to unset the m_currentWindow
- // variable so that succeeding makeCurrent's will not abort prematurely
- // because of the optimization in setActiveWindow.
- // Doing a full doneCurrent here is not preferable, because the GL context
- // might be rendering in a different thread at this time.
- m_currentWindow.clear();
+ if (format.samples() > 0) {
+ attrs << NSOpenGLPFAMultisample
+ << NSOpenGLPFASampleBuffers << NSOpenGLPixelFormatAttribute(1)
+ << NSOpenGLPFASamples << NSOpenGLPixelFormatAttribute(format.samples());
+ }
+
+ if (format.stereo())
+ attrs << NSOpenGLPFAStereo;
+
+ // Allow rendering on GPUs without a connected display
+ attrs << NSOpenGLPFAAllowOfflineRenderers;
+
+ // FIXME: Pull this information out of the NSView
+ QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER");
+ if (!useLayer.isEmpty() && useLayer.toInt() > 0) {
+ // Disable the software rendering fallback. This makes compositing
+ // OpenGL and raster NSViews using Core Animation layers possible.
+ attrs << NSOpenGLPFANoRecovery;
+ }
+
+ attrs << 0; // 0-terminate array
+ return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()];
}
-void QCocoaGLContext::swapBuffers(QPlatformSurface *surface)
+/*!
+ Updates the surface format of this context based on properties of
+ the native context and GL state, so that the result of creating
+ the context is reflected back in QOpenGLContext.
+*/
+void QCocoaGLContext::updateSurfaceFormat()
{
- if (surface->surface()->surfaceClass() == QSurface::Offscreen)
- return; // Nothing to do
+ NSOpenGLContext *oldContext = [NSOpenGLContext currentContext];
+ [m_context makeCurrentContext];
- QWindow *window = static_cast<QCocoaWindow *>(surface)->window();
- setActiveWindow(window);
+ // --------------------- Query GL state ---------------------
- QMutexLocker locker(&s_contextMutex);
- [m_context flushBuffer];
+ int major = 0, minor = 0;
+ QByteArray versionString(getGlString(GL_VERSION));
+ if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) {
+ m_format.setMajorVersion(major);
+ m_format.setMinorVersion(minor);
+ }
+
+ m_format.setProfile(QSurfaceFormat::NoProfile);
+ if (m_format.version() >= qMakePair(3, 2)) {
+ GLint value = 0;
+ glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value);
+ if (value & GL_CONTEXT_CORE_PROFILE_BIT)
+ m_format.setProfile(QSurfaceFormat::CoreProfile);
+ else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
+ m_format.setProfile(QSurfaceFormat::CompatibilityProfile);
+ }
+
+ m_format.setOption(QSurfaceFormat::DeprecatedFunctions, [&]() {
+ if (m_format.version() < qMakePair(3, 0)) {
+ return true;
+ } else {
+ GLint value = 0;
+ glGetIntegerv(GL_CONTEXT_FLAGS, &value);
+ return !(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT);
+ }
+ }());
+
+ // Debug contexts not supported on macOS
+ m_format.setOption(QSurfaceFormat::DebugContext, false);
+
+ // ------------------ Query the pixel format ------------------
+
+ NSOpenGLPixelFormat *pixelFormat = m_context.pixelFormat;
+
+ int colorSize = [pixelFormat qt_getAttribute:NSOpenGLPFAColorSize];
+ colorSize /= 4; // The attribute includes the alpha component
+ m_format.setRedBufferSize(colorSize);
+ m_format.setGreenBufferSize(colorSize);
+ m_format.setBlueBufferSize(colorSize);
+
+ // Surfaces on macOS always have an alpha channel, but unless the user requested
+ // one via setAlphaBufferSize(), which triggered setting NSOpenGLCPSurfaceOpacity
+ // to make the surface non-opaque, we don't want to report back the actual alpha
+ // size, as that will make the user believe the alpha channel can be used for
+ // something useful, when in reality it can't, due to the surface being opaque.
+ if (m_format.alphaBufferSize() > 0)
+ m_format.setAlphaBufferSize([pixelFormat qt_getAttribute:NSOpenGLPFAAlphaSize]);
+
+ m_format.setDepthBufferSize([pixelFormat qt_getAttribute:NSOpenGLPFADepthSize]);
+ m_format.setStencilBufferSize([pixelFormat qt_getAttribute:NSOpenGLPFAStencilSize]);
+ m_format.setSamples([pixelFormat qt_getAttribute:NSOpenGLPFASamples]);
+
+ if ([pixelFormat qt_getAttribute:NSOpenGLPFATripleBuffer])
+ m_format.setSwapBehavior(QSurfaceFormat::TripleBuffer);
+ else if ([pixelFormat qt_getAttribute:NSOpenGLPFADoubleBuffer])
+ m_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
+ else
+ m_format.setSwapBehavior(QSurfaceFormat::SingleBuffer);
+
+ m_format.setOption(QSurfaceFormat::StereoBuffers, [pixelFormat qt_getAttribute:NSOpenGLPFAStereo]);
+
+ // ------------------- Query the context -------------------
+
+ m_format.setSwapInterval([m_context qt_getParameter:NSOpenGLCPSwapInterval]);
+
+ if (oldContext)
+ [oldContext makeCurrentContext];
+ else
+ [NSOpenGLContext clearCurrentContext];
+}
+
+QCocoaGLContext::~QCocoaGLContext()
+{
+ [m_context release];
}
bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface)
{
- Q_ASSERT(surface->surface()->supportsOpenGL());
+ qCDebug(lcQpaOpenGLContext) << "Making" << m_context << "current"
+ << "in" << QThread::currentThread() << "for" << surface;
+
+ // No need to make context current if it already is. This also ensures
+ // that we only lock the context once, meaning we don't need to keep
+ // track of how many times we've locked it to undo it in doneCurrent().
+ // Note that we're not using QOpenGLContext::currentContext() here, as
+ // that has already been updated to match context() before this call.
+ if ([NSOpenGLContext currentContext] == m_context)
+ return true;
- QMacAutoReleasePool pool;
- [m_context makeCurrentContext];
+ Q_ASSERT(surface->surface()->supportsOpenGL());
- if (surface->surface()->surfaceClass() == QSurface::Offscreen)
+ if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
+ [m_context makeCurrentContext];
return true;
+ }
+
+ if (!setDrawable(surface))
+ return false;
- QWindow *window = static_cast<QCocoaWindow *>(surface)->window();
- setActiveWindow(window);
+ // The context may be owned and used by a dedicated render thread, but
+ // we will get notifications that trigger update() on the main thread,
+ // so we need to guard against concurrent uses of the context. We hold
+ // this lock until swapBuffer() or doneCurrent() gets called.
+ CGLLockContext(m_context.CGLContextObj);
+
+ [m_context makeCurrentContext];
// Disable high-resolution surfaces when using the software renderer, which has the
// problem that the system silently falls back to a to using a low-resolution buffer
@@ -274,6 +362,8 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface)
// convertSizeToBacking and backingScaleFactor APIs. A typical result of this is that Qt
// will display a quarter of the window content when running in a virtual machine.
if (!m_didCheckForSoftwareContext) {
+ // FIXME: This ensures we check only once per context,
+ // but the context may be used for multiple surfaces.
m_didCheckForSoftwareContext = true;
const GLubyte* renderer = glGetString(GL_RENDERER);
@@ -283,196 +373,135 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface)
}
}
- update();
return true;
}
-void QCocoaGLContext::setActiveWindow(QWindow *window)
+/*!
+ Sets the drawable object of the NSOpenGLContext, which is the
+ frame buffer that is the target of OpenGL drawing operations.
+*/
+bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
{
- if (window == m_currentWindow.data())
- return;
+ Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window);
+ NSView *view = static_cast<QCocoaWindow *>(surface)->view();
- if (m_currentWindow && m_currentWindow.data()->handle())
- static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0);
-
- Q_ASSERT(window->handle());
-
- m_currentWindow = window;
+ if (view == m_context.view)
+ return true;
- QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
- cocoaWindow->setCurrentContext(this);
+ m_observers.clear();
- Q_ASSERT(!cocoaWindow->isForeignWindow());
- [qnsview_cast(cocoaWindow->view()) setQCocoaGLContext:this];
-}
-
-void QCocoaGLContext::updateSurfaceFormat()
-{
- // At present it is impossible to turn an option off on a QSurfaceFormat (see
- // https://codereview.qt-project.org/#change,70599). So we have to populate
- // the actual surface format from scratch
- QSurfaceFormat requestedFormat = m_format;
- m_format = QSurfaceFormat();
- m_format.setRenderableType(QSurfaceFormat::OpenGL);
-
- // CoreGL doesn't require a drawable to make the context current
- CGLContextObj oldContext = CGLGetCurrentContext();
- CGLContextObj ctx = static_cast<CGLContextObj>([m_context CGLContextObj]);
- CGLSetCurrentContext(ctx);
-
- // Get the data that OpenGL provides
- updateFormatFromContext(&m_format);
-
- // Get the data contained within the pixel format
- CGLPixelFormatObj cglPixelFormat = static_cast<CGLPixelFormatObj>(CGLGetPixelFormat(ctx));
- NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithCGLPixelFormatObj:cglPixelFormat];
-
- int colorSize = -1;
- [pixelFormat getValues:&colorSize forAttribute:NSOpenGLPFAColorSize forVirtualScreen:0];
- if (colorSize > 0) {
- // This seems to return the total color buffer depth, including alpha
- m_format.setRedBufferSize(colorSize / 4);
- m_format.setGreenBufferSize(colorSize / 4);
- m_format.setBlueBufferSize(colorSize / 4);
+ if ((m_context.view = view) != view) {
+ qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context;
+ return false;
}
- // The pixel format always seems to return 8 for alpha. However, the framebuffer only
- // seems to have alpha enabled if we requested it explicitly. I can't find any other
- // attribute to check explicitly for this so we use our best guess for alpha.
- int alphaSize = -1;
- [pixelFormat getValues:&alphaSize forAttribute:NSOpenGLPFAAlphaSize forVirtualScreen:0];
- if (alphaSize > 0 && requestedFormat.alphaBufferSize() > 0)
- m_format.setAlphaBufferSize(alphaSize);
-
- int depthSize = -1;
- [pixelFormat getValues:&depthSize forAttribute:NSOpenGLPFADepthSize forVirtualScreen:0];
- if (depthSize > 0)
- m_format.setDepthBufferSize(depthSize);
-
- int stencilSize = -1;
- [pixelFormat getValues:&stencilSize forAttribute:NSOpenGLPFAStencilSize forVirtualScreen:0];
- if (stencilSize > 0)
- m_format.setStencilBufferSize(stencilSize);
-
- int samples = -1;
- [pixelFormat getValues:&samples forAttribute:NSOpenGLPFASamples forVirtualScreen:0];
- if (samples > 0)
- m_format.setSamples(samples);
-
- int doubleBuffered = -1;
- int tripleBuffered = -1;
- [pixelFormat getValues:&doubleBuffered forAttribute:NSOpenGLPFADoubleBuffer forVirtualScreen:0];
- [pixelFormat getValues:&tripleBuffered forAttribute:NSOpenGLPFATripleBuffer forVirtualScreen:0];
-
- if (tripleBuffered == 1)
- m_format.setSwapBehavior(QSurfaceFormat::TripleBuffer);
- else if (doubleBuffered == 1)
- m_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
- else
- m_format.setSwapBehavior(QSurfaceFormat::SingleBuffer);
-
- int steroBuffers = -1;
- [pixelFormat getValues:&steroBuffers forAttribute:NSOpenGLPFAStereo forVirtualScreen:0];
- if (steroBuffers == 1)
- m_format.setOption(QSurfaceFormat::StereoBuffers);
-
- [pixelFormat release];
-
- GLint swapInterval = -1;
- [m_context getValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
- if (swapInterval >= 0)
- m_format.setSwapInterval(swapInterval);
+ qCInfo(lcQpaOpenGLContext) << "Set drawable for" << m_context << "to" << m_context.view;
- // Restore the original context
- CGLSetCurrentContext(oldContext);
-}
+ auto updateCallback = [&]() { update(); };
-void QCocoaGLContext::doneCurrent()
-{
- if (m_currentWindow && m_currentWindow.data()->handle())
- static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0);
+ if (view.layer) {
+ m_observers.append(QMacScopedObserver(view, NSViewFrameDidChangeNotification, updateCallback));
+ m_observers.append(QMacScopedObserver(view.window, NSWindowDidChangeScreenNotification, updateCallback));
+ } else {
+ m_observers.append(QMacScopedObserver(view, NSViewGlobalFrameDidChangeNotification, updateCallback));
+ }
- m_currentWindow.clear();
+ m_observers.append(QMacScopedObserver([NSApplication sharedApplication],
+ NSApplicationDidChangeScreenParametersNotification, updateCallback));
- [NSOpenGLContext clearCurrentContext];
+ return true;
}
-QFunctionPointer QCocoaGLContext::getProcAddress(const char *procName)
-{
- return (QFunctionPointer)dlsym(RTLD_DEFAULT, procName);
-}
+// NSOpenGLContext is not re-entrant, which means that even when using separate
+// contexts per thread, per view, and window, calls into the API will still deadlock.
+// Note that this is different from the use of CGLLockContext and CGLUnlockContext
+// to prevent concurrent access to the _same_ context from two different threads.
+// The latter is expected due to NSOpenGLContext not being thread-safe, while the
+// former is working around bugs in NSOpenGLContext that make it not re-entrant.
+// For more information see https://openradar.appspot.com/37064579
+static QMutex s_contextMutex;
void QCocoaGLContext::update()
{
+ // Updating the context may result in a call to [NSSurface setFrame:], which
+ // will recurse back here through NSViewGlobalFrameDidChangeNotification. We
+ // could use a recursive mutex to prevent a deadlock, but since they are slower
+ // we opt for a manual recursion check.
+ static QAtomicPointer<void> updatingThread = nullptr;
+ if (updatingThread == QThread::currentThreadId())
+ return;
+
+ // Guard against concurrent access to the context in the case where there
+ // is a dedicated render thread operating on the context. See makeCurrent().
+ CGLLockContext(m_context.CGLContextObj);
+
QMutexLocker locker(&s_contextMutex);
+ QScopedValueRollback<QAtomicPointer<void>> rollback(updatingThread, QThread::currentThreadId());
+ qCInfo(lcQpaOpenGLContext) << "Updating" << m_context << "for" << m_context.view;
[m_context update];
+
+ CGLUnlockContext(m_context.CGLContextObj);
}
-NSOpenGLPixelFormat *QCocoaGLContext::createNSOpenGLPixelFormat(const QSurfaceFormat &format)
+void QCocoaGLContext::swapBuffers(QPlatformSurface *surface)
{
- QVector<NSOpenGLPixelFormatAttribute> attrs;
-
- if (format.swapBehavior() == QSurfaceFormat::DoubleBuffer
- || format.swapBehavior() == QSurfaceFormat::DefaultSwapBehavior)
- attrs.append(NSOpenGLPFADoubleBuffer);
- else if (format.swapBehavior() == QSurfaceFormat::TripleBuffer)
- attrs.append(NSOpenGLPFATripleBuffer);
+ qCDebug(lcQpaOpenGLContext) << "Swapping" << m_context
+ << "in" << QThread::currentThread() << "to" << surface;
+ if (surface->surface()->surfaceClass() == QSurface::Offscreen)
+ return; // Nothing to do
- // Select OpenGL profile
- attrs << NSOpenGLPFAOpenGLProfile;
- if (format.profile() == QSurfaceFormat::CoreProfile) {
- if (format.version() >= qMakePair(4, 1))
- attrs << NSOpenGLProfileVersion4_1Core;
- else if (format.version() >= qMakePair(3, 2))
- attrs << NSOpenGLProfileVersion3_2Core;
- else
- attrs << NSOpenGLProfileVersionLegacy;
- } else {
- attrs << NSOpenGLProfileVersionLegacy;
- }
-
- if (format.depthBufferSize() > 0)
- attrs << NSOpenGLPFADepthSize << format.depthBufferSize();
- if (format.stencilBufferSize() > 0)
- attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize();
- if (format.alphaBufferSize() > 0)
- attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize();
- if ((format.redBufferSize() > 0) &&
- (format.greenBufferSize() > 0) &&
- (format.blueBufferSize() > 0)) {
- const int colorSize = format.redBufferSize() +
- format.greenBufferSize() +
- format.blueBufferSize();
- attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy;
+ if (!setDrawable(surface)) {
+ qCWarning(lcQpaOpenGLContext) << "Can't flush" << m_context
+ << "without" << surface << "as drawable";
+ return;
}
- if (format.samples() > 0) {
- attrs << NSOpenGLPFAMultisample
- << NSOpenGLPFASampleBuffers << (NSOpenGLPixelFormatAttribute) 1
- << NSOpenGLPFASamples << (NSOpenGLPixelFormatAttribute) format.samples();
- }
+ QMutexLocker locker(&s_contextMutex);
+ [m_context flushBuffer];
- if (format.stereo())
- attrs << NSOpenGLPFAStereo;
+ // We're done flushing, and should release the lock we have on the
+ // context. To ensure that we're not leaving the context current
+ // without a lock held on it, we need to couple this with actually
+ // clearing the context. This should not be a performance hit for the
+ // case where the same context is made current and then cleared, and
+ // QOpenGLContext::swapBuffers is documented as requiring makeCurrent
+ // again before beginning a new frame, so the user can't expect the
+ // context to be current after a call to swapBuffers(). We explicitly
+ // go via QOpenGLContext for this, instead of calling our platform
+ // method directly, as that will ensure QOpenGLContext records the
+ // fact that there is no longer a current context. We then end up
+ // in QCocoaGLContext::doneCurrent, where we clear the lock.
+ context()->doneCurrent();
+}
- attrs << NSOpenGLPFAAllowOfflineRenderers;
+void QCocoaGLContext::doneCurrent()
+{
+ auto currentContext = QOpenGLContext::currentContext();
+ if (!currentContext)
+ return;
- QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER");
- if (!useLayer.isEmpty() && useLayer.toInt() > 0) {
- // Disable the software rendering fallback. This makes compositing
- // OpenGL and raster NSViews using Core Animation layers possible.
- attrs << NSOpenGLPFANoRecovery;
+ // QOpenGLContext::doneCurrent() clears the current context, but can
+ // be called on any context, not necessarily the current one. Since
+ // we rely on unlocking the context lock we must propagate the call
+ // to the right context.
+ if (context() != currentContext) {
+ currentContext->doneCurrent();
+ return;
}
- attrs << 0;
+ Q_ASSERT([NSOpenGLContext currentContext] == m_context);
- return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()];
+ qCDebug(lcQpaOpenGLContext) << "Clearing current context"
+ << [NSOpenGLContext currentContext] << "in" << QThread::currentThread();
+
+ [NSOpenGLContext clearCurrentContext];
+ CGLUnlockContext(m_context.CGLContextObj);
}
-NSOpenGLContext *QCocoaGLContext::nsOpenGLContext() const
+QSurfaceFormat QCocoaGLContext::format() const
{
- return m_context;
+ return m_format;
}
bool QCocoaGLContext::isValid() const
@@ -485,5 +514,14 @@ bool QCocoaGLContext::isSharing() const
return m_shareContext != nil;
}
-QT_END_NAMESPACE
+NSOpenGLContext *QCocoaGLContext::nativeContext() const
+{
+ return m_context;
+}
+QFunctionPointer QCocoaGLContext::getProcAddress(const char *procName)
+{
+ return (QFunctionPointer)dlsym(RTLD_DEFAULT, procName);
+}
+
+QT_END_NAMESPACE