5#include <QOpenGLContext>
6#include <QOpenGLExtraFunctions>
8#include <QStandardPaths>
11#include <QCoreApplication>
12#include <QCryptographicHash>
16#include <private/qcore_unix_p.h>
21using namespace Qt::StringLiterals;
25#ifndef GL_CONTEXT_LOST
26#define GL_CONTEXT_LOST 0x0507
29#ifndef GL_PROGRAM_BINARY_LENGTH
30#define GL_PROGRAM_BINARY_LENGTH 0x8741
33#ifndef GL_NUM_PROGRAM_BINARY_FORMATS
34#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
47 QByteArray glrenderer;
54 QOpenGLContext *ctx = QOpenGLContext::currentContext();
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));
61 glvendor = QByteArray(vendor);
63 glrenderer = QByteArray(renderer);
65 glversion = QByteArray(version);
68QByteArray QOpenGLProgramBinaryCache::ProgramDesc::cacheKey()
const
70 QCryptographicHash keyBuilder(QCryptographicHash::Sha1);
71 for (
const QOpenGLProgramBinaryCache::ShaderDesc &shader : shaders)
72 keyBuilder.addData(shader.source);
74 return keyBuilder.result().toHex();
79 QDir::root().mkpath(name);
80 return QFileInfo(name).isWritable();
83QOpenGLProgramBinaryCache::QOpenGLProgramBinaryCache()
84 : m_cacheWritable(
false)
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;
91 if (!sharedCachePath.isEmpty()) {
92 m_currentCacheDir = m_globalCacheDir;
93 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
95 if (!m_cacheWritable) {
96 m_currentCacheDir = m_localCacheDir;
97 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
100 qCDebug(lcOpenGLProgramDiskCache,
"Cache location '%s' writable = %d", qPrintable(m_currentCacheDir), m_cacheWritable);
103QString QOpenGLProgramBinaryCache::cacheFileName(
const QByteArray &cacheKey)
const
105 return m_currentCacheDir + QString::fromUtf8(cacheKey);
108#define BASE_HEADER_SIZE (int(4
* sizeof(quint32)))
110#define PADDING_SIZE(fullHeaderSize) (((fullHeaderSize + 3
) & ~3
) - fullHeaderSize)
115 memcpy(&v, *p,
sizeof(quint32));
116 *p +=
sizeof(quint32);
122 quint32 len = readUInt(p);
123 QByteArray ba = QByteArray::fromRawData(
reinterpret_cast<
const char *>(*p), len);
128bool QOpenGLProgramBinaryCache::verifyHeader(
const QByteArray &buf)
const
131 qCDebug(lcOpenGLProgramDiskCache,
"Cached size too small");
134 const uchar *p =
reinterpret_cast<
const uchar *>(buf.constData());
135 if (readUInt(&p) != BINSHADER_MAGIC) {
136 qCDebug(lcOpenGLProgramDiskCache,
"Magic does not match");
139 if (readUInt(&p) != BINSHADER_VERSION) {
140 qCDebug(lcOpenGLProgramDiskCache,
"Version does not match");
143 if (readUInt(&p) != BINSHADER_QTVERSION) {
144 qCDebug(lcOpenGLProgramDiskCache,
"Qt version does not match");
147 if (readUInt(&p) !=
sizeof(quintptr)) {
148 qCDebug(lcOpenGLProgramDiskCache,
"Architecture does not match");
154bool QOpenGLProgramBinaryCache::setProgramBinary(uint programId, uint blobFormat,
const void *p, uint blobSize)
156 QOpenGLContext *context = QOpenGLContext::currentContext();
157 QOpenGLExtraFunctions *funcs = context->extraFunctions();
159 GLenum error = funcs->glGetError();
163#if QT_CONFIG(opengles2)
164 if (context->isOpenGLES() && context->format().majorVersion() < 3) {
165 initializeProgramBinaryOES(context);
166 programBinaryOES(programId, blobFormat, p, blobSize);
169 funcs->glProgramBinary(programId, blobFormat, p, blobSize);
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);
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);
187 qCDebug(lcOpenGLProgramDiskCache,
"Program binary set for program %u, size %d, format 0x%x, err = 0x%x",
188 programId, blobSize, blobFormat, err);
196 FdWrapper(
const QString &fn)
199 fd = qt_safe_open(QFile::encodeName(fn).constData(), O_RDONLY);
203 if (ptr != MAP_FAILED)
204 munmap(ptr, mapSize);
210 off_t offs = lseek(fd, 0, SEEK_END);
211 if (offs == (off_t) -1) {
212 qErrnoWarning(errno,
"lseek failed for program binary");
215 mapSize =
static_cast<size_t>(offs);
216 ptr = mmap(
nullptr, mapSize, PROT_READ, MAP_SHARED, fd, 0);
217 return ptr != MAP_FAILED;
248bool QOpenGLProgramBinaryCache::load(
const QByteArray &cacheKey, uint programId)
250 QMutexLocker lock(&m_mutex);
251 if (
const MemCacheEntry *e = m_memCache.object(cacheKey))
252 return setProgramBinary(programId, e->format, e->blob.constData(), e->blob.size());
255 const QString fn = cacheFileName(cacheKey);
256 DeferredFileRemove undertaker(fn);
261 char header[BASE_HEADER_SIZE];
262 qint64 bytesRead = qt_safe_read(fdw.fd, header, BASE_HEADER_SIZE);
263 if (bytesRead == BASE_HEADER_SIZE)
264 buf = QByteArray::fromRawData(header, BASE_HEADER_SIZE);
267 if (!f.open(QIODevice::ReadOnly))
272 if (!verifyHeader(buf)) {
273 undertaker.setActive();
280 undertaker.setActive();
283 p =
static_cast<
const uchar *>(fdw.ptr) + BASE_HEADER_SIZE;
286 p =
reinterpret_cast<
const uchar *>(buf.constData());
291 QByteArray vendor = readStr(&p);
292 if (vendor != info.glvendor) {
295 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VENDOR does not match" << vendor << info.glvendor;
296 undertaker.setActive();
299 QByteArray renderer = readStr(&p);
300 if (renderer != info.glrenderer) {
301 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_RENDERER does not match" << renderer << info.glrenderer;
302 undertaker.setActive();
305 QByteArray version = readStr(&p);
306 if (version != info.glversion) {
307 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VERSION does not match" << version << info.glversion;
308 undertaker.setActive();
312 quint32 blobFormat = readUInt(&p);
313 quint32 blobSize = readUInt(&p);
317 return setProgramBinary(programId, blobFormat, p, blobSize)
318 && m_memCache.insert(cacheKey,
new MemCacheEntry(p, blobSize, blobFormat));
323 memcpy(*p, &value,
sizeof(quint32));
324 *p +=
sizeof(quint32);
327static inline void writeStr(uchar **p,
const QByteArray &str)
329 writeUInt(p, str.size());
330 memcpy(*p, str.constData(), str.size());
334static inline bool writeFile(
const QString &filename,
const QByteArray &data)
336#if QT_CONFIG(temporaryfile)
337 QSaveFile f(filename);
338 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
345 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
346 if (f.write(data) == data.length())
353void QOpenGLProgramBinaryCache::save(
const QByteArray &cacheKey, uint programId)
355 if (!m_cacheWritable)
360 QOpenGLContext *context = QOpenGLContext::currentContext();
361 QOpenGLExtraFunctions *funcs = context->extraFunctions();
364 GLenum error = funcs->glGetError();
370 const int headerSize =
FULL_HEADER_SIZE(info.glvendor.size() + info.glrenderer.size() + info.glversion.size());
377 const int totalSize = headerSize + paddingSize + blobSize;
379 qCDebug(lcOpenGLProgramDiskCache,
"Program binary is %d bytes, err = 0x%x, total %d", blobSize, funcs->glGetError(), totalSize);
383 QByteArray blob(totalSize, Qt::Uninitialized);
384 uchar *p =
reinterpret_cast<uchar *>(blob.data());
386 writeUInt(&p, BINSHADER_MAGIC);
387 writeUInt(&p, BINSHADER_VERSION);
388 writeUInt(&p, BINSHADER_QTVERSION);
389 writeUInt(&p,
sizeof(quintptr));
391 writeStr(&p, info.glvendor);
392 writeStr(&p, info.glrenderer);
393 writeStr(&p, info.glversion);
395 quint32 blobFormat = 0;
396 uchar *blobFormatPtr = p;
397 writeUInt(&p, blobFormat);
398 writeUInt(&p, blobSize);
400 for (
int i = 0; i < paddingSize; ++i)
404#if QT_CONFIG(opengles2)
405 if (context->isOpenGLES() && context->format().majorVersion() < 3) {
406 QMutexLocker lock(&m_mutex);
407 initializeProgramBinaryOES(context);
408 getProgramBinaryOES(programId, blobSize, &outSize, &blobFormat, p);
411 funcs->glGetProgramBinary(programId, blobSize, &outSize, &blobFormat, p);
412 if (blobSize != outSize) {
413 qCDebug(lcOpenGLProgramDiskCache,
"glGetProgramBinary returned size %d instead of %d", outSize, blobSize);
417 writeUInt(&blobFormatPtr, blobFormat);
419 QString filename = cacheFileName(cacheKey);
420 bool ok = writeFile(filename, blob);
421 if (!ok && m_currentCacheDir == m_globalCacheDir) {
422 m_currentCacheDir = m_localCacheDir;
423 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
424 qCDebug(lcOpenGLProgramDiskCache,
"Cache location changed to '%s' writable = %d",
425 qPrintable(m_currentCacheDir), m_cacheWritable);
426 if (m_cacheWritable) {
427 filename = cacheFileName(cacheKey);
428 ok = writeFile(filename, blob);
432 qCDebug(lcOpenGLProgramDiskCache,
"Failed to write %s to shader cache", qPrintable(filename));
435#if QT_CONFIG(opengles2)
436void QOpenGLProgramBinaryCache::initializeProgramBinaryOES(QOpenGLContext *context)
438 if (m_programBinaryOESInitialized)
440 m_programBinaryOESInitialized =
true;
443 getProgramBinaryOES = (
void (QOPENGLF_APIENTRYP)(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary))context->getProcAddress(
"glGetProgramBinaryOES");
444 programBinaryOES = (
void (QOPENGLF_APIENTRYP)(GLuint program, GLenum binaryFormat,
const GLvoid *binary, GLint length))context->getProcAddress(
"glProgramBinaryOES");
448QOpenGLProgramBinarySupportCheck::QOpenGLProgramBinarySupportCheck(QOpenGLContext *context)
449 : QOpenGLSharedResource(context->shareGroup()),
452 if (QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) {
453 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via app attribute");
456 if (qEnvironmentVariableIntValue(
"QT_DISABLE_SHADER_DISK_CACHE")) {
457 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via env var");
461 QOpenGLContext *ctx = QOpenGLContext::currentContext();
463 if (ctx->isOpenGLES()) {
464 qCDebug(lcOpenGLProgramDiskCache,
"OpenGL ES v%d context", ctx->format().majorVersion());
465 if (ctx->format().majorVersion() >= 3) {
468 const bool hasExt = ctx->hasExtension(
"GL_OES_get_program_binary");
469 qCDebug(lcOpenGLProgramDiskCache,
"GL_OES_get_program_binary support = %d", hasExt);
474 const bool hasExt = ctx->hasExtension(
"GL_ARB_get_program_binary");
475 qCDebug(lcOpenGLProgramDiskCache,
"GL_ARB_get_program_binary support = %d", hasExt);
482 qCDebug(lcOpenGLProgramDiskCache,
"Supported binary format count = %d", fmtCount);
483 m_supported = fmtCount > 0;
486 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache supported = %d", m_supported);
DeferredFileRemove(const QString &fn)
Combined button and popup list for selecting options.
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
#define GL_NUM_PROGRAM_BINARY_FORMATS
#define GL_PROGRAM_BINARY_LENGTH
static bool qt_ensureWritableDir(const QString &name)
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)