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