Add an OpenGL program binary disk cache

Introduce a glProgramBinary-based disk cache in QOpenGLShaderProgram.

By switching the typical

program->addShaderFromSourceCode(QOpenGLShader::Vertex, ...)
program->addShaderFromSourceCode(QOpenGLShader::Fragment, ...)

invocations to

program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, ...)
program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, ...)

the compilation may be skipped via gl(Get)ProgramBinary and a disk
cache, when supported.  Such QOpenGLShaderProgram instances will have
no QOpenGLShader instances attached. Instead, the entire program
binary (which is driver-specific) is loaded as-is.

Support means OpenGL ES 3.0 or the presence of
GL_ARB_get_program_binary, in combination with >= 1 supported binary
formats. Note that some drivers claim program binary support but
expose no formats. This amounts to no support in practice.

When support is not present, calling the new functions is equivalent
to the non-cacheable variants. If the OpenGL driver changes (vendor,
renderer, version strings), recompilation and storage of the new,
potentially incompatible binary program will happen transparently.

The cache can always be disabled by setting
QT_DISABLE_SHADER_DISK_CACHE=1 or the new application attribute
Qt::AA_DisableShaderDiskCache.

Location-wise the primary choice is the shared cache
(GenericCacheLocation). If this is not available or is not writable,
the per-process one (CacheLocation) is used instead.

In addition to the new public APIs in QOpenGLShaderProgram, the main
shader users in QtGui are migrated as well. (OpenGL paint engine,
glyph cache, blitter, eglfs mouse cursor).  This means that any
application using QPainter on OpenGL or widgets with eglfs will
benefit from the improved startup times. Qt Quick will follow suit as
well.

[ChangeLog][QtGui][OpenGL] QOpenGLShaderProgram offers a built-in
program binary disk cache for systems with OpenGL ES 3.x or
GL_ARB_get_program_binary. This can lead to significant increases in
performance when it comes to application startup times for
example. Usage is opt-in for direct C++ users of the class, however
Qt's own main users of shaders, including Qt Quick and QPainter's
OpenGL engine, are migrated to use the new, cache-enabled APIs.
Opting out on application level is always possible via
Qt::AA_DisableShaderDiskCache.

Task-number: QTBUG-55496
Change-Id: I556f053d258bfa6887b1d5238c9f6396914c5421
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2016-09-29 12:59:06 +02:00
parent ffd316ebe3
commit 85f868e73e
12 changed files with 788 additions and 90 deletions

View File

@ -505,6 +505,7 @@ public:
AA_SynthesizeMouseForUnhandledTabletEvents = 24,
AA_CompressHighFrequencyEvents = 25,
AA_DontCheckOpenGLContextThreadAffinity = 26,
AA_DisableShaderDiskCache = 27,
// Add new attributes before this line
AA_AttributeCount

View File

@ -257,6 +257,15 @@
\l{QOpenGLContext::makeCurrent}{makeCurrent()}. This value has been
added in Qt 5.8.
\value AA_DisableShaderDiskCache Disables caching of shader program binaries
on disk. By default Qt Quick, QPainter's OpenGL backend, and any
application using QOpenGLShaderProgram with one of its
\e addCacheableShaderFromSource overloads will employ a disk-based
\l{Caching Program Binaries}{program binary cache} in either the shared
or per-process cache storage location, on systems that support
\e glProgramBinary(). In the unlikely event of this being problematic,
set this attribute to disable all disk-based caching of shaders.
The following values are obsolete:
\value AA_ImmediateWidgetCreation This attribute is no longer fully

View File

