Add support for GPU bug list reading.

- Add support for reading out feature lists from GPU bug lists
  in Chromium format (supporting OS + version, exceptions lists,
  device ids and vendor ids).
  Add a overloads allowing for passing file name,
  data or JSON documents.

- Test reading in tst_qopenglconfig

- Prototypically have the Windows plugin read the file
  from the environment variable "QT_OPENGL_BUGLIST" and
  turn off renderers depending on the standard keyword
  "disable_d3d11" and newly introduced keywords
  "disable_d3d9", "disable_desktopgl".

- QT_OPENGL_BUGLIST can point to an absolute or relative
  path, in which case it is resolved via QLibraryInfo::SettingsPath
  and QStandardPaths::ConfigLocation.

[ChangeLog][QtGui][Windows]
Introduce experimental feature allowing the user to specify
a GPU driver buglist with some additional keywords to chooose
the GL renderer backend depending on GPU.

Task-number: QTBUG-43263
Change-Id: I8c20b0e8ee3cf575b91d0672bf405c42d3e9fb64
Reviewed-by: Laszlo Agocs <laszlo.agocs@theqtcompany.com>
bb10
Friedemann Kleint 2015-02-05 15:45:53 +01:00
parent af495c2042
commit 4dd7aeee10
6 changed files with 599 additions and 0 deletions

View File

