1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qopengl.h"
41 #include "qopengl_p.h"
42 
43 #include "qopenglcontext.h"
44 #include "qopenglfunctions.h"
45 #include "qoperatingsystemversion.h"
46 #include "qoffscreensurface.h"
47 
48 #include <QtCore/QDebug>
49 #include <QtCore/QJsonDocument>
50 #include <QtCore/QJsonValue>
51 #include <QtCore/QJsonObject>
52 #include <QtCore/QJsonArray>
53 #include <QtCore/QTextStream>
54 #include <QtCore/QFile>
55 #include <QtCore/QDir>
56 
57 QT_BEGIN_NAMESPACE
58 
59 #if defined(QT_OPENGL_3)
60 typedef const GLubyte * (QOPENGLF_APIENTRYP qt_glGetStringi)(GLenum, GLuint);
61 #endif
62 
63 #ifndef GL_CONTEXT_LOST
64 #define GL_CONTEXT_LOST                   0x0507
65 #endif
66 
QOpenGLExtensionMatcher()67 QOpenGLExtensionMatcher::QOpenGLExtensionMatcher()
68 {
69     QOpenGLContext *ctx = QOpenGLContext::currentContext();
70     if (!ctx) {
71         qWarning("QOpenGLExtensionMatcher::QOpenGLExtensionMatcher: No context");
72         return;
73     }
74     QOpenGLFunctions *funcs = ctx->functions();
75     const char *extensionStr = nullptr;
76 
77     if (ctx->isOpenGLES() || ctx->format().majorVersion() < 3)
78         extensionStr = reinterpret_cast<const char *>(funcs->glGetString(GL_EXTENSIONS));
79 
80     if (extensionStr) {
81         QByteArray ba(extensionStr);
82         QList<QByteArray> extensions = ba.split(' ');
83         m_extensions = QSet<QByteArray>(extensions.constBegin(), extensions.constEnd());
84     } else {
85 #ifdef QT_OPENGL_3
86         // clear error state
87         while (true) { // Clear error state.
88             GLenum error = funcs->glGetError();
89             if (error == GL_NO_ERROR)
90                 break;
91             if (error == GL_CONTEXT_LOST)
92                 return;
93         };
94         qt_glGetStringi glGetStringi = (qt_glGetStringi)ctx->getProcAddress("glGetStringi");
95 
96         if (!glGetStringi)
97             return;
98 
99         GLint numExtensions = 0;
100         funcs->glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
101 
102         for (int i = 0; i < numExtensions; ++i) {
103             const char *str = reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i));
104             m_extensions.insert(str);
105         }
106 #endif // QT_OPENGL_3
107     }
108 }
109 
110 /* Helpers to read out the list of features matching a device from
111  * a Chromium driver bug list. Note that not all keys are supported and
112  * some may behave differently: gl_vendor is a substring match instead of regex.
113  {
114   "entries": [
115  {
116       "id": 20,
117       "description": "Disable EXT_draw_buffers on GeForce GT 650M on Linux due to driver bugs",
118       "os": {
119         "type": "linux"
120       },
121       // Optional: "exceptions" list
122       "vendor_id": "0x10de",
123       "device_id": ["0x0fd5"],
124       "multi_gpu_category": "any",
125       "features": [
126         "disable_ext_draw_buffers"
127       ]
128     },
129    ....
130    }
131 */
132 
operator <<(QDebug d,const QOpenGLConfig::Gpu & g)133 QDebug operator<<(QDebug d, const QOpenGLConfig::Gpu &g)
134 {
135     QDebugStateSaver s(d);
136     d.nospace();
137     d << "Gpu(";
138     if (g.isValid()) {
139         d << "vendor=" << Qt::hex << Qt::showbase <<g.vendorId << ", device=" << g.deviceId
140           << "version=" << g.driverVersion;
141     } else {
142         d << 0;
143     }
144     d << ')';
145     return d;
146 }
147 
148 typedef QJsonArray::ConstIterator JsonArrayConstIt;
149 
contains(const QJsonArray & haystack,unsigned needle)150 static inline bool contains(const QJsonArray &haystack, unsigned needle)
151 {
152     for (JsonArrayConstIt it = haystack.constBegin(), cend = haystack.constEnd(); it != cend; ++it) {
153         if (needle == it->toString().toUInt(nullptr, /* base */ 0))
154             return true;
155     }
156     return false;
157 }
158 
contains(const QJsonArray & haystack,const QString & needle)159 static inline bool contains(const QJsonArray &haystack, const QString &needle)
160 {
161     for (JsonArrayConstIt it = haystack.constBegin(), cend = haystack.constEnd(); it != cend; ++it) {
162         if (needle == it->toString())
163             return true;
164     }
165     return false;
166 }
167 
168 namespace {
169 enum Operator { NotEqual, LessThan, LessEqualThan, Equals, GreaterThan, GreaterEqualThan };
170 static const char operators[][3] = {"!=", "<", "<=", "=", ">", ">="};
171 
172 // VersionTerm describing a version term consisting of number and operator
173 // found in os.version and driver_version.
174 struct VersionTerm {
VersionTerm__anonaf6913510111::VersionTerm175     VersionTerm() : op(NotEqual) {}
176     static VersionTerm fromJson(const QJsonValue &v);
isNull__anonaf6913510111::VersionTerm177     bool isNull() const { return number.isNull(); }
178     bool matches(const QVersionNumber &other) const;
179 
180     QVersionNumber number;
181     Operator op;
182 };
183 
matches(const QVersionNumber & other) const184 bool VersionTerm::matches(const QVersionNumber &other) const
185 {
186     if (isNull() || other.isNull()) {
187         qWarning("called with invalid parameters");
188         return false;
189     }
190     switch (op) {
191     case NotEqual:
192         return other != number;
193     case LessThan:
194         return other < number;
195     case LessEqualThan:
196         return other <= number;
197     case Equals:
198         return other == number;
199     case GreaterThan:
200         return other > number;
201     case GreaterEqualThan:
202         return other >= number;
203     }
204     return false;
205 }
206 
fromJson(const QJsonValue & v)207 VersionTerm VersionTerm::fromJson(const QJsonValue &v)
208 {
209     VersionTerm result;
210     if (!v.isObject())
211         return result;
212     const QJsonObject o = v.toObject();
213     result.number = QVersionNumber::fromString(o.value(QLatin1String("value")).toString());
214     const QString opS = o.value(QLatin1String("op")).toString();
215     for (size_t i = 0; i < sizeof(operators) / sizeof(operators[0]); ++i) {
216         if (opS == QLatin1String(operators[i])) {
217             result.op = static_cast<Operator>(i);
218             break;
219         }
220     }
221     return result;
222 }
223 
224 // OS term consisting of name and optional version found in
225 // under "os" in main array and in "exceptions" lists.
226 struct OsTypeTerm
227 {
228     static OsTypeTerm fromJson(const QJsonValue &v);
229     static QString hostOs();
hostKernelVersion__anonaf6913510111::OsTypeTerm230     static QVersionNumber hostKernelVersion() { return QVersionNumber::fromString(QSysInfo::kernelVersion()); }
hostOsRelease__anonaf6913510111::OsTypeTerm231     static QString hostOsRelease() {
232         QString ver;
233 #ifdef Q_OS_WIN
234         const auto osver = QOperatingSystemVersion::current();
235 #define Q_WINVER(major, minor) (major << 8 | minor)
236         switch (Q_WINVER(osver.majorVersion(), osver.minorVersion())) {
237         case Q_WINVER(6, 1):
238             ver = QStringLiteral("7");
239             break;
240         case Q_WINVER(6, 2):
241             ver = QStringLiteral("8");
242             break;
243         case Q_WINVER(6, 3):
244             ver = QStringLiteral("8.1");
245             break;
246         case Q_WINVER(10, 0):
247             ver = QStringLiteral("10");
248             break;
249         default:
250             break;
251         }
252 #undef Q_WINVER
253 #endif
254         return ver;
255     }
256 
isNull__anonaf6913510111::OsTypeTerm257     bool isNull() const { return type.isEmpty(); }
matches__anonaf6913510111::OsTypeTerm258     bool matches(const QString &osName, const QVersionNumber &kernelVersion, const QString &osRelease) const
259     {
260         if (isNull() || osName.isEmpty() || kernelVersion.isNull()) {
261             qWarning("called with invalid parameters");
262             return false;
263         }
264         if (type != osName)
265             return false;
266         if (!versionTerm.isNull() && !versionTerm.matches(kernelVersion))
267             return false;
268         // release is a list of Windows versions where the rule should match
269         if (!release.isEmpty() && !contains(release, osRelease))
270             return false;
271         return true;
272     }
273 
274     QString type;
275     VersionTerm versionTerm;
276     QJsonArray release;
277 };
278 
fromJson(const QJsonValue & v)279 OsTypeTerm OsTypeTerm::fromJson(const QJsonValue &v)
280 {
281     OsTypeTerm result;
282     if (!v.isObject())
283         return result;
284     const QJsonObject o = v.toObject();
285     result.type = o.value(QLatin1String("type")).toString();
286     result.versionTerm = VersionTerm::fromJson(o.value(QLatin1String("version")));
287     result.release = o.value(QLatin1String("release")).toArray();
288     return result;
289 }
290 
hostOs()291 QString OsTypeTerm::hostOs()
292 {
293     // Determine Host OS.
294 #if defined(Q_OS_WIN)
295     return  QStringLiteral("win");
296 #elif defined(Q_OS_LINUX)
297     return QStringLiteral("linux");
298 #elif defined(Q_OS_MACOS)
299     return  QStringLiteral("macosx");
300 #elif defined(Q_OS_ANDROID)
301     return  QStringLiteral("android");
302 #else
303     return QString();
304 #endif
305 }
306 } // anonymous namespace
307 
msgSyntaxWarning(const QJsonObject & object,const QString & what)308 static QString msgSyntaxWarning(const QJsonObject &object, const QString &what)
309 {
310     QString result;
311     QTextStream(&result) << "Id " << object.value(QLatin1String("id")).toInt()
312         << " (\"" << object.value(QLatin1String("description")).toString()
313         << "\"): " << what;
314     return result;
315 }
316 
317 // Check whether an entry matches. Called recursively for
318 // "exceptions" list.
319 
matches(const QJsonObject & object,const QString & osName,const QVersionNumber & kernelVersion,const QString & osRelease,const QOpenGLConfig::Gpu & gpu)320 static bool matches(const QJsonObject &object,
321                     const QString &osName,
322                     const QVersionNumber &kernelVersion,
323                     const QString &osRelease,
324                     const QOpenGLConfig::Gpu &gpu)
325 {
326     const OsTypeTerm os = OsTypeTerm::fromJson(object.value(QLatin1String("os")));
327     if (!os.isNull() && !os.matches(osName, kernelVersion, osRelease))
328         return false;
329 
330     const QJsonValue exceptionsV = object.value(QLatin1String("exceptions"));
331     if (exceptionsV.isArray()) {
332         const QJsonArray exceptionsA = exceptionsV.toArray();
333         for (JsonArrayConstIt it = exceptionsA.constBegin(), cend = exceptionsA.constEnd(); it != cend; ++it) {
334             if (matches(it->toObject(), osName, kernelVersion, osRelease, gpu))
335                 return false;
336         }
337     }
338 
339     const QJsonValue vendorV = object.value(QLatin1String("vendor_id"));
340     if (vendorV.isString()) {
341         if (gpu.vendorId != vendorV.toString().toUInt(nullptr, /* base */ 0))
342             return false;
343     } else {
344         if (object.contains(QLatin1String("gl_vendor"))) {
345             const QByteArray glVendorV = object.value(QLatin1String("gl_vendor")).toString().toUtf8();
346             if (!gpu.glVendor.contains(glVendorV))
347                 return false;
348         }
349     }
350 
351     if (gpu.deviceId) {
352         const QJsonValue deviceIdV = object.value(QLatin1String("device_id"));
353         switch (deviceIdV.type()) {
354         case QJsonValue::Array:
355             if (!contains(deviceIdV.toArray(), gpu.deviceId))
356                 return false;
357             break;
358         case QJsonValue::Undefined:
359         case QJsonValue::Null:
360             break;
361         default:
362             qWarning().noquote()
363                 << msgSyntaxWarning(object,
364                                     QLatin1String("Device ID must be of type array."));
365         }
366     }
367     if (!gpu.driverVersion.isNull()) {
368         const QJsonValue driverVersionV = object.value(QLatin1String("driver_version"));
369         switch (driverVersionV.type()) {
370         case QJsonValue::Object:
371             if (!VersionTerm::fromJson(driverVersionV).matches(gpu.driverVersion))
372                 return false;
373             break;
374         case QJsonValue::Undefined:
375         case QJsonValue::Null:
376             break;
377         default:
378             qWarning().noquote()
379                 << msgSyntaxWarning(object,
380                                     QLatin1String("Driver version must be of type object."));
381         }
382     }
383 
384     if (!gpu.driverDescription.isEmpty()) {
385         const QJsonValue driverDescriptionV = object.value(QLatin1String("driver_description"));
386         if (driverDescriptionV.isString()) {
387             if (!gpu.driverDescription.contains(driverDescriptionV.toString().toUtf8()))
388                 return false;
389         }
390     }
391 
392     return true;
393 }
394 
readGpuFeatures(const QOpenGLConfig::Gpu & gpu,const QString & osName,const QVersionNumber & kernelVersion,const QString & osRelease,const QJsonDocument & doc,QSet<QString> * result,QString * errorMessage)395 static bool readGpuFeatures(const QOpenGLConfig::Gpu &gpu,
396                             const QString &osName,
397                             const QVersionNumber &kernelVersion,
398                             const QString &osRelease,
399                             const QJsonDocument &doc,
400                             QSet<QString> *result,
401                             QString *errorMessage)
402 {
403     result->clear();
404     errorMessage->clear();
405     const QJsonValue entriesV = doc.object().value(QLatin1String("entries"));
406     if (!entriesV.isArray()) {
407         *errorMessage = QLatin1String("No entries read.");
408         return false;
409     }
410 
411     const QJsonArray entriesA = entriesV.toArray();
412     for (JsonArrayConstIt eit = entriesA.constBegin(), ecend = entriesA.constEnd(); eit != ecend; ++eit) {
413         if (eit->isObject()) {
414             const QJsonObject object = eit->toObject();
415             if (matches(object, osName, kernelVersion, osRelease, gpu)) {
416                 const QJsonValue featuresListV = object.value(QLatin1String("features"));
417                 if (featuresListV.isArray()) {
418                     const QJsonArray featuresListA = featuresListV.toArray();
419                     for (JsonArrayConstIt fit = featuresListA.constBegin(), fcend = featuresListA.constEnd(); fit != fcend; ++fit)
420                         result->insert(fit->toString());
421                 }
422             }
423         }
424     }
425     return true;
426 }
427 
readGpuFeatures(const QOpenGLConfig::Gpu & gpu,const QString & osName,const QVersionNumber & kernelVersion,const QString & osRelease,const QByteArray & jsonAsciiData,QSet<QString> * result,QString * errorMessage)428 static bool readGpuFeatures(const QOpenGLConfig::Gpu &gpu,
429                             const QString &osName,
430                             const QVersionNumber &kernelVersion,
431                             const QString &osRelease,
432                             const QByteArray &jsonAsciiData,
433                             QSet<QString> *result, QString *errorMessage)
434 {
435     result->clear();
436     errorMessage->clear();
437     QJsonParseError error;
438     const QJsonDocument document = QJsonDocument::fromJson(jsonAsciiData, &error);
439     if (document.isNull()) {
440         const int lineNumber = 1 + jsonAsciiData.left(error.offset).count('\n');
441         QTextStream str(errorMessage);
442         str << "Failed to parse data: \"" << error.errorString()
443             << "\" at line " << lineNumber << " (offset: "
444             << error.offset << ").";
445         return false;
446     }
447     return readGpuFeatures(gpu, osName, kernelVersion, osRelease, document, result, errorMessage);
448 }
449 
readGpuFeatures(const QOpenGLConfig::Gpu & gpu,const QString & osName,const QVersionNumber & kernelVersion,const QString & osRelease,const QString & fileName,QSet<QString> * result,QString * errorMessage)450 static bool readGpuFeatures(const QOpenGLConfig::Gpu &gpu,
451                             const QString &osName,
452                             const QVersionNumber &kernelVersion,
453                             const QString &osRelease,
454                             const QString &fileName,
455                             QSet<QString> *result, QString *errorMessage)
456 {
457     result->clear();
458     errorMessage->clear();
459     QFile file(fileName);
460     if (!file.open(QIODevice::ReadOnly)) {
461         QTextStream str(errorMessage);
462         str << "Cannot open \"" << QDir::toNativeSeparators(fileName) << "\": "
463             << file.errorString();
464         return false;
465     }
466     const bool success = readGpuFeatures(gpu, osName, kernelVersion, osRelease, file.readAll(), result, errorMessage);
467     if (!success) {
468         errorMessage->prepend(QLatin1String("Error reading \"")
469                               + QDir::toNativeSeparators(fileName)
470                               + QLatin1String("\": "));
471     }
472     return success;
473 }
474 
gpuFeatures(const QOpenGLConfig::Gpu & gpu,const QString & osName,const QVersionNumber & kernelVersion,const QString & osRelease,const QJsonDocument & doc)475 QSet<QString> QOpenGLConfig::gpuFeatures(const QOpenGLConfig::Gpu &gpu,
476                                          const QString &osName,
477                                          const QVersionNumber &kernelVersion,
478                                          const QString &osRelease,
479                                          const QJsonDocument &doc)
480 {
481     QSet<QString> result;
482     QString errorMessage;
483     if (!readGpuFeatures(gpu, osName, kernelVersion, osRelease, doc, &result, &errorMessage))
484         qWarning().noquote() << errorMessage;
485     return result;
486 }
487 
gpuFeatures(const QOpenGLConfig::Gpu & gpu,const QString & osName,const QVersionNumber & kernelVersion,const QString & osRelease,const QString & fileName)488 QSet<QString> QOpenGLConfig::gpuFeatures(const QOpenGLConfig::Gpu &gpu,
489                                          const QString &osName,
490                                          const QVersionNumber &kernelVersion,
491                                          const QString &osRelease,
492                                          const QString &fileName)
493 {
494     QSet<QString> result;
495     QString errorMessage;
496     if (!readGpuFeatures(gpu, osName, kernelVersion, osRelease, fileName, &result, &errorMessage))
497         qWarning().noquote() << errorMessage;
498     return result;
499 }
500 
gpuFeatures(const Gpu & gpu,const QJsonDocument & doc)501 QSet<QString> QOpenGLConfig::gpuFeatures(const Gpu &gpu, const QJsonDocument &doc)
502 {
503     return gpuFeatures(gpu, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), OsTypeTerm::hostOsRelease(), doc);
504 }
505 
gpuFeatures(const Gpu & gpu,const QString & fileName)506 QSet<QString> QOpenGLConfig::gpuFeatures(const Gpu &gpu, const QString &fileName)
507 {
508     return gpuFeatures(gpu, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), OsTypeTerm::hostOsRelease(), fileName);
509 }
510 
fromContext()511 QOpenGLConfig::Gpu QOpenGLConfig::Gpu::fromContext()
512 {
513     QOpenGLContext *ctx = QOpenGLContext::currentContext();
514     QScopedPointer<QOpenGLContext> tmpContext;
515     QScopedPointer<QOffscreenSurface> tmpSurface;
516     if (!ctx) {
517         tmpContext.reset(new QOpenGLContext);
518         if (!tmpContext->create()) {
519             qWarning("QOpenGLConfig::Gpu::fromContext: Failed to create temporary context");
520             return QOpenGLConfig::Gpu();
521         }
522         tmpSurface.reset(new QOffscreenSurface);
523         tmpSurface->setFormat(tmpContext->format());
524         tmpSurface->create();
525         tmpContext->makeCurrent(tmpSurface.data());
526     }
527 
528     QOpenGLConfig::Gpu gpu;
529     ctx = QOpenGLContext::currentContext();
530     const GLubyte *p = ctx->functions()->glGetString(GL_VENDOR);
531     if (p)
532         gpu.glVendor = QByteArray(reinterpret_cast<const char *>(p));
533 
534     return gpu;
535 }
536 
537 QT_END_NAMESPACE
538