summaryrefslogtreecommitdiffstats
path: root/src/gui/opengl/qopenglshaderprogram.cpp
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2016-09-29 12:59:06 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2016-11-24 10:27:16 +0000
commit85f868e73e4cf9dffe27b737f8dc3f5bb626ed04 (patch)
tree60f19e5319e14957e98edeeafaa8da8279972b75 /src/gui/opengl/qopenglshaderprogram.cpp
parentffd316ebe3963b7ff8d94a3484ac85de793b8299 (diff)
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>
Diffstat (limited to 'src/gui/opengl/qopenglshaderprogram.cpp')
-rw-r--r--src/gui/opengl/qopenglshaderprogram.cpp318
1 files changed, 315 insertions, 3 deletions
diff --git a/src/gui/opengl/qopenglshaderprogram.cpp b/src/gui/opengl/qopenglshaderprogram.cpp
index b92d97c143..d296f327c8 100644
--- a/src/gui/opengl/qopenglshaderprogram.cpp
+++ b/src/gui/opengl/qopenglshaderprogram.cpp
@@ -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 {
@@ -1023,6 +1054,139 @@ bool QOpenGLShaderProgram::addShaderFromSourceFile
}
/*!
+ 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.
The shader program must be valid in the current QOpenGLContext.
@@ -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