rhi: Make the new direct buffer update available for non-uniform buffers

The original restriction to UniformBuffer was due to the GL backend
where there is no GL buffer object for QRhiBuffers with usage
UniformBuffer. However, we can still implement this for cases when
there is a true GL buffer object underneath. With other backends it
should all work as-is already.

This becomes useful when one has buffers with usage Vertex that need
full updates every frame. (f.ex. instance data)

Unfortunately this involves renaming the function. But while at it, add
an autotest case as well.

Change-Id: Iff59e4509a8bae06654cc92fe8428bd79eb012fb
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2020-10-11 22:02:43 +02:00
parent e7c456d339
commit 2189e0f2fc
14 changed files with 287 additions and 43 deletions

View File

@ -2144,10 +2144,9 @@ QRhiBuffer::NativeBuffer QRhiBuffer::nativeBuffer()
\warning This function can only be called when recording a frame, so
between QRhi::beginFrame() and QRhi::endFrame().
\warning This function can only be called on Dynamic buffers with the
UniformBuffer usage flag.
\warning This function can only be called on Dynamic buffers.
*/
char *QRhiBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
char *QRhiBuffer::beginFullDynamicBufferUpdateForCurrentFrame()
{
return nullptr;
}
@ -2155,9 +2154,9 @@ char *QRhiBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
/*!
To be called when the entire contents of the buffer data has been updated
in the memory block returned from
beginFullDynamicUniformBufferUpdateForCurrentFrame().
beginFullDynamicBufferUpdateForCurrentFrame().
*/
void QRhiBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame()
void QRhiBuffer::endFullDynamicBufferUpdateForCurrentFrame()
{
}

View File

@ -717,8 +717,8 @@ public:
virtual NativeBuffer nativeBuffer();
virtual char *beginFullDynamicUniformBufferUpdateForCurrentFrame();
virtual void endFullDynamicUniformBufferUpdateForCurrentFrame();
virtual char *beginFullDynamicBufferUpdateForCurrentFrame();
virtual void endFullDynamicBufferUpdateForCurrentFrame();
protected:
QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, int size_);

View File

