rhi: metal: Make sure the resources are sorted based on the native bindings

...before generating batches for the encoder's set* methods. Otherwise there
is a chance we end up in an assertion in case the native binding number for
a buffer/texture/sampler happens to be smaller than the native binding of the
previous. (we pre-sort based on the SPIR-V binding but that is not what the
Metal API works with in the end)

Task-number: QTBUG-81822
Change-Id: Iddfed168e065e3c7f6a09ad6dd4efdafa891b339
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
This commit is contained in:
Laszlo Agocs 2020-02-03 15:53:36 +01:00
parent 26f6aa3e50
commit 1d77945094

View File

@ -684,11 +684,27 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
bool offsetOnlyChange, bool offsetOnlyChange,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]) const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES])
{ {
struct { struct Stage {
QRhiBatchedBindings<id<MTLBuffer> > buffers; struct Buffer {
QRhiBatchedBindings<NSUInteger> bufferOffsets; int nativeBinding;
QRhiBatchedBindings<id<MTLTexture> > textures; id<MTLBuffer> mtlbuf;
QRhiBatchedBindings<id<MTLSamplerState> > samplers; uint offset;
};
struct Texture {
int nativeBinding;
id<MTLTexture> mtltex;
};
struct Sampler {
int nativeBinding;
id<MTLSamplerState> mtlsampler;
};
QVarLengthArray<Buffer, 8> buffers;
QVarLengthArray<Texture, 8> textures;
QVarLengthArray<Sampler, 8> samplers;
QRhiBatchedBindings<id<MTLBuffer> > bufferBatches;
QRhiBatchedBindings<NSUInteger> bufferOffsetBatches;
QRhiBatchedBindings<id<MTLTexture> > textureBatches;
QRhiBatchedBindings<id<MTLSamplerState> > samplerBatches;
} res[SUPPORTED_STAGES]; } res[SUPPORTED_STAGES];
enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 }; enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 };
@ -709,24 +725,18 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer); const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0) { if (nativeBinding >= 0)
res[VERTEX].buffers.feed(nativeBinding, mtlbuf); res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset });
res[VERTEX].bufferOffsets.feed(b->binding, offset);
}
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0) { if (nativeBinding >= 0)
res[FRAGMENT].buffers.feed(nativeBinding, mtlbuf); res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset });
res[FRAGMENT].bufferOffsets.feed(b->binding, offset);
}
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0) { if (nativeBinding >= 0)
res[COMPUTE].buffers.feed(nativeBinding, mtlbuf); res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset });
res[COMPUTE].bufferOffsets.feed(b->binding, offset);
}
} }
} }
break; break;
@ -738,24 +748,24 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture);
const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler); const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler);
if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
res[VERTEX].textures.feed(nativeBindingTexture, texD->d->tex); res[VERTEX].textures.append({ nativeBindingTexture, texD->d->tex });
res[VERTEX].samplers.feed(nativeBindingSampler, samplerD->d->samplerState); res[VERTEX].samplers.append({ nativeBindingSampler, samplerD->d->samplerState });
} }
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
const int nativeBindingTexture = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); const int nativeBindingTexture = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture);
const int nativeBindingSampler = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler); const int nativeBindingSampler = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler);
if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
res[FRAGMENT].textures.feed(nativeBindingTexture, texD->d->tex); res[FRAGMENT].textures.append({ nativeBindingTexture, texD->d->tex });
res[FRAGMENT].samplers.feed(nativeBindingSampler, samplerD->d->samplerState); res[FRAGMENT].samplers.append({ nativeBindingSampler, samplerD->d->samplerState });
} }
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
const int nativeBindingTexture = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture); const int nativeBindingTexture = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture);
const int nativeBindingSampler = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler); const int nativeBindingSampler = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler);
if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
res[COMPUTE].textures.feed(nativeBindingTexture, texD->d->tex); res[COMPUTE].textures.append({ nativeBindingTexture, texD->d->tex });
res[COMPUTE].samplers.feed(nativeBindingSampler, samplerD->d->samplerState); res[COMPUTE].samplers.append({ nativeBindingSampler, samplerD->d->samplerState });
} }
} }
} }
@ -769,17 +779,17 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture);
if (nativeBinding >= 0) if (nativeBinding >= 0)
res[VERTEX].textures.feed(nativeBinding, t); res[VERTEX].textures.append({ nativeBinding, t });
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture);
if (nativeBinding >= 0) if (nativeBinding >= 0)
res[FRAGMENT].textures.feed(nativeBinding, t); res[FRAGMENT].textures.append({ nativeBinding, t });
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture); const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture);
if (nativeBinding >= 0) if (nativeBinding >= 0)
res[COMPUTE].textures.feed(nativeBinding, t); res[COMPUTE].textures.append({ nativeBinding, t });
} }
} }
break; break;
@ -792,24 +802,18 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
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)) {
const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer); const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0) { if (nativeBinding >= 0)
res[VERTEX].buffers.feed(nativeBinding, mtlbuf); res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset });
res[VERTEX].bufferOffsets.feed(b->binding, offset);
}
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0) { if (nativeBinding >= 0)
res[FRAGMENT].buffers.feed(nativeBinding, mtlbuf); res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset });
res[FRAGMENT].bufferOffsets.feed(b->binding, offset);
}
} }
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0) { if (nativeBinding >= 0)
res[COMPUTE].buffers.feed(nativeBinding, mtlbuf); res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset });
res[COMPUTE].bufferOffsets.feed(b->binding, offset);
}
} }
} }
break; break;
@ -825,12 +829,26 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == COMPUTE) if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == COMPUTE)
continue; continue;
res[stage].buffers.finish(); // QRhiBatchedBindings works with the native bindings and expects
res[stage].bufferOffsets.finish(); // sorted input. The pre-sorted QRhiShaderResourceBinding list (based
// on the QRhi (SPIR-V) binding) is not helpful in this regard, so we
// have to sort here every time.
for (int i = 0, ie = res[stage].buffers.batches.count(); i != ie; ++i) { std::sort(res[stage].buffers.begin(), res[stage].buffers.end(), [](const Stage::Buffer &a, const Stage::Buffer &b) {
const auto &bufferBatch(res[stage].buffers.batches[i]); return a.nativeBinding < b.nativeBinding;
const auto &offsetBatch(res[stage].bufferOffsets.batches[i]); });
for (const Stage::Buffer &buf : qAsConst(res[stage].buffers)) {
res[stage].bufferBatches.feed(buf.nativeBinding, buf.mtlbuf);
res[stage].bufferOffsetBatches.feed(buf.nativeBinding, buf.offset);
}
res[stage].bufferBatches.finish();
res[stage].bufferOffsetBatches.finish();
for (int i = 0, ie = res[stage].bufferBatches.batches.count(); i != ie; ++i) {
const auto &bufferBatch(res[stage].bufferBatches.batches[i]);
const auto &offsetBatch(res[stage].bufferOffsetBatches.batches[i]);
switch (stage) { switch (stage) {
case VERTEX: case VERTEX:
[cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData() [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
@ -856,11 +874,25 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
if (offsetOnlyChange) if (offsetOnlyChange)
continue; continue;
res[stage].textures.finish(); std::sort(res[stage].textures.begin(), res[stage].textures.end(), [](const Stage::Texture &a, const Stage::Texture &b) {
res[stage].samplers.finish(); return a.nativeBinding < b.nativeBinding;
});
for (int i = 0, ie = res[stage].textures.batches.count(); i != ie; ++i) { std::sort(res[stage].samplers.begin(), res[stage].samplers.end(), [](const Stage::Sampler &a, const Stage::Sampler &b) {
const auto &batch(res[stage].textures.batches[i]); return a.nativeBinding < b.nativeBinding;
});
for (const Stage::Texture &t : qAsConst(res[stage].textures))
res[stage].textureBatches.feed(t.nativeBinding, t.mtltex);
for (const Stage::Sampler &s : qAsConst(res[stage].samplers))
res[stage].samplerBatches.feed(s.nativeBinding, s.mtlsampler);
res[stage].textureBatches.finish();
res[stage].samplerBatches.finish();
for (int i = 0, ie = res[stage].textureBatches.batches.count(); i != ie; ++i) {
const auto &batch(res[stage].textureBatches.batches[i]);
switch (stage) { switch (stage) {
case VERTEX: case VERTEX:
[cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData() [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData()
@ -879,8 +911,8 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
break; break;
} }
} }
for (int i = 0, ie = res[stage].samplers.batches.count(); i != ie; ++i) { for (int i = 0, ie = res[stage].samplerBatches.batches.count(); i != ie; ++i) {
const auto &batch(res[stage].samplers.batches[i]); const auto &batch(res[stage].samplerBatches.batches[i]);
switch (stage) { switch (stage) {
case VERTEX: case VERTEX:
[cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData() [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData()