diff options
author | Friedemann Kleint <Friedemann.Kleint@theqtcompany.com> | 2015-02-05 15:45:53 +0100 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@theqtcompany.com> | 2015-02-14 16:23:47 +0000 |
commit | 4dd7aeee10fc4d3883fd26fd70662405b533b6b4 (patch) | |
tree | 4e3d848561bb3571db83f94b74d5dd09bca0de55 /src/gui | |
parent | af495c2042b6c460580041fd0c39697c144e45f7 (diff) |
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>
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/opengl/qopengl.cpp | 369 | ||||
-rw-r--r-- | src/gui/opengl/qopengl_p.h | 26 |
2 files changed, 395 insertions, 0 deletions
diff --git a/src/gui/opengl/qopengl.cpp b/src/gui/opengl/qopengl.cpp index f700d65b17..61d0614724 100644 --- a/src/gui/opengl/qopengl.cpp +++ b/src/gui/opengl/qopengl.cpp @@ -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 diff --git a/src/gui/opengl/qopengl_p.h b/src/gui/opengl/qopengl_p.h index a82195757d..377440455a 100644 --- a/src/gui/opengl/qopengl_p.h +++ b/src/gui/opengl/qopengl_p.h @@ -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 |