diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/opengl/opengl.pri | 6 | ||||
-rw-r--r-- | src/gui/opengl/qopenglengineshadermanager.cpp | 97 | ||||
-rw-r--r-- | src/gui/opengl/qopenglengineshadermanager_p.h | 1 | ||||
-rw-r--r-- | src/gui/opengl/qopenglprogrambinarycache.cpp | 332 | ||||
-rw-r--r-- | src/gui/opengl/qopenglprogrambinarycache_p.h | 89 | ||||
-rw-r--r-- | src/gui/opengl/qopenglshaderprogram.cpp | 318 | ||||
-rw-r--r-- | src/gui/opengl/qopenglshaderprogram.h | 5 | ||||
-rw-r--r-- | src/gui/opengl/qopengltextureblitter.cpp | 4 | ||||
-rw-r--r-- | src/gui/opengl/qopengltextureglyphcache.cpp | 12 |
9 files changed, 776 insertions, 88 deletions
diff --git a/src/gui/opengl/opengl.pri b/src/gui/opengl/opengl.pri index 712cf144e0..e7eff5b7a0 100644 --- a/src/gui/opengl/opengl.pri +++ b/src/gui/opengl/opengl.pri @@ -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 \ diff --git a/src/gui/opengl/qopenglengineshadermanager.cpp b/src/gui/opengl/qopenglengineshadermanager.cpp index 1d3e47f93b..c7e457b364 100644 --- a/src/gui/opengl/qopenglengineshadermanager.cpp +++ b/src/gui/opengl/qopenglengineshadermanager.cpp @@ -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; } diff --git a/src/gui/opengl/qopenglengineshadermanager_p.h b/src/gui/opengl/qopenglengineshadermanager_p.h index 23b2ccf486..377501457d 100644 --- a/src/gui/opengl/qopenglengineshadermanager_p.h +++ b/src/gui/opengl/qopenglengineshadermanager_p.h @@ -366,7 +366,6 @@ private: QOpenGLShaderProgram *blitShaderProg; QOpenGLShaderProgram *simpleShaderProg; QList<QOpenGLEngineShaderProg*> cachedPrograms; - QList<QOpenGLShader *> shaders; static const char* qShaderSnippets[TotalSnippetCount]; }; diff --git a/src/gui/opengl/qopenglprogrambinarycache.cpp b/src/gui/opengl/qopenglprogrambinarycache.cpp new file mode 100644 index 0000000000..8598221c15 --- /dev/null +++ b/src/gui/opengl/qopenglprogrambinarycache.cpp @@ -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 diff --git a/src/gui/opengl/qopenglprogrambinarycache_p.h b/src/gui/opengl/qopenglprogrambinarycache_p.h new file mode 100644 index 0000000000..5625ad9c21 --- /dev/null +++ b/src/gui/opengl/qopenglprogrambinarycache_p.h @@ -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 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 diff --git a/src/gui/opengl/qopenglshaderprogram.h b/src/gui/opengl/qopenglshaderprogram.h index 2da359c535..cf67e59c8f 100644 --- a/src/gui/opengl/qopenglshaderprogram.h +++ b/src/gui/opengl/qopenglshaderprogram.h @@ -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(); diff --git a/src/gui/opengl/qopengltextureblitter.cpp b/src/gui/opengl/qopengltextureblitter.cpp index 858fc0d857..b65df9dc82 100644 --- a/src/gui/opengl/qopengltextureblitter.cpp +++ b/src/gui/opengl/qopengltextureblitter.cpp @@ -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(); diff --git a/src/gui/opengl/qopengltextureglyphcache.cpp b/src/gui/opengl/qopengltextureglyphcache.cpp index 9a7b1eb21d..afd5004cec 100644 --- a/src/gui/opengl/qopengltextureglyphcache.cpp +++ b/src/gui/opengl/qopengltextureglyphcache.cpp @@ -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); |