OpenGL: Don't request a context version higher than is supported

The function wglCreateContextAttribsARB will fail if we request a
context version higher than is supported. We therefore upper-bound the
requested version by the version of the static context. This results
in context creation succeeding and having the closest possible match
to the requested format.

The xcb qpa plugin is modified to operate similarly to the windows
plugin in that it now creates a "static" context which is used to
limit the versions of contexts requested by the user.

Change-Id: I277ad7cc82edfdf7b9d8502ad921c8175feb1a4a
Reviewed-by: Samuel Rødal <samuel.rodal@digia.com>
This commit is contained in:
Sean Harmer 2012-09-27 15:24:34 +01:00 committed by The Qt Project
parent 84100c9085
commit 394249616c
10 changed files with 300 additions and 68 deletions

View File

@ -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()
{

View File

@ -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<QByteArray> parts = versionString.split(' ');
if (versionString.startsWith(QByteArrayLiteral("OpenGL ES"))) {
if (parts.size() >= 3) {
QStringList versionParts = parts.at(2).split(QLatin1Char('.'));
QList<QByteArray> 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<QByteArray> versionParts = parts.at(0).split('.');
if (versionParts.size() >= 2) {
major = versionParts.at(0).toInt(&majorOk);
minor = versionParts.at(1).toInt(&minorOk);

View File

@ -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;

View File

@ -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.

View File

@ -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);

View File

@ -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<const char*>(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<QOpenGLTemporaryContext> 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<int> 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
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);
window = createDummyWindow(screen, visualInfo);
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<const char*>(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);

View File

@ -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);

View File

@ -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()

View File

@ -90,8 +90,8 @@
QT_BEGIN_NAMESPACE
QXcbIntegration::QXcbIntegration(const QStringList &parameters)
: 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 &parameters)
QXcbIntegration::~QXcbIntegration()
{
qDeleteAll(m_defaultContextInfos);
qDeleteAll(m_connections);
}
@ -176,7 +177,14 @@ QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLCont
{
QXcbScreen *screen = static_cast<QXcbScreen *>(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

View File

@ -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<QXcbConnection *> m_connections;
@ -101,6 +110,10 @@ private:
QScopedPointer<QPlatformInputContext> m_inputContext;
QAbstractEventDispatcher *m_eventDispatcher;
#if !defined(QT_NO_OPENGL) && defined(XCB_USE_GLX)
mutable QHash<QXcbScreen *, QOpenGLDefaultContextInfo *> m_defaultContextInfos;
#endif
#ifndef QT_NO_ACCESSIBILITY
QScopedPointer<QPlatformAccessibility> m_accessibility;
#endif