@ -37,6 +37,15 @@
#include "qopenglcontext.h"
#include "qopenglfunctions.h"
#include <QtCore/QDebug>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonValue>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QTextStream>
#include <QtCore/QFile>
#include <QtCore/QDir>
QT_BEGIN_NAMESPACE
#if defined(QT_OPENGL_3)
@ -79,4 +88,364 @@ QOpenGLExtensionMatcher::QOpenGLExtensionMatcher()
}
}
/* Helpers to read out the list of features matching a device from
* a Chromium driver bug list of the format using a subset of keys
* (namely, matching by gl_vendor RegExp is not implemented):
{
"entries": [
{
"id": 20,
"description": "Disable EXT_draw_buffers on GeForce GT 650M on Linux due to driver bugs",
"os": {
"type": "linux"
},
// Optional: "exceptions" list
"vendor_id": "0x10de",
"device_id": ["0x0fd5"],
"multi_gpu_category": "any",
"features": [
"disable_ext_draw_buffers"
]
},
....
}
*/
QDebug operator<<(QDebug d, const QOpenGLConfig::Gpu &g)
{
QDebugStateSaver s(d);
d.nospace();
d << "Gpu(";
if (g.isValid()) {
d << "vendor=" << hex << showbase <<g.vendorId << ", device=" << g.deviceId
<< "version=" << g.driverVersion;
} else {
d << 0;
}
d << ')';
return d;
}
enum Operator { NotEqual, LessThan, LessEqualThan, Equals, GreaterThan, GreaterEqualThan };
static const char *operators[] = {"!=", "<", "<=", "=", ">", ">="};
static inline QString valueKey() { return QStringLiteral("value"); }
static inline QString opKey() { return QStringLiteral("op"); }
static inline QString versionKey() { return QStringLiteral("version"); }
static inline QString typeKey() { return QStringLiteral("type"); }
static inline QString osKey() { return QStringLiteral("os"); }
static inline QString vendorIdKey() { return QStringLiteral("vendor_id"); }
static inline QString glVendorKey() { return QStringLiteral("gl_vendor"); }
static inline QString deviceIdKey() { return QStringLiteral("device_id"); }
static inline QString driverVersionKey() { return QStringLiteral("driver_version"); }
static inline QString featuresKey() { return QStringLiteral("features"); }
static inline QString idKey() { return QStringLiteral("id"); }
static inline QString descriptionKey() { return QStringLiteral("description"); }
static inline QString exceptionsKey() { return QStringLiteral("exceptions"); }
namespace {
// VersionTerm describing a version term consisting of number and operator
// found in "os", "driver_version", "gl_version".
struct VersionTerm {
VersionTerm() : op(NotEqual) {}
static VersionTerm fromJson(const QJsonValue &v);
bool isNull() const { return number.isNull(); }
bool matches(const QVersionNumber &other) const;
QVersionNumber number;
Operator op;
};
bool VersionTerm::matches(const QVersionNumber &other) const
{
if (isNull() || other.isNull()) {
qWarning() << Q_FUNC_INFO << "called with invalid parameters";
return false;
}
switch (op) {
case NotEqual:
return other != number;
case LessThan:
return other < number;
case LessEqualThan:
return other <= number;
case Equals:
return other == number;
case GreaterThan:
return other > number;
case GreaterEqualThan:
return other >= number;
}
return false;
}
VersionTerm VersionTerm::fromJson(const QJsonValue &v)
{
VersionTerm result;
if (!v.isObject())
return result;
const QJsonObject o = v.toObject();
result.number = QVersionNumber::fromString(o.value(valueKey()).toString());
const QString opS = o.value(opKey()).toString();
for (size_t i = 0; i < sizeof(operators) / sizeof(operators[0]); ++i) {
if (opS == QLatin1String(operators[i])) {
result.op = static_cast<Operator>(i);
break;
}
}
return result;
}
// OS term consisting of name and optional version found in
// under "os" in main array and in "exceptions" lists.
struct OsTypeTerm
{
static OsTypeTerm fromJson(const QJsonValue &v);
static QString hostOs();
static QVersionNumber hostKernelVersion() { return QVersionNumber::fromString(QSysInfo::kernelVersion()); }
bool isNull() const { return type.isEmpty(); }
bool matches(const QString &osName, const QVersionNumber &kernelVersion) const
{
if (isNull() || osName.isEmpty() || kernelVersion.isNull()) {
qWarning() << Q_FUNC_INFO << "called with invalid parameters";
return false;
}
if (type != osName)
return false;
return versionTerm.isNull() || versionTerm.matches(kernelVersion);
}
QString type;
VersionTerm versionTerm;
};
OsTypeTerm OsTypeTerm::fromJson(const QJsonValue &v)
{
OsTypeTerm result;
if (!v.isObject())
return result;
const QJsonObject o = v.toObject();
result.type = o.value(typeKey()).toString();
result.versionTerm = VersionTerm::fromJson(o.value(versionKey()));
return result;
}
QString OsTypeTerm::hostOs()
{
// Determine Host OS.
#if defined(Q_OS_WIN)
return QStringLiteral("win");
#elif defined(Q_OS_LINUX)
return QStringLiteral("linux");
#elif defined(Q_OS_OSX)
return QStringLiteral("macosx");
#elif defined(Q_OS_ANDROID)
return QStringLiteral("android");
#else
return QString();
#endif
}
} // anonymous namespace
typedef QJsonArray::ConstIterator JsonArrayConstIt;
static inline bool contains(const QJsonArray &a, unsigned needle)
{
for (JsonArrayConstIt it = a.constBegin(), cend = a.constEnd(); it != cend; ++it) {
if (needle == it->toString().toUInt(Q_NULLPTR, /* base */ 0))
return true;
}
return false;
}
static QString msgSyntaxWarning(const QJsonObject &object, const QString &what)
{
QString result;
QTextStream(&result) << "Id " << object.value(idKey()).toInt()
<< " (\"" << object.value(descriptionKey()).toString()
<< "\"): " << what;
return result;
}
// Check whether an entry matches. Called recursively for
// "exceptions" list.
static bool matches(const QJsonObject &object,
const QString &osName,
const QVersionNumber &kernelVersion,
const QOpenGLConfig::Gpu &gpu)
{
const OsTypeTerm os = OsTypeTerm::fromJson(object.value(osKey()));
if (!os.isNull() && !os.matches(osName, kernelVersion))
return false;
const QJsonValue exceptionsV = object.value(exceptionsKey());
if (exceptionsV.isArray()) {
const QJsonArray exceptionsA = exceptionsV.toArray();
for (JsonArrayConstIt it = exceptionsA.constBegin(), cend = exceptionsA.constEnd(); it != cend; ++it) {
if (matches(it->toObject(), osName, kernelVersion, gpu))
return false;
}
}
const QJsonValue vendorV = object.value(vendorIdKey());
if (vendorV.isString()) {
if (gpu.vendorId != vendorV.toString().toUInt(Q_NULLPTR, /* base */ 0))
return false;
} else {
if (object.contains(glVendorKey())) {
qWarning().nospace() << "Id " << object.value(idKey()).toInt()
<< ": Matching by " << glVendorKey() << " is not implemented.";
return false;
}
}
if (gpu.deviceId) {
const QJsonValue deviceIdV = object.value(deviceIdKey());
switch (deviceIdV.type()) {
case QJsonValue::Array:
if (!contains(deviceIdV.toArray(), gpu.deviceId))
return false;
break;
case QJsonValue::Undefined:
case QJsonValue::Null:
break;
default:
qWarning().noquote()
<< msgSyntaxWarning(object,
QLatin1String("Device ID must be of type array."));
}
}
if (!gpu.driverVersion.isNull()) {
const QJsonValue driverVersionV = object.value(driverVersionKey());
switch (driverVersionV.type()) {
case QJsonValue::Object:
if (!VersionTerm::fromJson(driverVersionV).matches(gpu.driverVersion))
return false;
break;
case QJsonValue::Undefined:
case QJsonValue::Null:
break;
default:
qWarning().noquote()
<< msgSyntaxWarning(object,
QLatin1String("Driver version must be of type object."));
}
}
return true;
}
static bool readGpuFeatures(const QOpenGLConfig::Gpu &gpu,
const QString &osName,
const QVersionNumber &kernelVersion,
const QJsonDocument &doc,
QSet<QString> *result,
QString *errorMessage)
{
result->clear();
errorMessage->clear();
const QJsonValue entriesV = doc.object().value(QStringLiteral("entries"));
if (!entriesV.isArray()) {
*errorMessage = QLatin1String("No entries read.");
return false;
}
const QJsonArray entriesA = entriesV.toArray();
for (JsonArrayConstIt eit = entriesA.constBegin(), ecend = entriesA.constEnd(); eit != ecend; ++eit) {
if (eit->isObject()) {
const QJsonObject object = eit->toObject();
if (matches(object, osName, kernelVersion, gpu)) {
const QJsonValue featuresListV = object.value(featuresKey());
if (featuresListV.isArray()) {
const QJsonArray featuresListA = featuresListV.toArray();
for (JsonArrayConstIt fit = featuresListA.constBegin(), fcend = featuresListA.constEnd(); fit != fcend; ++fit)
result->insert(fit->toString());
}
}
}
}
return true;
}
static bool readGpuFeatures(const QOpenGLConfig::Gpu &gpu,
const QString &osName,
const QVersionNumber &kernelVersion,
const QByteArray &jsonAsciiData,
QSet<QString> *result, QString *errorMessage)
{
result->clear();
errorMessage->clear();
QJsonParseError error;
const QJsonDocument document = QJsonDocument::fromJson(jsonAsciiData, &error);
if (document.isNull()) {
const int lineNumber = 1 + jsonAsciiData.left(error.offset).count('\n');
QTextStream str(errorMessage);
str << "Failed to parse data: \"" << error.errorString()
<< "\" at line " << lineNumber << " (offset: "
<< error.offset << ").";
return false;
}
return readGpuFeatures(gpu, osName, kernelVersion, document, result, errorMessage);
}
static bool readGpuFeatures(const QOpenGLConfig::Gpu &gpu,
const QString &osName,
const QVersionNumber &kernelVersion,
const QString &fileName,
QSet<QString> *result, QString *errorMessage)
{
result->clear();
errorMessage->clear();
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
QTextStream str(errorMessage);
str << "Cannot open \"" << QDir::toNativeSeparators(fileName) << "\": "
<< file.errorString();
return false;
}
const bool success = readGpuFeatures(gpu, osName, kernelVersion, file.readAll(), result, errorMessage);
if (!success) {
errorMessage->prepend(QLatin1String("Error reading \"")
+ QDir::toNativeSeparators(fileName)
+ QLatin1String("\": "));
}
return success;
}
QSet<QString> QOpenGLConfig::gpuFeatures(const QOpenGLConfig::Gpu &gpu,
const QString &osName,
const QVersionNumber &kernelVersion,
const QJsonDocument &doc)
{
QSet<QString> result;
QString errorMessage;
if (!readGpuFeatures(gpu, osName, kernelVersion, doc, &result, &errorMessage))
qWarning().noquote() << errorMessage;
return result;
}
QSet<QString> QOpenGLConfig::gpuFeatures(const QOpenGLConfig::Gpu &gpu,
const QString &osName,
const QVersionNumber &kernelVersion,
const QString &fileName)
{
QSet<QString> result;
QString errorMessage;
if (!readGpuFeatures(gpu, osName, kernelVersion, fileName, &result, &errorMessage))
qWarning().noquote() << errorMessage;
return result;
}
QSet<QString> QOpenGLConfig::gpuFeatures(const Gpu &gpu, const QJsonDocument &doc)
{
return gpuFeatures(gpu, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), doc);
}
QSet<QString> QOpenGLConfig::gpuFeatures(const Gpu &gpu, const QString &fileName)
{
return gpuFeatures(gpu, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), fileName);
}
QT_END_NAMESPACE

