summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@theqtcompany.com>2015-02-05 15:45:53 +0100
committerFriedemann Kleint <Friedemann.Kleint@theqtcompany.com>2015-02-14 16:23:47 +0000
commit4dd7aeee10fc4d3883fd26fd70662405b533b6b4 (patch)
tree4e3d848561bb3571db83f94b74d5dd09bca0de55 /src
parentaf495c2042b6c460580041fd0c39697c144e45f7 (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')
-rw-r--r--src/gui/opengl/qopengl.cpp369
-rw-r--r--src/gui/opengl/qopengl_p.h26
-rw-r--r--src/plugins/platforms/windows/qwindowsopengltester.cpp60
3 files changed, 455 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
diff --git a/src/plugins/platforms/windows/qwindowsopengltester.cpp b/src/plugins/platforms/windows/qwindowsopengltester.cpp
index 12d45f432b..ba4c95544e 100644
--- a/src/plugins/platforms/windows/qwindowsopengltester.cpp
+++ b/src/plugins/platforms/windows/qwindowsopengltester.cpp
@@ -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;