diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-09-03 11:59:24 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-09-09 20:42:38 +0200 |
commit | 7af6649e884f4ba9fa3fde0333090b93f62a13c0 (patch) | |
tree | 50c8123a114b6f29a949c910751ce999e9bc7c81 /src/gui/rhi/qrhimetal.mm | |
parent | 89d0a03c067b42155b1a2d310f8514f595abfd61 (diff) |
rhi: gl, metal, d3d: Reuse shader objects when source is the same
Now that Qt Quick's batch renderer misses one level of shader source
caching due to the nature of pipeline state objects, it can be useful
to keep and reuse shader objects when the hash of the source code
matches.
The goal here is to allow Qt Quick to be on par with what the direct
OpenGL path has when it comes to caching shader sources and compilation
results. The program binary disk cache is not in scope in this patch.
Also adds QRhi::releaseCachedResources(), similarly to what the scenegraph
has. This can be called to clear caches such as the shader object
cache we keep here.
Change-Id: Ie3d81d823f61fa65ec814439e882c498f7774d43
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
Diffstat (limited to 'src/gui/rhi/qrhimetal.mm')
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 229 |
1 files changed, 137 insertions, 92 deletions
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index dfa79edb00..a14ffa7173 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -138,6 +138,20 @@ QT_BEGIN_NAMESPACE \l{QRhiCommandBuffer::endPass()}. */ +struct QMetalShader +{ + id<MTLLibrary> lib = nil; + id<MTLFunction> func = nil; + std::array<uint, 3> localSize; + + void release() { + [lib release]; + lib = nil; + [func release]; + func = nil; + } +}; + struct QRhiMetalData { QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { } @@ -206,6 +220,8 @@ struct QRhiMetalData API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil; static const int TEXBUF_ALIGN = 256; // probably not accurate + + QHash<QRhiShaderStage, QMetalShader> shaderCache; }; Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE); @@ -289,17 +305,14 @@ struct QMetalGraphicsPipelineData MTLPrimitiveType primitiveType; MTLWinding winding; MTLCullMode cullMode; - id<MTLLibrary> vsLib = nil; - id<MTLFunction> vsFunc = nil; - id<MTLLibrary> fsLib = nil; - id<MTLFunction> fsFunc = nil; + QMetalShader vs; + QMetalShader fs; }; struct QMetalComputePipelineData { id<MTLComputePipelineState> ps = nil; - id<MTLLibrary> csLib = nil; - id<MTLFunction> csFunc = nil; + QMetalShader cs; MTLSize localSize; }; @@ -404,6 +417,10 @@ void QRhiMetal::destroy() executeDeferredReleases(true); finishActiveReadbacks(true); + for (QMetalShader &s : d->shaderCache) + s.release(); + d->shaderCache.clear(); + if (@available(macOS 10.13, iOS 11.0, *)) { [d->captureScope release]; d->captureScope = nil; @@ -570,6 +587,14 @@ void QRhiMetal::makeThreadLocalNativeContextCurrent() // nothing to do here } +void QRhiMetal::releaseCachedResources() +{ + for (QMetalShader &s : d->shaderCache) + s.release(); + + d->shaderCache.clear(); +} + QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) { @@ -2843,36 +2868,17 @@ void QMetalGraphicsPipeline::release() { QRHI_RES_RHI(QRhiMetal); - if (!d->ps) - return; - - if (d->ps) { - [d->ps release]; - d->ps = nil; - } + d->vs.release(); + d->fs.release(); - if (d->ds) { - [d->ds release]; - d->ds = nil; - } + [d->ds release]; + d->ds = nil; - if (d->vsFunc) { - [d->vsFunc release]; - d->vsFunc = nil; - } - if (d->vsLib) { - [d->vsLib release]; - d->vsLib = nil; - } + if (!d->ps) + return; - if (d->fsFunc) { - [d->fsFunc release]; - d->fsFunc = nil; - } - if (d->fsLib) { - [d->fsLib release]; - d->fsLib = nil; - } + [d->ps release]; + d->ps = nil; rhiD->unregisterResource(this); } @@ -3159,34 +3165,66 @@ bool QMetalGraphicsPipeline::build() // buffers not just the resource binding layout) so leave it at the default for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) { - QString error; - QByteArray entryPoint; - id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint); - if (!lib) { - qWarning("MSL shader compilation failed: %s", qPrintable(error)); - return false; - } - id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint); - if (!func) { - qWarning("MSL function for entry point %s not found", entryPoint.constData()); - [lib release]; - return false; - } - switch (shaderStage.type()) { - case QRhiShaderStage::Vertex: - rpDesc.vertexFunction = func; - d->vsLib = lib; - d->vsFunc = func; - break; - case QRhiShaderStage::Fragment: - rpDesc.fragmentFunction = func; - d->fsLib = lib; - d->fsFunc = func; - break; - default: - [func release]; - [lib release]; - break; + auto cacheIt = rhiD->d->shaderCache.constFind(shaderStage); + if (cacheIt != rhiD->d->shaderCache.constEnd()) { + switch (shaderStage.type()) { + case QRhiShaderStage::Vertex: + d->vs = *cacheIt; + [d->vs.lib retain]; + [d->vs.func retain]; + rpDesc.vertexFunction = d->vs.func; + break; + case QRhiShaderStage::Fragment: + d->fs = *cacheIt; + [d->fs.lib retain]; + [d->fs.func retain]; + rpDesc.fragmentFunction = d->fs.func; + break; + default: + break; + } + } else { + QString error; + QByteArray entryPoint; + id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint); + if (!lib) { + qWarning("MSL shader compilation failed: %s", qPrintable(error)); + return false; + } + id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint); + if (!func) { + qWarning("MSL function for entry point %s not found", entryPoint.constData()); + [lib release]; + return false; + } + if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) { + // Use the simplest strategy: too many cached shaders -> drop them all. + for (QMetalShader &s : rhiD->d->shaderCache) + s.release(); + rhiD->d->shaderCache.clear(); + } + switch (shaderStage.type()) { + case QRhiShaderStage::Vertex: + d->vs.lib = lib; + d->vs.func = func; + rhiD->d->shaderCache.insert(shaderStage, d->vs); + [d->vs.lib retain]; + [d->vs.func retain]; + rpDesc.vertexFunction = func; + break; + case QRhiShaderStage::Fragment: + d->fs.lib = lib; + d->fs.func = func; + rhiD->d->shaderCache.insert(shaderStage, d->fs); + [d->fs.lib retain]; + [d->fs.func retain]; + rpDesc.fragmentFunction = func; + break; + default: + [func release]; + [lib release]; + break; + } } } @@ -3286,22 +3324,13 @@ void QMetalComputePipeline::release() { QRHI_RES_RHI(QRhiMetal); - if (d->csFunc) { - [d->csFunc release]; - d->csFunc = nil; - } - if (d->csLib) { - [d->csLib release]; - d->csLib = nil; - } + d->cs.release(); if (!d->ps) return; - if (d->ps) { - [d->ps release]; - d->ps = nil; - } + [d->ps release]; + d->ps = nil; rhiD->unregisterResource(this); } @@ -3313,28 +3342,44 @@ bool QMetalComputePipeline::build() QRHI_RES_RHI(QRhiMetal); - const QShader shader = m_shaderStage.shader(); - QString error; - QByteArray entryPoint; - id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(), - &error, &entryPoint); - if (!lib) { - qWarning("MSL shader compilation failed: %s", qPrintable(error)); - return false; - } - id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint); - if (!func) { - qWarning("MSL function for entry point %s not found", entryPoint.constData()); - [lib release]; - return false; + auto cacheIt = rhiD->d->shaderCache.constFind(m_shaderStage); + if (cacheIt != rhiD->d->shaderCache.constEnd()) { + d->cs = *cacheIt; + } else { + const QShader shader = m_shaderStage.shader(); + QString error; + QByteArray entryPoint; + id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(), + &error, &entryPoint); + if (!lib) { + qWarning("MSL shader compilation failed: %s", qPrintable(error)); + return false; + } + id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint); + if (!func) { + qWarning("MSL function for entry point %s not found", entryPoint.constData()); + [lib release]; + return false; + } + d->cs.lib = lib; + d->cs.func = func; + d->cs.localSize = shader.description().computeShaderLocalSize(); + + if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) { + for (QMetalShader &s : rhiD->d->shaderCache) + s.release(); + rhiD->d->shaderCache.clear(); + } + rhiD->d->shaderCache.insert(m_shaderStage, d->cs); } - d->csLib = lib; - d->csFunc = func; - std::array<uint, 3> localSize = shader.description().computeShaderLocalSize(); - d->localSize = MTLSizeMake(localSize[0], localSize[1], localSize[2]); + + [d->cs.lib retain]; + [d->cs.func retain]; + + d->localSize = MTLSizeMake(d->cs.localSize[0], d->cs.localSize[1], d->cs.localSize[2]); NSError *err = nil; - d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->csFunc error: &err]; + d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->cs.func error: &err]; if (!d->ps) { const QString msg = QString::fromNSString(err.localizedDescription); qWarning("Failed to create render pipeline state: %s", qPrintable(msg)); |