View File

@ -48,9 +48,13 @@
#include <qopengl.h>
#include <private/qopenglcontext_p.h>
#include <QtCore/qset.h>
#include <QtCore/qstring.h>
#include <private/qversionnumber_p.h>
QT_BEGIN_NAMESPACE
class QJsonDocument;
class Q_GUI_EXPORT QOpenGLExtensionMatcher
{
public:
@ -67,6 +71,28 @@ private:
QSet<QByteArray> m_extensions;
};
class Q_GUI_EXPORT QOpenGLConfig
{
public:
struct Gpu {
Gpu() : vendorId(0), deviceId(0) {}
bool isValid() const { return deviceId; }
uint vendorId;
uint deviceId;
QVersionNumber driverVersion;
};
static QSet<QString> gpuFeatures(const Gpu &gpu,
const QString &osName, const QVersionNumber &kernelVersion,
const QJsonDocument &doc);
static QSet<QString> gpuFeatures(const Gpu &gpu,
const QString &osName, const QVersionNumber &kernelVersion,
const QString &fileName);
static QSet<QString> gpuFeatures(const Gpu &gpu, const QJsonDocument &doc);
static QSet<QString> gpuFeatures(const Gpu &gpu, const QString &fileName);
};
QT_END_NAMESPACE
#endif // QOPENGL_H

