5#include <QOpenGLContext>
6#include <QOpenGLExtraFunctions>
8#include <QStandardPaths>
11#include <QCoreApplication>
12#include <QCryptographicHash>
17#include <private/qcore_unix_p.h>
22using namespace Qt::StringLiterals;
26#ifndef GL_CONTEXT_LOST
27#define GL_CONTEXT_LOST 0x0507
30#ifndef GL_PROGRAM_BINARY_LENGTH
31#define GL_PROGRAM_BINARY_LENGTH 0x8741
34#ifndef GL_NUM_PROGRAM_BINARY_FORMATS
35#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
48 QByteArray glrenderer;
55 QOpenGLContext *ctx = QOpenGLContext::currentContext();
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));
62 glvendor = QByteArray(vendor);
64 glrenderer = QByteArray(renderer);
66 glversion = QByteArray(version);
69QByteArray QOpenGLProgramBinaryCache::ProgramDesc::cacheKey()
const
71 QCryptographicHash keyBuilder(QCryptographicHash::Sha1);
72 for (
const QOpenGLProgramBinaryCache::ShaderDesc &shader : shaders)
73 keyBuilder.addData(shader.source);
75 return keyBuilder.result().toHex();
80 QDir::root().mkpath(name);
81 return QFileInfo(name).isWritable();
84QOpenGLProgramBinaryCache::QOpenGLProgramBinaryCache()
85 : m_cacheWritable(
false)
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;
92 if (!sharedCachePath.isEmpty()) {
93 m_currentCacheDir = m_globalCacheDir;
94 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
96 if (!m_cacheWritable) {
97 m_currentCacheDir = m_localCacheDir;
98 m_cacheWritable = qt_ensureWritableDir(m_currentCacheDir);
101 qCDebug(lcOpenGLProgramDiskCache,
"Cache location '%s' writable = %d", qPrintable(m_currentCacheDir), m_cacheWritable);
104QString QOpenGLProgramBinaryCache::cacheFileName(
const QByteArray &cacheKey)
const
106 return m_currentCacheDir + QString::fromUtf8(cacheKey);
109#define BASE_HEADER_SIZE (int(4
* sizeof(quint32)))
111#define PADDING_SIZE(fullHeaderSize) (((fullHeaderSize + 3
) & ~3
) - fullHeaderSize)
116 memcpy(&v, *p,
sizeof(quint32));
117 *p +=
sizeof(quint32);
123 quint32 len = readUInt(p);
124 QByteArray ba = QByteArray::fromRawData(
reinterpret_cast<
const char *>(*p), len);
129bool QOpenGLProgramBinaryCache::verifyHeader(
const QByteArray &buf)
const
132 qCDebug(lcOpenGLProgramDiskCache,
"Cached size too small");
135 const uchar *p =
reinterpret_cast<
const uchar *>(buf.constData());
136 if (readUInt(&p) != BINSHADER_MAGIC) {
137 qCDebug(lcOpenGLProgramDiskCache,
"Magic does not match");
140 if (readUInt(&p) != BINSHADER_VERSION) {
141 qCDebug(lcOpenGLProgramDiskCache,
"Version does not match");
144 if (readUInt(&p) != BINSHADER_QTVERSION) {
145 qCDebug(lcOpenGLProgramDiskCache,
"Qt version does not match");
148 if (readUInt(&p) !=
sizeof(quintptr)) {
149 qCDebug(lcOpenGLProgramDiskCache,
"Architecture does not match");
155bool QOpenGLProgramBinaryCache::setProgramBinary(uint programId, uint blobFormat,
const void *p, uint blobSize)
157 QOpenGLContext *context = QOpenGLContext::currentContext();
158 QOpenGLExtraFunctions *funcs = context->extraFunctions();
160 GLenum error = funcs->glGetError();
164#if QT_CONFIG(opengles2)
165 if (context->isOpenGLES() && context->format().majorVersion() < 3) {
166 initializeProgramBinaryOES(context);
167 programBinaryOES(programId, blobFormat, p, blobSize);
170 funcs->glProgramBinary(programId, blobFormat, p, blobSize);
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);
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);
188 qCDebug(lcOpenGLProgramDiskCache,
"Program binary set for program %u, size %d, format 0x%x, err = 0x%x",
189 programId, blobSize, blobFormat, err);
196 Q_DISABLE_COPY_MOVE(FdWrapper)
198 FdWrapper(
const QString &fn)
200 fd = qt_safe_open(QFile::encodeName(fn).constData(), O_RDONLY);
213 Q_DISABLE_COPY_MOVE(R)
214 explicit R(size_t sz,
void *p)
215 : mapSize{sz}, ptr{p} {}
218 if (ptr != MAP_FAILED)
219 munmap(ptr, mapSize);
222 explicit operator
bool()
const noexcept {
return ptr != MAP_FAILED; }
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};
230 auto mapSize =
static_cast<size_t>(offs);
233 mmap(
nullptr, mapSize, PROT_READ, MAP_SHARED, fd, 0),
263bool QOpenGLProgramBinaryCache::load(
const QByteArray &cacheKey, uint programId)
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());
270 const QString fn = cacheFileName(cacheKey);
271 DeferredFileRemove undertaker(fn);
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);
282 if (!f.open(QIODevice::ReadOnly))
287 if (!verifyHeader(buf)) {
288 undertaker.setActive();
294 const auto map = fdw.map();
296 undertaker.setActive();
299 p =
static_cast<
const uchar *>(map.ptr) + BASE_HEADER_SIZE;
303 p =
reinterpret_cast<
const uchar *>(q20::to_address(buf.cbegin()));
308 QByteArray vendor = readStr(&p);
309 if (vendor != info.glvendor) {
312 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VENDOR does not match" << vendor << info.glvendor;
313 undertaker.setActive();
316 QByteArray renderer = readStr(&p);
317 if (renderer != info.glrenderer) {
318 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_RENDERER does not match" << renderer << info.glrenderer;
319 undertaker.setActive();
322 QByteArray version = readStr(&p);
323 if (version != info.glversion) {
324 qCDebug(lcOpenGLProgramDiskCache) <<
"GL_VERSION does not match" << version << info.glversion;
325 undertaker.setActive();
329 quint32 blobFormat = readUInt(&p);
330 quint32 blobSize = readUInt(&p);
334 return setProgramBinary(programId, blobFormat, p, blobSize)
335 && m_memCache.insert(cacheKey,
new MemCacheEntry(p, blobSize, blobFormat));
340 memcpy(*p, &value,
sizeof(quint32));
341 *p +=
sizeof(quint32);
344static inline void writeStr(uchar **p,
const QByteArray &str)
346 writeUInt(p, str.size());
347 memcpy(*p, str.constData(), str.size());
351static inline bool writeFile(
const QString &filename,
const QByteArray &data)
353#if QT_CONFIG(temporaryfile)
354 QSaveFile f(filename);
355 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
362 if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
363 if (f.write(data) == data.length())
370void QOpenGLProgramBinaryCache::save(
const QByteArray &cacheKey, uint programId)
372 if (!m_cacheWritable)
377 QOpenGLContext *context = QOpenGLContext::currentContext();
378 QOpenGLExtraFunctions *funcs = context->extraFunctions();
381 GLenum error = funcs->glGetError();
387 const int headerSize =
FULL_HEADER_SIZE(info.glvendor.size() + info.glrenderer.size() + info.glversion.size());
394 const int totalSize = headerSize + paddingSize + blobSize;
396 qCDebug(lcOpenGLProgramDiskCache,
"Program binary is %d bytes, err = 0x%x, total %d", blobSize, funcs->glGetError(), totalSize);
400 QByteArray blob(totalSize, Qt::Uninitialized);
401 uchar *p =
reinterpret_cast<uchar *>(blob.data());
403 writeUInt(&p, BINSHADER_MAGIC);
404 writeUInt(&p, BINSHADER_VERSION);
405 writeUInt(&p, BINSHADER_QTVERSION);
406 writeUInt(&p,
sizeof(quintptr));
408 writeStr(&p, info.glvendor);
409 writeStr(&p, info.glrenderer);
410 writeStr(&p, info.glversion);
412 quint32 blobFormat = 0;
413 uchar *blobFormatPtr = p;
414 writeUInt(&p, blobFormat);
415 writeUInt(&p, blobSize);
417 for (
int i = 0; i < paddingSize; ++i)
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);
428 funcs->glGetProgramBinary(programId, blobSize, &outSize, &blobFormat, p);
429 if (blobSize != outSize) {
430 qCDebug(lcOpenGLProgramDiskCache,
"glGetProgramBinary returned size %d instead of %d", outSize, blobSize);
434 writeUInt(&blobFormatPtr, blobFormat);
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);
449 qCDebug(lcOpenGLProgramDiskCache,
"Failed to write %s to shader cache", qPrintable(filename));
452#if QT_CONFIG(opengles2)
453void QOpenGLProgramBinaryCache::initializeProgramBinaryOES(QOpenGLContext *context)
455 if (m_programBinaryOESInitialized)
457 m_programBinaryOESInitialized =
true;
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");
465QOpenGLProgramBinarySupportCheck::QOpenGLProgramBinarySupportCheck(QOpenGLContext *context)
466 : QOpenGLSharedResource(context->shareGroup()),
469 if (QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) {
470 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via app attribute");
473 if (qEnvironmentVariableIntValue(
"QT_DISABLE_SHADER_DISK_CACHE")) {
474 qCDebug(lcOpenGLProgramDiskCache,
"Shader cache disabled via env var");
478 QOpenGLContext *ctx = QOpenGLContext::currentContext();
480 if (ctx->isOpenGLES()) {
481 qCDebug(lcOpenGLProgramDiskCache,
"OpenGL ES v%d context", ctx->format().majorVersion());
482 if (ctx->format().majorVersion() >= 3) {
485 const bool hasExt = ctx->hasExtension(
"GL_OES_get_program_binary");
486 qCDebug(lcOpenGLProgramDiskCache,
"GL_OES_get_program_binary support = %d", hasExt);
491 const bool hasExt = ctx->hasExtension(
"GL_ARB_get_program_binary");
492 qCDebug(lcOpenGLProgramDiskCache,
"GL_ARB_get_program_binary support = %d", hasExt);
499 qCDebug(lcOpenGLProgramDiskCache,
"Supported binary format count = %d", fmtCount);
500 m_supported = fmtCount > 0;
503 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)