@ -34,7 +34,8 @@ qtConfig(opengl) {
opengl/qopengltexture_p.h \
opengl/qopengltexturehelper_p.h \
opengl/qopenglpixeltransferoptions.h \
opengl/qopenglextrafunctions.h
opengl/qopenglextrafunctions.h \
opengl/qopenglprogrambinarycache_p.h
SOURCES += opengl/qopengl.cpp \
opengl/qopenglfunctions.cpp \
@ -56,7 +57,8 @@ qtConfig(opengl) {
opengl/qopengltextureblitter.cpp \
opengl/qopengltexture.cpp \
opengl/qopengltexturehelper.cpp \
opengl/qopenglpixeltransferoptions.cpp
opengl/qopenglpixeltransferoptions.cpp \
opengl/qopenglprogrambinarycache.cpp
!qtConfig(opengles2) {
HEADERS += opengl/qopenglfunctions_1_0.h \

View File

@ -208,8 +208,6 @@ QOpenGLEngineSharedShaders::QOpenGLEngineSharedShaders(QOpenGLContext* context)
snippetsPopulated = true;
}
QOpenGLShader* fragShader;
QOpenGLShader* vertexShader;
QByteArray vertexSource;
QByteArray fragSource;
@ -227,19 +225,11 @@ QOpenGLEngineSharedShaders::QOpenGLEngineSharedShaders(QOpenGLContext* context)
bool inCache = simpleShaderCache.load(simpleShaderProg, context);
if (!inCache) {
vertexShader = new QOpenGLShader(QOpenGLShader::Vertex);
shaders.append(vertexShader);
if (!vertexShader->compileSourceCode(vertexSource))
if (!simpleShaderProg->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexSource))
qWarning("Vertex shader for simpleShaderProg (MainVertexShader & PositionOnlyVertexShader) failed to compile");
fragShader = new QOpenGLShader(QOpenGLShader::Fragment);
shaders.append(fragShader);
if (!fragShader->compileSourceCode(fragSource))
if (!simpleShaderProg->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragSource))
qWarning("Fragment shader for simpleShaderProg (MainFragmentShader & ShockingPinkSrcFragmentShader) failed to compile");
simpleShaderProg->addShader(vertexShader);
simpleShaderProg->addShader(fragShader);
simpleShaderProg->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);
simpleShaderProg->bindAttributeLocation("pmvMatrix1", QT_PMV_MATRIX_1_ATTR);
simpleShaderProg->bindAttributeLocation("pmvMatrix2", QT_PMV_MATRIX_2_ATTR);
@ -271,19 +261,11 @@ QOpenGLEngineSharedShaders::QOpenGLEngineSharedShaders(QOpenGLContext* context)
inCache = blitShaderCache.load(blitShaderProg, context);
if (!inCache) {
vertexShader = new QOpenGLShader(QOpenGLShader::Vertex);
shaders.append(vertexShader);
if (!vertexShader->compileSourceCode(vertexSource))
if (!blitShaderProg->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexSource))
qWarning("Vertex shader for blitShaderProg (MainWithTexCoordsVertexShader & UntransformedPositionVertexShader) failed to compile");
fragShader = new QOpenGLShader(QOpenGLShader::Fragment);
shaders.append(fragShader);
if (!fragShader->compileSourceCode(fragSource))
if (!blitShaderProg->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragSource))
qWarning("Fragment shader for blitShaderProg (MainFragmentShader & ImageSrcFragmentShader) failed to compile");
blitShaderProg->addShader(vertexShader);
blitShaderProg->addShader(fragShader);
blitShaderProg->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR);
blitShaderProg->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);
}
@ -306,9 +288,6 @@ QOpenGLEngineSharedShaders::~QOpenGLEngineSharedShaders()
#ifdef QT_GL_SHARED_SHADER_DEBUG
qDebug(" -> ~QOpenGLEngineSharedShaders() %p for thread %p.", this, QThread::currentThread());
#endif
qDeleteAll(shaders);
shaders.clear();
qDeleteAll(cachedPrograms);
cachedPrograms.clear();
@ -370,50 +349,37 @@ QOpenGLEngineShaderProg *QOpenGLEngineSharedShaders::findProgramInCache(const QO
bool inCache = shaderCache.load(shaderProgram.data(), QOpenGLContext::currentContext());
if (!inCache) {
QScopedPointer<QOpenGLShader> fragShader(new QOpenGLShader(QOpenGLShader::Fragment));
QByteArray description;
if (!shaderProgram->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexSource)) {
QByteArray description;
#if defined(QT_DEBUG)
// Name the shader for easier debugging
description.append("Fragment shader: main=");
description.append(snippetNameStr(prog.mainFragShader));
description.append(", srcPixel=");
description.append(snippetNameStr(prog.srcPixelFragShader));
if (prog.compositionFragShader) {
description.append(", composition=");
description.append(snippetNameStr(prog.compositionFragShader));
}
if (prog.maskFragShader) {
description.append(", mask=");
description.append(snippetNameStr(prog.maskFragShader));
}
fragShader->setObjectName(QString::fromLatin1(description));
description.append("Vertex shader: main=");
description.append(snippetNameStr(prog.mainVertexShader));
description.append(", position=");
description.append(snippetNameStr(prog.positionVertexShader));
#endif
if (!fragShader->compileSourceCode(fragSource)) {
qWarning("Warning: \"%s\" failed to compile!", description.constData());
break;
}
QScopedPointer<QOpenGLShader> vertexShader(new QOpenGLShader(QOpenGLShader::Vertex));
if (!shaderProgram->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragSource)) {
QByteArray description;
#if defined(QT_DEBUG)
// Name the shader for easier debugging
description.clear();
description.append("Vertex shader: main=");
description.append(snippetNameStr(prog.mainVertexShader));
description.append(", position=");
description.append(snippetNameStr(prog.positionVertexShader));
vertexShader->setObjectName(QString::fromLatin1(description));
description.append("Fragment shader: main=");
description.append(snippetNameStr(prog.mainFragShader));
description.append(", srcPixel=");
description.append(snippetNameStr(prog.srcPixelFragShader));
if (prog.compositionFragShader) {
description.append(", composition=");
description.append(snippetNameStr(prog.compositionFragShader));
}
if (prog.maskFragShader) {
description.append(", mask=");
description.append(snippetNameStr(prog.maskFragShader));
}
#endif
if (!vertexShader->compileSourceCode(vertexSource)) {
qWarning("Warning: \"%s\" failed to compile!", description.constData());
break;
}
shaders.append(vertexShader.data());
shaders.append(fragShader.data());
shaderProgram->addShader(vertexShader.take());
shaderProgram->addShader(fragShader.take());
// We have to bind the vertex attribute names before the program is linked:
shaderProgram->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);
if (prog.useTextureCoords)
@ -436,18 +402,9 @@ QOpenGLEngineShaderProg *QOpenGLEngineSharedShaders::findProgramInCache(const QO
shaderCache.store(newProg->program, QOpenGLContext::currentContext());
} else {
QString error;
error = QLatin1String("Shader program failed to link,");
#if defined(QT_DEBUG)
QLatin1String br("\n");
error += QLatin1String("\n Shaders Used:\n");
for (int i = 0; i < newProg->program->shaders().count(); ++i) {
QOpenGLShader *shader = newProg->program->shaders().at(i);
error += QLatin1String(" ") + shader->objectName() + QLatin1String(": \n")
+ QLatin1String(shader->sourceCode()) + br;
}
#endif
error += QLatin1String(" Error Log:\n")
+ QLatin1String(" ") + newProg->program->log();
error = QLatin1String("Shader program failed to link")
+ QLatin1String(" Error Log:\n")
+ QLatin1String(" ") + newProg->program->log();
qWarning() << error;
break;
}

View File

@ -366,7 +366,6 @@ private:
QOpenGLShaderProgram *blitShaderProg;
QOpenGLShaderProgram *simpleShaderProg;
QList<QOpenGLEngineShaderProg*> cachedPrograms;
QList<QOpenGLShader *> shaders;
static const char* qShaderSnippets[TotalSnippetCount];
};

