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
57QT_BEGIN_NAMESPACE
58
59#if defined(QT_OPENGL_3)
60typedef 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
67QOpenGLExtensionMatcher::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 = 0;
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
133QDebug 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
148typedef QJsonArray::ConstIterator JsonArrayConstIt;
149
150static 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
159static 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
168namespace {
169enum Operator { NotEqual, LessThan, LessEqualThan, Equals, GreaterThan, GreaterEqualThan };
170static const char operators[][3] = {"!=", "<", "<=", "=", ">", ">="};
171
172// VersionTerm describing a version term consisting of number and operator
173// found in os.version and driver_version.
174struct VersionTerm {
175 VersionTerm() : op(NotEqual) {}
176 static VersionTerm fromJson(const QJsonValue &v);
177 bool isNull() const { return number.isNull(); }
178 bool matches(const QVersionNumber &other) const;
179
180 QVersionNumber number;
181 Operator op;
182};
183
184bool 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
207VersionTerm 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.
226struct OsTypeTerm
227{
228 static OsTypeTerm fromJson(const QJsonValue &v);
229 static QString hostOs();
230 static QVersionNumber hostKernelVersion() { return QVersionNumber::fromString(QSysInfo::kernelVersion()); }
231 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
257 bool isNull() const { return type.isEmpty(); }
258 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
279OsTypeTerm 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
291QString 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_OSX)
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
308static 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
320static 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
395static 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
428static 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
450static 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
475QSet<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
488QSet<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
501QSet<QString> QOpenGLConfig::gpuFeatures(const Gpu &gpu, const QJsonDocument &doc)
502{
503 return gpuFeatures(gpu, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), OsTypeTerm::hostOsRelease(), doc);
504}
505
506QSet<QString> QOpenGLConfig::gpuFeatures(const Gpu &gpu, const QString &fileName)
507{
508 return gpuFeatures(gpu, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), OsTypeTerm::hostOsRelease(), fileName);
509}
510
511QOpenGLConfig::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
537QT_END_NAMESPACE
538