rhi: d3d: Fix dynamic offsets with multiple buffers

Fixes: QTBUG-86821
Change-Id: I57f86bf0f7e95b92f5b2c5fee587112ecf0fc8e6
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
bb10
Laszlo Agocs 2020-09-22 11:01:24 +02:00
parent 9d55eee8da
commit fe3a1617af
16 changed files with 246 additions and 15 deletions

View File

@ -1903,14 +1903,17 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[])
{
srbD->vsubufs.clear();
srbD->vsubuforigbindings.clear();
srbD->vsubufoffsets.clear();
srbD->vsubufsizes.clear();
srbD->fsubufs.clear();
srbD->fsubuforigbindings.clear();
srbD->fsubufoffsets.clear();
srbD->fsubufsizes.clear();
srbD->csubufs.clear();
srbD->csubuforigbindings.clear();
srbD->csubufoffsets.clear();
srbD->csubufsizes.clear();
@ -1927,6 +1930,7 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
struct Stage {
struct Buffer {
int binding; // stored and sent along in XXorigbindings just for applyDynamicOffsets()
int breg; // b0, b1, ...
ID3D11Buffer *buffer;
uint offsetInConstants;
@ -1960,9 +1964,12 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
Q_ASSERT(aligned(b->u.ubuf.offset, 256) == b->u.ubuf.offset);
bd.ubuf.id = bufD->m_id;
bd.ubuf.generation = bufD->generation;
// dynamic ubuf offsets are not considered here, those are baked in
// Dynamic ubuf offsets are not considered here, those are baked in
// at a later stage, which is good as vsubufoffsets and friends are
// per-srb, not per-setShaderResources call
// per-srb, not per-setShaderResources call. Other backends (GL,
// Metal) are different in this respect since those do not store
// per-srb vsubufoffsets etc. data so life's a bit easier for them.
// But here we have to defer baking in the dynamic offset.
const uint offsetInConstants = uint(b->u.ubuf.offset) / 16;
// size must be 16 mult. (in constants, i.e. multiple of 256 bytes).
// We can round up if needed since the buffers's actual size
@ -1971,17 +1978,17 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps);
if (nativeBinding.first >= 0)
res[RBM_VERTEX].buffers.append({ nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
res[RBM_VERTEX].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
}
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps);
if (nativeBinding.first >= 0)
res[RBM_FRAGMENT].buffers.append({ nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
res[RBM_FRAGMENT].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
}
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps);
if (nativeBinding.first >= 0)
res[RBM_COMPUTE].buffers.append({ nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
res[RBM_COMPUTE].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
}
}
break;
@ -2088,28 +2095,34 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
for (const Stage::Buffer &buf : qAsConst(res[RBM_VERTEX].buffers)) {
srbD->vsubufs.feed(buf.breg, buf.buffer);
srbD->vsubuforigbindings.feed(buf.breg, UINT(buf.binding));
srbD->vsubufoffsets.feed(buf.breg, buf.offsetInConstants);
srbD->vsubufsizes.feed(buf.breg, buf.sizeInConstants);
}
srbD->vsubufs.finish();
srbD->vsubuforigbindings.finish();
srbD->vsubufoffsets.finish();
srbD->vsubufsizes.finish();
for (const Stage::Buffer &buf : qAsConst(res[RBM_FRAGMENT].buffers)) {
srbD->fsubufs.feed(buf.breg, buf.buffer);
srbD->fsubuforigbindings.feed(buf.breg, UINT(buf.binding));
srbD->fsubufoffsets.feed(buf.breg, buf.offsetInConstants);
srbD->fsubufsizes.feed(buf.breg, buf.sizeInConstants);
}
srbD->fsubufs.finish();
srbD->fsubuforigbindings.finish();
srbD->fsubufoffsets.finish();
srbD->fsubufsizes.finish();
for (const Stage::Buffer &buf : qAsConst(res[RBM_COMPUTE].buffers)) {
srbD->csubufs.feed(buf.breg, buf.buffer);
srbD->csubuforigbindings.feed(buf.breg, UINT(buf.binding));
srbD->csubufoffsets.feed(buf.breg, buf.offsetInConstants);
srbD->csubufsizes.feed(buf.breg, buf.sizeInConstants);
}
srbD->csubufs.finish();
srbD->csubuforigbindings.finish();
srbD->csubufoffsets.finish();
srbD->csubufsizes.finish();
@ -2158,17 +2171,20 @@ void QRhiD3D11::executeBufferHostWrites(QD3D11Buffer *bufD)
static void applyDynamicOffsets(QVarLengthArray<UINT, 4> *offsets,
int batchIndex,
QRhiBatchedBindings<ID3D11Buffer *> *ubufs,
QRhiBatchedBindings<UINT> *ubufoffsets,
QRhiBatchedBindings<UINT> *originalBindings,
QRhiBatchedBindings<UINT> *staticOffsets,
const uint *dynOfsPairs, int dynOfsPairCount)
{
const int count = ubufs->batches[batchIndex].resources.count();
const UINT startBinding = ubufs->batches[batchIndex].startBinding;
*offsets = ubufoffsets->batches[batchIndex].resources;
const int count = staticOffsets->batches[batchIndex].resources.count();
// Make a copy of the offset list, the entries that have no corresponding
// dynamic offset will continue to use the existing offset value.
*offsets = staticOffsets->batches[batchIndex].resources;
for (int b = 0; b < count; ++b) {
for (int di = 0; di < dynOfsPairCount; ++di) {
const uint binding = dynOfsPairs[2 * di];
if (binding == startBinding + UINT(b)) {
// binding is the SPIR-V style binding point here, nothing to do
// with the native one.
if (binding == originalBindings->batches[batchIndex].resources[b]) {
const uint offsetInConstants = dynOfsPairs[2 * di + 1];
(*offsets)[b] = offsetInConstants;
break;
@ -2258,7 +2274,8 @@ void QRhiD3D11::bindShaderResources(QD3D11ShaderResourceBindings *srbD,
srbD->vsubufsizes.batches[i].resources.constData());
} else {
QVarLengthArray<UINT, 4> offsets;
applyDynamicOffsets(&offsets, i, &srbD->vsubufs, &srbD->vsubufoffsets, dynOfsPairs, dynOfsPairCount);
applyDynamicOffsets(&offsets, i, &srbD->vsubuforigbindings, &srbD->vsubufoffsets,
dynOfsPairs, dynOfsPairCount);
context->VSSetConstantBuffers1(srbD->vsubufs.batches[i].startBinding,
count,
srbD->vsubufs.batches[i].resources.constData(),
@ -2282,7 +2299,8 @@ void QRhiD3D11::bindShaderResources(QD3D11ShaderResourceBindings *srbD,
srbD->fsubufsizes.batches[i].resources.constData());
} else {
QVarLengthArray<UINT, 4> offsets;
applyDynamicOffsets(&offsets, i, &srbD->fsubufs, &srbD->fsubufoffsets, dynOfsPairs, dynOfsPairCount);
applyDynamicOffsets(&offsets, i, &srbD->fsubuforigbindings, &srbD->fsubufoffsets,
dynOfsPairs, dynOfsPairCount);
context->PSSetConstantBuffers1(srbD->fsubufs.batches[i].startBinding,
count,
srbD->fsubufs.batches[i].resources.constData(),
@ -2306,7 +2324,8 @@ void QRhiD3D11::bindShaderResources(QD3D11ShaderResourceBindings *srbD,
srbD->csubufsizes.batches[i].resources.constData());
} else {
QVarLengthArray<UINT, 4> offsets;
applyDynamicOffsets(&offsets, i, &srbD->csubufs, &srbD->csubufoffsets, dynOfsPairs, dynOfsPairCount);
applyDynamicOffsets(&offsets, i, &srbD->csubuforigbindings, &srbD->csubufoffsets,
dynOfsPairs, dynOfsPairCount);
context->CSSetConstantBuffers1(srbD->csubufs.batches[i].startBinding,
count,
srbD->csubufs.batches[i].resources.constData(),

View File

@ -238,14 +238,17 @@ struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings
QVarLengthArray<BoundResourceData, 8> boundResourceData;
QRhiBatchedBindings<ID3D11Buffer *> vsubufs;
QRhiBatchedBindings<UINT> vsubuforigbindings;
QRhiBatchedBindings<UINT> vsubufoffsets;
QRhiBatchedBindings<UINT> vsubufsizes;
QRhiBatchedBindings<ID3D11Buffer *> fsubufs;
QRhiBatchedBindings<UINT> fsubuforigbindings;
QRhiBatchedBindings<UINT> fsubufoffsets;
QRhiBatchedBindings<UINT> fsubufsizes;
QRhiBatchedBindings<ID3D11Buffer *> csubufs;
QRhiBatchedBindings<UINT> csubuforigbindings;
QRhiBatchedBindings<UINT> csubufoffsets;
QRhiBatchedBindings<UINT> csubufsizes;

View File

@ -390,7 +390,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
QRhiComputePipeline *maybeComputePs;
QRhiShaderResourceBindings *srb;
int dynamicOffsetCount;
uint dynamicOffsetPairs[MAX_UBUF_BINDINGS * 2]; // binding, offsetInConstants
uint dynamicOffsetPairs[MAX_UBUF_BINDINGS * 2]; // binding, offset
} bindShaderResources;
struct {
GLbitfield mask;

View File

@ -44,3 +44,5 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb sim
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 20 -o simpletextured_array.frag.qsb simpletextured_array.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.vert.qsb textured_multiubuf.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.frag.qsb textured_multiubuf.frag

View File

@ -0,0 +1,18 @@
#version 440
layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 1) uniform buf {
float opacity;
} fubuf;
layout(binding = 2) uniform sampler2D tex;
void main()
{
vec4 c = texture(tex, uv);
c.a *= fubuf.opacity;
c.rgb *= c.a;
fragColor = c;
}

View File

@ -0,0 +1,18 @@
#version 440
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texcoord;
layout(location = 0) out vec2 uv;
layout(std140, binding = 0) uniform buf {
mat4 matrix;
} vubuf;
out gl_PerVertex { vec4 gl_Position; };
void main()
{
uv = texcoord;
gl_Position = vubuf.matrix * position;
}

View File

@ -112,6 +112,8 @@ private slots:
void renderToTextureTexturedQuadAndUniformBuffer();
void renderToTextureDeferredSrb_data();
void renderToTextureDeferredSrb();
void renderToTextureMultipleUniformBuffersAndDynamicOffset_data();
void renderToTextureMultipleUniformBuffersAndDynamicOffset();
void renderToWindowSimple_data();
void renderToWindowSimple();
void finishWithinSwapchainFrame_data();
@ -2354,6 +2356,175 @@ void tst_QRhi::renderToTextureDeferredSrb()
QCOMPARE(result.pixel(4, 227), empty);
}
void tst_QRhi::renderToTextureMultipleUniformBuffersAndDynamicOffset_data()
{
rhiTestData();
}
void tst_QRhi::renderToTextureMultipleUniformBuffersAndDynamicOffset()
{
QFETCH(QRhi::Implementation, impl);
QFETCH(QRhiInitParams *, initParams);
QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
if (!rhi)
QSKIP("QRhi could not be created, skipping testing rendering");
QImage inputImage;
inputImage.load(QLatin1String(":/data/qt256.png"));
QVERIFY(!inputImage.isNull());
QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1,
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
QVERIFY(texture->create());
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->create());
QRhiCommandBuffer *cb = nullptr;
QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
QVERIFY(cb);
QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
static const float verticesUvs[] = {
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs)));
QVERIFY(vbuf->create());
updates->uploadStaticBuffer(vbuf.data(), verticesUvs);
QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
QVERIFY(inputTexture->create());
updates->uploadTexture(inputTexture.data(), inputImage);
QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
QVERIFY(sampler->create());
const int MATRIX_COUNT = 4; // put 4 mat4s into the buffer, will only use one
const int ubufElemSize = rhi->ubufAligned(64);
QVERIFY(ubufElemSize >= 64);
QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, MATRIX_COUNT * ubufElemSize));
QVERIFY(ubuf->create());
float zeroes[16];
memset(zeroes, 0, sizeof(zeroes));
updates->updateDynamicBuffer(ubuf.data(), 0, 64, zeroes);
updates->updateDynamicBuffer(ubuf.data(), ubufElemSize, 64, zeroes);
// the only correct matrix is the third one
QMatrix4x4 matrix;
updates->updateDynamicBuffer(ubuf.data(), ubufElemSize * 2, 64, matrix.constData());
updates->updateDynamicBuffer(ubuf.data(), ubufElemSize * 3, 64, zeroes);
const int OPACITY_COUNT = 6; // put 6 floats into the buffer, will only use one
const int ubuf2ElemSize = rhi->ubufAligned(4);
QVERIFY(ubuf2ElemSize >= 4);
QScopedPointer<QRhiBuffer> ubuf2(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, OPACITY_COUNT * ubuf2ElemSize));
QVERIFY(ubuf2->create());
updates->updateDynamicBuffer(ubuf2.data(), 0, 4, &zeroes[0]);
updates->updateDynamicBuffer(ubuf2.data(), ubuf2ElemSize, 4, &zeroes[0]);
updates->updateDynamicBuffer(ubuf2.data(), ubuf2ElemSize * 2, 4, &zeroes[0]);
// the only correct opacity value is the fourth one
float opacity = 0.5f;
updates->updateDynamicBuffer(ubuf2.data(), ubuf2ElemSize * 3, 4, &opacity);
updates->updateDynamicBuffer(ubuf2.data(), ubuf2ElemSize * 4, 4, &zeroes[0]);
updates->updateDynamicBuffer(ubuf2.data(), ubuf2ElemSize * 5, 4, &zeroes[0]);
const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
srb->setBindings({
QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, commonVisibility, ubuf.data(), 64),
QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(1, commonVisibility, ubuf2.data(), 4),
QRhiShaderResourceBinding::sampledTexture(2, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data())
});
QVERIFY(srb->create());
QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
QShader vs = loadShader(":/data/textured_multiubuf.vert.qsb");
QVERIFY(vs.isValid());
QShader fs = loadShader(":/data/textured_multiubuf.frag.qsb");
QVERIFY(fs.isValid());
pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({ { 4 * sizeof(float) } });
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
});
pipeline->setVertexInputLayout(inputLayout);
pipeline->setShaderResourceBindings(srb.data());
pipeline->setRenderPassDescriptor(rpDesc.data());
QVERIFY(pipeline->create());
cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates);
cb->setGraphicsPipeline(pipeline.data());
// Now the magic, expose the 3rd matrix and 4th opacity value to the shader.
// If the handling of dynamic offsets were broken, the shaders would likely
// "see" an all zero matrix and zero opacity, thus leading to different
// rendering output. This way we can verify if using dynamic offsets, and
// more than one at the same time, is functional.
QVarLengthArray<QPair<int, quint32>, 2> dynamicOffset = {
{ 0, quint32(ubufElemSize * 2) },
{ 1, quint32(ubuf2ElemSize * 3) },
};
cb->setShaderResources(srb.data(), 2, dynamicOffset.constData());
cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) });
QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
cb->setVertexInput(0, 1, &vbindings);
cb->draw(4);
QRhiReadbackResult readResult;
QImage result;
readResult.completed = [&readResult, &result] {
result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888_Premultiplied);
};
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture({ texture.data() }, &readResult);
cb->endPass(readbackBatch);
rhi->endOffscreenFrame();
QVERIFY(!result.isNull());
if (impl == QRhi::Null)
return;
if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC())
result = std::move(result).mirrored();
// opacity 0.5 (premultiplied)
static const auto checkSemiWhite = [](const QRgb &c) {
QRgb semiWhite127 = qPremultiply(qRgba(255, 255, 255, 127));
QRgb semiWhite128 = qPremultiply(qRgba(255, 255, 255, 128));
return c == semiWhite127 || c == semiWhite128;
};
QVERIFY(checkSemiWhite(result.pixel(79, 77)));
QVERIFY(checkSemiWhite(result.pixel(124, 81)));
QVERIFY(checkSemiWhite(result.pixel(128, 149)));
QVERIFY(checkSemiWhite(result.pixel(120, 189)));
QVERIFY(checkSemiWhite(result.pixel(116, 185)));
QVERIFY(checkSemiWhite(result.pixel(191, 172)));
QRgb empty = qRgba(0, 0, 0, 0);
QCOMPARE(result.pixel(11, 45), empty);
QCOMPARE(result.pixel(246, 202), empty);
QCOMPARE(result.pixel(130, 18), empty);
QCOMPARE(result.pixel(4, 227), empty);
}
void tst_QRhi::setWindowType(QWindow *window, QRhi::Implementation impl)
{
switch (impl) {