Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qopenglprogrambinarycache.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
5#include <QOpenGLContext>
6#include <QOpenGLExtraFunctions>
7#include <QSysInfo>
8#include <QStandardPaths>
9#include <QDir>
10#include <QSaveFile>
11#include <QCoreApplication>
12#include <QCryptographicHash>
13#include <q20memory.h>
14
15#ifdef Q_OS_UNIX
16#include <sys/mman.h>
17#include <private/qcore_unix_p.h>
18#endif
19
21
22using namespace Qt::StringLiterals;
23
24Q_LOGGING_CATEGORY(lcOpenGLProgramDiskCache, "qt.opengl.diskcache")
25
26#ifndef GL_CONTEXT_LOST
27#define GL_CONTEXT_LOST 0x0507
28#endif
29
30#ifndef GL_PROGRAM_BINARY_LENGTH
31#define GL_PROGRAM_BINARY_LENGTH 0x8741
32#endif
33
34#ifndef GL_NUM_PROGRAM_BINARY_FORMATS
35#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
36#endif
37
38const quint32 BINSHADER_MAGIC = 0x5174;
40const quint32 BINSHADER_QTVERSION = QT_VERSION;
41
42namespace {
43struct GLEnvInfo
44{
45 GLEnvInfo();
46
47 QByteArray glvendor;
48 QByteArray glrenderer;
49 QByteArray glversion;
50};
51}
52
53GLEnvInfo::GLEnvInfo()
54{
55 QOpenGLContext *ctx = QOpenGLContext::currentContext();
56 Q_ASSERT(ctx);
57 QOpenGLFunctions *f = ctx->functions();
58 const char *vendor = reinterpret_cast<const char *>(f->glGetString(GL_VENDOR));
59 const char *renderer = reinterpret_cast<const char *>(f->glGetString(GL_RENDERER));
60 const char *version = reinterpret_cast<const char *>(f->glGetString(GL_VERSION));
61 if (vendor)
62 glvendor = QByteArray(vendor);
63 if (renderer)
64 glrenderer = QByteArray(renderer);
65 if (version)
66 glversion = QByteArray(version);
67}
68
69QByteArray QOpenGLProgramBinaryCache::ProgramDesc::cacheKey() const
70{
71 QCryptographicHash keyBuilder(QCryptographicHash::Sha1);
72 for (const QOpenGLProgramBinaryCache::ShaderDesc &shader : shaders)
73 keyBuilder.addData(shader.source);
74
75 return keyBuilder.result().toHex();
76}
77
78static inline bool qt_ensureWritableDir(const QString &name)
79{
80 QDir::root().mkpath(name);
81 return QFileInfo(name).isWritable();
82}
83
84QOpenGLProgramBinaryCache::QOpenGLProgramBinaryCache()
85 : m_cacheWritable(false)
86{
87 const QString subPath = "/qtshadercache-"_L1 + QSysInfo::buildAbi() + u'/';
88 const QString sharedCachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
89 m_globalCacheDir = sharedCachePath + subPath;
90 m_localCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + subPath;
91
92 if (!sharedCachePath.isEmpty()) {
93 m_currentCacheDir = m_globalCacheDir;
94 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
95 }
96 if (!m_cacheWritable) {
97 m_currentCacheDir = m_localCacheDir;
98 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
99 }
100
101 qCDebug(lcOpenGLProgramDiskCache, "Cache location '%s' writable = %d", qPrintable(m_currentCacheDir), m_cacheWritable);
102}
103
104QString QOpenGLProgramBinaryCache::cacheFileName(const QByteArray &cacheKey) const
105{
106 return m_currentCacheDir + QString::fromUtf8(cacheKey);
107}
108
109#define BASE_HEADER_SIZE (int(4 * sizeof(quint32)))
110#define FULL_HEADER_SIZE(stringsSize) (BASE_HEADER_SIZE + 12 + stringsSize + 8)
111#define PADDING_SIZE(fullHeaderSize) (((fullHeaderSize + 3) & ~3) - fullHeaderSize)
112
113static inline quint32 readUInt(const uchar **p)
114{
115 quint32 v;
116 memcpy(&v, *p, sizeof(quint32));
117 *p += sizeof(quint32);
118 return v;
119}
120
121static inline QByteArray readStr(const uchar **p)
122{
123 quint32 len = readUInt(p);
124 QByteArray ba = QByteArray::fromRawData(reinterpret_cast<const char *>(*p), len);
125 *p += len;
126 return ba;
127}
128
129bool QOpenGLProgramBinaryCache::verifyHeader(const QByteArray &buf) const
130{
131 if (buf.size() < BASE_HEADER_SIZE) {
132 qCDebug(lcOpenGLProgramDiskCache, "Cached size too small");
133 return false;
134 }
135 const uchar *p = reinterpret_cast<const uchar *>(buf.constData());
136 if (readUInt(&p) != BINSHADER_MAGIC) {
137 qCDebug(lcOpenGLProgramDiskCache, "Magic does not match");
138 return false;
139 }
140 if (readUInt(&p) != BINSHADER_VERSION) {
141 qCDebug(lcOpenGLProgramDiskCache, "Version does not match");
142 return false;
143 }
144 if (readUInt(&p) != BINSHADER_QTVERSION) {
145 qCDebug(lcOpenGLProgramDiskCache, "Qt version does not match");
146 return false;
147 }
148 if (readUInt(&p) != sizeof(quintptr)) {
149 qCDebug(lcOpenGLProgramDiskCache, "Architecture does not match");
150 return false;
151 }
152 return true;
153}
154
155bool QOpenGLProgramBinaryCache::setProgramBinary(uint programId, uint blobFormat, const void *p, uint blobSize)
156{
157 QOpenGLContext *context = QOpenGLContext::currentContext();
158 QOpenGLExtraFunctions *funcs = context->extraFunctions();
159 while (true) {
160 GLenum error = funcs->glGetError();
161 if (error == GL_NO_ERROR || error == GL_CONTEXT_LOST)
162 break;
163 }
164#if QT_CONFIG(opengles2)
165 if (context->isOpenGLES() && context->format().majorVersion() < 3) {
166 initializeProgramBinaryOES(context);
167 programBinaryOES(programId, blobFormat, p, blobSize);
168 } else
169#endif
170 funcs->glProgramBinary(programId, blobFormat, p, blobSize);
171
172 GLenum err = funcs->glGetError();
173 if (err != GL_NO_ERROR) {
174 qCDebug(lcOpenGLProgramDiskCache, "Program binary failed to load for program %u, size %d, "
175 "format 0x%x, err = 0x%x",
176 programId, blobSize, blobFormat, err);
177 return false;
178 }
179 GLint linkStatus = 0;
180 funcs->glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
181 if (linkStatus != GL_TRUE) {
182 qCDebug(lcOpenGLProgramDiskCache, "Program binary failed to load for program %u, size %d, "
183 "format 0x%x, linkStatus = 0x%x, err = 0x%x",
184 programId, blobSize, blobFormat, linkStatus, err);
185 return false;
186 }
187
188 qCDebug(lcOpenGLProgramDiskCache, "Program binary set for program %u, size %d, format 0x%x, err = 0x%x",
189 programId, blobSize, blobFormat, err);
190 return true;
191}
192
193#ifdef Q_OS_UNIX
194class FdWrapper
195{
196 Q_DISABLE_COPY_MOVE(FdWrapper)
197public:
198 FdWrapper(const QString &fn)
199 {
200 fd = qt_safe_open(QFile::encodeName(fn).constData(), O_RDONLY);
201 }
202 ~FdWrapper()
203 {
204 if (fd != -1)
205 qt_safe_close(fd);
206 }
207 auto map()
208 {
209 struct R {
210 size_t mapSize;
211 void *ptr;
212
213 Q_DISABLE_COPY_MOVE(R)
214 explicit R(size_t sz, void *p)
215 : mapSize{sz}, ptr{p} {}
216 ~R()
217 {
218 if (ptr != MAP_FAILED)
219 munmap(ptr, mapSize);
220 }
221
222 explicit operator bool() const noexcept { return ptr != MAP_FAILED; }
223 };
224
225 off_t offs = lseek(fd, 0, SEEK_END);
226 if (offs == (off_t) -1) {
227 qErrnoWarning(errno, "lseek failed for program binary");
228 return R{0, MAP_FAILED};
229 }
230 auto mapSize = static_cast<size_t>(offs);
231 return R{
232 mapSize,
233 mmap(nullptr, mapSize, PROT_READ, MAP_SHARED, fd, 0),
234 };
235 }
236
237 int fd;
238};
239#endif
240
242{
243public:
244 DeferredFileRemove(const QString &fn)
245 : fn(fn),
246 active(false)
247 {
248 }
250 {
251 if (active)
252 QFile(fn).remove();
253 }
255 {
256 active = true;
257 }
258
260 bool active;
261};
262
263bool QOpenGLProgramBinaryCache::load(const QByteArray &cacheKey, uint programId)
264{
265 QMutexLocker lock(&m_mutex);
266 if (const MemCacheEntry *e = m_memCache.object(cacheKey))
267 return setProgramBinary(programId, e->format, e->blob.constData(), e->blob.size());
268
269 QByteArray buf;
270 const QString fn = cacheFileName(cacheKey);
271 DeferredFileRemove undertaker(fn);
272#ifdef Q_OS_UNIX
273 FdWrapper fdw(fn);
274 if (fdw.fd == -1)
275 return false;
276 char header[BASE_HEADER_SIZE];
277 qint64 bytesRead = qt_safe_read(fdw.fd, header, BASE_HEADER_SIZE);
278 if (bytesRead == BASE_HEADER_SIZE)
279 buf = QByteArray::fromRawData(header, BASE_HEADER_SIZE);
280#else
281 QFile f(fn);
282 if (!f.open(QIODevice::ReadOnly))
283 return false;
284 buf = f.read(BASE_HEADER_SIZE);
285#endif
286
287 if (!verifyHeader(buf)) {
288 undertaker.setActive();
289 return false;
290 }
291
292 const uchar *p;
293#ifdef Q_OS_UNIX
294 const auto map = fdw.map();
295 if (!map) {
296 undertaker.setActive();
297 return false;
298 }
299 p = static_cast<const uchar *>(map.ptr) + BASE_HEADER_SIZE;
300#else
301 buf = f.readAll();
302 // QTBUG-142080: QByteArray::constData() confuses GCC, do it differently
303 p = reinterpret_cast<const uchar *>(q20::to_address(buf.cbegin()));
304#endif
305
306 GLEnvInfo info;
307
308 QByteArray vendor = readStr(&p);
309 if (vendor != info.glvendor) {
310 // readStr returns non-null terminated strings just pointing to inside
311 // 'p' so must print these via the stream qCDebug and not constData().
312 qCDebug(lcOpenGLProgramDiskCache) << "GL_VENDOR does not match" << vendor << info.glvendor;
313 undertaker.setActive();
314 return false;
315 }
316 QByteArray renderer = readStr(&p);
317 if (renderer != info.glrenderer) {
318 qCDebug(lcOpenGLProgramDiskCache) << "GL_RENDERER does not match" << renderer << info.glrenderer;
319 undertaker.setActive();
320 return false;
321 }
322 QByteArray version = readStr(&p);
323 if (version != info.glversion) {
324 qCDebug(lcOpenGLProgramDiskCache) << "GL_VERSION does not match" << version << info.glversion;
325 undertaker.setActive();
326 return false;
327 }
328
329 quint32 blobFormat = readUInt(&p);
330 quint32 blobSize = readUInt(&p);
331
332 p += PADDING_SIZE(FULL_HEADER_SIZE(vendor.size() + renderer.size() + version.size()));
333
334 return setProgramBinary(programId, blobFormat, p, blobSize)
335 && m_memCache.insert(cacheKey, new MemCacheEntry(p, blobSize, blobFormat));
336}
337
338static inline void writeUInt(uchar **p, quint32 value)
339{
340 memcpy(*p, &value, sizeof(quint32));
341 *p += sizeof(quint32);
342}
343
344static inline void writeStr(uchar **p, const QByteArray &str)
345{
346 writeUInt(p, str.size());
347 memcpy(*p, str.constData(), str.size());
348 *p += str.size();
349}
350
351static inline bool writeFile(const QString &filename, const QByteArray &data)
352{
353#if QT_CONFIG(temporaryfile)
354 QSaveFile f(filename);
355 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
356 f.write(data);
357 if (f.commit())
358 return true;
359 }
360#else
361 QFile f(filename);
362 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
363 if (f.write(data) == data.length())
364 return true;
365 }
366#endif
367 return false;
368}
369
370void QOpenGLProgramBinaryCache::save(const QByteArray &cacheKey, uint programId)
371{
372 if (!m_cacheWritable)
373 return;
374
375 GLEnvInfo info;
376
377 QOpenGLContext *context = QOpenGLContext::currentContext();
378 QOpenGLExtraFunctions *funcs = context->extraFunctions();
379 GLint blobSize = 0;
380 while (true) {
381 GLenum error = funcs->glGetError();
382 if (error == GL_NO_ERROR || error == GL_CONTEXT_LOST)
383 break;
384 }
385 funcs->glGetProgramiv(programId, GL_PROGRAM_BINARY_LENGTH, &blobSize);
386
387 const int headerSize = FULL_HEADER_SIZE(info.glvendor.size() + info.glrenderer.size() + info.glversion.size());
388
389 // Add padding to make the blob start 4-byte aligned in order to support
390 // OpenGL implementations on ARM that choke on non-aligned pointers passed
391 // to glProgramBinary.
392 const int paddingSize = PADDING_SIZE(headerSize);
393
394 const int totalSize = headerSize + paddingSize + blobSize;
395
396 qCDebug(lcOpenGLProgramDiskCache, "Program binary is %d bytes, err = 0x%x, total %d", blobSize, funcs->glGetError(), totalSize);
397 if (!blobSize)
398 return;
399
400 QByteArray blob(totalSize, Qt::Uninitialized);
401 uchar *p = reinterpret_cast<uchar *>(blob.data());
402
403 writeUInt(&p, BINSHADER_MAGIC);
404 writeUInt(&p, BINSHADER_VERSION);
405 writeUInt(&p, BINSHADER_QTVERSION);
406 writeUInt(&p, sizeof(quintptr));
407
408 writeStr(&p, info.glvendor);
409 writeStr(&p, info.glrenderer);
410 writeStr(&p, info.glversion);
411
412 quint32 blobFormat = 0;
413 uchar *blobFormatPtr = p;
414 writeUInt(&p, blobFormat);
415 writeUInt(&p, blobSize);
416
417 for (int i = 0; i < paddingSize; ++i)
418 *p++ = 0;
419
420 GLint outSize = 0;
421#if QT_CONFIG(opengles2)
422 if (context->isOpenGLES() && context->format().majorVersion() < 3) {
423 QMutexLocker lock(&m_mutex);
424 initializeProgramBinaryOES(context);
425 getProgramBinaryOES(programId, blobSize, &outSize, &blobFormat, p);
426 } else
427#endif
428 funcs->glGetProgramBinary(programId, blobSize, &outSize, &blobFormat, p);
429 if (blobSize != outSize) {
430 qCDebug(lcOpenGLProgramDiskCache, "glGetProgramBinary returned size %d instead of %d", outSize, blobSize);
431 return;
432 }
433
434 writeUInt(&blobFormatPtr, blobFormat);
435
436 QString filename = cacheFileName(cacheKey);
437 bool ok = writeFile(filename, blob);
438 if (!ok && m_currentCacheDir == m_globalCacheDir) {
439 m_currentCacheDir = m_localCacheDir;
440 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
441 qCDebug(lcOpenGLProgramDiskCache, "Cache location changed to '%s' writable = %d",
442 qPrintable(m_currentCacheDir), m_cacheWritable);
443 if (m_cacheWritable) {
444 filename = cacheFileName(cacheKey);
445 ok = writeFile(filename, blob);
446 }
447 }
448 if (!ok)
449 qCDebug(lcOpenGLProgramDiskCache, "Failed to write %s to shader cache", qPrintable(filename));
450}
451
452#if QT_CONFIG(opengles2)
453void QOpenGLProgramBinaryCache::initializeProgramBinaryOES(QOpenGLContext *context)
454{
455 if (m_programBinaryOESInitialized)
456 return;
457 m_programBinaryOESInitialized = true;
458
459 Q_ASSERT(context);
460 getProgramBinaryOES = (void (QOPENGLF_APIENTRYP)(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary))context->getProcAddress("glGetProgramBinaryOES");
461 programBinaryOES = (void (QOPENGLF_APIENTRYP)(GLuint program, GLenum binaryFormat, const GLvoid *binary, GLint length))context->getProcAddress("glProgramBinaryOES");
462}
463#endif
464
465QOpenGLProgramBinarySupportCheck::QOpenGLProgramBinarySupportCheck(QOpenGLContext *context)
466 : QOpenGLSharedResource(context->shareGroup()),
467 m_supported(false)
468{
469 if (QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) {
470 qCDebug(lcOpenGLProgramDiskCache, "Shader cache disabled via app attribute");
471 return;
472 }
473 if (qEnvironmentVariableIntValue("QT_DISABLE_SHADER_DISK_CACHE")) {
474 qCDebug(lcOpenGLProgramDiskCache, "Shader cache disabled via env var");
475 return;
476 }
477
478 QOpenGLContext *ctx = QOpenGLContext::currentContext();
479 if (ctx) {
480 if (ctx->isOpenGLES()) {
481 qCDebug(lcOpenGLProgramDiskCache, "OpenGL ES v%d context", ctx->format().majorVersion());
482 if (ctx->format().majorVersion() >= 3) {
483 m_supported = true;
484 } else {
485 const bool hasExt = ctx->hasExtension("GL_OES_get_program_binary");
486 qCDebug(lcOpenGLProgramDiskCache, "GL_OES_get_program_binary support = %d", hasExt);
487 if (hasExt)
488 m_supported = true;
489 }
490 } else {
491 const bool hasExt = ctx->hasExtension("GL_ARB_get_program_binary");
492 qCDebug(lcOpenGLProgramDiskCache, "GL_ARB_get_program_binary support = %d", hasExt);
493 if (hasExt)
494 m_supported = true;
495 }
496 if (m_supported) {
497 GLint fmtCount = 0;
498 ctx->functions()->glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &fmtCount);
499 qCDebug(lcOpenGLProgramDiskCache, "Supported binary format count = %d", fmtCount);
500 m_supported = fmtCount > 0;
501 }
502 }
503 qCDebug(lcOpenGLProgramDiskCache, "Shader cache supported = %d", m_supported);
504}
505
506QT_END_NAMESPACE
DeferredFileRemove(const QString &fn)
Combined button and popup list for selecting options.
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
#define GL_CONTEXT_LOST
Definition qopengl.cpp:30
#define GL_NUM_PROGRAM_BINARY_FORMATS
#define GL_PROGRAM_BINARY_LENGTH
static bool qt_ensureWritableDir(const QString &name)
#define BASE_HEADER_SIZE
const quint32 BINSHADER_VERSION
static bool writeFile(const QString &filename, const QByteArray &data)
const quint32 BINSHADER_QTVERSION
static quint32 readUInt(const uchar **p)
#define PADDING_SIZE(fullHeaderSize)
static void writeStr(uchar **p, const QByteArray &str)
static QByteArray readStr(const uchar **p)
const quint32 BINSHADER_MAGIC
static void writeUInt(uchar **p, quint32 value)
#define FULL_HEADER_SIZE(stringsSize)