View File

@ -0,0 +1,332 @@
/****************************************************************************
**
** 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 <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 = 0x1;
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);
}
static const int HEADER_SIZE = 3 * sizeof(quint32);
bool QOpenGLProgramBinaryCache::verifyHeader(const QByteArray &buf) const
{
if (buf.size() < HEADER_SIZE) {
qCDebug(DBG_SHADER_CACHE, "Cached size too small");
return false;
}
const quint32 *p = reinterpret_cast<const quint32 *>(buf.constData());
if (*p++ != BINSHADER_MAGIC) {
qCDebug(DBG_SHADER_CACHE, "Magic does not match");
return false;
}
if (*p++ != BINSHADER_VERSION) {
qCDebug(DBG_SHADER_CACHE, "Version does not match");
return false;
}
if (*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[HEADER_SIZE];
qint64 bytesRead = qt_safe_read(fdw.fd, header, HEADER_SIZE);
if (bytesRead == HEADER_SIZE)
buf = QByteArray::fromRawData(header, HEADER_SIZE);
#else
QFile f(fn);
if (!f.open(QIODevice::ReadOnly))
return false;
buf = f.read(HEADER_SIZE);
#endif
if (!verifyHeader(buf)) {
undertaker.setActive();
return false;
}
const quint32 *p;
#ifdef Q_OS_UNIX
if (!fdw.map()) {
undertaker.setActive();
return false;
}
p = reinterpret_cast<const quint32 *>(static_cast<const char *>(fdw.ptr) + HEADER_SIZE);
#else
buf = f.readAll();
p = reinterpret_cast<const quint32 *>(buf.constData());
#endif
GLEnvInfo info;
quint32 v = *p++;
QByteArray vendor = QByteArray::fromRawData(reinterpret_cast<const char *>(p), v);
if (vendor != info.glvendor) {
qCDebug(DBG_SHADER_CACHE, "GL_VENDOR does not match (%s, %s)", vendor.constData(), info.glvendor.constData());
undertaker.setActive();
return false;
}
p = reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(p) + v);
v = *p++;
QByteArray renderer = QByteArray::fromRawData(reinterpret_cast<const char *>(p), v);
if (renderer != info.glrenderer) {
qCDebug(DBG_SHADER_CACHE, "GL_RENDERER does not match (%s, %s)", renderer.constData(), info.glrenderer.constData());
undertaker.setActive();
return false;
}
p = reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(p) + v);
v = *p++;
QByteArray version = QByteArray::fromRawData(reinterpret_cast<const char *>(p), v);
if (version != info.glversion) {
qCDebug(DBG_SHADER_CACHE, "GL_VERSION does not match (%s, %s)", version.constData(), info.glversion.constData());
undertaker.setActive();
return false;
}
p = reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(p) + v);
quint32 blobFormat = *p++;
quint32 blobSize = *p++;
return setProgramBinary(programId, blobFormat, p, blobSize)
&& m_memCache.insert(cacheKey, new MemCacheEntry(p, blobSize, blobFormat));
}
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);
int totalSize = blobSize + 8 + 12 + 12 + info.glvendor.size() + info.glrenderer.size() + info.glversion.size();
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);
quint32 *p = reinterpret_cast<quint32 *>(blob.data());
*p++ = BINSHADER_MAGIC;
*p++ = BINSHADER_VERSION;
*p++ = BINSHADER_QTVERSION;
*p++ = info.glvendor.size();
memcpy(p, info.glvendor.constData(), info.glvendor.size());
p = reinterpret_cast<quint32 *>(reinterpret_cast<char *>(p) + info.glvendor.size());
*p++ = info.glrenderer.size();
memcpy(p, info.glrenderer.constData(), info.glrenderer.size());
p = reinterpret_cast<quint32 *>(reinterpret_cast<char *>(p) + info.glrenderer.size());
*p++ = info.glversion.size();
memcpy(p, info.glversion.constData(), info.glversion.size());
p = reinterpret_cast<quint32 *>(reinterpret_cast<char *>(p) + info.glversion.size());
GLint outSize = 0;
quint32 *blobFormat = p++;
*p++ = blobSize;
funcs->glGetProgramBinary(programId, blobSize, &outSize, blobFormat, p);
if (blobSize != outSize) {
qCDebug(DBG_SHADER_CACHE, "glGetProgramBinary returned size %d instead of %d", outSize, blobSize);
return;
}
QFile f(cacheFileName(cacheKey));
if (f.open(QIODevice::WriteOnly | QIODevice::Truncate))
f.write(blob);
else
qCDebug(DBG_SHADER_CACHE, "Failed to write %s to shader cache", qPrintable(f.fileName()));
}
QT_END_NAMESPACE

View File

@ -0,0 +1,89 @@
/****************************************************************************
**
** 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$
**
****************************************************************************/
#ifndef QOPENGLPROGRAMBINARYCACHE_P_H
#define QOPENGLPROGRAMBINARYCACHE_P_H
#include <QtGui/qtguiglobal.h>
#include <QtGui/qopenglshaderprogram.h>
#include <QtCore/qcache.h>
QT_BEGIN_NAMESPACE
class QOpenGLProgramBinaryCache
{
public:
struct ShaderDesc {
ShaderDesc() { }
ShaderDesc(QOpenGLShader::ShaderType type, const QByteArray &source = QByteArray())
: type(type), source(source)
{ }
QOpenGLShader::ShaderType type;
QByteArray source;
};
struct ProgramDesc {
QVector<ShaderDesc> shaders;
};
QOpenGLProgramBinaryCache();
bool load(const QByteArray &cacheKey, uint programId);
void save(const QByteArray &cacheKey, uint programId);
private:
QString cacheFileName(const QByteArray &cacheKey) const;
bool verifyHeader(const QByteArray &buf) const;
bool setProgramBinary(uint programId, uint blobFormat, const void *p, uint blobSize);
QString m_cacheDir;
bool m_cacheWritable;
struct MemCacheEntry {
MemCacheEntry(const void *p, int size, uint format)
: blob(reinterpret_cast<const char *>(p), size),
format(format)
{ }
QByteArray blob;
uint format;
};
QCache<QByteArray, MemCacheEntry> m_memCache;
};
QT_END_NAMESPACE
#endif

