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__anondcdaf32e0111::VersionTerm175 VersionTerm() : op(NotEqual) {}
176 static VersionTerm fromJson(const QJsonValue &v);
isNull__anondcdaf32e0111::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__anondcdaf32e0111::OsTypeTerm230 static QVersionNumber hostKernelVersion() { return QVersionNumber::fromString(QSysInfo::kernelVersion()); }
hostOsRelease__anondcdaf32e0111::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__anondcdaf32e0111::OsTypeTerm257 bool isNull() const { return type.isEmpty(); }
matches__anondcdaf32e0111::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