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:
parent
e7c456d339
commit
2189e0f2fc
@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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_);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ struct QNullBuffer : public QRhiBuffer
|
||||
~QNullBuffer();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override;
|
||||
char *beginFullDynamicBufferUpdateForCurrentFrame() override;
|
||||
|
||||
char *data = nullptr;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user