View File

@ -38,6 +38,12 @@
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
#include <QtCore/QCoreApplication>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QStandardPaths>
#include <QtCore/QLibraryInfo>
#include <private/qopengl_p.h>
#ifndef Q_OS_WINCE
# include <QtCore/qt_windows.h>
@ -183,6 +189,57 @@ QWindowsOpenGLTester::Renderer QWindowsOpenGLTester::requestedRenderer()
return QWindowsOpenGLTester::InvalidRenderer;
}
#ifndef Q_OS_WINCE
static inline QString resolveBugListFile(const QString &fileName)
{
if (QFileInfo(fileName).isAbsolute())
return fileName;
// Try QLibraryInfo::SettingsPath which is typically empty unless specified in qt.conf,
// then resolve via QStandardPaths::ConfigLocation.
const QString settingsPath = QLibraryInfo::location(QLibraryInfo::SettingsPath);
if (!settingsPath.isEmpty()) { // SettingsPath is empty unless specified in qt.conf.
const QFileInfo fi(settingsPath + QLatin1Char('/') + fileName);
if (fi.isFile())
return fi.absoluteFilePath();
}
return QStandardPaths::locate(QStandardPaths::ConfigLocation, fileName);
}
static void readDriverBugList(const GpuDescription &gpu,
QWindowsOpenGLTester::Renderers *result)
{
const char bugListFileVar[] = "QT_OPENGL_BUGLIST";
if (!qEnvironmentVariableIsSet(bugListFileVar))
return;
const QString fileName = resolveBugListFile(QFile::decodeName(qgetenv(bugListFileVar)));
if (fileName.isEmpty())
return;
QOpenGLConfig::Gpu qgpu;
qgpu.deviceId = gpu.deviceId;
qgpu.vendorId = gpu.vendorId;
qgpu.driverVersion = gpu.driverVersion;
const QSet<QString> features = QOpenGLConfig::gpuFeatures(qgpu, fileName);
if (features.contains(QStringLiteral("disable_desktopgl"))) { // Qt-specific
qCWarning(lcQpaGl) << "Disabling Desktop GL: " << gpu;
*result &= ~QWindowsOpenGLTester::DesktopGl;
}
if (features.contains(QStringLiteral("disable_angle"))) { // Qt-specific keyword
qCWarning(lcQpaGl) << "Disabling ANGLE: " << gpu;
*result &= ~QWindowsOpenGLTester::GlesMask;
} else {
if (features.contains(QStringLiteral("disable_d3d11"))) { // standard keyword
qCWarning(lcQpaGl) << "Disabling D3D11: " << gpu;
*result &= ~QWindowsOpenGLTester::AngleRendererD3d11;
}
if (features.contains(QStringLiteral("disable_d3d9"))) { // Qt-specific
qCWarning(lcQpaGl) << "Disabling D3D9: " << gpu;
*result &= ~QWindowsOpenGLTester::AngleRendererD3d9;
}
}
}
#endif // !Q_OS_WINCE
static inline QWindowsOpenGLTester::Renderers
detectSupportedRenderers(const GpuDescription &gpu, bool glesOnly)
{
@ -196,6 +253,9 @@ static inline QWindowsOpenGLTester::Renderers
if (!glesOnly && QWindowsOpenGLTester::testDesktopGL())
result |= QWindowsOpenGLTester::DesktopGl;
readDriverBugList(gpu, &result);
return result;
#else // !Q_OS_WINCE
return QWindowsOpenGLTester::Gles;

View File

@ -0,0 +1,106 @@
{
"name": "gpu driver bug list example",
"version": "0.9",
"entries": [
{
"id": 1,
"description": "non-applicable feature: different OS",
"vendor_id": "0x10de",
"device_id": [ "0x0DE9" ],
"os": {
"type": "android"
},
"features": [
"wrong_feature1"
]
},
{
"id": 2,
"description": "non-applicable feature: ancient OS",
"vendor_id": "0x10de",
"device_id": [ "0x0DE9" ],
"os": {
"type": "win",
"version": {
"op": "<",
"value": "4.0"
}
},
"features": [
"wrong_feature2"
]
},
{
"id": 3,
"description": "non-applicable feature: excluded OS",
"vendor_id": "0x10de",
"device_id": [ "0x0DE9" ],
"exceptions": [
{
"os": {
"type": "win"
}
}
],
"features": [
"wrong_feature3"
]
},
{
"id": 4,
"description": "non-applicable feature: wrong vendor",
"vendor_id": "0x10df",
"device_id": [ "0x0DE9" ],
"os": {
"type": "win"
},
"features": [
"wrong_feature4"
]
},
{
"id": 4,
"description": "non-applicable feature: wrong device",
"vendor_id": "0x10de",
"device_id": [ "0x0DEA" ],
"os": {
"type": "win"
},
"features": [
"wrong_feature5"
]
},
{
"id": 5,
"description": "feature 1",
"vendor_id": "0x10de",
"device_id": [ "0x0DE9" ],
"os": {
"type": "win"
},
"driver_version": {
"op": ">",
"value": "9.18.0.0"
},
"features": [
"feature1"
]
},
{
"id": 6,
"description": "non-applicable feature: too new driver",
"vendor_id": "0x10de",
"device_id": [ "0x0DE9" ],
"os": {
"type": "win"
},
"driver_version": {
"op": "<=",
"value": "9.17.13.4344"
},
"features": [
"feature1"
]
}
]
}

View File

@ -8,3 +8,4 @@ TARGET = tst_qopenglconfig
QT += gui-private core-private testlib
SOURCES += tst_qopenglconfig.cpp
OTHER_FILES = buglist.json

View File

@ -37,6 +37,7 @@
#include <private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformnativeinterface.h>
#include <private/qopengl_p.h>
#include <QtTest/QtTest>
@ -105,6 +106,7 @@ class tst_QOpenGlConfig : public QObject
private slots:
void testConfiguration();
void testGlConfiguration();
void testBugList();
};
static void dumpConfiguration(QTextStream &str)
@ -217,6 +219,41 @@ void tst_QOpenGlConfig::testGlConfiguration()
qDebug().noquote() << '\n' << result;
}
static inline QByteArray msgSetMismatch(const QSet<QString> &expected,
const QSet<QString> &actual)
{
const QString result = QStringList(expected.toList()).join(QLatin1Char(','))
+ QLatin1String(" != ")
+ QStringList(actual.toList()).join(QLatin1Char(','));
return result.toLatin1();
}
void tst_QOpenGlConfig::testBugList()
{
// Check bug list parsing for some arbitrary NVidia card
// faking Windows OS.
const QString fileName = QFINDTESTDATA("buglist.json");
QVERIFY(!fileName.isEmpty());
QSet<QString> expectedFeatures;
expectedFeatures << "feature1";
QOpenGLConfig::Gpu gpu;
gpu.vendorId = 0x10DE;
gpu.deviceId = 0x0DE9;
#ifdef Q_COMPILER_INITIALIZER_LISTS
gpu.driverVersion = QVersionNumber({9, 18, 13, 4460});
#else
gpu.driverVersion = QVersionNumber(QVector<int>() << 9 << 18 << 13 << 4460);
#endif
const QSet<QString> actualFeatures =
QOpenGLConfig::gpuFeatures(gpu, QStringLiteral("win"),
QVersionNumber(6, 3), fileName);
QVERIFY2(expectedFeatures == actualFeatures,
msgSetMismatch(expectedFeatures, actualFeatures));
}
QTEST_MAIN(tst_QOpenGlConfig)
#include "tst_qopenglconfig.moc"