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

source code of qtbase/src/gui/opengl/qopengl.cpp