Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qopengl.cpp
Go to the documentation of this file.
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"
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
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
34{
36 if (!ctx) {
37 qWarning("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(' ');
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;
58 return;
59 };
60 qt_glGetStringi glGetStringi = (qt_glGetStringi)ctx->getProcAddress("glGetStringi");
61
62 if (!glGetStringi)
63 return;
64
65 GLint numExtensions = 0;
66 funcs->glGetIntegerv(GL_NUM_EXTENSIONS, &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(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
100{
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
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(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
147 Operator op;
148};
149
150bool VersionTerm::matches(const QVersionNumber &other) const
151{
152 if (isNull() || other.isNull()) {
153 qWarning("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(o.value("value"_L1).toString());
180 const QString opS = o.value("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(QSysInfo::kernelVersion()); }
197 static QString hostOsRelease() {
198#ifdef Q_OS_WIN
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("called with invalid parameters");
212 return false;
213 }
214 if (type != osName)
215 return false;
216 if (!versionTerm.isNull() && !versionTerm.matches(kernelVersion))
217 return false;
218 // release is a list of Windows versions where the rule should match
219 if (!release.isEmpty() && !contains(release, osRelease))
220 return false;
221 return true;
222 }
223
225 VersionTerm versionTerm;
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("type"_L1).toString();
236 result.versionTerm = VersionTerm::fromJson(o.value("version"_L1));
237 result.release = o.value("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{
261 QTextStream(&result) << "Id " << object.value("id"_L1).toInt()
262 << " (\"" << object.value("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(object.value("os"_L1));
277 if (!os.isNull() && !os.matches(osName, kernelVersion, osRelease))
278 return false;
279
280 const QJsonValue exceptionsV = object.value("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(it->toObject(), osName, kernelVersion, osRelease, gpu))
285 return false;
286 }
287 }
288
289 const QJsonValue vendorV = object.value("vendor_id"_L1);
290 if (vendorV.isString()) {
291 if (gpu.vendorId != vendorV.toString().toUInt(nullptr, /* base */ 0))
292 return false;
293 } else {
294 if (object.contains("gl_vendor"_L1)) {
295 const QByteArray glVendorV = object.value("gl_vendor"_L1).toString().toUtf8();
296 if (!gpu.glVendor.contains(glVendorV))
297 return false;
298 }
299 }
300
301 if (gpu.deviceId) {
302 const QJsonValue deviceIdV = object.value("device_id"_L1);
303 switch (deviceIdV.type()) {
305 if (!contains(deviceIdV.toArray(), gpu.deviceId))
306 return false;
307 break;
309 case QJsonValue::Null:
310 break;
311 default:
312 qWarning().noquote()
313 << msgSyntaxWarning(object, "Device ID must be of type array."_L1);
314 }
315 }
316 if (!gpu.driverVersion.isNull()) {
317 const QJsonValue driverVersionV = object.value("driver_version"_L1);
318 switch (driverVersionV.type()) {
320 if (!VersionTerm::fromJson(driverVersionV).matches(gpu.driverVersion))
321 return false;
322 break;
324 case QJsonValue::Null:
325 break;
326 default:
327 qWarning().noquote()
328 << msgSyntaxWarning(object, "Driver version must be of type object."_L1);
329 }
330 }
331
332 if (!gpu.driverDescription.isEmpty()) {
333 const QJsonValue driverDescriptionV = object.value("driver_description"_L1);
334 if (driverDescriptionV.isString()) {
335 if (!gpu.driverDescription.contains(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,
350{
351 result->clear();
352 errorMessage->clear();
353 const QJsonValue entriesV = doc.object().value("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("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(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();
386 const QJsonDocument document = QJsonDocument::fromJson(jsonAsciiData, &error);
387 if (document.isNull()) {
388 const qsizetype lineNumber =
389 QByteArrayView(jsonAsciiData).left(error.offset).count('\n') + 1;
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, 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();
411 str << "Cannot open \"" << QDir::toNativeSeparators(fileName) << "\": "
412 << file.errorString();
413 return false;
414 }
415 const bool success = readGpuFeatures(gpu, osName, kernelVersion, osRelease, file.readAll(), result, errorMessage);
416 if (!success) {
417 errorMessage->prepend("Error reading \""_L1
419 + "\": "_L1);
420 }
421 return success;
422}
423
425 const QString &osName,
426 const QVersionNumber &kernelVersion,
427 const QString &osRelease,
428 const QJsonDocument &doc)
429{
430 QSet<QString> result;
432 if (!readGpuFeatures(gpu, osName, kernelVersion, osRelease, doc, &result, &errorMessage))
433 qWarning().noquote() << errorMessage;
434 return result;
435}
436
438 const QString &osName,
439 const QVersionNumber &kernelVersion,
440 const QString &osRelease,
441 const QString &fileName)
442{
443 QSet<QString> result;
445 if (!readGpuFeatures(gpu, osName, kernelVersion, osRelease, fileName, &result, &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, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), OsTypeTerm::hostOsRelease(), doc);
453}
454
455QSet<QString> QOpenGLConfig::gpuFeatures(const Gpu &gpu, const QString &fileName)
456{
457 return gpuFeatures(gpu, OsTypeTerm::hostOs(), OsTypeTerm::hostKernelVersion(), OsTypeTerm::hostOsRelease(), fileName);
458}
459
461{
463 QScopedPointer<QOpenGLContext> tmpContext;
464 QScopedPointer<QOffscreenSurface> tmpSurface;
465 if (!ctx) {
466 tmpContext.reset(new QOpenGLContext);
467 if (!tmpContext->create()) {
468 qWarning("QOpenGLConfig::Gpu::fromContext: Failed to create temporary context");
469 return QOpenGLConfig::Gpu();
470 }
471 tmpSurface.reset(new QOffscreenSurface);
472 tmpSurface->setFormat(tmpContext->format());
473 tmpSurface->create();
474 tmpContext->makeCurrent(tmpSurface.data());
475 }
476
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
constexpr QByteArrayView left(qsizetype n) const
\inmodule QtCore
Definition qbytearray.h:57
QList< QByteArray > split(char sep) const
Splits the byte array into subarrays wherever sep occurs, and returns the list of those arrays.
bool contains(char c) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:660
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
\inmodule QtCore
\inmodule QtCore
static QString toNativeSeparators(const QString &pathName)
Definition qdir.cpp:929
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
QString errorString() const
Returns a human-readable description of the last device error that occurred.
\inmodule QtCore\reentrant
Definition qjsonarray.h:18
void insert(qsizetype i, const QJsonValue &value)
Inserts value at index position i in the array.
\inmodule QtCore\reentrant
QJsonObject object() const
Returns the QJsonObject contained in the document.
static QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error=nullptr)
Parses json as a UTF-8 encoded JSON document, and creates a QJsonDocument from it.
\inmodule QtCore\reentrant
Definition qjsonobject.h:20
QJsonValue value(const QString &key) const
Returns a QJsonValue representing the value for the key key.
\inmodule QtCore\reentrant
Definition qjsonvalue.h:25
\inmodule QtGui
static QSet< QString > gpuFeatures(const Gpu &gpu, const QString &osName, const QVersionNumber &kernelVersion, const QString &osVersion, const QJsonDocument &doc)
Definition qopengl.cpp:424
\inmodule QtGui
static QOpenGLContext * currentContext()
Returns the last context which called makeCurrent in the current thread, or \nullptr,...
QSet< QByteArray > extensions() const
Definition qopengl_p.h:39
The QOpenGLFunctions class provides cross-platform access to the OpenGL ES 2.0 API.
static Q_CORE_EXPORT QOperatingSystemVersionBase current()
static constexpr QOperatingSystemVersionBase Windows11
\variable QOperatingSystemVersion::Windows11
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
iterator insert(const T &value)
Definition qset.h:155
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString kernelVersion()
Definition qsysinfo.cpp:719
\inmodule QtCore
\inmodule QtCore
bool isNull() const noexcept
Returns true if there are zero numerical segments, otherwise returns false.
static Q_CORE_EXPORT QVersionNumber fromString(QAnyStringView string, qsizetype *suffixIndex=nullptr)
EGLContext ctx
static VulkanServerBufferGlFunctions * funcs
QString str
[2]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
bool isNull(const T &t)
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
QTextStream & showbase(QTextStream &stream)
Calls QTextStream::setNumberFlags(QTextStream::numberFlags() | QTextStream::ShowBase) on stream and r...
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
static QCborValue fromJson(const QByteArray &json, QJsonParseError *error)
static bool readGpuFeatures(const QOpenGLConfig::Gpu &gpu, const QString &osName, const QVersionNumber &kernelVersion, const QString &osRelease, const QJsonDocument &doc, QSet< QString > *result, QString *errorMessage)
Definition qopengl.cpp:343
const GLubyte *QOPENGLF_APIENTRYP qt_glGetStringi(GLenum, GLuint)
Definition qopengl.cpp:26
static QString msgSyntaxWarning(const QJsonObject &object, const QString &what)
Definition qopengl.cpp:258
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
QDebug operator<<(QDebug d, const QOpenGLConfig::Gpu &g)
Definition qopengl.cpp:99
static bool matches(const QJsonObject &object, const QString &osName, const QVersionNumber &kernelVersion, const QString &osRelease, const QOpenGLConfig::Gpu &gpu)
Definition qopengl.cpp:270
QJsonArray::ConstIterator JsonArrayConstIt
Definition qopengl.cpp:114
#define GL_CONTEXT_LOST
Definition qopengl.cpp:30
#define QOPENGLF_APIENTRYP
Definition qopengl.h:275
typedef GLint(GL_APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXEXTPROC)(GLuint program
GLsizei const GLfloat * v
[13]
GLenum type
typedef GLenum(GL_APIENTRYP PFNGLGETGRAPHICSRESETSTATUSKHRPROC)(void)
GLboolean GLboolean g
GLdouble s
[6]
Definition qopenglext.h:235
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
#define GL_NUM_EXTENSIONS
Definition qopenglext.h:906
#define GLuint
#define QStringLiteral(str)
ptrdiff_t qsizetype
Definition qtypes.h:165
static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &errorSource, qsizetype errorPosition)
Definition qurl.cpp:3517
QByteArray ba
[0]
QFile file
[0]
sem release()
QSharedPointer< T > other(t)
[5]
\inmodule QtCore\reentrant
static Gpu fromContext()
Definition qopengl.cpp:460
QVersionNumber driverVersion
Definition qopengl_p.h:58
QByteArray glVendor
Definition qopengl_p.h:60
QByteArray driverDescription
Definition qopengl_p.h:59