diff --git a/src/gui/kernel/qopenglcontext.cpp b/src/gui/kernel/qopenglcontext.cpp index 3106b4cd91..6c006ab8b3 100644 --- a/src/gui/kernel/qopenglcontext.cpp +++ b/src/gui/kernel/qopenglcontext.cpp @@ -297,10 +297,18 @@ void QOpenGLContext::setScreen(QScreen *screen) The current configuration includes the format, the share context, and the screen. + If the OpenGL implementation on your system does not support the requested + version of OpenGL context, then QOpenGLContext will try to create the closest + matching version. The actual created context properties can be queried + using the QSurfaceFormat returned by the format() function. For example, if + you request a context that supports OpenGL 4.3 Core profile but the driver + and/or hardware only supports version 3.2 Core profile contexts then you will + get a 3.2 Core profile context. + Returns true if the native context was successfully created and is ready to be used with makeCurrent(), swapBuffers(), etc. - \sa makeCurrent(), destroy() + \sa makeCurrent(), destroy(), format() */ bool QOpenGLContext::create() { diff --git a/src/gui/kernel/qplatformopenglcontext.cpp b/src/gui/kernel/qplatformopenglcontext.cpp index 8e6a9da562..f2d6f6199c 100644 --- a/src/gui/kernel/qplatformopenglcontext.cpp +++ b/src/gui/kernel/qplatformopenglcontext.cpp @@ -118,14 +118,14 @@ void QPlatformOpenGLContext::setContext(QOpenGLContext *context) d->context = context; } -bool QPlatformOpenGLContext::parseOpenGLVersion(const QString& versionString, int &major, int &minor) +bool QPlatformOpenGLContext::parseOpenGLVersion(const QByteArray &versionString, int &major, int &minor) { bool majorOk = false; bool minorOk = false; - QStringList parts = versionString.split(QLatin1Char(' ')); - if (versionString.startsWith(QLatin1String("OpenGL ES"))) { + QList parts = versionString.split(' '); + if (versionString.startsWith(QByteArrayLiteral("OpenGL ES"))) { if (parts.size() >= 3) { - QStringList versionParts = parts.at(2).split(QLatin1Char('.')); + QList versionParts = parts.at(2).split('.'); if (versionParts.size() >= 2) { major = versionParts.at(0).toInt(&majorOk); minor = versionParts.at(1).toInt(&minorOk); @@ -138,7 +138,7 @@ bool QPlatformOpenGLContext::parseOpenGLVersion(const QString& versionString, in } } else { // Not OpenGL ES, but regular OpenGL, the version numbers are first in the string - QStringList versionParts = parts.at(0).split(QLatin1Char('.')); + QList versionParts = parts.at(0).split('.'); if (versionParts.size() >= 2) { major = versionParts.at(0).toInt(&majorOk); minor = versionParts.at(1).toInt(&minorOk); diff --git a/src/gui/kernel/qplatformopenglcontext.h b/src/gui/kernel/qplatformopenglcontext.h index bed83b0913..e64dad64c2 100644 --- a/src/gui/kernel/qplatformopenglcontext.h +++ b/src/gui/kernel/qplatformopenglcontext.h @@ -88,7 +88,7 @@ public: QOpenGLContext *context() const; - static bool parseOpenGLVersion(const QString& versionString, int &major, int &minor); + static bool parseOpenGLVersion(const QByteArray &versionString, int &major, int &minor); private: friend class QOpenGLContext; diff --git a/src/opengl/qgl.cpp b/src/opengl/qgl.cpp index 147a823431..9b456f6a64 100644 --- a/src/opengl/qgl.cpp +++ b/src/opengl/qgl.cpp @@ -3002,6 +3002,14 @@ bool QGLContext::areSharing(const QGLContext *context1, const QGLContext *contex specified in the constructor; otherwise returns false (i.e. the context is invalid). + If the OpenGL implementation on your system does not support the requested + version of OpenGL context, then QGLContext will try to create the closest + matching version. The actual created context properties can be queried + using the QGLFormat returned by the format() function. For example, if + you request a context that supports OpenGL 4.3 Core profile but the driver + and/or hardware only supports version 3.2 Core profile contexts then you will + get a 3.2 Core profile context. + After successful creation, format() returns the set of features of the created GL rendering context. diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp index 45e344416d..1c9a7d36f5 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp @@ -562,12 +562,21 @@ static HGLRC createContext(const QOpenGLStaticContext &staticContext, int attributes[attribSize]; int attribIndex = 0; qFill(attributes, attributes + attribSize, int(0)); - const int requestedVersion = (format.majorVersion() << 8) + format.minorVersion(); + + // We limit the requested version by the version of the static context as + // wglCreateContextAttribsARB fails and returns NULL if the requested context + // version is not supported. This means that we will get the closest supported + // context format that that which was requested and is supported by the driver + const int requestedVersion = qMin((format.majorVersion() << 8) + format.minorVersion(), + staticContext.defaultFormat.version); + const int majorVersion = requestedVersion >> 8; + const int minorVersion = requestedVersion & 0xFF; + if (requestedVersion > 0x0101) { attributes[attribIndex++] = WGL_CONTEXT_MAJOR_VERSION_ARB; - attributes[attribIndex++] = format.majorVersion(); + attributes[attribIndex++] = majorVersion; attributes[attribIndex++] = WGL_CONTEXT_MINOR_VERSION_ARB; - attributes[attribIndex++] = format.minorVersion(); + attributes[attribIndex++] = minorVersion; } if (requestedVersion >= 0x0300) { attributes[attribIndex++] = WGL_CONTEXT_FLAGS_ARB; @@ -594,7 +603,7 @@ static HGLRC createContext(const QOpenGLStaticContext &staticContext, } if (QWindowsContext::verboseGL) qDebug("%s: Creating context version %d.%d with %d attributes", - __FUNCTION__, format.majorVersion(), format.minorVersion(), attribIndex / 2); + __FUNCTION__, majorVersion, minorVersion, attribIndex / 2); const HGLRC result = staticContext.wglCreateContextAttribsARB(hdc, shared, attributes); diff --git a/src/plugins/platforms/xcb/qglxintegration.cpp b/src/plugins/platforms/xcb/qglxintegration.cpp index 089bca8a63..8c300d6c19 100644 --- a/src/plugins/platforms/xcb/qglxintegration.cpp +++ b/src/plugins/platforms/xcb/qglxintegration.cpp @@ -74,7 +74,197 @@ typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXC #define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 #endif -QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlatformOpenGLContext *share) +static Window createDummyWindow(QXcbScreen *screen, XVisualInfo *visualInfo) +{ + Colormap cmap = XCreateColormap(DISPLAY_FROM_XCB(screen), screen->root(), visualInfo->visual, AllocNone); + XSetWindowAttributes a; + a.background_pixel = WhitePixel(DISPLAY_FROM_XCB(screen), screen->screenNumber()); + a.border_pixel = BlackPixel(DISPLAY_FROM_XCB(screen), screen->screenNumber()); + a.colormap = cmap; + + Window window = XCreateWindow(DISPLAY_FROM_XCB(screen), screen->root(), + 0, 0, 100, 100, + 0, visualInfo->depth, InputOutput, visualInfo->visual, + CWBackPixel|CWBorderPixel|CWColormap, &a); + return window; +} + +static Window createDummyWindow(QXcbScreen *screen, GLXFBConfig config) +{ + XVisualInfo *visualInfo = glXGetVisualFromFBConfig(DISPLAY_FROM_XCB(screen), config); + if (!visualInfo) + qFatal("Could not initialize GLX"); + Window window = createDummyWindow(screen, visualInfo); + XFree(visualInfo); + return window; +} + +// Per-window data for active OpenGL contexts. +struct QOpenGLContextData +{ + QOpenGLContextData(Display *display, Window window, GLXContext context) + : m_display(display), + m_window(window), + m_context(context) + {} + + QOpenGLContextData() + : m_display(0), + m_window(0), + m_context(0) + {} + + Display *m_display; + Window m_window; + GLXContext m_context; +}; + +static inline QOpenGLContextData currentOpenGLContextData() +{ + QOpenGLContextData result; + result.m_display = glXGetCurrentDisplay(); + result.m_window = glXGetCurrentDrawable(); + result.m_context = glXGetCurrentContext(); + return result; +} + +static inline QOpenGLContextData createDummyWindowOpenGLContextData(QXcbScreen *screen) +{ + QOpenGLContextData result; + result.m_display = DISPLAY_FROM_XCB(screen); + + QSurfaceFormat format; + GLXFBConfig config = qglx_findConfig(DISPLAY_FROM_XCB(screen), screen->screenNumber(), format); + if (config) { + result.m_context = glXCreateNewContext(DISPLAY_FROM_XCB(screen), config, GLX_RGBA_TYPE, 0, true); + result.m_window = createDummyWindow(screen, config); + } else { + XVisualInfo *visualInfo = qglx_findVisualInfo(DISPLAY_FROM_XCB(screen), screen->screenNumber(), &format); + if (!visualInfo) + qFatal("Could not initialize GLX"); + result.m_context = glXCreateContext(DISPLAY_FROM_XCB(screen), visualInfo, 0, true); + result.m_window = createDummyWindow(screen, visualInfo); + XFree(visualInfo); + } + + return result; +} + +static inline QByteArray getGlString(GLenum param) +{ + if (const GLubyte *s = glGetString(param)) + return QByteArray(reinterpret_cast(s)); + return QByteArray(); +} + +static void updateFormatFromContext(QSurfaceFormat &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); + } + + const int version = (major << 8) + minor; + if (version < 0x0300) { + format.setProfile(QSurfaceFormat::NoProfile); + format.setOption(QSurfaceFormat::DeprecatedFunctions); + return; + } + + // Version 3.0 onwards - check if it includes deprecated functionality or is + // a debug context + GLint value = 0; + glGetIntegerv(GL_CONTEXT_FLAGS, &value); + if (value & ~GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) + format.setOption(QSurfaceFormat::DeprecatedFunctions); + if (value & GLX_CONTEXT_DEBUG_BIT_ARB) + format.setOption(QSurfaceFormat::DebugContext); + if (version < 0x0302) + return; + + // Version 3.2 and newer have a profile + value = 0; + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value); + switch (value) { + case GLX_CONTEXT_CORE_PROFILE_BIT_ARB: + format.setProfile(QSurfaceFormat::CoreProfile); + break; + case GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: + format.setProfile(QSurfaceFormat::CompatibilityProfile); + break; + default: + format.setProfile(QSurfaceFormat::NoProfile); + break; + } +} + +/*! + \class QOpenGLTemporaryContext + \brief A temporary context that can be instantiated on the stack. + + Functions like glGetString() only work if there is a current GL context. + + \internal + \ingroup qt-lighthouse-xcb +*/ +class QOpenGLTemporaryContext +{ + Q_DISABLE_COPY(QOpenGLTemporaryContext) +public: + QOpenGLTemporaryContext(QXcbScreen *screen); + ~QOpenGLTemporaryContext(); + +private: + const QOpenGLContextData m_previous; + const QOpenGLContextData m_current; +}; + +QOpenGLTemporaryContext::QOpenGLTemporaryContext(QXcbScreen *screen) + : m_previous(currentOpenGLContextData()), + m_current(createDummyWindowOpenGLContextData(screen)) +{ + // Make our temporary context current on our temporary window + glXMakeCurrent(m_current.m_display, m_current.m_window, m_current.m_context); +} + +QOpenGLTemporaryContext::~QOpenGLTemporaryContext() +{ + // Restore the previous context if possible, otherwise just release our temporary context + if (m_previous.m_display) + glXMakeCurrent(m_previous.m_display, m_previous.m_window, m_previous.m_context); + else + glXMakeCurrent(m_current.m_display, 0, 0); + + // Destroy our temporary window + XDestroyWindow(m_current.m_display, m_current.m_window); + + // Finally destroy our temporary context itself + glXDestroyContext(m_current.m_display, m_current.m_context); +} + +QOpenGLDefaultContextInfo::QOpenGLDefaultContextInfo() + : vendor(getGlString(GL_VENDOR)), + renderer(getGlString(GL_RENDERER)) +{ + updateFormatFromContext(format); +} + +QOpenGLDefaultContextInfo *QOpenGLDefaultContextInfo::create(QXcbScreen *screen) +{ + // We need a current context for getGLString() to work. To have + // the QOpenGLDefaultContextInfo contain the latest supported + // context version, we rely upon the QOpenGLTemporaryContext to + // correctly obtain a context with the latest version + QScopedPointer temporaryContext(new QOpenGLTemporaryContext(screen)); + QOpenGLDefaultContextInfo *result = new QOpenGLDefaultContextInfo; + return result; +} + + +QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlatformOpenGLContext *share, QOpenGLDefaultContextInfo *defaultContextInfo) : QPlatformOpenGLContext() , m_screen(screen) , m_context(0) @@ -95,10 +285,20 @@ QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlat // Use glXCreateContextAttribsARB if is available if (glXCreateContextAttribsARB != 0) { + // We limit the requested version by the version of the static context as + // glXCreateContextAttribsARB fails and returns NULL if the requested context + // version is not supported. This means that we will get the closest supported + // context format that that which was requested and is supported by the driver + const int maxSupportedVersion = (defaultContextInfo->format.majorVersion() << 8) + + defaultContextInfo->format.minorVersion(); + const int requestedVersion = qMin((format.majorVersion() << 8) + format.minorVersion(), + maxSupportedVersion); + const int majorVersion = requestedVersion >> 8; + const int minorVersion = requestedVersion & 0xFF; QVector contextAttributes; - contextAttributes << GLX_CONTEXT_MAJOR_VERSION_ARB << m_format.majorVersion() - << GLX_CONTEXT_MINOR_VERSION_ARB << m_format.minorVersion(); + contextAttributes << GLX_CONTEXT_MAJOR_VERSION_ARB << majorVersion + << GLX_CONTEXT_MINOR_VERSION_ARB << minorVersion; // If asking for OpenGL 3.2 or newer we should also specify a profile if (m_format.majorVersion() > 3 || (m_format.majorVersion() == 3 && m_format.minorVersion() > 1)) { @@ -136,10 +336,8 @@ QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlat if (m_context) m_format = qglx_surfaceFormatFromGLXFBConfig(DISPLAY_FROM_XCB(screen), config, m_context); - // Used for creating the temporary window - visualInfo = glXGetVisualFromFBConfig(DISPLAY_FROM_XCB(screen), config); - if (!visualInfo) - qFatal("Could not initialize GLX"); + // Create a temporary window so that we can make the new context current + window = createDummyWindow(screen, config); } else { // Note that m_format gets updated with the used surface format visualInfo = qglx_findVisualInfo(DISPLAY_FROM_XCB(screen), screen->screenNumber(), &m_format); @@ -151,56 +349,16 @@ QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlat m_shareContext = 0; m_context = glXCreateContext(DISPLAY_FROM_XCB(screen), visualInfo, 0, true); } + + // Create a temporary window so that we can make the new context current + window = createDummyWindow(screen, visualInfo); + XFree(visualInfo); } - // Create a temporary window so that we can make the new context current - Colormap cmap = XCreateColormap(DISPLAY_FROM_XCB(screen), screen->root(), visualInfo->visual, AllocNone); - XSetWindowAttributes a; - a.background_pixel = WhitePixel(DISPLAY_FROM_XCB(screen), screen->screenNumber()); - a.border_pixel = BlackPixel(DISPLAY_FROM_XCB(screen), screen->screenNumber()); - a.colormap = cmap; - - window = XCreateWindow(DISPLAY_FROM_XCB(screen), screen->root(), - 0, 0, 100, 100, - 0, visualInfo->depth, InputOutput, visualInfo->visual, - CWBackPixel|CWBorderPixel|CWColormap, &a); - - XFree(visualInfo); - // Query the OpenGL version and profile if (m_context && window) { glXMakeCurrent(DISPLAY_FROM_XCB(screen), window, m_context); - - int major = 0, minor = 0; - QString versionString(QLatin1String(reinterpret_cast(glGetString(GL_VERSION)))); - if (parseOpenGLVersion(versionString, major, minor)) { - m_format.setMajorVersion(major); - m_format.setMinorVersion(minor); - } - - // If we have OpenGL 3.2 or newer we also need to query the profile in use - if (m_format.majorVersion() > 3 || (m_format.majorVersion() == 3 && m_format.minorVersion() > 1)) { - // nVidia drivers have a bug where querying GL_CONTEXT_PROFILE_MASK always returns 0. - // In this case let's assume that we got the profile that we asked for since nvidia implements - // both Core and Compatibility profiles - if (versionString.contains(QStringLiteral("NVIDIA"))) { - m_format.setProfile(format.profile()); - } else { - GLint profileMask; - glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profileMask); - switch (profileMask) { - case GLX_CONTEXT_CORE_PROFILE_BIT_ARB: - m_format.setProfile(QSurfaceFormat::CoreProfile); - break; - - case GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: - default: - m_format.setProfile(QSurfaceFormat::CompatibilityProfile); - } - } - } else { - m_format.setProfile(QSurfaceFormat::NoProfile); - } + updateFormatFromContext(m_format); // Make our context non-current glXMakeCurrent(DISPLAY_FROM_XCB(screen), 0, 0); diff --git a/src/plugins/platforms/xcb/qglxintegration.h b/src/plugins/platforms/xcb/qglxintegration.h index f8afb2db77..b8285c965a 100644 --- a/src/plugins/platforms/xcb/qglxintegration.h +++ b/src/plugins/platforms/xcb/qglxintegration.h @@ -54,10 +54,23 @@ QT_BEGIN_NAMESPACE +class QOpenGLDefaultContextInfo +{ + Q_DISABLE_COPY(QOpenGLDefaultContextInfo) + QOpenGLDefaultContextInfo(); +public: + static QOpenGLDefaultContextInfo *create(QXcbScreen *screen); + + const QByteArray vendor; + const QByteArray renderer; + QSurfaceFormat format; +}; + + class QGLXContext : public QPlatformOpenGLContext { public: - QGLXContext(QXcbScreen *xd, const QSurfaceFormat &format, QPlatformOpenGLContext *share); + QGLXContext(QXcbScreen *xd, const QSurfaceFormat &format, QPlatformOpenGLContext *share, QOpenGLDefaultContextInfo *defaultContextInfo); ~QGLXContext(); bool makeCurrent(QPlatformSurface *surface); diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index 76534cf5c4..401739f7d5 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -224,11 +224,13 @@ void QXcbConnection::updateScreens() // Now activeScreens is the complete set of screens which are active at this time. // Delete any existing screens which are not in activeScreens - for (int i = m_screens.count() - 1; i >= 0; --i) + for (int i = m_screens.count() - 1; i >= 0; --i) { if (!activeScreens.contains(m_screens[i])) { + ((QXcbIntegration*)QGuiApplicationPrivate::platformIntegration())->removeDefaultOpenGLContextInfo(m_screens[i]); delete m_screens[i]; m_screens.removeAt(i); } + } // Add any new screens, and make sure the primary screen comes first // since it is used by QGuiApplication::primaryScreen() diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index 8784046560..029a6de17f 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -90,8 +90,8 @@ QT_BEGIN_NAMESPACE QXcbIntegration::QXcbIntegration(const QStringList ¶meters) - : m_eventDispatcher(createUnixEventDispatcher()), - m_services(new QGenericUnixServices) + : m_eventDispatcher(createUnixEventDispatcher()) + , m_services(new QGenericUnixServices) { QGuiApplicationPrivate::instance()->setEventDispatcher(m_eventDispatcher); @@ -119,6 +119,7 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters) QXcbIntegration::~QXcbIntegration() { + qDeleteAll(m_defaultContextInfos); qDeleteAll(m_connections); } @@ -176,7 +177,14 @@ QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLCont { QXcbScreen *screen = static_cast(context->screen()->handle()); #if defined(XCB_USE_GLX) - return new QGLXContext(screen, context->format(), context->shareHandle()); + QOpenGLDefaultContextInfo *defaultContextInfo; + if (m_defaultContextInfos.contains(screen)) { + defaultContextInfo = m_defaultContextInfos.value(screen); + } else { + defaultContextInfo = QOpenGLDefaultContextInfo::create(screen); + m_defaultContextInfos.insert(screen, defaultContextInfo); + } + return new QGLXContext(screen, context->format(), context->shareHandle(), defaultContextInfo); #elif defined(XCB_USE_EGL) return new QEGLXcbPlatformContext(context->format(), context->shareHandle(), screen->connection()->egl_display(), screen->connection()); @@ -274,4 +282,17 @@ QPlatformTheme *QXcbIntegration::createPlatformTheme(const QString &name) const return QGenericUnixTheme::createUnixTheme(name); } +/*! + Called by QXcbConnection prior to a QQnxScreen being deleted. + + Destroys and cleans up any default OpenGL context info for this screen. +*/ +void QXcbIntegration::removeDefaultOpenGLContextInfo(QXcbScreen *screen) +{ + if (!m_defaultContextInfos.contains(screen)) + return; + QOpenGLDefaultContextInfo* info = m_defaultContextInfos.take(screen); + delete info; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h index 7f7c52372c..669a0d00f6 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.h +++ b/src/plugins/platforms/xcb/qxcbintegration.h @@ -51,6 +51,11 @@ class QXcbConnection; class QAbstractEventDispatcher; class QXcbNativeInterface; +#if !defined(QT_NO_OPENGL) && defined(XCB_USE_GLX) +class QOpenGLDefaultContextInfo; +class QXcbScreen; +#endif + class QXcbIntegration : public QPlatformIntegration { public: @@ -92,6 +97,10 @@ public: QStringList themeNames() const; QPlatformTheme *createPlatformTheme(const QString &name) const; +#if !defined(QT_NO_OPENGL) && defined(XCB_USE_GLX) + void removeDefaultOpenGLContextInfo(QXcbScreen *screen); +#endif + private: QList m_connections; @@ -101,6 +110,10 @@ private: QScopedPointer m_inputContext; QAbstractEventDispatcher *m_eventDispatcher; +#if !defined(QT_NO_OPENGL) && defined(XCB_USE_GLX) + mutable QHash m_defaultContextInfos; +#endif + #ifndef QT_NO_ACCESSIBILITY QScopedPointer m_accessibility; #endif