rhi: metal: Remap resource bindings based on the QShader table

...when available. Fall back to the QRhi (i.e. SPIR-V) binding
point otherwise (which becomes unsafe once shadertools bumps
its SPIRV-Cross snapshot, but is fine for existing .qsb files)

Task-number: QTBUG-79368
Change-Id: I2d452fdd4efb484867732c358171a800d3261dcd
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
This commit is contained in:
Laszlo Agocs 2019-10-22 12:37:34 +02:00
parent 6326d1df39
commit cc631c15a0
2 changed files with 73 additions and 31 deletions

View File

@ -35,8 +35,6 @@
****************************************************************************/ ****************************************************************************/
#include "qrhimetal_p_p.h" #include "qrhimetal_p_p.h"
#include "qshader_p.h"
#include "qshaderdescription_p.h"
#include <QGuiApplication> #include <QGuiApplication>
#include <QWindow> #include <QWindow>
#include <qmath.h> #include <qmath.h>
@ -143,8 +141,10 @@ struct QMetalShader
id<MTLLibrary> lib = nil; id<MTLLibrary> lib = nil;
id<MTLFunction> func = nil; id<MTLFunction> func = nil;
std::array<uint, 3> localSize; std::array<uint, 3> localSize;
QShader::NativeResourceBindingMap nativeResourceBindingMap;
void release() { void release() {
nativeResourceBindingMap.clear();
[lib release]; [lib release];
lib = nil; lib = nil;
[func release]; [func release];
@ -164,7 +164,7 @@ struct QRhiMetalData
const QRhiDepthStencilClearValue &depthStencilClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue,
int colorAttCount); int colorAttCount);
id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant, id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
QString *error, QByteArray *entryPoint); QString *error, QByteArray *entryPoint, QShaderKey *activeKey);
id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint); id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
struct DeferredReleaseEntry { struct DeferredReleaseEntry {
@ -653,18 +653,39 @@ QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings()
return new QMetalShaderResourceBindings(this); return new QMetalShaderResourceBindings(this);
} }
void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, enum class BindingType {
Buffer,
Texture,
Sampler
};
static inline int mapBinding(int binding,
int stageIndex,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[],
BindingType type)
{
const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex];
if (map) {
auto it = map->constFind(binding);
if (it != map->cend())
return type == BindingType::Sampler ? it->second : it->first;
}
return binding;
}
void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD,
QMetalCommandBuffer *cbD,
int dynamicOffsetCount, int dynamicOffsetCount,
const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
bool offsetOnlyChange) bool offsetOnlyChange,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES])
{ {
static const int KNOWN_STAGES = 3;
struct { struct {
QRhiBatchedBindings<id<MTLBuffer> > buffers; QRhiBatchedBindings<id<MTLBuffer> > buffers;
QRhiBatchedBindings<NSUInteger> bufferOffsets; QRhiBatchedBindings<NSUInteger> bufferOffsets;
QRhiBatchedBindings<id<MTLTexture> > textures; QRhiBatchedBindings<id<MTLTexture> > textures;
QRhiBatchedBindings<id<MTLSamplerState> > samplers; QRhiBatchedBindings<id<MTLSamplerState> > samplers;
} res[KNOWN_STAGES]; } res[SUPPORTED_STAGES];
for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) { for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
const QRhiShaderResourceBinding::Data *b = binding.data(); const QRhiShaderResourceBinding::Data *b = binding.data();
@ -682,15 +703,15 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
} }
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
res[0].buffers.feed(b->binding, mtlbuf); res[0].buffers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf);
res[0].bufferOffsets.feed(b->binding, offset); res[0].bufferOffsets.feed(b->binding, offset);
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
res[1].buffers.feed(b->binding, mtlbuf); res[1].buffers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf);
res[1].bufferOffsets.feed(b->binding, offset); res[1].bufferOffsets.feed(b->binding, offset);
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
res[2].buffers.feed(b->binding, mtlbuf); res[2].buffers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf);
res[2].bufferOffsets.feed(b->binding, offset); res[2].bufferOffsets.feed(b->binding, offset);
} }
} }
@ -700,16 +721,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
res[0].textures.feed(b->binding, texD->d->tex); res[0].textures.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex);
res[0].samplers.feed(b->binding, samplerD->d->samplerState); res[0].samplers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState);
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
res[1].textures.feed(b->binding, texD->d->tex); res[1].textures.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex);
res[1].samplers.feed(b->binding, samplerD->d->samplerState); res[1].samplers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState);
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
res[2].textures.feed(b->binding, texD->d->tex); res[2].textures.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex);
res[2].samplers.feed(b->binding, samplerD->d->samplerState); res[2].samplers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState);
} }
} }
break; break;
@ -722,11 +743,11 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level); id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage))
res[0].textures.feed(b->binding, t); res[0].textures.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Texture), t);
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage))
res[1].textures.feed(b->binding, t); res[1].textures.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Texture), t);
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage))
res[2].textures.feed(b->binding, t); res[2].textures.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Texture), t);
} }
break; break;
case QRhiShaderResourceBinding::BufferLoad: case QRhiShaderResourceBinding::BufferLoad:
@ -739,15 +760,15 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
id<MTLBuffer> mtlbuf = bufD->d->buf[0]; id<MTLBuffer> mtlbuf = bufD->d->buf[0];
uint offset = uint(b->u.sbuf.offset); uint offset = uint(b->u.sbuf.offset);
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
res[0].buffers.feed(b->binding, mtlbuf); res[0].buffers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf);
res[0].bufferOffsets.feed(b->binding, offset); res[0].bufferOffsets.feed(b->binding, offset);
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
res[1].buffers.feed(b->binding, mtlbuf); res[1].buffers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf);
res[1].bufferOffsets.feed(b->binding, offset); res[1].bufferOffsets.feed(b->binding, offset);
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
res[2].buffers.feed(b->binding, mtlbuf); res[2].buffers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf);
res[2].bufferOffsets.feed(b->binding, offset); res[2].bufferOffsets.feed(b->binding, offset);
} }
} }
@ -758,7 +779,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
} }
} }
for (int idx = 0; idx < KNOWN_STAGES; ++idx) { for (int idx = 0; idx < SUPPORTED_STAGES; ++idx) {
res[idx].buffers.finish(); res[idx].buffers.finish();
res[idx].bufferOffsets.finish(); res[idx].bufferOffsets.finish();
@ -973,18 +994,22 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
// dynamic uniform buffer offsets always trigger a rebind // dynamic uniform buffer offsets always trigger a rebind
if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) { if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) {
const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr };
if (gfxPsD) { if (gfxPsD) {
cbD->currentGraphicsSrb = srb; cbD->currentGraphicsSrb = srb;
cbD->currentComputeSrb = nullptr; cbD->currentComputeSrb = nullptr;
resBindMaps[0] = &gfxPsD->d->vs.nativeResourceBindingMap;
resBindMaps[1] = &gfxPsD->d->fs.nativeResourceBindingMap;
} else { } else {
cbD->currentGraphicsSrb = nullptr; cbD->currentGraphicsSrb = nullptr;
cbD->currentComputeSrb = srb; cbD->currentComputeSrb = srb;
resBindMaps[2] = &compPsD->d->cs.nativeResourceBindingMap;
} }
cbD->currentSrbGeneration = srbD->generation; cbD->currentSrbGeneration = srbD->generation;
cbD->currentResSlot = resSlot; cbD->currentResSlot = resSlot;
const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt; const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt;
enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange); enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange, resBindMaps);
} }
} }
@ -3081,9 +3106,10 @@ static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
} }
id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant, id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
QString *error, QByteArray *entryPoint) QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
{ {
QShaderCode mtllib = shader.shader({ QShader::MetalLibShader, 12, shaderVariant }); QShaderKey key = { QShader::MetalLibShader, 12, shaderVariant };
QShaderCode mtllib = shader.shader(key);
if (!mtllib.shader().isEmpty()) { if (!mtllib.shader().isEmpty()) {
dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(), dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
size_t(mtllib.shader().size()), size_t(mtllib.shader().size()),
@ -3094,6 +3120,7 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var
dispatch_release(data); dispatch_release(data);
if (!err) { if (!err) {
*entryPoint = mtllib.entryPoint(); *entryPoint = mtllib.entryPoint();
*activeKey = key;
return lib; return lib;
} else { } else {
const QString msg = QString::fromNSString(err.localizedDescription); const QString msg = QString::fromNSString(err.localizedDescription);
@ -3101,7 +3128,8 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var
} }
} }
QShaderCode mslSource = shader.shader({ QShader::MslShader, 12, shaderVariant }); key = { QShader::MslShader, 12, shaderVariant };
QShaderCode mslSource = shader.shader(key);
if (mslSource.shader().isEmpty()) { if (mslSource.shader().isEmpty()) {
qWarning() << "No MSL 1.2 code found in baked shader" << shader; qWarning() << "No MSL 1.2 code found in baked shader" << shader;
return nil; return nil;
@ -3122,6 +3150,7 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var
} }
*entryPoint = mslSource.entryPoint(); *entryPoint = mslSource.entryPoint();
*activeKey = key;
return lib; return lib;
} }
@ -3195,9 +3224,12 @@ bool QMetalGraphicsPipeline::build()
break; break;
} }
} else { } else {
const QShader shader = shaderStage.shader();
QString error; QString error;
QByteArray entryPoint; QByteArray entryPoint;
id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint); QShaderKey activeKey;
id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, shaderStage.shaderVariant(),
&error, &entryPoint, &activeKey);
if (!lib) { if (!lib) {
qWarning("MSL shader compilation failed: %s", qPrintable(error)); qWarning("MSL shader compilation failed: %s", qPrintable(error));
return false; return false;
@ -3218,6 +3250,8 @@ bool QMetalGraphicsPipeline::build()
case QRhiShaderStage::Vertex: case QRhiShaderStage::Vertex:
d->vs.lib = lib; d->vs.lib = lib;
d->vs.func = func; d->vs.func = func;
if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey))
d->vs.nativeResourceBindingMap = *map;
rhiD->d->shaderCache.insert(shaderStage, d->vs); rhiD->d->shaderCache.insert(shaderStage, d->vs);
[d->vs.lib retain]; [d->vs.lib retain];
[d->vs.func retain]; [d->vs.func retain];
@ -3226,6 +3260,8 @@ bool QMetalGraphicsPipeline::build()
case QRhiShaderStage::Fragment: case QRhiShaderStage::Fragment:
d->fs.lib = lib; d->fs.lib = lib;
d->fs.func = func; d->fs.func = func;
if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey))
d->fs.nativeResourceBindingMap = *map;
rhiD->d->shaderCache.insert(shaderStage, d->fs); rhiD->d->shaderCache.insert(shaderStage, d->fs);
[d->fs.lib retain]; [d->fs.lib retain];
[d->fs.func retain]; [d->fs.func retain];
@ -3360,8 +3396,9 @@ bool QMetalComputePipeline::build()
const QShader shader = m_shaderStage.shader(); const QShader shader = m_shaderStage.shader();
QString error; QString error;
QByteArray entryPoint; QByteArray entryPoint;
QShaderKey activeKey;
id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(), id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(),
&error, &entryPoint); &error, &entryPoint, &activeKey);
if (!lib) { if (!lib) {
qWarning("MSL shader compilation failed: %s", qPrintable(error)); qWarning("MSL shader compilation failed: %s", qPrintable(error));
return false; return false;
@ -3375,6 +3412,8 @@ bool QMetalComputePipeline::build()
d->cs.lib = lib; d->cs.lib = lib;
d->cs.func = func; d->cs.func = func;
d->cs.localSize = shader.description().computeShaderLocalSize(); d->cs.localSize = shader.description().computeShaderLocalSize();
if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey))
d->cs.nativeResourceBindingMap = *map;
if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) { if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
for (QMetalShader &s : rhiD->d->shaderCache) for (QMetalShader &s : rhiD->d->shaderCache)

View File

@ -433,10 +433,13 @@ public:
qsizetype *curOfs); qsizetype *curOfs);
void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD); void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD);
void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, static const int SUPPORTED_STAGES = 3;
void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD,
QMetalCommandBuffer *cbD,
int dynamicOffsetCount, int dynamicOffsetCount,
const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
bool offsetOnlyChange); bool offsetOnlyChange,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]);
int effectiveSampleCount(int sampleCount) const; int effectiveSampleCount(int sampleCount) const;
bool importedDevice = false; bool importedDevice = false;