@ -2708,15 +2708,15 @@ QRhiBuffer::NativeBuffer QD3D11Buffer::nativeBuffer()
return { { &buffer }, 1 };
}
char *QD3D11Buffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
char *QD3D11Buffer::beginFullDynamicBufferUpdateForCurrentFrame()
{
// Shortcut the entire buffer update mechanism and allow the client to do
// the host writes directly to the buffer. This will lead to unexpected
// results when combined with QRhiResourceUpdateBatch-based updates for the
// buffer, since dynBuf is left untouched and out of sync, but provides a
// fast path for uniform buffers that have all their content changed in
// fast path for dynamic buffers that have all their content changed in
// every frame.
Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer));
Q_ASSERT(m_type == Dynamic);
D3D11_MAPPED_SUBRESOURCE mp;
QRHI_RES_RHI(QRhiD3D11);
HRESULT hr = rhiD->context->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp);
@ -2727,7 +2727,7 @@ char *QD3D11Buffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
return static_cast<char *>(mp.pData);
}
void QD3D11Buffer::endFullDynamicUniformBufferUpdateForCurrentFrame()
void QD3D11Buffer::endFullDynamicBufferUpdateForCurrentFrame()
{
QRHI_RES_RHI(QRhiD3D11);
rhiD->context->Unmap(buffer, 0);

View File

@ -65,8 +65,8 @@ struct QD3D11Buffer : public QRhiBuffer
void destroy() override;
bool create() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override;
void endFullDynamicUniformBufferUpdateForCurrentFrame() override;
char *beginFullDynamicBufferUpdateForCurrentFrame() override;
void endFullDynamicBufferUpdateForCurrentFrame() override;
ID3D11UnorderedAccessView *unorderedAccessView();

View File

@ -297,6 +297,10 @@ QT_BEGIN_NAMESPACE
#define GL_MAP_READ_BIT 0x0001
#endif
#ifndef GL_MAP_WRITE_BIT
#define GL_MAP_WRITE_BIT 0x0002
#endif
#ifndef GL_TEXTURE_2D_MULTISAMPLE
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
#endif
@ -1697,7 +1701,7 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
memcpy(bufD->ubuf + u.offset, u.data.constData(), size_t(u.data.size()));
memcpy(bufD->data + u.offset, u.data.constData(), size_t(u.data.size()));
} else {
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
QGles2CommandBuffer::Command cmd;
@ -1714,7 +1718,7 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
memcpy(bufD->ubuf + u.offset, u.data.constData(), size_t(u.data.size()));
memcpy(bufD->data + u.offset, u.data.constData(), size_t(u.data.size()));
} else {
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
QGles2CommandBuffer::Command cmd;
@ -1730,7 +1734,7 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
u.result->data.resize(u.readSize);
memcpy(u.result->data.data(), bufD->ubuf + u.offset, size_t(u.readSize));
memcpy(u.result->data.data(), bufD->data + u.offset, size_t(u.readSize));
if (u.result->completed)
u.result->completed();
} else {
@ -2867,7 +2871,7 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD,
}
}
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf);
const char *bufView = bufD->ubuf + viewOffset;
const char *bufView = bufD->data + viewOffset;
for (const QGles2UniformDescription &uniform : qAsConst(uniforms)) {
if (uniform.binding == b->binding) {
// in a uniform buffer everything is at least 4 byte aligned
@ -3836,8 +3840,8 @@ void QGles2Buffer::destroy()
e.buffer.buffer = buffer;
buffer = 0;
delete[] ubuf;
ubuf = nullptr;
delete[] data;
data = nullptr;
QRHI_RES_RHI(QRhiGles2);
rhiD->releaseQueue.append(e);
@ -3854,14 +3858,14 @@ bool QGles2Buffer::create()
QRHI_RES_RHI(QRhiGles2);
QRHI_PROF;
const int nonZeroSize = m_size <= 0 ? 256 : m_size;
nonZeroSize = m_size <= 0 ? 256 : m_size;
if (m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
if (int(m_usage) != QRhiBuffer::UniformBuffer) {
qWarning("Uniform buffer: multiple usages specified, this is not supported by the OpenGL backend");
return false;
}
ubuf = new char[nonZeroSize];
data = new char[nonZeroSize];
QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), 0, 1));
return true;
}
@ -3894,10 +3898,33 @@ QRhiBuffer::NativeBuffer QGles2Buffer::nativeBuffer()
return { { &buffer }, 1 };
}
char *QGles2Buffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
char *QGles2Buffer::beginFullDynamicBufferUpdateForCurrentFrame()
{
Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer));
return ubuf;
Q_ASSERT(m_type == Dynamic);
if (!m_usage.testFlag(UniformBuffer)) {
QRHI_RES_RHI(QRhiGles2);
rhiD->f->glBindBuffer(targetForDataOps, buffer);
if (rhiD->caps.properMapBuffer) {
return static_cast<char *>(rhiD->f->glMapBufferRange(targetForDataOps, 0, nonZeroSize,
GL_MAP_READ_BIT | GL_MAP_WRITE_BIT));
} else {
// Need some storage for the data, use the otherwise unused 'data' member.
if (!data)
data = new char[nonZeroSize];
}
}
return data;
}
void QGles2Buffer::endFullDynamicBufferUpdateForCurrentFrame()
{
if (!m_usage.testFlag(UniformBuffer)) {
QRHI_RES_RHI(QRhiGles2);
if (rhiD->caps.properMapBuffer)
rhiD->f->glUnmapBuffer(targetForDataOps);
else
rhiD->f->glBufferSubData(targetForDataOps, 0, nonZeroSize, data);
}
}
QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,

View File

