summaryrefslogtreecommitdiffstats
path: root/src/gui/rhi/qrhigles2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/rhi/qrhigles2.cpp')
-rw-r--r--src/gui/rhi/qrhigles2.cpp312
1 files changed, 274 insertions, 38 deletions
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 3259912c74..fac05f4947 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -328,6 +328,18 @@ QT_BEGIN_NAMESPACE
#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F
#endif
+#ifndef GL_CONTEXT_LOST
+#define GL_CONTEXT_LOST 0x0507
+#endif
+
+#ifndef GL_PROGRAM_BINARY_LENGTH
+#define GL_PROGRAM_BINARY_LENGTH 0x8741
+#endif
+
+#ifndef GL_NUM_PROGRAM_BINARY_FORMATS
+#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
+#endif
+
/*!
Constructs a new QRhiGles2InitParams.
@@ -438,8 +450,8 @@ bool QRhiGles2::ensureContext(QSurface *surface) const
bool QRhiGles2::create(QRhi::Flags flags)
{
- Q_UNUSED(flags);
Q_ASSERT(fallbackSurface);
+ rhiFlags = flags;
if (!importedContext) {
ctx = new QOpenGLContext;
@@ -582,12 +594,24 @@ bool QRhiGles2::create(QRhi::Flags flags)
caps.intAttributes = caps.ctxMajor >= 3; // 3.0 or ES 3.0
caps.screenSpaceDerivatives = f->hasOpenGLExtension(QOpenGLExtensions::StandardDerivatives);
- // TO DO: We could also check for ARB_texture_multisample but it is not
- // currently in QOpenGLExtensions
- // 3.0 or ES 3.1
- caps.multisampledTexture = caps.gles
- ? (caps.ctxMajor > 3 || (caps.ctxMajor >= 3 && caps.ctxMinor >= 1))
- : (caps.ctxMajor >= 3);
+ if (caps.gles)
+ caps.multisampledTexture = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // ES 3.1
+ else
+ caps.multisampledTexture = caps.ctxMajor >= 3; // 3.0
+
+ // Program binary support: only the core stuff, do not bother with the old
+ // extensions like GL_OES_get_program_binary
+ if (caps.gles)
+ caps.programBinary = caps.ctxMajor >= 3; // ES 3.0
+ else
+ caps.programBinary = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 1); // 4.1
+
+ if (caps.programBinary) {
+ GLint fmtCount = 0;
+ f->glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &fmtCount);
+ if (fmtCount < 1)
+ caps.programBinary = false;
+ }
if (!caps.gles) {
f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
@@ -966,6 +990,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return caps.screenSpaceDerivatives;
case QRhi::ReadBackAnyTextureFormat:
return false;
+ case QRhi::PipelineCacheDataLoadSave:
+ return caps.programBinary;
default:
Q_UNREACHABLE();
return false;
@@ -1035,6 +1061,8 @@ void QRhiGles2::releaseCachedResources()
f->glDeleteShader(shader);
m_shaderCache.clear();
+
+ m_pipelineCache.clear();
}
bool QRhiGles2::isDeviceLost() const
@@ -1042,6 +1070,137 @@ bool QRhiGles2::isDeviceLost() const
return contextLost;
}
+struct QGles2PipelineCacheDataHeader
+{
+ quint32 rhiId;
+ quint32 arch;
+ quint32 programBinaryCount;
+ quint32 dataSize;
+ char driver[240];
+};
+
+QByteArray QRhiGles2::pipelineCacheData()
+{
+ Q_STATIC_ASSERT(sizeof(QGles2PipelineCacheDataHeader) == 256);
+
+ if (m_pipelineCache.isEmpty())
+ return QByteArray();
+
+ QGles2PipelineCacheDataHeader header;
+ memset(&header, 0, sizeof(header));
+ header.rhiId = pipelineCacheRhiId();
+ header.arch = quint32(sizeof(void*));
+ header.programBinaryCount = m_pipelineCache.count();
+ const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.count()));
+ if (driverStrLen)
+ memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen);
+ header.driver[driverStrLen] = '\0';
+
+ const size_t dataOffset = sizeof(header);
+ size_t dataSize = 0;
+ for (auto it = m_pipelineCache.cbegin(), end = m_pipelineCache.cend(); it != end; ++it) {
+ dataSize += sizeof(quint32) + it.key().size()
+ + sizeof(quint32) + it->data.size()
+ + sizeof(quint32);
+ }
+
+ QByteArray buf(dataOffset + dataSize, Qt::Uninitialized);
+ char *p = buf.data() + dataOffset;
+ for (auto it = m_pipelineCache.cbegin(), end = m_pipelineCache.cend(); it != end; ++it) {
+ const QByteArray key = it.key();
+ const QByteArray data = it->data;
+ const quint32 format = it->format;
+
+ quint32 i = key.size();
+ memcpy(p, &i, 4);
+ p += 4;
+ memcpy(p, key.constData(), key.size());
+ p += key.size();
+
+ i = data.size();
+ memcpy(p, &i, 4);
+ p += 4;
+ memcpy(p, data.constData(), data.size());
+ p += data.size();
+
+ memcpy(p, &format, 4);
+ p += 4;
+ }
+ Q_ASSERT(p == buf.data() + dataOffset + dataSize);
+
+ header.dataSize = quint32(dataSize);
+ memcpy(buf.data(), &header, sizeof(header));
+
+ return buf;
+}
+
+void QRhiGles2::setPipelineCacheData(const QByteArray &data)
+{
+ if (data.isEmpty())
+ return;
+
+ const size_t headerSize = sizeof(QGles2PipelineCacheDataHeader);
+ if (data.size() < qsizetype(headerSize)) {
+ qWarning("setPipelineCacheData: Invalid blob size (header incomplete)");
+ return;
+ }
+ const size_t dataOffset = headerSize;
+ QGles2PipelineCacheDataHeader header;
+ memcpy(&header, data.constData(), headerSize);
+
+ const quint32 rhiId = pipelineCacheRhiId();
+ if (header.rhiId != rhiId) {
+ qWarning("setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)",
+ rhiId, header.rhiId);
+ return;
+ }
+ const quint32 arch = quint32(sizeof(void*));
+ if (header.arch != arch) {
+ qWarning("setPipelineCacheData: Architecture does not match (%u, %u)",
+ arch, header.arch);
+ return;
+ }
+ if (header.programBinaryCount == 0)
+ return;
+
+ const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.count()));
+ if (strncmp(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen)) {
+ qWarning("setPipelineCacheData: OpenGL vendor/renderer/version does not match");
+ return;
+ }
+
+ if (data.size() < qsizetype(dataOffset + header.dataSize)) {
+ qWarning("setPipelineCacheData: Invalid blob size (data incomplete)");
+ return;
+ }
+
+ m_pipelineCache.clear();
+
+ const char *p = data.constData() + dataOffset;
+ for (quint32 i = 0; i < header.programBinaryCount; ++i) {
+ quint32 len = 0;
+ memcpy(&len, p, 4);
+ p += 4;
+ QByteArray key(len, Qt::Uninitialized);
+ memcpy(key.data(), p, len);
+ p += len;
+
+ memcpy(&len, p, 4);
+ p += 4;
+ QByteArray data(len, Qt::Uninitialized);
+ memcpy(data.data(), p, len);
+ p += len;
+
+ quint32 format;
+ memcpy(&format, p, 4);
+ p += 4;
+
+ m_pipelineCache.insert(key, { format, data });
+ }
+
+ qCDebug(QRHI_LOG_INFO, "Seeded pipeline cache with %d program binaries", int(m_pipelineCache.count()));
+}
+
QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags,
QRhiTexture::Format backingFormatHint)
@@ -3784,22 +3943,28 @@ static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type)
}
}
-QRhiGles2::DiskCacheResult QRhiGles2::tryLoadFromDiskCache(const QRhiShaderStage *stages,
- int stageCount,
- GLuint program,
- const QVector<QShaderDescription::InOutVariable> &inputVars,
- QByteArray *cacheKey)
+QRhiGles2::ProgramCacheResult QRhiGles2::tryLoadFromDiskOrPipelineCache(const QRhiShaderStage *stages,
+ int stageCount,
+ GLuint program,
+ const QVector<QShaderDescription::InOutVariable> &inputVars,
+ QByteArray *cacheKey)
{
- QRhiGles2::DiskCacheResult result = QRhiGles2::DiskCacheMiss;
- QByteArray diskCacheKey;
+ Q_ASSERT(cacheKey);
- if (isProgramBinaryDiskCacheEnabled()) {
+ // the traditional QOpenGL disk cache since Qt 5.9
+ const bool legacyDiskCacheEnabled = isProgramBinaryDiskCacheEnabled();
+
+ // QRhi's own (set)PipelineCacheData()
+ const bool pipelineCacheEnabled = caps.programBinary && !m_pipelineCache.isEmpty();
+
+ // calculating the cache key based on the source code is common for both types of caches
+ if (legacyDiskCacheEnabled || pipelineCacheEnabled) {
QOpenGLProgramBinaryCache::ProgramDesc binaryProgram;
for (int i = 0; i < stageCount; ++i) {
const QRhiShaderStage &stage(stages[i]);
QByteArray source = shaderSource(stage, nullptr);
if (source.isEmpty())
- return QRhiGles2::DiskCacheError;
+ return QRhiGles2::ProgramCacheError;
if (stage.type() == QRhiShaderStage::Vertex) {
// Now add something to the key that indicates the vertex input locations.
@@ -3832,30 +3997,71 @@ QRhiGles2::DiskCacheResult QRhiGles2::tryLoadFromDiskCache(const QRhiShaderStage
binaryProgram.shaders.append(QOpenGLProgramBinaryCache::ShaderDesc(toShaderStage(stage.type()), source));
}
- diskCacheKey = binaryProgram.cacheKey();
+ *cacheKey = binaryProgram.cacheKey();
+
+ // Try our pipeline cache simulation first, if it got seeded with
+ // setPipelineCacheData and there's a hit, then no need to go to the
+ // filesystem at all.
+ if (pipelineCacheEnabled) {
+ auto it = m_pipelineCache.constFind(*cacheKey);
+ if (it != m_pipelineCache.constEnd()) {
+ GLenum err;
+ for ( ; ; ) {
+ err = f->glGetError();
+ if (err == GL_NO_ERROR || err == GL_CONTEXT_LOST)
+ break;
+ }
+ f->glProgramBinary(program, it->format, it->data.constData(), it->data.size());
+ err = f->glGetError();
+ if (err == GL_NO_ERROR) {
+ GLint linkStatus = 0;
+ f->glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus == GL_TRUE)
+ return QRhiGles2::ProgramCacheHit;
+ }
+ }
+ }
- if (qrhi_programBinaryCache()->load(diskCacheKey, program)) {
+ if (legacyDiskCacheEnabled && qrhi_programBinaryCache()->load(*cacheKey, program)) {
+ // use the logging category QOpenGLShaderProgram would
qCDebug(lcOpenGLProgramDiskCache, "Program binary received from cache, program %u, key %s",
- program, diskCacheKey.constData());
- result = QRhiGles2::DiskCacheHit;
+ program, cacheKey->constData());
+ return QRhiGles2::ProgramCacheHit;
}
}
- if (cacheKey)
- *cacheKey = diskCacheKey;
-
- return result;
+ return QRhiGles2::ProgramCacheMiss;
}
void QRhiGles2::trySaveToDiskCache(GLuint program, const QByteArray &cacheKey)
{
+ // This is only for the traditional QOpenGL disk cache since Qt 5.9.
+
if (isProgramBinaryDiskCacheEnabled()) {
+ // use the logging category QOpenGLShaderProgram would
qCDebug(lcOpenGLProgramDiskCache, "Saving program binary, program %u, key %s",
program, cacheKey.constData());
qrhi_programBinaryCache()->save(cacheKey, program);
}
}
+void QRhiGles2::trySaveToPipelineCache(GLuint program, const QByteArray &cacheKey, bool force)
+{
+ // This handles our own simulated "pipeline cache". (specific to QRhi, not
+ // shared with legacy QOpenGL* stuff)
+
+ if (caps.programBinary && (force || !m_pipelineCache.contains(cacheKey))) {
+ GLint blobSize = 0;
+ f->glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &blobSize);
+ QByteArray blob(blobSize, Qt::Uninitialized);
+ GLint outSize = 0;
+ GLenum binaryFormat = 0;
+ f->glGetProgramBinary(program, blobSize, &outSize, &binaryFormat, blob.data());
+ if (blobSize == outSize)
+ m_pipelineCache.insert(cacheKey, { binaryFormat, blob });
+ }
+}
+
QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
: QRhiBuffer(rhi, type, usage, size)
{
@@ -4585,16 +4791,16 @@ bool QGles2GraphicsPipeline::create()
fsDesc = shaderStage.shader().description();
}
- QByteArray diskCacheKey;
- QRhiGles2::DiskCacheResult diskCacheResult = rhiD->tryLoadFromDiskCache(m_shaderStages.constData(),
- m_shaderStages.count(),
- program,
- vsDesc.inputVariables(),
- &diskCacheKey);
- if (diskCacheResult == QRhiGles2::DiskCacheError)
+ QByteArray cacheKey;
+ QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(m_shaderStages.constData(),
+ m_shaderStages.count(),
+ program,
+ vsDesc.inputVariables(),
+ &cacheKey);
+ if (cacheResult == QRhiGles2::ProgramCacheError)
return false;
- if (diskCacheResult == QRhiGles2::DiskCacheMiss) {
+ if (cacheResult == QRhiGles2::ProgramCacheMiss) {
for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
if (shaderStage.type() == QRhiShaderStage::Vertex) {
if (!rhiD->compileShader(program, shaderStage, nullptr))
@@ -4612,7 +4818,22 @@ bool QGles2GraphicsPipeline::create()
if (!rhiD->linkProgram(program))
return false;
- rhiD->trySaveToDiskCache(program, diskCacheKey);
+ if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) {
+ // force replacing existing cache entry (if there is one, then
+ // something is wrong with it, as there was no hit)
+ rhiD->trySaveToPipelineCache(program, cacheKey, true);
+ } else {
+ // legacy QOpenGLShaderProgram style behavior: the "pipeline cache"
+ // was not enabled, so instead store to the Qt 5 disk cache
+ rhiD->trySaveToDiskCache(program, cacheKey);
+ }
+ } else {
+ Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit);
+ if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) {
+ // just so that it ends up in the pipeline cache also when the hit was
+ // from the disk cache
+ rhiD->trySaveToPipelineCache(program, cacheKey);
+ }
}
// Use the same work area for the vertex & fragment stages, thus ensuring
@@ -4688,19 +4909,34 @@ bool QGles2ComputePipeline::create()
const QShaderDescription csDesc = m_shaderStage.shader().description();
program = rhiD->f->glCreateProgram();
- QByteArray diskCacheKey;
- QRhiGles2::DiskCacheResult diskCacheResult = rhiD->tryLoadFromDiskCache(&m_shaderStage, 1, program, {}, &diskCacheKey);
- if (diskCacheResult == QRhiGles2::DiskCacheError)
+ QByteArray cacheKey;
+ QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(&m_shaderStage, 1, program, {}, &cacheKey);
+ if (cacheResult == QRhiGles2::ProgramCacheError)
return false;
- if (diskCacheResult == QRhiGles2::DiskCacheMiss) {
+ if (cacheResult == QRhiGles2::ProgramCacheMiss) {
if (!rhiD->compileShader(program, m_shaderStage, nullptr))
return false;
if (!rhiD->linkProgram(program))
return false;
- rhiD->trySaveToDiskCache(program, diskCacheKey);
+ if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) {
+ // force replacing existing cache entry (if there is one, then
+ // something is wrong with it, as there was no hit)
+ rhiD->trySaveToPipelineCache(program, cacheKey, true);
+ } else {
+ // legacy QOpenGLShaderProgram style behavior: the "pipeline cache"
+ // was not enabled, so instead store to the Qt 5 disk cache
+ rhiD->trySaveToDiskCache(program, cacheKey);
+ }
+ } else {
+ Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit);
+ if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) {
+ // just so that it ends up in the pipeline cache also when the hit was
+ // from the disk cache
+ rhiD->trySaveToPipelineCache(program, cacheKey);
+ }
}
QSet<int> activeUniformLocations;