diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoaglcontext.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaglcontext.mm | 585 |
1 files changed, 282 insertions, 303 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 7ffe0003d3..069429796e 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -47,8 +47,6 @@ #import <AppKit/AppKit.h> -QT_BEGIN_NAMESPACE - static inline QByteArray getGlString(GLenum param) { if (const GLubyte *s = glGetString(param)) @@ -56,109 +54,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 + +QT_BEGIN_NAMESPACE - // NSOpenGLContext is not re-entrant (https://openradar.appspot.com/37064579) -static QMutex s_contextMutex; +Q_LOGGING_CATEGORY(lcQpaOpenGLContext, "qt.qpa.openglcontext", QtWarningMsg); -QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share, - const QVariant &nativeHandle) - : m_context(nil), - m_shareContext(nil), - m_format(format), - m_didCheckForSoftwareContext(false) +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,312 +122,327 @@ 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()); + } -void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) -{ - if (surface->surface()->surfaceClass() == QSurface::Offscreen) - return; // Nothing to do + // Allow rendering on GPUs without a connected display + attrs << NSOpenGLPFAAllowOfflineRenderers; - QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); - setActiveWindow(window); + // 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; + } - QMutexLocker locker(&s_contextMutex); - [m_context flushBuffer]; + attrs << 0; // 0-terminate array + return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()]; } -bool QCocoaGLContext::makeCurrent(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() { - Q_ASSERT(surface->surface()->supportsOpenGL()); - - QMacAutoReleasePool pool; + NSOpenGLContext *oldContext = [NSOpenGLContext currentContext]; [m_context makeCurrentContext]; - if (surface->surface()->surfaceClass() == QSurface::Offscreen) - return true; + // --------------------- Query GL state --------------------- - QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); - setActiveWindow(window); - - // 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 - // when a high-resolution buffer is requested. This is not detectable using the NSWindow - // 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) { - m_didCheckForSoftwareContext = true; - - const GLubyte* renderer = glGetString(GL_RENDERER); - if (qstrcmp((const char *)renderer, "Apple Software Renderer") == 0) { - NSView *view = static_cast<QCocoaWindow *>(surface)->m_view; - [view setWantsBestResolutionOpenGLSurface:NO]; - } + int major = 0, minor = 0; + QByteArray versionString(getGlString(GL_VERSION)); + if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) { + m_format.setMajorVersion(major); + m_format.setMinorVersion(minor); } - update(); - return true; -} + 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); + } -void QCocoaGLContext::setActiveWindow(QWindow *window) -{ - if (window == m_currentWindow.data()) - return; + 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); + } + }()); - if (m_currentWindow && m_currentWindow.data()->handle()) - static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + // Debug contexts not supported on macOS + m_format.setOption(QSurfaceFormat::DebugContext, false); - Q_ASSERT(window->handle()); + // Nor are stereo buffers (deprecated in macOS 10.12) + m_format.setOption(QSurfaceFormat::StereoBuffers, false); - m_currentWindow = window; + // ------------------ Query the pixel format ------------------ - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - cocoaWindow->setCurrentContext(this); + NSOpenGLPixelFormat *pixelFormat = m_context.pixelFormat; - Q_ASSERT(!cocoaWindow->isForeignWindow()); - [qnsview_cast(cocoaWindow->view()) setQCocoaGLContext:this]; -} + 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); -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); - } + // 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]); - // 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.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 (doubleBuffered == 1) + else if ([pixelFormat qt_getAttribute:NSOpenGLPFADoubleBuffer]) 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); + // ------------------- Query the context ------------------- - [pixelFormat release]; + m_format.setSwapInterval([m_context qt_getParameter:NSOpenGLCPSwapInterval]); - GLint swapInterval = -1; - [m_context getValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; - if (swapInterval >= 0) - m_format.setSwapInterval(swapInterval); + if (oldContext) + [oldContext makeCurrentContext]; + else + [NSOpenGLContext clearCurrentContext]; +} - // Restore the original context - CGLSetCurrentContext(oldContext); +QCocoaGLContext::~QCocoaGLContext() +{ + [m_context release]; } -void QCocoaGLContext::doneCurrent() +bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) { - if (m_currentWindow && m_currentWindow.data()->handle()) - static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + qCDebug(lcQpaOpenGLContext) << "Making" << m_context << "current" + << "in" << QThread::currentThread() << "for" << surface; + + Q_ASSERT(surface->surface()->supportsOpenGL()); - m_currentWindow.clear(); + if (!setDrawable(surface)) + return false; - [NSOpenGLContext clearCurrentContext]; + [m_context makeCurrentContext]; + + if (surface->surface()->surfaceClass() == QSurface::Window) { + // 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 + // when a high-resolution buffer is requested. This is not detectable using the NSWindow + // 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); + if (qstrcmp((const char *)renderer, "Apple Software Renderer") == 0) { + NSView *view = static_cast<QCocoaWindow *>(surface)->m_view; + [view setWantsBestResolutionOpenGLSurface:NO]; + } + } + + update(); + } + + return true; } -QFunctionPointer QCocoaGLContext::getProcAddress(const char *procName) +/*! + 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) { - return (QFunctionPointer)dlsym(RTLD_DEFAULT, procName); + // Make sure any surfaces released during this process are deallocated + // straight away, otherwise we may run out of surfaces when spinning a + // render-loop that doesn't return to one of the outer pools. + QMacAutoReleasePool pool; + + if (!surface || surface->surface()->surfaceClass() == QSurface::Offscreen) { + // Clear the current drawable and reset the active window, so that GL + // commands that don't target a specific FBO will not end up stomping + // on the previously set drawable. + qCDebug(lcQpaOpenGLContext) << "Clearing current drawable" << m_context.view << "for" << m_context; + [m_context clearDrawable]; + return true; + } + + Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window); + QNSView *view = qnsview_cast(static_cast<QCocoaWindow *>(surface)->view()); + + if (view == m_context.view) + return true; + + if ((m_context.view = view) != view) { + qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context; + return false; + } + + qCInfo(lcQpaOpenGLContext) << "Set drawable for" << m_context << "to" << m_context.view; + + return true; } +// NSOpenGLContext is not re-entrant. Even when using separate contexts per thread, +// view, and window, calls into the API will still deadlock. For more information +// see https://openradar.appspot.com/37064579 +static QMutex s_reentrancyMutex; + void QCocoaGLContext::update() { - QMutexLocker locker(&s_contextMutex); + // Make sure any surfaces released during this process are deallocated + // straight away, otherwise we may run out of surfaces when spinning a + // render-loop that doesn't return to one of the outer pools. + QMacAutoReleasePool pool; + + QMutexLocker locker(&s_reentrancyMutex); + qCInfo(lcQpaOpenGLContext) << "Updating" << m_context << "for" << m_context.view; [m_context update]; } -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); - - - // 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; - } + qCDebug(lcQpaOpenGLContext) << "Swapping" << m_context + << "in" << QThread::currentThread() << "to" << surface; - 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 (surface->surface()->surfaceClass() == QSurface::Offscreen) + return; // Nothing to do - if (format.samples() > 0) { - attrs << NSOpenGLPFAMultisample - << NSOpenGLPFASampleBuffers << (NSOpenGLPixelFormatAttribute) 1 - << NSOpenGLPFASamples << (NSOpenGLPixelFormatAttribute) format.samples(); + if (!setDrawable(surface)) { + qCWarning(lcQpaOpenGLContext) << "Can't flush" << m_context + << "without" << surface << "as drawable"; + return; } - if (format.stereo()) - attrs << NSOpenGLPFAStereo; - - attrs << NSOpenGLPFAAllowOfflineRenderers; + QMutexLocker locker(&s_reentrancyMutex); + [m_context flushBuffer]; +} - 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; - } +void QCocoaGLContext::doneCurrent() +{ + qCDebug(lcQpaOpenGLContext) << "Clearing current context" + << [NSOpenGLContext currentContext] << "in" << QThread::currentThread(); - attrs << 0; + // Note: We do not need to clear the current drawable here. + // As long as there is no current context, GL calls will + // do nothing. - return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()]; + [NSOpenGLContext clearCurrentContext]; } -NSOpenGLContext *QCocoaGLContext::nsOpenGLContext() const +QSurfaceFormat QCocoaGLContext::format() const { - return m_context; + return m_format; } bool QCocoaGLContext::isValid() const @@ -485,5 +455,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 |