View File

@ -38,6 +38,7 @@
****************************************************************************/
#include "qopenglshaderprogram.h"
#include "qopenglprogrambinarycache_p.h"
#include "qopenglfunctions.h"
#include "private/qopenglcontext_p.h"
#include <QtCore/private/qobject_p.h>
@ -46,6 +47,9 @@
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qvector.h>
#include <QtCore/qregularexpression.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qcryptographichash.h>
#include <QtCore/qcoreapplication.h>
#include <QtGui/qtransform.h>
#include <QtGui/QColor>
#include <QtGui/QSurfaceFormat>
@ -127,6 +131,20 @@ QT_BEGIN_NAMESPACE
on the shader program. The shader program's id can be explicitly
created using the create() function.
\section2 Caching Program Binaries
As of Qt 5.9, support for caching program binaries on disk is built in. To
enable this, switch to using addCacheableShaderFromSourceCode() and
addCacheableShaderFromSourceFile(). With an OpenGL ES 3.x context or support
for \c{GL_ARB_get_program_binary}, this will transparently cache program
binaries under QStandardPaths::GenericCacheLocation or
QStandardPaths::CacheLocation. When support is not available, calling the
cacheable function variants is equivalent to the normal ones.
\note Some drivers do not have any binary formats available, even though
they advertise the extension or offer OpenGL ES 3.0. In this case program
binary support will be disabled.
\sa QOpenGLShader
*/
@ -162,6 +180,7 @@ QT_BEGIN_NAMESPACE
based on the core feature (requires OpenGL >= 4.3).
*/
Q_LOGGING_CATEGORY(DBG_SHADER_CACHE, "qt.opengl.diskcache")
// For GLES 3.1/3.2
#ifndef GL_GEOMETRY_SHADER
@ -192,6 +211,10 @@ QT_BEGIN_NAMESPACE
#define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73
#endif
#ifndef GL_NUM_PROGRAM_BINARY_FORMATS
#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
#endif
static inline bool isFormatGLES(const QSurfaceFormat &f)
{
return (f.renderableType() == QSurfaceFormat::OpenGLES);
@ -771,6 +794,7 @@ public:
#ifndef QT_OPENGL_ES_2
, tessellationFuncs(0)
#endif
, linkBinaryRecursion(false)
{
}
~QOpenGLShaderProgramPrivate();
@ -792,6 +816,13 @@ public:
#endif
bool hasShader(QOpenGLShader::ShaderType type) const;
QOpenGLProgramBinaryCache::ProgramDesc binaryProgram;
bool isCacheDisabled() const;
bool compileCacheable();
bool linkBinary();
bool linkBinaryRecursion;
};
namespace {
@ -1022,6 +1053,139 @@ bool QOpenGLShaderProgram::addShaderFromSourceFile
return addShader(shader);
}
/*!
Registers the shader of the specified \a type and \a source to this
program. Unlike addShaderFromSourceCode(), this function does not perform
compilation. Compilation is deferred to link(), and may not happen at all,
because link() may potentially use a program binary from Qt's shader disk
cache. This will typically lead to a significant increase in performance.
\return true if the shader has been registered or, in the non-cached case,
compiled successfully; false if there was an error. The compilation error
messages can be retrieved via log().
When the disk cache is disabled, via Qt::AA_DisableShaderDiskCache for
example, or the OpenGL context has no support for context binaries, calling
this function is equivalent to addShaderFromSourceCode().
\since 5.9
\sa addShaderFromSourceCode(), addCacheableShaderFromSourceFile()
*/
bool QOpenGLShaderProgram::addCacheableShaderFromSourceCode(QOpenGLShader::ShaderType type, const char *source)
{
Q_D(QOpenGLShaderProgram);
if (!init())
return false;
if (d->isCacheDisabled())
return addShaderFromSourceCode(type, source);
return addCacheableShaderFromSourceCode(type, QByteArray(source));
}
/*!
\overload
Registers the shader of the specified \a type and \a source to this
program. Unlike addShaderFromSourceCode(), this function does not perform
compilation. Compilation is deferred to link(), and may not happen at all,
because link() may potentially use a program binary from Qt's shader disk
cache. This will typically lead to a significant increase in performance.
\return true if the shader has been registered or, in the non-cached case,
compiled successfully; false if there was an error. The compilation error
messages can be retrieved via log().
When the disk cache is disabled, via Qt::AA_DisableShaderDiskCache for
example, or the OpenGL context has no support for context binaries, calling
this function is equivalent to addShaderFromSourceCode().
\since 5.9
\sa addShaderFromSourceCode(), addCacheableShaderFromSourceFile()
*/
bool QOpenGLShaderProgram::addCacheableShaderFromSourceCode(QOpenGLShader::ShaderType type, const QByteArray &source)
{
Q_D(QOpenGLShaderProgram);
if (!init())
return false;
if (d->isCacheDisabled())
return addShaderFromSourceCode(type, source);
d->binaryProgram.shaders.append(QOpenGLProgramBinaryCache::ShaderDesc(type, source));
return true;
}
/*!
\overload
Registers the shader of the specified \a type and \a source to this
program. Unlike addShaderFromSourceCode(), this function does not perform
compilation. Compilation is deferred to link(), and may not happen at all,
because link() may potentially use a program binary from Qt's shader disk
cache. This will typically lead to a significant increase in performance.
When the disk cache is disabled, via Qt::AA_DisableShaderDiskCache for
example, or the OpenGL context has no support for context binaries, calling
this function is equivalent to addShaderFromSourceCode().
\since 5.9
\sa addShaderFromSourceCode(), addCacheableShaderFromSourceFile()
*/
bool QOpenGLShaderProgram::addCacheableShaderFromSourceCode(QOpenGLShader::ShaderType type, const QString &source)
{
Q_D(QOpenGLShaderProgram);
if (!init())
return false;
if (d->isCacheDisabled())
return addShaderFromSourceCode(type, source);
return addCacheableShaderFromSourceCode(type, source.toUtf8().constData());
}
/*!
Registers the shader of the specified \a type and \a fileName to this
program. Unlike addShaderFromSourceFile(), this function does not perform
compilation. Compilation is deferred to link(), and may not happen at all,
because link() may potentially use a program binary from Qt's shader disk
cache. This will typically lead to a significant increase in performance.
\return true if the file has been read successfully, false if the file could
not be opened or the normal, non-cached compilation of the shader has
failed. The compilation error messages can be retrieved via log().
When the disk cache is disabled, via Qt::AA_DisableShaderDiskCache for
example, or the OpenGL context has no support for context binaries, calling
this function is equivalent to addShaderFromSourceFile().
\since 5.9
\sa addShaderFromSourceFile(), addCacheableShaderFromSourceCode()
*/
bool QOpenGLShaderProgram::addCacheableShaderFromSourceFile(QOpenGLShader::ShaderType type, const QString &fileName)
{
Q_D(QOpenGLShaderProgram);
if (!init())
return false;
if (d->isCacheDisabled())
return addShaderFromSourceFile(type, fileName);
QOpenGLProgramBinaryCache::ShaderDesc shader(type);
// NB! It could be tempting to defer reading the file contents and just
// hash the filename as the cache key, perhaps combined with last-modified
// timestamp checks. However, this would raise a number of issues (no
// timestamps for files in the resource system; preference for global, not
// per-application cache items (where filenames may clash); resource-based
// shaders from libraries like Qt Quick; etc.), so just avoid it.
QFile f(fileName);
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
shader.source = f.readAll();
f.close();
} else {
qWarning("QOpenGLShaderProgram: Unable to open file %s", qPrintable(fileName));
return false;
}
d->binaryProgram.shaders.append(shader);
return true;
}
/*!
Removes \a shader from this shader program. The object is not deleted.
@ -1080,6 +1244,7 @@ void QOpenGLShaderProgram::removeAllShaders()
qDeleteAll(d->anonShaders);
d->shaders.clear();
d->anonShaders.clear();
d->binaryProgram = QOpenGLProgramBinaryCache::ProgramDesc();
d->linked = false; // Program needs to be relinked.
d->removingShaders = false;
}
@ -1096,6 +1261,16 @@ void QOpenGLShaderProgram::removeAllShaders()
If the shader program was already linked, calling this
function again will force it to be re-linked.
When shaders were added to this program via
addCacheableShaderFromSourceCode() or addCacheableShaderFromSourceFile(),
program binaries are supported, and a cached binary is available on disk,
actual compilation and linking are skipped. Instead, link() will initialize
the program with the binary blob via glProgramBinary(). If there is no
cached version of the program or it was generated with a different driver
version, the shaders will be compiled from source and the program will get
linked normally. This allows seamless upgrading of the graphics drivers,
without having to worry about potentially incompatible binary formats.
\sa addShader(), log()
*/
bool QOpenGLShaderProgram::link()
@ -1105,12 +1280,17 @@ bool QOpenGLShaderProgram::link()
if (!program)
return false;
if (!d->linkBinaryRecursion && d->shaders.isEmpty() && !d->binaryProgram.shaders.isEmpty())
return d->linkBinary();
GLint value;
if (d->shaders.isEmpty()) {
// If there are no explicit shaders, then it is possible that the
// application added a program binary with glProgramBinaryOES(),
// or otherwise populated the shaders itself. Check to see if the
// program is already linked and bail out if so.
// application added a program binary with glProgramBinaryOES(), or
// otherwise populated the shaders itself. This is also the case when
// we are recursively called back from linkBinary() after a successful
// glProgramBinary(). Check to see if the program is already linked and
// bail out if so.
value = 0;
d->glfuncs->glGetProgramiv(program, GL_LINK_STATUS, &value);
d->linked = (value != 0);
@ -3537,4 +3717,136 @@ bool QOpenGLShader::hasOpenGLShaders(ShaderType type, QOpenGLContext *context)
return true;
}
// While unlikely, one application can in theory use contexts with different versions
// or profiles. Therefore any version- or extension-specific checks must be done on a
// per-context basis, not just once per process. QOpenGLSharedResource enables this,
// although it's once-per-sharing-context-group, not per-context. Still, this should
// be good enough in practice.
class QOpenGLProgramBinarySupportCheck : public QOpenGLSharedResource
{
public:
QOpenGLProgramBinarySupportCheck(QOpenGLContext *context);
void invalidateResource() override { }
void freeResource(QOpenGLContext *) override { }
bool isSupported() const { return m_supported; }
private:
bool m_supported;
};
QOpenGLProgramBinarySupportCheck::QOpenGLProgramBinarySupportCheck(QOpenGLContext *context)
: QOpenGLSharedResource(context->shareGroup()),
m_supported(false)
{
if (QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) {
qCDebug(DBG_SHADER_CACHE, "Shader cache disabled via app attribute");
return;
}
if (qEnvironmentVariableIntValue("QT_DISABLE_SHADER_DISK_CACHE")) {
qCDebug(DBG_SHADER_CACHE, "Shader cache disabled via env var");
return;
}
QOpenGLContext *ctx = QOpenGLContext::currentContext();
if (ctx) {
if (ctx->isOpenGLES()) {
qCDebug(DBG_SHADER_CACHE, "OpenGL ES v%d context", ctx->format().majorVersion());
if (ctx->format().majorVersion() >= 3)
m_supported = true;
} else {
const bool hasExt = ctx->hasExtension("GL_ARB_get_program_binary");
qCDebug(DBG_SHADER_CACHE, "GL_ARB_get_program_binary support = %d", hasExt);
if (hasExt)
m_supported = true;
}
if (m_supported) {
GLint fmtCount = 0;
ctx->functions()->glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &fmtCount);
qCDebug(DBG_SHADER_CACHE, "Supported binary format count = %d", fmtCount);
m_supported = fmtCount > 0;
}
}
qCDebug(DBG_SHADER_CACHE, "Shader cache supported = %d", m_supported);
}
class QOpenGLProgramBinarySupportCheckWrapper
{
public:
QOpenGLProgramBinarySupportCheck *get(QOpenGLContext *context)
{
return m_resource.value<QOpenGLProgramBinarySupportCheck>(context);
}
private:
QOpenGLMultiGroupSharedResource m_resource;
};
bool QOpenGLShaderProgramPrivate::isCacheDisabled() const
{
static QOpenGLProgramBinarySupportCheckWrapper binSupportCheck;
return !binSupportCheck.get(QOpenGLContext::currentContext())->isSupported();
}
bool QOpenGLShaderProgramPrivate::compileCacheable()
{
Q_Q(QOpenGLShaderProgram);
for (const QOpenGLProgramBinaryCache::ShaderDesc &shader : qAsConst(binaryProgram.shaders)) {
QScopedPointer<QOpenGLShader> s(new QOpenGLShader(shader.type, q));
if (!s->compileSourceCode(shader.source)) {
log = s->log();
return false;
}
anonShaders.append(s.take());
if (!q->addShader(anonShaders.last()))
return false;
}
return true;
}
bool QOpenGLShaderProgramPrivate::linkBinary()
{
static QOpenGLProgramBinaryCache binCache;
Q_Q(QOpenGLShaderProgram);
QCryptographicHash keyBuilder(QCryptographicHash::Sha1);
for (const QOpenGLProgramBinaryCache::ShaderDesc &shader : qAsConst(binaryProgram.shaders))
keyBuilder.addData(shader.source);
const QByteArray cacheKey = keyBuilder.result().toHex();
if (DBG_SHADER_CACHE().isEnabled(QtDebugMsg))
qCDebug(DBG_SHADER_CACHE, "program with %d shaders, cache key %s",
binaryProgram.shaders.count(), cacheKey.constData());
bool needsCompile = true;
if (binCache.load(cacheKey, q->programId())) {
qCDebug(DBG_SHADER_CACHE, "Program binary received from cache");
linkBinaryRecursion = true;
bool ok = q->link();
linkBinaryRecursion = false;
if (ok)
needsCompile = false;
else
qCDebug(DBG_SHADER_CACHE, "Link failed after glProgramBinary");
}
bool needsSave = false;
if (needsCompile) {
qCDebug(DBG_SHADER_CACHE, "Program binary not in cache, compiling");
if (compileCacheable())
needsSave = true;
else
return false;
}
linkBinaryRecursion = true;
bool ok = q->link();
linkBinaryRecursion = false;
if (ok && needsSave)
binCache.save(cacheKey, q->programId());
return ok;
}
QT_END_NAMESPACE

