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);
195 Q_DISABLE_COPY_MOVE(FdWrapper)
197 FdWrapper(
const QString &fn)
199 fd = qt_safe_open(QFile::encodeName(fn).constData(), O_RDONLY);
212 Q_DISABLE_COPY_MOVE(R)
213 explicit R(size_t sz,
void *p)
214 : mapSize{sz}, ptr{p} {}
217 if (ptr != MAP_FAILED)
218 munmap(ptr, mapSize);
221 explicit operator
bool()
const noexcept {
return ptr != MAP_FAILED; }
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};
229 auto mapSize =
static_cast<size_t>(offs);
232 mmap(
nullptr, mapSize, PROT_READ, MAP_SHARED, fd, 0),
262bool QOpenGLProgramBinaryCache::load(
const QByteArray &cacheKey, uint programId)
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());
269 const QString fn = cacheFileName(cacheKey);
270 DeferredFileRemove undertaker(fn);
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);
281 if (!f.open(QIODevice::ReadOnly))
286 if (!verifyHeader(buf)) {
287 undertaker.setActive();
293 const auto map = fdw.map();
295 undertaker.setActive();
298 p =
static_cast<
const uchar *>(map.ptr) + BASE_HEADER_SIZE;
301 p =
reinterpret_cast<
const uchar *>(buf.constData());
306 QByteArray vendor = readStr(&p);
307 if (vendor != info.glvendor) {
310 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VENDOR does not match" << vendor << info.glvendor;
311 undertaker.setActive();
314 QByteArray renderer = readStr(&p);
315 if (renderer != info.glrenderer) {
316 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_RENDERER does not match" << renderer << info.glrenderer;
317 undertaker.setActive();
320 QByteArray version = readStr(&p);
321 if (version != info.glversion) {
322 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VERSION does not match" << version << info.glversion;
323 undertaker.setActive();
327 quint32 blobFormat = readUInt(&p);
328 quint32 blobSize = readUInt(&p);
332 return setProgramBinary(programId, blobFormat, p, blobSize)
333 && m_memCache.insert(cacheKey,
new MemCacheEntry(p, blobSize, blobFormat));
338 memcpy(*p, &value,
sizeof(quint32));
339 *p +=
sizeof(quint32);
342static inline void writeStr(uchar **p,
const QByteArray &str)
344 writeUInt(p, str.size());
345 memcpy(*p, str.constData(), str.size());
349static inline bool writeFile(
const QString &filename,
const QByteArray &data)
351#if QT_CONFIG(temporaryfile)
352 QSaveFile f(filename);
353 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
360 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
361 if (f.write(data) == data.length())
368void QOpenGLProgramBinaryCache::save(
const QByteArray &cacheKey, uint programId)
370 if (!m_cacheWritable)
375 QOpenGLContext *context = QOpenGLContext::currentContext();
376 QOpenGLExtraFunctions *funcs = context->extraFunctions();
379 GLenum error = funcs->glGetError();
385 const int headerSize =
FULL_HEADER_SIZE(info.glvendor.size() + info.glrenderer.size() + info.glversion.size());
392 const int totalSize = headerSize + paddingSize + blobSize;
394 qCDebug(lcOpenGLProgramDiskCache,
"Program binary is %d bytes, err = 0x%x, total %d", blobSize, funcs->glGetError(), totalSize);
398 QByteArray blob(totalSize, Qt::Uninitialized);
399 uchar *p =
reinterpret_cast<uchar *>(blob.data());
401 writeUInt(&p, BINSHADER_MAGIC);
402 writeUInt(&p, BINSHADER_VERSION);
403 writeUInt(&p, BINSHADER_QTVERSION);
404 writeUInt(&p,
sizeof(quintptr));
406 writeStr(&p, info.glvendor);
407 writeStr(&p, info.glrenderer);
408 writeStr(&p, info.glversion);
410 quint32 blobFormat = 0;
411 uchar *blobFormatPtr = p;
412 writeUInt(&p, blobFormat);
413 writeUInt(&p, blobSize);
415 for (
int i = 0; i < paddingSize; ++i)
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);
426 funcs->glGetProgramBinary(programId, blobSize, &outSize, &blobFormat, p);
427 if (blobSize != outSize) {
428 qCDebug(lcOpenGLProgramDiskCache,
"glGetProgramBinary returned size %d instead of %d", outSize, blobSize);
432 writeUInt(&blobFormatPtr, blobFormat);
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);
447 qCDebug(lcOpenGLProgramDiskCache,
"Failed to write %s to shader cache", qPrintable(filename));
450#if QT_CONFIG(opengles2)
451void QOpenGLProgramBinaryCache::initializeProgramBinaryOES(QOpenGLContext *context)
453 if (m_programBinaryOESInitialized)
455 m_programBinaryOESInitialized =
true;
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");
463QOpenGLProgramBinarySupportCheck::QOpenGLProgramBinarySupportCheck(QOpenGLContext *context)
464 : QOpenGLSharedResource(context->shareGroup()),
467 if (QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) {
468 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via app attribute");
471 if (qEnvironmentVariableIntValue(
"QT_DISABLE_SHADER_DISK_CACHE")) {
472 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via env var");
476 QOpenGLContext *ctx = QOpenGLContext::currentContext();
478 if (ctx->isOpenGLES()) {
479 qCDebug(lcOpenGLProgramDiskCache,
"OpenGL ES v%d context", ctx->format().majorVersion());
480 if (ctx->format().majorVersion() >= 3) {
483 const bool hasExt = ctx->hasExtension(
"GL_OES_get_program_binary");
484 qCDebug(lcOpenGLProgramDiskCache,
"GL_OES_get_program_binary support = %d", hasExt);
489 const bool hasExt = ctx->hasExtension(
"GL_ARB_get_program_binary");
490 qCDebug(lcOpenGLProgramDiskCache,
"GL_ARB_get_program_binary support = %d", hasExt);
497 qCDebug(lcOpenGLProgramDiskCache,
"Supported binary format count = %d", fmtCount);
498 m_supported = fmtCount > 0;
501 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache supported = %d", m_supported);
DeferredFileRemove(const QString &fn)
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)