@ -65,11 +65,13 @@ struct QGles2Buffer : public QRhiBuffer
void destroy() override;
bool create() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override;
char *beginFullDynamicBufferUpdateForCurrentFrame() override;
void endFullDynamicBufferUpdateForCurrentFrame() override;
int nonZeroSize = 0;
GLuint buffer = 0;
GLenum targetForDataOps;
char *ubuf = nullptr;
char *data = nullptr;
enum Access {
AccessNone,
AccessVertex,

View File

@ -2254,14 +2254,14 @@ QRhiBuffer::NativeBuffer QMetalBuffer::nativeBuffer()
return { { &d->buf[0] }, 1 };
}
char *QMetalBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
char *QMetalBuffer::beginFullDynamicBufferUpdateForCurrentFrame()
{
// Shortcut the entire buffer update mechanism and allow the client to do
// the host writes directly to the buffer. This will lead to unexpected
// results when combined with QRhiResourceUpdateBatch-based updates for the
// buffer, but provides a fast path for uniform buffers that have all their
// buffer, but provides a fast path for dynamic buffers that have all their
// content changed in every frame.
Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer));
Q_ASSERT(m_type == Dynamic);
QRHI_RES_RHI(QRhiMetal);
Q_ASSERT(rhiD->inFrame);
const int slot = rhiD->currentFrameSlot;
@ -2269,7 +2269,7 @@ char *QMetalBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
return static_cast<char *>(p);
}
void QMetalBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame()
void QMetalBuffer::endFullDynamicBufferUpdateForCurrentFrame()
{
#ifdef Q_OS_MACOS
if (d->managed) {

View File

@ -66,8 +66,8 @@ struct QMetalBuffer : public QRhiBuffer
void destroy() override;
bool create() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override;
void endFullDynamicUniformBufferUpdateForCurrentFrame() override;
char *beginFullDynamicBufferUpdateForCurrentFrame() override;
void endFullDynamicBufferUpdateForCurrentFrame() override;
QMetalBufferData *d;
uint generation = 0;

View File

@ -583,9 +583,9 @@ bool QNullBuffer::create()
return true;
}
char *QNullBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
char *QNullBuffer::beginFullDynamicBufferUpdateForCurrentFrame()
{
Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer));
Q_ASSERT(m_type == Dynamic);
return data;
}

View File

@ -59,7 +59,7 @@ struct QNullBuffer : public QRhiBuffer
~QNullBuffer();
void destroy() override;
bool create() override;
char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override;
char *beginFullDynamicBufferUpdateForCurrentFrame() override;
char *data = nullptr;
};

View File

@ -5370,14 +5370,14 @@ QRhiBuffer::NativeBuffer QVkBuffer::nativeBuffer()
return { { &buffers[0] }, 1 };
}
char *QVkBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
char *QVkBuffer::beginFullDynamicBufferUpdateForCurrentFrame()
{
// Shortcut the entire buffer update mechanism and allow the client to do
// the host writes directly to the buffer. This will lead to unexpected
// results when combined with QRhiResourceUpdateBatch-based updates for the
// buffer, but provides a fast path for uniform buffers that have all their
// buffer, but provides a fast path for dynamic buffers that have all their
// content changed in every frame.
Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer));
Q_ASSERT(m_type == Dynamic);
QRHI_RES_RHI(QRhiVulkan);
Q_ASSERT(rhiD->inFrame);
const int slot = rhiD->currentFrameSlot;
@ -5391,7 +5391,7 @@ char *QVkBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame()
return static_cast<char *>(p);
}
void QVkBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame()
void QVkBuffer::endFullDynamicBufferUpdateForCurrentFrame()
{
QRHI_RES_RHI(QRhiVulkan);
const int slot = rhiD->currentFrameSlot;

View File

@ -77,8 +77,8 @@ struct QVkBuffer : public QRhiBuffer
void destroy() override;
bool create() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override;
void endFullDynamicUniformBufferUpdateForCurrentFrame() override;
char *beginFullDynamicBufferUpdateForCurrentFrame() override;
void endFullDynamicBufferUpdateForCurrentFrame() override;
VkBuffer buffers[QVK_FRAMES_IN_FLIGHT];
QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT];