View File

@ -119,6 +119,11 @@ public:
bool addShaderFromSourceCode(QOpenGLShader::ShaderType type, const QString& source);
bool addShaderFromSourceFile(QOpenGLShader::ShaderType type, const QString& fileName);
bool addCacheableShaderFromSourceCode(QOpenGLShader::ShaderType type, const char *source);
bool addCacheableShaderFromSourceCode(QOpenGLShader::ShaderType type, const QByteArray &source);
bool addCacheableShaderFromSourceCode(QOpenGLShader::ShaderType type, const QString &source);
bool addCacheableShaderFromSourceFile(QOpenGLShader::ShaderType type, const QString &fileName);
void removeAllShaders();
virtual bool link();

View File

@ -330,8 +330,8 @@ bool QOpenGLTextureBlitterPrivate::buildProgram(ProgramIndex idx, const char *vs
p->glProgram.reset(new QOpenGLShaderProgram);
p->glProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vs);
p->glProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fs);
p->glProgram->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vs);
p->glProgram->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fs);
p->glProgram->link();
if (!p->glProgram->isLinked()) {
qWarning() << "Could not link shader program:\n" << p->glProgram->log();

View File

@ -342,22 +342,14 @@ void QOpenGLTextureGlyphCache::resizeTextureData(int width, int height)
QString source;
source.append(QLatin1String(isCoreProfile ? qopenglslMainWithTexCoordsVertexShader_core : qopenglslMainWithTexCoordsVertexShader));
source.append(QLatin1String(isCoreProfile ? qopenglslUntransformedPositionVertexShader_core : qopenglslUntransformedPositionVertexShader));
QOpenGLShader *vertexShader = new QOpenGLShader(QOpenGLShader::Vertex, m_blitProgram);
vertexShader->compileSourceCode(source);
m_blitProgram->addShader(vertexShader);
m_blitProgram->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, source);
}
{
QString source;
source.append(QLatin1String(isCoreProfile ? qopenglslMainFragmentShader_core : qopenglslMainFragmentShader));
source.append(QLatin1String(isCoreProfile ? qopenglslImageSrcFragmentShader_core : qopenglslImageSrcFragmentShader));
QOpenGLShader *fragmentShader = new QOpenGLShader(QOpenGLShader::Fragment, m_blitProgram);
fragmentShader->compileSourceCode(source);
m_blitProgram->addShader(fragmentShader);
m_blitProgram->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, source);
}
m_blitProgram->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR);

View File

@ -146,8 +146,8 @@ void QEglFSCursor::createShaderPrograms()
GraphicsContextData &gfx(m_gfx[QOpenGLContext::currentContext()]);
gfx.program = new QOpenGLShaderProgram;
gfx.program->addShaderFromSourceCode(QOpenGLShader::Vertex, textureVertexProgram);
gfx.program->addShaderFromSourceCode(QOpenGLShader::Fragment, textureFragmentProgram);
gfx.program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, textureVertexProgram);
gfx.program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, textureFragmentProgram);
gfx.program->bindAttributeLocation("vertexCoordEntry", 0);
gfx.program->bindAttributeLocation("textureCoordEntry", 1);
gfx.program->link();