diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoaglcontext.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaglcontext.mm | 620 |
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 |