View File

@ -110,6 +110,8 @@ private slots:
void renderToTextureArrayOfTexturedQuad();
void renderToTextureTexturedQuadAndUniformBuffer_data();
void renderToTextureTexturedQuadAndUniformBuffer();
void renderToTextureTexturedQuadAllDynamicBuffers_data();
void renderToTextureTexturedQuadAllDynamicBuffers();
void renderToTextureDeferredSrb_data();
void renderToTextureDeferredSrb();
void renderToTextureMultipleUniformBuffersAndDynamicOffset_data();
@ -2222,6 +2224,220 @@ void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer()
QCOMPARE(result1.pixel(28, 178), empty);
}
void tst_QRhi::renderToTextureTexturedQuadAllDynamicBuffers_data()
{
rhiTestData();
}
void tst_QRhi::renderToTextureTexturedQuadAllDynamicBuffers()
{
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);
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
};
// Do like renderToTextureTexturedQuadAndUniformBuffer but only use Dynamic
// buffers, and do updates with the direct beginFullDynamicBufferUpdate
// function. (for some backend this is different for UniformBuffer and
// others, hence useful exercising it also on a VertexBuffer)
QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, sizeof(verticesUvs)));
QVERIFY(vbuf->create());
char *p = vbuf->beginFullDynamicBufferUpdateForCurrentFrame();
QVERIFY(p);
memcpy(p, verticesUvs, sizeof(verticesUvs));
vbuf->endFullDynamicBufferUpdateForCurrentFrame();
const int UNIFORM_BLOCK_SIZE = 64 + 4; // matrix + opacity
const int secondUbufOffset = rhi->ubufAligned(UNIFORM_BLOCK_SIZE);
const int UBUF_SIZE = secondUbufOffset + UNIFORM_BLOCK_SIZE;
QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE));
QVERIFY(ubuf->create());
p = ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
QVERIFY(p);
QMatrix4x4 matrix;
memcpy(p, matrix.constData(), 64);
float opacity = 0.5f;
memcpy(p + 64, &opacity, 4);
// rotation by 45 degrees around the Z axis
matrix.rotate(45, 0, 0, 1);
memcpy(p + secondUbufOffset, matrix.constData(), 64);
memcpy(p + secondUbufOffset + 64, &opacity, 4);
ubuf->endFullDynamicBufferUpdateForCurrentFrame();
QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
QVERIFY(inputTexture->create());
updates->uploadTexture(inputTexture.data(), inputImage);
cb->resourceUpdate(updates);
QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
QVERIFY(sampler->create());
const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
QScopedPointer<QRhiShaderResourceBindings> srb0(rhi->newShaderResourceBindings());
srb0->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), 0, UNIFORM_BLOCK_SIZE),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data())
});
QVERIFY(srb0->create());
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
srb1->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), secondUbufOffset, UNIFORM_BLOCK_SIZE),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data())
});
QVERIFY(srb1->create());
QVERIFY(srb1->isLayoutCompatible(srb0.data())); // hence no need for a second pipeline
QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
QShader vs = loadShader(":/data/textured.vert.qsb");
QVERIFY(vs.isValid());
QShaderDescription shaderDesc = vs.description();
QVERIFY(!shaderDesc.uniformBlocks().isEmpty());
QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE);
QShader fs = loadShader(":/data/textured.frag.qsb");
QVERIFY(fs.isValid());
shaderDesc = fs.description();
QVERIFY(!shaderDesc.uniformBlocks().isEmpty());
QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE);
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(srb0.data());
pipeline->setRenderPassDescriptor(rpDesc.data());
QVERIFY(pipeline->create());
cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 });
cb->setGraphicsPipeline(pipeline.data());
cb->setShaderResources();
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 readResult0;
QImage result0;
readResult0.completed = [&readResult0, &result0] {
result0 = QImage(reinterpret_cast<const uchar *>(readResult0.data.constData()),
readResult0.pixelSize.width(), readResult0.pixelSize.height(),
QImage::Format_RGBA8888_Premultiplied);
};
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture({ texture.data() }, &readResult0);
cb->endPass(readbackBatch);
// second pass (rotated)
cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 });
cb->setGraphicsPipeline(pipeline.data());
cb->setShaderResources(srb1.data()); // sources data from a different offset in ubuf
cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) });
cb->setVertexInput(0, 1, &vbindings);
cb->draw(4);
QRhiReadbackResult readResult1;
QImage result1;
readResult1.completed = [&readResult1, &result1] {
result1 = QImage(reinterpret_cast<const uchar *>(readResult1.data.constData()),
readResult1.pixelSize.width(), readResult1.pixelSize.height(),
QImage::Format_RGBA8888_Premultiplied);
};
readbackBatch = rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture({ texture.data() }, &readResult1);
cb->endPass(readbackBatch);
rhi->endOffscreenFrame();
QVERIFY(!result0.isNull());
QVERIFY(!result1.isNull());
if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) {
result0 = std::move(result0).mirrored();
result1 = std::move(result1).mirrored();
}
if (impl == QRhi::Null)
return;
// 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(result0.pixel(79, 77)));
QVERIFY(checkSemiWhite(result0.pixel(124, 81)));
QVERIFY(checkSemiWhite(result0.pixel(128, 149)));
QVERIFY(checkSemiWhite(result0.pixel(120, 189)));
QVERIFY(checkSemiWhite(result0.pixel(116, 185)));
QVERIFY(checkSemiWhite(result0.pixel(191, 172)));
QRgb empty = qRgba(0, 0, 0, 0);
QCOMPARE(result0.pixel(11, 45), empty);
QCOMPARE(result0.pixel(246, 202), empty);
QCOMPARE(result0.pixel(130, 18), empty);
QCOMPARE(result0.pixel(4, 227), empty);
// also rotated 45 degrees around Z
QRgb black = qRgba(0, 0, 0, 255);
QCOMPARE(result1.pixel(20, 23), black);
QCOMPARE(result1.pixel(47, 5), black);
QCOMPARE(result1.pixel(238, 22), black);
QCOMPARE(result1.pixel(250, 203), black);
QCOMPARE(result1.pixel(224, 237), black);
QCOMPARE(result1.pixel(12, 221), black);
QVERIFY(checkSemiWhite(result1.pixel(142, 67)));
QVERIFY(checkSemiWhite(result1.pixel(81, 79)));
QVERIFY(checkSemiWhite(result1.pixel(79, 168)));
QVERIFY(checkSemiWhite(result1.pixel(146, 204)));
QVERIFY(checkSemiWhite(result1.pixel(186, 156)));
QCOMPARE(result1.pixel(204, 45), empty);
QCOMPARE(result1.pixel(28, 178), empty);
}
void tst_QRhi::renderToTextureDeferredSrb_data()
{
rhiTestData();

View File

@ -149,7 +149,7 @@ void Window::customRender()
}
for (int i = 0; i < INSTANCE_COUNT; ++i) {
char *p = d.ubuf[i]->beginFullDynamicUniformBufferUpdateForCurrentFrame();
char *p = d.ubuf[i]->beginFullDynamicBufferUpdateForCurrentFrame();
QMatrix4x4 mvp = m_proj;
mvp.rotate(d.rot, 0, 1, 0);
mvp.scale(0.05f);
@ -171,7 +171,7 @@ void Window::customRender()
memcpy(p + 84, &d.instData[i].g, 4);
memcpy(p + 88, &d.instData[i].b, 4);
d.ubuf[i]->endFullDynamicUniformBufferUpdateForCurrentFrame();
d.ubuf[i]->endFullDynamicBufferUpdateForCurrentFrame();
}
cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 }, u, QRhiCommandBuffer::DoNotTrackResourcesForCompute);