diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index c67659478a..06781686fa 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -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() { } diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index fa58718ace..977b68cd8e 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -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_); diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 9fccff7b5c..48e626106c 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -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(mp.pData); } -void QD3D11Buffer::endFullDynamicUniformBufferUpdateForCurrentFrame() +void QD3D11Buffer::endFullDynamicBufferUpdateForCurrentFrame() { QRHI_RES_RHI(QRhiD3D11); rhiD->context->Unmap(buffer, 0); diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index 6de899310c..1572751e6c 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -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(); diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 5411d779e1..39f03a784f 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -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(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, diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index b7577ce51b..6ce3786445 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -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, diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 5bbcd50047..9108ad825f 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -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(p); } -void QMetalBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame() +void QMetalBuffer::endFullDynamicBufferUpdateForCurrentFrame() { #ifdef Q_OS_MACOS if (d->managed) { diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index 1691b03ec6..d2082d04b6 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -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; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 737e5fb48b..9666f4c688 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -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; } diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h index e0e796f7ca..eda30614a0 100644 --- a/src/gui/rhi/qrhinull_p_p.h +++ b/src/gui/rhi/qrhinull_p_p.h @@ -59,7 +59,7 @@ struct QNullBuffer : public QRhiBuffer ~QNullBuffer(); void destroy() override; bool create() override; - char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override; + char *beginFullDynamicBufferUpdateForCurrentFrame() override; char *data = nullptr; }; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 05b7afae75..fc539c03a9 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -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(p); } -void QVkBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame() +void QVkBuffer::endFullDynamicBufferUpdateForCurrentFrame() { QRHI_RES_RHI(QRhiVulkan); const int slot = rhiD->currentFrameSlot; diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index c16a990894..5819e303c3 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -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]; diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 74a197808b..08a9bb3f95 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -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 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 texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer 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 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 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 inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->create()); + updates->uploadTexture(inputTexture.data(), inputImage); + cb->resourceUpdate(updates); + + QScopedPointer 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 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 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 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(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(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(); diff --git a/tests/manual/rhi/noninstanced/noninstanced.cpp b/tests/manual/rhi/noninstanced/noninstanced.cpp index 31ea6e24a2..e014e302e0 100644 --- a/tests/manual/rhi/noninstanced/noninstanced.cpp +++ b/tests/manual/rhi/noninstanced/noninstanced.cpp @@ -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);