5#include <QOpenGLContext>
6#include <QOpenGLExtraFunctions>
8#include <QStandardPaths>
11#include <QCoreApplication>
12#include <QCryptographicHash>
18#include <private/qcore_unix_p.h>
23using namespace Qt::StringLiterals;
27#ifndef GL_CONTEXT_LOST
28#define GL_CONTEXT_LOST 0x0507
31#ifndef GL_PROGRAM_BINARY_LENGTH
32#define GL_PROGRAM_BINARY_LENGTH 0x8741
35#ifndef GL_NUM_PROGRAM_BINARY_FORMATS
36#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
49 QByteArray glrenderer;
56 QOpenGLContext *ctx = QOpenGLContext::currentContext();
58 QOpenGLFunctions *f = ctx->functions();
59 const char *vendor =
reinterpret_cast<
const char *>(f->glGetString(GL_VENDOR));
60 const char *renderer =
reinterpret_cast<
const char *>(f->glGetString(GL_RENDERER));
61 const char *version =
reinterpret_cast<
const char *>(f->glGetString(GL_VERSION));
63 glvendor = QByteArray(vendor);
65 glrenderer = QByteArray(renderer);
67 glversion = QByteArray(version);
70QByteArray QOpenGLProgramBinaryCache::ProgramDesc::cacheKey()
const
72 QCryptographicHash keyBuilder(QCryptographicHash::Sha1);
73 for (
const QOpenGLProgramBinaryCache::ShaderDesc &shader : shaders)
74 keyBuilder.addData(shader.source);
76 return keyBuilder.result().toHex();
81 QDir::root().mkpath(name);
82 return QFileInfo(name).isWritable();
85QOpenGLProgramBinaryCache::QOpenGLProgramBinaryCache()
86 : m_cacheWritable(
false)
88 const QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
89 if (!cachePath.isEmpty()) {
90 m_cacheDir = cachePath +
"/qtshadercache-"_L1 + QSysInfo::buildAbi() + u'/';
91 m_cacheWritable = qt_ensureWritableDir(m_cacheDir);
94 qCDebug(lcOpenGLProgramDiskCache,
"Cache location '%s' writable = %d", qPrintable(m_cacheDir), m_cacheWritable);
97QString QOpenGLProgramBinaryCache::cacheFileName(
const QByteArray &cacheKey)
const
99 return m_cacheDir + QString::fromUtf8(cacheKey);
102#define BASE_HEADER_SIZE (int(4
* sizeof(quint32)))
104#define PADDING_SIZE(fullHeaderSize) (((fullHeaderSize + 3
) & ~3
) - fullHeaderSize)
110 CacheFileReader(
const uchar *p, qsizetype size) : m_p(p), m_end(p + size) { }
112 quint64 remaining()
const {
return quint64(m_end - m_p); }
113 const uchar *cursor()
const {
return m_p; }
115 bool readUInt(quint32 *v)
117 if (remaining() <
sizeof(quint32))
119 memcpy(v, m_p,
sizeof(quint32));
120 m_p +=
sizeof(quint32);
127 bool readStr(QByteArray *ba)
130 if (!readUInt(&len) || quint64(len) > remaining())
132 *ba = QByteArray::fromRawData(
reinterpret_cast<
const char *>(m_p), len);
151bool QOpenGLProgramBinaryCache::verifyHeader(
const QByteArray &buf)
const
154 qCDebug(lcOpenGLProgramDiskCache,
"Cached size too small");
157 CacheFileReader reader(
reinterpret_cast<
const uchar *>(buf.constData()), buf.size());
159 if (!reader.readUInt(&v) || v != BINSHADER_MAGIC) {
160 qCDebug(lcOpenGLProgramDiskCache,
"Magic does not match");
163 if (!reader.readUInt(&v) || v != BINSHADER_VERSION) {
164 qCDebug(lcOpenGLProgramDiskCache,
"Version does not match");
167 if (!reader.readUInt(&v) || v != BINSHADER_QTVERSION) {
168 qCDebug(lcOpenGLProgramDiskCache,
"Qt version does not match");
171 if (!reader.readUInt(&v) || v !=
sizeof(quintptr)) {
172 qCDebug(lcOpenGLProgramDiskCache,
"Architecture does not match");
178bool QOpenGLProgramBinaryCache::setProgramBinary(uint programId, uint blobFormat,
const void *p, uint blobSize)
180 QOpenGLContext *context = QOpenGLContext::currentContext();
181 QOpenGLExtraFunctions *funcs = context->extraFunctions();
183 GLenum error = funcs->glGetError();
187#if QT_CONFIG(opengles2)
188 if (context->isOpenGLES() && context->format().majorVersion() < 3) {
189 initializeProgramBinaryOES(context);
190 programBinaryOES(programId, blobFormat, p, blobSize);
193 funcs->glProgramBinary(programId, blobFormat, p, blobSize);
195 GLenum err = funcs->glGetError();
196 if (err != GL_NO_ERROR) {
197 qCDebug(lcOpenGLProgramDiskCache,
"Program binary failed to load for program %u, size %d, "
198 "format 0x%x, err = 0x%x",
199 programId, blobSize, blobFormat, err);
202 GLint linkStatus = 0;
203 funcs->glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
204 if (linkStatus != GL_TRUE) {
205 qCDebug(lcOpenGLProgramDiskCache,
"Program binary failed to load for program %u, size %d, "
206 "format 0x%x, linkStatus = 0x%x, err = 0x%x",
207 programId, blobSize, blobFormat, linkStatus, err);
211 qCDebug(lcOpenGLProgramDiskCache,
"Program binary set for program %u, size %d, format 0x%x, err = 0x%x",
212 programId, blobSize, blobFormat, err);
219 Q_DISABLE_COPY_MOVE(FdWrapper)
221 FdWrapper(
const QString &fn)
223 fd = qt_safe_open(QFile::encodeName(fn).constData(), O_RDONLY);
236 Q_DISABLE_COPY_MOVE(R)
237 explicit R(size_t sz,
void *p)
238 : mapSize{sz}, ptr{p} {}
241 if (ptr != MAP_FAILED)
242 munmap(ptr, mapSize);
245 explicit operator
bool()
const noexcept {
return ptr != MAP_FAILED; }
248 off_t offs = lseek(fd, 0, SEEK_END);
249 if (offs == (off_t) -1) {
250 qErrnoWarning(errno,
"lseek failed for program binary");
251 return R{0, MAP_FAILED};
253 auto mapSize =
static_cast<size_t>(offs);
256 mmap(
nullptr, mapSize, PROT_READ, MAP_SHARED, fd, 0),
286bool QOpenGLProgramBinaryCache::load(
const QByteArray &cacheKey, uint programId)
288 QMutexLocker lock(&m_mutex);
289 if (
const MemCacheEntry *e = m_memCache.object(cacheKey))
290 return setProgramBinary(programId, e->format, e->blob.constData(), e->blob.size());
293 const QString fn = cacheFileName(cacheKey);
294 DeferredFileRemove undertaker(fn);
299 char header[BASE_HEADER_SIZE];
300 qint64 bytesRead = qt_safe_read(fdw.fd, header, BASE_HEADER_SIZE);
301 if (bytesRead == BASE_HEADER_SIZE)
302 buf = QByteArray::fromRawData(header, BASE_HEADER_SIZE);
305 if (!f.open(QIODevice::ReadOnly))
310 if (!verifyHeader(buf)) {
311 undertaker.setActive();
316 const auto map = fdw.map();
317 if (!map || map.mapSize < size_t(BASE_HEADER_SIZE)) {
318 undertaker.setActive();
321 CacheFileReader reader(
static_cast<
const uchar *>(map.ptr) + BASE_HEADER_SIZE,
322 qsizetype(map.mapSize) - BASE_HEADER_SIZE);
326 CacheFileReader reader(
reinterpret_cast<
const uchar *>(q20::to_address(buf.cbegin())), buf.size());
334 quint32 blobFormat = 0;
335 quint32 blobSize = 0;
336 if (!reader.readStr(&vendor) || !reader.readStr(&renderer) || !reader.readStr(&version)
337 || !reader.readUInt(&blobFormat) || !reader.readUInt(&blobSize)
340 qCDebug(lcOpenGLProgramDiskCache,
"Cached program binary is truncated");
341 undertaker.setActive();
345 if (vendor != info.glvendor) {
346 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VENDOR does not match" << vendor << info.glvendor;
347 undertaker.setActive();
350 if (renderer != info.glrenderer) {
351 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_RENDERER does not match" << renderer << info.glrenderer;
352 undertaker.setActive();
355 if (version != info.glversion) {
356 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VERSION does not match" << version << info.glversion;
357 undertaker.setActive();
361 if (blobSize > reader.remaining() || blobSize > quint32(std::numeric_limits<
int>::max())) {
362 qCDebug(lcOpenGLProgramDiskCache,
"Cached program binary claims %u bytes, file has %llu",
363 blobSize, reader.remaining());
364 undertaker.setActive();
368 const uchar *p = reader.cursor();
369 return setProgramBinary(programId, blobFormat, p, blobSize)
370 && m_memCache.insert(cacheKey,
new MemCacheEntry(p, blobSize, blobFormat));
375 memcpy(*p, &value,
sizeof(quint32));
376 *p +=
sizeof(quint32);
379static inline void writeStr(uchar **p,
const QByteArray &str)
381 writeUInt(p, str.size());
382 memcpy(*p, str.constData(), str.size());
386static inline bool writeFile(
const QString &filename,
const QByteArray &data)
388#if QT_CONFIG(temporaryfile)
389 QSaveFile f(filename);
390 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
397 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
398 if (f.write(data) == data.length())
405void QOpenGLProgramBinaryCache::save(
const QByteArray &cacheKey, uint programId)
407 QMutexLocker lock(&m_mutex);
409 if (!m_cacheWritable)
414 QOpenGLContext *context = QOpenGLContext::currentContext();
415 QOpenGLExtraFunctions *funcs = context->extraFunctions();
418 GLenum error = funcs->glGetError();
424 const int headerSize =
FULL_HEADER_SIZE(info.glvendor.size() + info.glrenderer.size() + info.glversion.size());
431 const int totalSize = headerSize + paddingSize + blobSize;
433 qCDebug(lcOpenGLProgramDiskCache,
"Program binary is %d bytes, err = 0x%x, total %d", blobSize, funcs->glGetError(), totalSize);
437 QByteArray blob(totalSize, Qt::Uninitialized);
438 uchar *p =
reinterpret_cast<uchar *>(blob.data());
440 writeUInt(&p, BINSHADER_MAGIC);
441 writeUInt(&p, BINSHADER_VERSION);
442 writeUInt(&p, BINSHADER_QTVERSION);
443 writeUInt(&p,
sizeof(quintptr));
445 writeStr(&p, info.glvendor);
446 writeStr(&p, info.glrenderer);
447 writeStr(&p, info.glversion);
449 quint32 blobFormat = 0;
450 uchar *blobFormatPtr = p;
451 writeUInt(&p, blobFormat);
452 writeUInt(&p, blobSize);
454 for (
int i = 0; i < paddingSize; ++i)
458#if QT_CONFIG(opengles2)
459 if (context->isOpenGLES() && context->format().majorVersion() < 3) {
460 initializeProgramBinaryOES(context);
461 getProgramBinaryOES(programId, blobSize, &outSize, &blobFormat, p);
464 funcs->glGetProgramBinary(programId, blobSize, &outSize, &blobFormat, p);
465 if (blobSize != outSize) {
466 qCDebug(lcOpenGLProgramDiskCache,
"glGetProgramBinary returned size %d instead of %d", outSize, blobSize);
470 writeUInt(&blobFormatPtr, blobFormat);
472 const QString filename = cacheFileName(cacheKey);
473 if (!writeFile(filename, blob))
474 qCDebug(lcOpenGLProgramDiskCache,
"Failed to write %s to shader cache", qPrintable(filename));
477#if QT_CONFIG(opengles2)
478void QOpenGLProgramBinaryCache::initializeProgramBinaryOES(QOpenGLContext *context)
480 if (m_programBinaryOESInitialized)
482 m_programBinaryOESInitialized =
true;
485 getProgramBinaryOES = (
void (QOPENGLF_APIENTRYP)(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary))context->getProcAddress(
"glGetProgramBinaryOES");
486 programBinaryOES = (
void (QOPENGLF_APIENTRYP)(GLuint program, GLenum binaryFormat,
const GLvoid *binary, GLint length))context->getProcAddress(
"glProgramBinaryOES");
490QOpenGLProgramBinarySupportCheck::QOpenGLProgramBinarySupportCheck(QOpenGLContext *context)
491 : QOpenGLSharedResource(context->shareGroup()),
494 if (QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) {
495 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via app attribute");
498 if (qEnvironmentVariableIntValue(
"QT_DISABLE_SHADER_DISK_CACHE")) {
499 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via env var");
503 QOpenGLContext *ctx = QOpenGLContext::currentContext();
505 if (ctx->isOpenGLES()) {
506 qCDebug(lcOpenGLProgramDiskCache,
"OpenGL ES v%d context", ctx->format().majorVersion());
507 if (ctx->format().majorVersion() >= 3) {
510 const bool hasExt = ctx->hasExtension(
"GL_OES_get_program_binary");
511 qCDebug(lcOpenGLProgramDiskCache,
"GL_OES_get_program_binary support = %d", hasExt);
516 const bool hasExt = ctx->hasExtension(
"GL_ARB_get_program_binary");
517 qCDebug(lcOpenGLProgramDiskCache,
"GL_ARB_get_program_binary support = %d", hasExt);
524 qCDebug(lcOpenGLProgramDiskCache,
"Supported binary format count = %d", fmtCount);
525 m_supported = fmtCount > 0;
528 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
#define PADDING_SIZE(fullHeaderSize)
static void writeStr(uchar **p, const QByteArray &str)
const quint32 BINSHADER_MAGIC
static void writeUInt(uchar **p, quint32 value)
#define FULL_HEADER_SIZE(stringsSize)