summaryrefslogtreecommitdiffstats
path: root/src/gui/opengl/qopenglprogrambinarycache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/opengl/qopenglprogrambinarycache.cpp')
-rw-r--r--src/gui/opengl/qopenglprogrambinarycache.cpp376
1 files changed, 376 insertions, 0 deletions
diff --git a/src/gui/opengl/qopenglprogrambinarycache.cpp b/src/gui/opengl/qopenglprogrambinarycache.cpp
new file mode 100644
index 0000000000..06373e1113
--- /dev/null
+++ b/src/gui/opengl/qopenglprogrambinarycache.cpp
@@ -0,0 +1,376 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qopenglprogrambinarycache_p.h"
+#include <QOpenGLContext>
+#include <QOpenGLExtraFunctions>
+#include <QStandardPaths>
+#include <QDir>
+#include <QSaveFile>
+#include <QLoggingCategory>
+
+#ifdef Q_OS_UNIX
+#include <sys/mman.h>
+#include <private/qcore_unix_p.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(DBG_SHADER_CACHE)
+
+#ifndef GL_PROGRAM_BINARY_LENGTH
+#define GL_PROGRAM_BINARY_LENGTH 0x8741
+#endif
+
+const quint32 BINSHADER_MAGIC = 0x5174;
+const quint32 BINSHADER_VERSION = 0x2;
+const quint32 BINSHADER_QTVERSION = QT_VERSION;
+
+struct GLEnvInfo
+{
+ GLEnvInfo();
+
+ QByteArray glvendor;
+ QByteArray glrenderer;
+ QByteArray glversion;
+};
+
+GLEnvInfo::GLEnvInfo()
+{
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ Q_ASSERT(ctx);
+ QOpenGLFunctions *f = ctx->functions();
+ const char *vendor = reinterpret_cast<const char *>(f->glGetString(GL_VENDOR));
+ const char *renderer = reinterpret_cast<const char *>(f->glGetString(GL_RENDERER));
+ const char *version = reinterpret_cast<const char *>(f->glGetString(GL_VERSION));
+ if (vendor)
+ glvendor = QByteArray(vendor);
+ if (renderer)
+ glrenderer = QByteArray(renderer);
+ if (version)
+ glversion = QByteArray(version);
+}
+
+static inline bool qt_ensureWritableDir(const QString &name)
+{
+ QDir::root().mkpath(name);
+ return QFileInfo(name).isWritable();
+}
+
+QOpenGLProgramBinaryCache::QOpenGLProgramBinaryCache()
+ : m_cacheWritable(false)
+{
+ const QString subPath = QLatin1String("/qtshadercache/");
+ const QString sharedCachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
+ if (!sharedCachePath.isEmpty()) {
+ m_cacheDir = sharedCachePath + subPath;
+ m_cacheWritable = qt_ensureWritableDir(m_cacheDir);
+ }
+ if (!m_cacheWritable) {
+ m_cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + subPath;
+ m_cacheWritable = qt_ensureWritableDir(m_cacheDir);
+ }
+ qCDebug(DBG_SHADER_CACHE, "Cache location '%s' writable = %d", qPrintable(m_cacheDir), m_cacheWritable);
+}
+
+QString QOpenGLProgramBinaryCache::cacheFileName(const QByteArray &cacheKey) const
+{
+ return m_cacheDir + QString::fromUtf8(cacheKey);
+}
+
+#define BASE_HEADER_SIZE (int(3 * sizeof(quint32)))
+#define FULL_HEADER_SIZE(stringsSize) (BASE_HEADER_SIZE + 12 + stringsSize + 8)
+#define PADDING_SIZE(fullHeaderSize) (((fullHeaderSize + 3) & ~3) - fullHeaderSize)
+
+static inline quint32 readUInt(const uchar **p)
+{
+ quint32 v;
+ memcpy(&v, *p, sizeof(quint32));
+ *p += sizeof(quint32);
+ return v;
+}
+
+static inline QByteArray readStr(const uchar **p)
+{
+ quint32 len = readUInt(p);
+ QByteArray ba = QByteArray::fromRawData(reinterpret_cast<const char *>(*p), len);
+ *p += len;
+ return ba;
+}
+
+bool QOpenGLProgramBinaryCache::verifyHeader(const QByteArray &buf) const
+{
+ if (buf.size() < BASE_HEADER_SIZE) {
+ qCDebug(DBG_SHADER_CACHE, "Cached size too small");
+ return false;
+ }
+ const uchar *p = reinterpret_cast<const uchar *>(buf.constData());
+ if (readUInt(&p) != BINSHADER_MAGIC) {
+ qCDebug(DBG_SHADER_CACHE, "Magic does not match");
+ return false;
+ }
+ if (readUInt(&p) != BINSHADER_VERSION) {
+ qCDebug(DBG_SHADER_CACHE, "Version does not match");
+ return false;
+ }
+ if (readUInt(&p) != BINSHADER_QTVERSION) {
+ qCDebug(DBG_SHADER_CACHE, "Qt version does not match");
+ return false;
+ }
+ return true;
+}
+
+bool QOpenGLProgramBinaryCache::setProgramBinary(uint programId, uint blobFormat, const void *p, uint blobSize)
+{
+ QOpenGLExtraFunctions *funcs = QOpenGLContext::currentContext()->extraFunctions();
+ while (funcs->glGetError() != GL_NO_ERROR) { }
+ funcs->glProgramBinary(programId, blobFormat, p, blobSize);
+ int err = funcs->glGetError();
+ qCDebug(DBG_SHADER_CACHE, "Program binary set for program %u, size %d, format 0x%x, err = 0x%x",
+ programId, blobSize, blobFormat, err);
+ return err == 0;
+}
+
+#ifdef Q_OS_UNIX
+class FdWrapper
+{
+public:
+ FdWrapper(const QString &fn)
+ : ptr(MAP_FAILED)
+ {
+ fd = qt_safe_open(QFile::encodeName(fn).constData(), O_RDONLY);
+ }
+ ~FdWrapper()
+ {
+ if (ptr != MAP_FAILED)
+ munmap(ptr, mapSize);
+ if (fd != -1)
+ qt_safe_close(fd);
+ }
+ bool map()
+ {
+ off_t offs = lseek(fd, 0, SEEK_END);
+ if (offs == (off_t) -1) {
+ qErrnoWarning(errno, "lseek failed for program binary");
+ return false;
+ }
+ mapSize = static_cast<size_t>(offs);
+ ptr = mmap(nullptr, mapSize, PROT_READ, MAP_SHARED, fd, 0);
+ return ptr != MAP_FAILED;
+ }
+
+ int fd;
+ void *ptr;
+ size_t mapSize;
+};
+#endif
+
+class DeferredFileRemove
+{
+public:
+ DeferredFileRemove(const QString &fn)
+ : fn(fn),
+ active(false)
+ {
+ }
+ ~DeferredFileRemove()
+ {
+ if (active)
+ QFile(fn).remove();
+ }
+ void setActive()
+ {
+ active = true;
+ }
+
+ QString fn;
+ bool active;
+};
+
+bool QOpenGLProgramBinaryCache::load(const QByteArray &cacheKey, uint programId)
+{
+ if (m_memCache.contains(cacheKey)) {
+ const MemCacheEntry *e = m_memCache[cacheKey];
+ return setProgramBinary(programId, e->format, e->blob.constData(), e->blob.size());
+ }
+
+ QByteArray buf;
+ const QString fn = cacheFileName(cacheKey);
+ DeferredFileRemove undertaker(fn);
+#ifdef Q_OS_UNIX
+ FdWrapper fdw(fn);
+ if (fdw.fd == -1)
+ return false;
+ char header[BASE_HEADER_SIZE];
+ qint64 bytesRead = qt_safe_read(fdw.fd, header, BASE_HEADER_SIZE);
+ if (bytesRead == BASE_HEADER_SIZE)
+ buf = QByteArray::fromRawData(header, BASE_HEADER_SIZE);
+#else
+ QFile f(fn);
+ if (!f.open(QIODevice::ReadOnly))
+ return false;
+ buf = f.read(BASE_HEADER_SIZE);
+#endif
+
+ if (!verifyHeader(buf)) {
+ undertaker.setActive();
+ return false;
+ }
+
+ const uchar *p;
+#ifdef Q_OS_UNIX
+ if (!fdw.map()) {
+ undertaker.setActive();
+ return false;
+ }
+ p = static_cast<const uchar *>(fdw.ptr) + BASE_HEADER_SIZE;
+#else
+ buf = f.readAll();
+ p = reinterpret_cast<const uchar *>(buf.constData());
+#endif
+
+ GLEnvInfo info;
+
+ QByteArray vendor = readStr(&p);
+ if (vendor != info.glvendor) {
+ // readStr returns non-null terminated strings just pointing to inside
+ // 'p' so must print these via the stream qCDebug and not constData().
+ qCDebug(DBG_SHADER_CACHE) << "GL_VENDOR does not match" << vendor << info.glvendor;
+ undertaker.setActive();
+ return false;
+ }
+ QByteArray renderer = readStr(&p);
+ if (renderer != info.glrenderer) {
+ qCDebug(DBG_SHADER_CACHE) << "GL_RENDERER does not match" << renderer << info.glrenderer;
+ undertaker.setActive();
+ return false;
+ }
+ QByteArray version = readStr(&p);
+ if (version != info.glversion) {
+ qCDebug(DBG_SHADER_CACHE) << "GL_VERSION does not match" << version << info.glversion;
+ undertaker.setActive();
+ return false;
+ }
+
+ quint32 blobFormat = readUInt(&p);
+ quint32 blobSize = readUInt(&p);
+
+ p += PADDING_SIZE(FULL_HEADER_SIZE(vendor.size() + renderer.size() + version.size()));
+
+ return setProgramBinary(programId, blobFormat, p, blobSize)
+ && m_memCache.insert(cacheKey, new MemCacheEntry(p, blobSize, blobFormat));
+}
+
+static inline void writeUInt(uchar **p, quint32 value)
+{
+ memcpy(*p, &value, sizeof(quint32));
+ *p += sizeof(quint32);
+}
+
+static inline void writeStr(uchar **p, const QByteArray &str)
+{
+ writeUInt(p, str.size());
+ memcpy(*p, str.constData(), str.size());
+ *p += str.size();
+}
+
+void QOpenGLProgramBinaryCache::save(const QByteArray &cacheKey, uint programId)
+{
+ if (!m_cacheWritable)
+ return;
+
+ GLEnvInfo info;
+
+ QOpenGLExtraFunctions *funcs = QOpenGLContext::currentContext()->extraFunctions();
+ GLint blobSize = 0;
+ while (funcs->glGetError() != GL_NO_ERROR) { }
+ funcs->glGetProgramiv(programId, GL_PROGRAM_BINARY_LENGTH, &blobSize);
+
+ const int headerSize = FULL_HEADER_SIZE(info.glvendor.size() + info.glrenderer.size() + info.glversion.size());
+
+ // Add padding to make the blob start 4-byte aligned in order to support
+ // OpenGL implementations on ARM that choke on non-aligned pointers passed
+ // to glProgramBinary.
+ const int paddingSize = PADDING_SIZE(headerSize);
+
+ const int totalSize = headerSize + paddingSize + blobSize;
+
+ qCDebug(DBG_SHADER_CACHE, "Program binary is %d bytes, err = 0x%x, total %d", blobSize, funcs->glGetError(), totalSize);
+ if (!blobSize)
+ return;
+
+ QByteArray blob(totalSize, Qt::Uninitialized);
+ uchar *p = reinterpret_cast<uchar *>(blob.data());
+
+ writeUInt(&p, BINSHADER_MAGIC);
+ writeUInt(&p, BINSHADER_VERSION);
+ writeUInt(&p, BINSHADER_QTVERSION);
+
+ writeStr(&p, info.glvendor);
+ writeStr(&p, info.glrenderer);
+ writeStr(&p, info.glversion);
+
+ quint32 blobFormat = 0;
+ uchar *blobFormatPtr = p;
+ writeUInt(&p, blobFormat);
+ writeUInt(&p, blobSize);
+
+ for (int i = 0; i < paddingSize; ++i)
+ *p++ = 0;
+
+ GLint outSize = 0;
+ funcs->glGetProgramBinary(programId, blobSize, &outSize, &blobFormat, p);
+ if (blobSize != outSize) {
+ qCDebug(DBG_SHADER_CACHE, "glGetProgramBinary returned size %d instead of %d", outSize, blobSize);
+ return;
+ }
+
+ writeUInt(&blobFormatPtr, blobFormat);
+
+ QSaveFile f(cacheFileName(cacheKey));
+ if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ f.write(blob);
+ if (!f.commit())
+ qCDebug(DBG_SHADER_CACHE, "Failed to write %s to shader cache", qPrintable(f.fileName()));
+ } else {
+ qCDebug(DBG_SHADER_CACHE, "Failed to create %s in shader cache", qPrintable(f.fileName()));
+ }
+}
+
+QT_END_NAMESPACE