From ee9bc61cd906505271bad887664c15b9397906ab Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 3 Oct 2019 08:37:28 +0200 Subject: [PATCH 01/23] evdevtouch: Report stationary touchpoints that include pressure changes Task-number: QTBUG-77142 Change-Id: I35446092679573df51891302155c896a3bb6fc1c Reviewed-by: Laszlo Agocs --- src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp index bf044229ff3..2802c9b6473 100644 --- a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp +++ b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp @@ -577,6 +577,7 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) m_lastTouchPoints = m_touchPoints; m_touchPoints.clear(); Qt::TouchPointStates combinedStates; + bool hasPressure = false; for (auto i = m_contacts.begin(), end = m_contacts.end(); i != end; /*erasing*/) { auto it = i++; @@ -607,6 +608,9 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) continue; } + if (contact.pressure) + hasPressure = true; + addTouchPoint(contact, &combinedStates); } @@ -651,7 +655,7 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) m_contacts.clear(); - if (!m_touchPoints.isEmpty() && combinedStates != Qt::TouchPointStationary) + if (!m_touchPoints.isEmpty() && (hasPressure || combinedStates != Qt::TouchPointStationary)) reportPoints(); } From 00e8f044e1954ea47f1f0c5c5ff35fff97b732be Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Tue, 8 Oct 2019 16:34:49 +0200 Subject: [PATCH 02/23] QWidget: document that a widget might only get a mouseDoubleClickEvent If two widgets are on top of each other, and the top widget disappears in response to a press or release that is followed by a double click, then the widget at the bottom only receives the double click event. This is a sequence of events that the application developer that choses to build such a UI has to take care of. Change-Id: I440efd2cac01631de8995abf9a9fb76815de8f9a Fixes: QTBUG-61173 Reviewed-by: Paul Wicking --- src/widgets/kernel/qwidget.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 6889c2e9e5f..b70a03b3118 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -9218,9 +9218,11 @@ void QWidget::mouseReleaseEvent(QMouseEvent *event) The default implementation calls mousePressEvent(). \note The widget will also receive mouse press and mouse release - events in addition to the double click event. It is up to the - developer to ensure that the application interprets these events - correctly. + events in addition to the double click event. And if another widget + that overlaps this widget disappears in response to press or + release events, then this widget will only receive the double click + event. It is up to the developer to ensure that the application + interprets these events correctly. \sa mousePressEvent(), mouseReleaseEvent(), mouseMoveEvent(), event(), QMouseEvent From 0683de205f1d5f54e7fe62a5be5ad43d6de15a9e Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Tue, 8 Oct 2019 10:13:50 +0200 Subject: [PATCH 03/23] QtConcurrent: fix warning about function parameters shadowing class members No functional change. Change-Id: I76aa01e8eb044c794d518ca72e6861cf95060dfc Fixes: QTBUG-79071 Reviewed-by: Thiago Macieira --- src/concurrent/qtconcurrentmapkernel.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/concurrent/qtconcurrentmapkernel.h b/src/concurrent/qtconcurrentmapkernel.h index 87fcf30cf9b..7c9538a015d 100644 --- a/src/concurrent/qtconcurrentmapkernel.h +++ b/src/concurrent/qtconcurrentmapkernel.h @@ -118,16 +118,16 @@ public: return false; } - bool runIterations(Iterator sequenceBeginIterator, int begin, int end, ReducedResultType *) override + bool runIterations(Iterator sequenceBeginIterator, int beginIndex, int endIndex, ReducedResultType *) override { IntermediateResults results; - results.begin = begin; - results.end = end; - results.vector.reserve(end - begin); + results.begin = beginIndex; + results.end = endIndex; + results.vector.reserve(endIndex - beginIndex); Iterator it = sequenceBeginIterator; - std::advance(it, begin); - for (int i = begin; i < end; ++i) { + std::advance(it, beginIndex); + for (int i = beginIndex; i < endIndex; ++i) { results.vector.append(map(*(it))); std::advance(it, 1); } @@ -176,13 +176,13 @@ public: return true; } - bool runIterations(Iterator sequenceBeginIterator, int begin, int end, T *results) override + bool runIterations(Iterator sequenceBeginIterator, int beginIndex, int endIndex, T *results) override { Iterator it = sequenceBeginIterator; - std::advance(it, begin); - for (int i = begin; i < end; ++i) { - runIteration(it, i, results + (i - begin)); + std::advance(it, beginIndex); + for (int i = beginIndex; i < endIndex; ++i) { + runIteration(it, i, results + (i - beginIndex)); std::advance(it, 1); } From 9115c7ae451d04fa326cc9df587e2f99d9dc117a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 2 Oct 2019 16:53:45 +0200 Subject: [PATCH 04/23] qmake: Avoid writing glue project file when only generating prl files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When qmake is run with -prl we don't need to write the glue makefile, and doing so will end up with MakefileGenerator::writeProjectMakefile trying to write to an invalid Option::output, resulting in warnings: QIODevice::write device not open Change-Id: I196b185570e7329c621c2ccb8530b43f4be51ee6 Reviewed-by: Simon Hausmann Reviewed-by: Edward Welbourne Reviewed-by: Joerg Bornemann Reviewed-by: Tor Arne Vestbø --- qmake/generators/metamakefile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qmake/generators/metamakefile.cpp b/qmake/generators/metamakefile.cpp index b8b93bc8cbc..7776d77008c 100644 --- a/qmake/generators/metamakefile.cpp +++ b/qmake/generators/metamakefile.cpp @@ -141,7 +141,8 @@ bool BuildsMetaMakefileGenerator::write() { Build *glue = nullptr; - if(!makefiles.isEmpty() && !makefiles.first()->build.isNull()) { + if(!makefiles.isEmpty() && !makefiles.first()->build.isNull() + && Option::qmake_mode != Option::QMAKE_GENERATE_PRL) { glue = new Build; glue->name = name; glue->makefile = createMakefileGenerator(project, true); From b7a915398a2ee928df4db2c5dd8e308f480c7906 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Mon, 7 Oct 2019 15:13:57 +0200 Subject: [PATCH 05/23] QAuthenticator - use GSS framework, as Apple requires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit since declarations in gssapi.h are marked as deprecated. Fixes: QTBUG-78810 Change-Id: I241ae4913f362f6e9219438e9bfe7a63dfc91b7c Reviewed-by: Timur Pocheptsov Reviewed-by: Mårten Nordheim --- src/network/configure.json | 12 +++++++++++- src/network/kernel/qauthenticator.cpp | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/network/configure.json b/src/network/configure.json index a1cb77b6d17..f501465c913 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -102,13 +102,23 @@ "gssapi": { "label": "KRB5 GSSAPI Support", "test": { + "head": [ + "#if defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__))", + "# include ", + "# if defined(TARGET_OS_MAC) && TARGET_OS_MAC", + "# include ", + "# endif", + "#else", + "# include ", + "#endif" + ], "main": [ "gss_ctx_id_t ctx;", "gss_context_time(nullptr, ctx, nullptr);" ] }, - "headers": [ "gssapi/gssapi.h" ], "sources": [ + { "libs": "-framework GSS", "condition": "config.darwin" }, { "type": "pkgConfig", "args": "krb5-gssapi" }, "-lgssapi_krb5" ] diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index 4100dfd7843..858a5bc2deb 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -1,3 +1,4 @@ + /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -59,8 +60,12 @@ #define SECURITY_WIN32 1 #include #elif QT_CONFIG(gssapi) // GSSAPI +#if defined(Q_OS_DARWIN) +#include +#else #include -#endif +#endif // Q_OS_DARWIN +#endif // Q_CONFIG(sspi) QT_BEGIN_NAMESPACE From 61ec1abc08e7f5bad930ec2c8ca59a190f0a6cac Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Thu, 3 Oct 2019 19:59:10 +0200 Subject: [PATCH 06/23] Suppress deprecation warnings on a test of a deprecated QString method QString::fromAscii() is deprecated since 5.0 but still tested. So suppress deprecations for its code. Change-Id: Ic048a843c43551021da39a16d94c3222201573dc Reviewed-by: Sona Kurazyan --- tests/auto/corelib/text/qstring/tst_qstring.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp index cce3e601cdf..c96210f53d9 100644 --- a/tests/auto/corelib/text/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp @@ -4593,6 +4593,8 @@ void tst_QString::fromLatin1() } #if QT_DEPRECATED_SINCE(5, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QString::fromAscii() { QString a; @@ -4613,6 +4615,7 @@ void tst_QString::fromAscii() a = QString::fromAscii("\0abcd", 5); QVERIFY(a.size() == 5); } +QT_WARNING_POP #endif void tst_QString::fromUcs4() From 4a3ee77f650868ba408827d9793e8b86a93400f0 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Thu, 3 Oct 2019 12:02:03 +0200 Subject: [PATCH 07/23] rhi: Exercise nativeHandles() in qrhi test Task-number: QTBUG-78971 Task-number: QTBUG-78972 Change-Id: Ibce10caf1ccd74ae7efead9579f4a4342ef896b8 Reviewed-by: Paul Olav Tvete --- tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 246 ++++++++++++++++++++++++++- 1 file changed, 244 insertions(+), 2 deletions(-) diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 897613d5250..ac64cf5265d 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -34,6 +34,7 @@ #include #if QT_CONFIG(opengl) +# include # include # define TST_GL #endif @@ -65,8 +66,11 @@ private slots: void initTestCase(); void cleanupTestCase(); + void rhiTestData(); void create_data(); void create(); + void nativeHandles_data(); + void nativeHandles(); private: struct { @@ -113,7 +117,7 @@ void tst_QRhi::cleanupTestCase() delete fallbackSurface; } -void tst_QRhi::create_data() +void tst_QRhi::rhiTestData() { QTest::addColumn("impl"); QTest::addColumn("initParams"); @@ -134,6 +138,11 @@ void tst_QRhi::create_data() #endif } +void tst_QRhi::create_data() +{ + rhiTestData(); +} + static int aligned(int v, int a) { return (v + a - 1) & ~(a - 1); @@ -154,6 +163,8 @@ void tst_QRhi::create() QCOMPARE(rhi->backend(), impl); QCOMPARE(rhi->thread(), QThread::currentThread()); + // do a basic smoke test for the apis that do not directly render anything + int cleanupOk = 0; QRhi *rhiPtr = rhi.data(); auto cleanupFunc = [rhiPtr, &cleanupOk](QRhi *dyingRhi) { @@ -211,9 +222,11 @@ void tst_QRhi::create() const int texMin = rhi->resourceLimit(QRhi::TextureSizeMin); const int texMax = rhi->resourceLimit(QRhi::TextureSizeMax); const int maxAtt = rhi->resourceLimit(QRhi::MaxColorAttachments); + const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight); QVERIFY(texMin >= 1); QVERIFY(texMax >= texMin); QVERIFY(maxAtt >= 1); + QVERIFY(framesInFlight >= 1); QVERIFY(rhi->nativeHandles()); QVERIFY(rhi->profiler()); @@ -230,17 +243,246 @@ void tst_QRhi::create() QRhi::NonFourAlignedEffectiveIndexBufferOffset, QRhi::NPOTTextureRepeat, QRhi::RedOrAlpha8IsRed, - QRhi::ElementIndexUint + QRhi::ElementIndexUint, + QRhi::Compute, + QRhi::WideLines, + QRhi::VertexShaderPointSize, + QRhi::BaseVertex, + QRhi::BaseInstance }; for (size_t i = 0; i isFeatureSupported(features[i]); QVERIFY(rhi->isTextureFormatSupported(QRhiTexture::RGBA8)); + rhi->releaseCachedResources(); + + QVERIFY(!rhi->isDeviceLost()); + rhi.reset(); QCOMPARE(cleanupOk, 1); } } +void tst_QRhi::nativeHandles_data() +{ + rhiTestData(); +} + +void tst_QRhi::nativeHandles() +{ + 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 native handles"); + + // QRhi::nativeHandles() + { + const QRhiNativeHandles *rhiHandles = rhi->nativeHandles(); + Q_ASSERT(rhiHandles); + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanNativeHandles *vkHandles = static_cast(rhiHandles); + QVERIFY(vkHandles->physDev); + QVERIFY(vkHandles->dev); + QVERIFY(vkHandles->gfxQueueFamilyIdx >= 0); + QVERIFY(vkHandles->gfxQueue); + QVERIFY(vkHandles->cmdPool); + QVERIFY(vkHandles->vmemAllocator); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + { + const QRhiGles2NativeHandles *glHandles = static_cast(rhiHandles); + QVERIFY(glHandles->context); + QVERIFY(glHandles->context->isValid()); + glHandles->context->doneCurrent(); + QVERIFY(!QOpenGLContext::currentContext()); + rhi->makeThreadLocalNativeContextCurrent(); + QVERIFY(QOpenGLContext::currentContext() == glHandles->context); + } + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + { + const QRhiD3D11NativeHandles *d3dHandles = static_cast(rhiHandles); + QVERIFY(d3dHandles->dev); + QVERIFY(d3dHandles->context); + } + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalNativeHandles *mtlHandles = static_cast(rhiHandles); + QVERIFY(mtlHandles->dev); + QVERIFY(mtlHandles->cmdQueue); + } + break; +#endif + default: + Q_ASSERT(false); + } + } + + // QRhiTexture::nativeHandles() + { + QScopedPointer tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 256))); + QVERIFY(tex->build()); + + const QRhiNativeHandles *texHandles = tex->nativeHandles(); + QVERIFY(texHandles); + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanTextureNativeHandles *vkHandles = static_cast(texHandles); + QVERIFY(vkHandles->image); + QVERIFY(vkHandles->layout >= 1); // VK_IMAGE_LAYOUT_GENERAL + QVERIFY(vkHandles->layout <= 8); // VK_IMAGE_LAYOUT_PREINITIALIZED + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + { + const QRhiGles2TextureNativeHandles *glHandles = static_cast(texHandles); + QVERIFY(glHandles->texture); + } + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + { + const QRhiD3D11TextureNativeHandles *d3dHandles = static_cast(texHandles); + QVERIFY(d3dHandles->texture); + } + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalTextureNativeHandles *mtlHandles = static_cast(texHandles); + QVERIFY(mtlHandles->texture); + } + break; +#endif + default: + Q_ASSERT(false); + } + } + + // QRhiCommandBuffer::nativeHandles() + { + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + QVERIFY(result == QRhi::FrameOpSuccess); + QVERIFY(cb); + + const QRhiNativeHandles *cbHandles = cb->nativeHandles(); + // no null check here, backends where not applicable will return null + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanCommandBufferNativeHandles *vkHandles = static_cast(cbHandles); + QVERIFY(vkHandles); + QVERIFY(vkHandles->commandBuffer); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalCommandBufferNativeHandles *mtlHandles = static_cast(cbHandles); + QVERIFY(mtlHandles); + QVERIFY(mtlHandles->commandBuffer); + QVERIFY(!mtlHandles->encoder); + + QScopedPointer tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(tex->build()); + QScopedPointer rt(rhi->newTextureRenderTarget({ tex.data() })); + QScopedPointer rpDesc(rt->newCompatibleRenderPassDescriptor()); + QVERIFY(rpDesc); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 }); + QVERIFY(static_cast(cb->nativeHandles())->encoder); + cb->endPass(); + } + break; +#endif + default: + Q_ASSERT(false); + } + + rhi->endOffscreenFrame(); + } + + // QRhiRenderPassDescriptor::nativeHandles() + { + QScopedPointer tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(tex->build()); + QScopedPointer rt(rhi->newTextureRenderTarget({ tex.data() })); + QScopedPointer rpDesc(rt->newCompatibleRenderPassDescriptor()); + QVERIFY(rpDesc); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + const QRhiNativeHandles *rpHandles = rpDesc->nativeHandles(); + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanRenderPassNativeHandles *vkHandles = static_cast(rpHandles); + QVERIFY(vkHandles); + QVERIFY(vkHandles->renderPass); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + break; +#endif + default: + Q_ASSERT(false); + } + } +} + #include QTEST_MAIN(tst_QRhi) From 89ec1b3618f0f26a8f92049aabfc2df4d84c34c5 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Thu, 3 Oct 2019 14:28:23 +0200 Subject: [PATCH 08/23] rhi: Add support for buffer readbacks This also marks the beginnings of significantly extending autotesting of the resource and rendering functionality in QRhi. Also involves fixing up the buffer operation lists like we did for textures before. This is to ensure updates and reads on the same batch execute in the correct order. So just have two lists: one with buffer, one with texture operations. Also simplify the struct layouts. No need for those inner structs with many duplicate members. This reduces the size even, since using a union was never an option here. Also switch to a VLA, the size is around 253 KB per batch. The Null backend now keeps track of the QRhiBuffer data so it can return valid results in readbacks. Task-number: QTBUG-78984 Task-number: QTBUG-78986 Task-number: QTBUG-78971 Task-number: QTBUG-78883 Change-Id: I9694bd7fec523a4e71cf8a5c77c828123ebbb3bd Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhi.cpp | 55 +++- src/gui/rhi/qrhi_p.h | 10 +- src/gui/rhi/qrhi_p_p.h | 129 +++++---- src/gui/rhi/qrhid3d11.cpp | 222 ++++++++++------ src/gui/rhi/qrhid3d11_p_p.h | 15 +- src/gui/rhi/qrhigles2.cpp | 151 +++++++---- src/gui/rhi/qrhigles2_p_p.h | 14 +- src/gui/rhi/qrhimetal.mm | 150 ++++++----- src/gui/rhi/qrhinull.cpp | 29 +- src/gui/rhi/qrhinull_p_p.h | 2 + src/gui/rhi/qrhivulkan.cpp | 384 +++++++++++++++++---------- src/gui/rhi/qrhivulkan_p_p.h | 23 +- tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 109 +++++++- 13 files changed, 856 insertions(+), 437 deletions(-) diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index c8ec8c74100..8ec8ae9208e 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -566,6 +566,13 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \value TriangleFanTopology Indicates that QRhiGraphicsPipeline::setTopology() supports QRhiGraphicsPipeline::TriangleFan. + + \value ReadBackNonUniformBuffer Indicates that + {QRhiResourceUpdateBatch::readBackBuffer()}{reading buffer contents} is + supported for QRhiBuffer instances with a usage different than + UniformBuffer. While this is supported in the majority of cases, it can be + unsupported, for example, on OpenGL ES 2.0 implementations without the + MapBuffer extension. */ /*! @@ -4175,7 +4182,7 @@ void QRhiResourceUpdateBatch::merge(QRhiResourceUpdateBatch *other) void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data) { if (size > 0) - d->dynamicBufferUpdates.append({ buf, offset, size, data }); + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::dynamicUpdate(buf, offset, size, data)); } /*! @@ -4189,7 +4196,7 @@ void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, i void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data) { if (size > 0) - d->staticBufferUploads.append({ buf, offset, size, data }); + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::staticUpload(buf, offset, size, data)); } /*! @@ -4199,7 +4206,28 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, in void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *data) { if (buf->size() > 0) - d->staticBufferUploads.append({ buf, 0, 0, data }); + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::staticUpload(buf, 0, 0, data)); +} + +/*! + Enqueues reading back a region of the QRhiBuffer \a buf. The size of the + region is specified by \a size in bytes, \a offset is the offset in bytes + to start reading from. + + A readback is asynchronous. \a result contains a callback that is invoked + when the operation has completed. The data is provided in + QRhiBufferReadbackResult::data. Upon successful completion that QByteArray + will have a size equal to \a size. On failure the QByteArray will be empty. + + \note Reading buffers with a usage different than QRhiBuffer::UniformBuffer + is supported only when the QRhi::ReadBackNonUniformBuffer feature is + reported as supported. + + \a readBackTexture(), QRhi::isFeatureSupported() + */ +void QRhiResourceUpdateBatch::readBackBuffer(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result) +{ + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::read(buf, offset, size, result)); } /*! @@ -4212,7 +4240,7 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *da void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) { if (desc.cbeginEntries() != desc.cendEntries()) - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureUpload(tex, desc)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::upload(tex, desc)); } /*! @@ -4237,7 +4265,7 @@ void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QImage &imag */ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) { - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureCopy(dst, src, desc)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::copy(dst, src, desc)); } /*! @@ -4289,7 +4317,7 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co */ void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) { - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureRead(rb, result)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::read(rb, result)); } /*! @@ -4301,7 +4329,7 @@ void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, */ void QRhiResourceUpdateBatch::generateMips(QRhiTexture *tex, int layer) { - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureMipGen(tex, layer)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::genMips(tex, layer)); } /*! @@ -4350,8 +4378,7 @@ void QRhiResourceUpdateBatchPrivate::free() { Q_ASSERT(poolIndex >= 0 && rhi->resUpdPool[poolIndex] == q); - dynamicBufferUpdates.clear(); - staticBufferUploads.clear(); + bufferOps.clear(); textureOps.clear(); rhi->resUpdPoolMap.clearBit(poolIndex); @@ -4360,9 +4387,13 @@ void QRhiResourceUpdateBatchPrivate::free() void QRhiResourceUpdateBatchPrivate::merge(QRhiResourceUpdateBatchPrivate *other) { - dynamicBufferUpdates += other->dynamicBufferUpdates; - staticBufferUploads += other->staticBufferUploads; - textureOps += other->textureOps; + bufferOps.reserve(bufferOps.size() + other->bufferOps.size()); + for (const BufferOp &op : qAsConst(other->bufferOps)) + bufferOps.append(op); + + textureOps.reserve(textureOps.size() + other->textureOps.size()); + for (const TextureOp &op : qAsConst(other->textureOps)) + textureOps.append(op); } /*! diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index 5b371af727d..993fc44b582 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -1355,6 +1355,12 @@ struct Q_GUI_EXPORT QRhiReadbackResult QByteArray data; }; // non-movable due to the std::function +struct Q_GUI_EXPORT QRhiBufferReadbackResult +{ + std::function completed = nullptr; + QByteArray data; +}; + class Q_GUI_EXPORT QRhiResourceUpdateBatch { public: @@ -1367,6 +1373,7 @@ public: void updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data); void uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data); void uploadStaticBuffer(QRhiBuffer *buf, const void *data); + void readBackBuffer(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result); void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc); void uploadTexture(QRhiTexture *tex, const QImage &image); void copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc = QRhiTextureCopyDescription()); @@ -1428,7 +1435,8 @@ public: VertexShaderPointSize, BaseVertex, BaseInstance, - TriangleFanTopology + TriangleFanTopology, + ReadBackNonUniformBuffer }; enum BeginFrameFlag { diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h index 80a95e2fcce..cc142935809 100644 --- a/src/gui/rhi/qrhi_p_p.h +++ b/src/gui/rhi/qrhi_p_p.h @@ -218,7 +218,7 @@ private: QRhi::Implementation implType; QThread *implThread; QRhiProfiler profiler; - QVector resUpdPool; + QVarLengthArray resUpdPool; QBitArray resUpdPoolMap; QSet resources; QSet pendingReleaseAndDestroyResources; @@ -271,26 +271,49 @@ bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array(data_), size_) - { } - - QRhiBuffer *buf = nullptr; - int offset = 0; + struct BufferOp { + enum Type { + DynamicUpdate, + StaticUpload, + Read + }; + Type type; + QRhiBuffer *buf; + int offset; QByteArray data; - }; + int readSize; + QRhiBufferReadbackResult *result; - struct StaticBufferUpload { - StaticBufferUpload() { } - StaticBufferUpload(QRhiBuffer *buf_, int offset_, int size_, const void *data_) - : buf(buf_), offset(offset_), data(reinterpret_cast(data_), size_ ? size_ : buf_->size()) - { } + static BufferOp dynamicUpdate(QRhiBuffer *buf, int offset, int size, const void *data) + { + BufferOp op; + op.type = DynamicUpdate; + op.buf = buf; + op.offset = offset; + op.data = QByteArray(reinterpret_cast(data), size ? size : buf->size()); + return op; + } - QRhiBuffer *buf = nullptr; - int offset = 0; - QByteArray data; + static BufferOp staticUpload(QRhiBuffer *buf, int offset, int size, const void *data) + { + BufferOp op; + op.type = StaticUpload; + op.buf = buf; + op.offset = offset; + op.data = QByteArray(reinterpret_cast(data), size ? size : buf->size()); + return op; + } + + static BufferOp read(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result) + { + BufferOp op; + op.type = Read; + op.buf = buf; + op.offset = offset; + op.readSize = size; + op.result = result; + return op; + } }; struct TextureOp { @@ -298,73 +321,62 @@ public: Upload, Copy, Read, - MipGen + GenMips }; Type type; - struct SUpload { - QRhiTexture *tex = nullptr; - // Specifying multiple uploads for a subresource must be supported. - // In the backend this can then end up, where applicable, as a - // single, batched copy operation with only one set of barriers. - // This helps when doing for example glyph cache fills. - QVector subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS]; - } upload; - struct SCopy { - QRhiTexture *dst = nullptr; - QRhiTexture *src = nullptr; - QRhiTextureCopyDescription desc; - } copy; - struct SRead { - QRhiReadbackDescription rb; - QRhiReadbackResult *result; - } read; - struct SMipGen { - QRhiTexture *tex = nullptr; - int layer = 0; - } mipgen; + QRhiTexture *dst; + // Specifying multiple uploads for a subresource must be supported. + // In the backend this can then end up, where applicable, as a + // single, batched copy operation with only one set of barriers. + // This helps when doing for example glyph cache fills. + QVector subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS]; + QRhiTexture *src; + QRhiTextureCopyDescription desc; + QRhiReadbackDescription rb; + QRhiReadbackResult *result; + int layer; - static TextureOp textureUpload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) + static TextureOp upload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) { TextureOp op; op.type = Upload; - op.upload.tex = tex; + op.dst = tex; for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) - op.upload.subresDesc[it->layer()][it->level()].append(it->description()); + op.subresDesc[it->layer()][it->level()].append(it->description()); return op; } - static TextureOp textureCopy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) + static TextureOp copy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) { TextureOp op; op.type = Copy; - op.copy.dst = dst; - op.copy.src = src; - op.copy.desc = desc; + op.dst = dst; + op.src = src; + op.desc = desc; return op; } - static TextureOp textureRead(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) + static TextureOp read(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) { TextureOp op; op.type = Read; - op.read.rb = rb; - op.read.result = result; + op.rb = rb; + op.result = result; return op; } - static TextureOp textureMipGen(QRhiTexture *tex, int layer) + static TextureOp genMips(QRhiTexture *tex, int layer) { TextureOp op; - op.type = MipGen; - op.mipgen.tex = tex; - op.mipgen.layer = layer; + op.type = GenMips; + op.dst = tex; + op.layer = layer; return op; } }; - QVector dynamicBufferUpdates; - QVector staticBufferUploads; - QVector textureOps; + QVarLengthArray bufferOps; + QVarLengthArray textureOps; QRhiResourceUpdateBatch *q = nullptr; QRhiImplementation *rhi = nullptr; @@ -376,8 +388,7 @@ public: static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; } }; -Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::StaticBufferUpload, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::BufferOp, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::TextureOp, Q_MOVABLE_TYPE); template diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index f011d68adad..7c39fe0b118 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -471,6 +471,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::TriangleFanTopology: return false; + case QRhi::ReadBackNonUniformBuffer: + return true; default: Q_UNREACHABLE(); return false; @@ -1323,54 +1325,97 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); - bufD->hasPendingDynamicUpdates = true; - } + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); + bufD->hasPendingDynamicUpdates = true; + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes; + cmd.args.updateSubRes.dst = bufD->buffer; + cmd.args.updateSubRes.dstSubRes = 0; + cmd.args.updateSubRes.src = cbD->retainData(u.data); + cmd.args.updateSubRes.srcRowPitch = 0; + // Specify the region (even when offset is 0 and all data is provided) + // since the ID3D11Buffer's size is rounded up to be a multiple of 256 + // while the data we have has the original size. + D3D11_BOX box; + box.left = UINT(u.offset); + box.top = box.front = 0; + box.back = box.bottom = 1; + box.right = UINT(u.offset + u.data.size()); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc + cmd.args.updateSubRes.hasDstBox = true; + cmd.args.updateSubRes.dstBox = box; + cbD->commands.append(cmd); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); + if (bufD->m_type == QRhiBuffer::Dynamic) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), bufD->dynBuf.constData() + u.offset, size_t(u.readSize)); + } else { + BufferReadback readback; + readback.result = u.result; + readback.byteSize = u.readSize; - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); - Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); - Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); - QD3D11CommandBuffer::Command cmd; - cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes; - cmd.args.updateSubRes.dst = bufD->buffer; - cmd.args.updateSubRes.dstSubRes = 0; - cmd.args.updateSubRes.src = cbD->retainData(u.data); - cmd.args.updateSubRes.srcRowPitch = 0; - // Specify the region (even when offset is 0 and all data is provided) - // since the ID3D11Buffer's size is rounded up to be a multiple of 256 - // while the data we have has the original size. - D3D11_BOX box; - box.left = UINT(u.offset); - box.top = box.front = 0; - box.back = box.bottom = 1; - box.right = UINT(u.offset + u.data.size()); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc - cmd.args.updateSubRes.hasDstBox = true; - cmd.args.updateSubRes.dstBox = box; - cbD->commands.append(cmd); + D3D11_BUFFER_DESC desc; + memset(&desc, 0, sizeof(desc)); + desc.ByteWidth = readback.byteSize; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + HRESULT hr = dev->CreateBuffer(&desc, nullptr, &readback.stagingBuf); + if (FAILED(hr)) { + qWarning("Failed to create buffer: %s", qPrintable(comErrorMessage(hr))); + continue; + } + QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.stagingBuf)), bufD, readback.byteSize)); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes; + cmd.args.copySubRes.dst = readback.stagingBuf; + cmd.args.copySubRes.dstSubRes = 0; + cmd.args.copySubRes.dstX = 0; + cmd.args.copySubRes.dstY = 0; + cmd.args.copySubRes.src = bufD->buffer; + cmd.args.copySubRes.srcSubRes = 0; + cmd.args.copySubRes.hasSrcBox = true; + D3D11_BOX box; + box.left = UINT(u.offset); + box.top = box.front = 0; + box.back = box.bottom = 1; + box.right = UINT(u.offset + u.readSize); + cmd.args.copySubRes.srcBox = box; + cbD->commands.append(cmd); + + activeBufferReadbacks.append(readback); + } + if (u.result->completed) + u.result->completed(); + } } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.upload.tex); + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.dst); for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) enqueueSubresUpload(texD, cbD, layer, level, subresDesc); } } } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.copy.src); - QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.copy.dst); - UINT srcSubRes = D3D11CalcSubresource(UINT(u.copy.desc.sourceLevel()), UINT(u.copy.desc.sourceLayer()), srcD->mipLevelCount); - UINT dstSubRes = D3D11CalcSubresource(UINT(u.copy.desc.destinationLevel()), UINT(u.copy.desc.destinationLayer()), dstD->mipLevelCount); - const QPoint dp = u.copy.desc.destinationTopLeft(); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); - const QPoint sp = u.copy.desc.sourceTopLeft(); + Q_ASSERT(u.src && u.dst); + QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.src); + QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.dst); + UINT srcSubRes = D3D11CalcSubresource(UINT(u.desc.sourceLevel()), UINT(u.desc.sourceLayer()), srcD->mipLevelCount); + UINT dstSubRes = D3D11CalcSubresource(UINT(u.desc.destinationLevel()), UINT(u.desc.destinationLayer()), dstD->mipLevelCount); + const QPoint dp = u.desc.destinationTopLeft(); + const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); + const QPoint sp = u.desc.sourceTopLeft(); D3D11_BOX srcBox; srcBox.left = UINT(sp.x()); srcBox.top = UINT(sp.y()); @@ -1391,16 +1436,16 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate cmd.args.copySubRes.srcBox = srcBox; cbD->commands.append(cmd); } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - ActiveReadback aRb; - aRb.desc = u.read.rb; - aRb.result = u.read.result; + TextureReadback readback; + readback.desc = u.rb; + readback.result = u.result; ID3D11Resource *src; DXGI_FORMAT dxgiFormat; QSize pixelSize; QRhiTexture::Format format; UINT subres = 0; - QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.read.rb.texture()); + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.rb.texture()); QD3D11SwapChain *swapChainD = nullptr; if (texD) { @@ -1410,9 +1455,9 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } src = texD->tex; dxgiFormat = texD->dxgiFormat; - pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) : texD->m_pixelSize; + pixelSize = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize) : texD->m_pixelSize; format = texD->m_format; - subres = D3D11CalcSubresource(UINT(u.read.rb.level()), UINT(u.read.rb.layer()), texD->mipLevelCount); + subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(u.rb.layer()), texD->mipLevelCount); } else { Q_ASSERT(contextState.currentSwapChain); swapChainD = QRHI_RES(QD3D11SwapChain, contextState.currentSwapChain); @@ -1435,9 +1480,9 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate if (format == QRhiTexture::UnknownFormat) continue; } - quint32 bufSize = 0; + quint32 byteSize = 0; quint32 bpl = 0; - textureFormatInfo(format, pixelSize, &bpl, &bufSize); + textureFormatInfo(format, pixelSize, &bpl, &byteSize); D3D11_TEXTURE2D_DESC desc; memset(&desc, 0, sizeof(desc)); @@ -1457,7 +1502,7 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(stagingTex)), texD ? static_cast(texD) : static_cast(swapChainD), - bufSize)); + byteSize)); QD3D11CommandBuffer::Command cmd; cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes; @@ -1470,18 +1515,18 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate cmd.args.copySubRes.hasSrcBox = false; cbD->commands.append(cmd); - aRb.stagingTex = stagingTex; - aRb.bufSize = bufSize; - aRb.bpl = bpl; - aRb.pixelSize = pixelSize; - aRb.format = format; + readback.stagingTex = stagingTex; + readback.byteSize = byteSize; + readback.bpl = bpl; + readback.pixelSize = pixelSize; + readback.format = format; - activeReadbacks.append(aRb); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - Q_ASSERT(u.mipgen.tex->flags().testFlag(QRhiTexture::UsedWithGenerateMips)); + activeTextureReadbacks.append(readback); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + Q_ASSERT(u.dst->flags().testFlag(QRhiTexture::UsedWithGenerateMips)); QD3D11CommandBuffer::Command cmd; cmd.cmd = QD3D11CommandBuffer::Command::GenMip; - cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.mipgen.tex)->srv; + cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.dst)->srv; cbD->commands.append(cmd); } } @@ -1494,37 +1539,58 @@ void QRhiD3D11::finishActiveReadbacks() QVarLengthArray, 4> completedCallbacks; QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (int i = activeReadbacks.count() - 1; i >= 0; --i) { - const QRhiD3D11::ActiveReadback &aRb(activeReadbacks[i]); - aRb.result->format = aRb.format; - aRb.result->pixelSize = aRb.pixelSize; - aRb.result->data.resize(int(aRb.bufSize)); + for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) { + const QRhiD3D11::TextureReadback &readback(activeTextureReadbacks[i]); + readback.result->format = readback.format; + readback.result->pixelSize = readback.pixelSize; D3D11_MAPPED_SUBRESOURCE mp; - HRESULT hr = context->Map(aRb.stagingTex, 0, D3D11_MAP_READ, 0, &mp); - if (FAILED(hr)) { + HRESULT hr = context->Map(readback.stagingTex, 0, D3D11_MAP_READ, 0, &mp); + if (SUCCEEDED(hr)) { + readback.result->data.resize(int(readback.byteSize)); + // nothing says the rows are tightly packed in the texture, must take + // the stride into account + char *dst = readback.result->data.data(); + char *src = static_cast(mp.pData); + for (int y = 0, h = readback.pixelSize.height(); y != h; ++y) { + memcpy(dst, src, readback.bpl); + dst += readback.bpl; + src += mp.RowPitch; + } + context->Unmap(readback.stagingTex, 0); + } else { qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr))); - aRb.stagingTex->Release(); - continue; } - // nothing says the rows are tightly packed in the texture, must take - // the stride into account - char *dst = aRb.result->data.data(); - char *src = static_cast(mp.pData); - for (int y = 0, h = aRb.pixelSize.height(); y != h; ++y) { - memcpy(dst, src, aRb.bpl); - dst += aRb.bpl; - src += mp.RowPitch; + + readback.stagingTex->Release(); + QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.stagingTex)))); + + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); + + activeTextureReadbacks.removeAt(i); + } + + for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) { + const QRhiD3D11::BufferReadback &readback(activeBufferReadbacks[i]); + + D3D11_MAPPED_SUBRESOURCE mp; + HRESULT hr = context->Map(readback.stagingBuf, 0, D3D11_MAP_READ, 0, &mp); + if (SUCCEEDED(hr)) { + readback.result->data.resize(int(readback.byteSize)); + memcpy(readback.result->data.data(), mp.pData, readback.byteSize); + context->Unmap(readback.stagingBuf, 0); + } else { + qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr))); } - context->Unmap(aRb.stagingTex, 0); - aRb.stagingTex->Release(); - QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(aRb.stagingTex)))); + readback.stagingBuf->Release(); + QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.stagingBuf)))); - if (aRb.result->completed) - completedCallbacks.append(aRb.result->completed); + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); - activeReadbacks.removeAt(i); + activeBufferReadbacks.removeAt(i); } for (auto f : completedCallbacks) diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index 6699e7ad641..26de34ae0a4 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -678,16 +678,22 @@ public: QD3D11CommandBuffer cbWrapper; } ofr; - struct ActiveReadback { + struct TextureReadback { QRhiReadbackDescription desc; QRhiReadbackResult *result; ID3D11Texture2D *stagingTex; - quint32 bufSize; + quint32 byteSize; quint32 bpl; QSize pixelSize; QRhiTexture::Format format; }; - QVector activeReadbacks; + QVector activeTextureReadbacks; + struct BufferReadback { + QRhiBufferReadbackResult *result; + quint32 byteSize; + ID3D11Buffer *stagingBuf; + }; + QVector activeBufferReadbacks; struct Shader { Shader() = default; @@ -711,7 +717,8 @@ public: } deviceCurse; }; -Q_DECLARE_TYPEINFO(QRhiD3D11::ActiveReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiD3D11::TextureReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiD3D11::BufferReadback, Q_MOVABLE_TYPE); QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 277cf12fd2f..13dc016fd6d 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -492,6 +492,8 @@ bool QRhiGles2::create(QRhi::Flags flags) else caps.textureCompareMode = true; + caps.mapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBuffer); + if (!caps.gles) { f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); f->glEnable(GL_POINT_SPRITE); @@ -734,6 +736,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return false; // not in ES 3.2, so won't bother case QRhi::TriangleFanTopology: return true; + case QRhi::ReadBackNonUniformBuffer: + return !caps.gles || caps.mapBuffer; default: Q_UNREACHABLE(); return false; @@ -1417,65 +1421,82 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { - memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); - } else { - trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); - QGles2CommandBuffer::Command cmd; - cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; - cmd.args.bufferSubData.target = bufD->targetForDataOps; - cmd.args.bufferSubData.buffer = bufD->buffer; - cmd.args.bufferSubData.offset = u.offset; - cmd.args.bufferSubData.size = u.data.size(); - cmd.args.bufferSubData.data = cbD->retainData(u.data); - cbD->commands.append(cmd); - } - } - - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); - 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.data() + u.offset, u.data.constData(), size_t(u.data.size())); - } else { - trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); - QGles2CommandBuffer::Command cmd; - cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; - cmd.args.bufferSubData.target = bufD->targetForDataOps; - cmd.args.bufferSubData.buffer = bufD->buffer; - cmd.args.bufferSubData.offset = u.offset; - cmd.args.bufferSubData.size = u.data.size(); - cmd.args.bufferSubData.data = cbD->retainData(u.data); - cbD->commands.append(cmd); + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { + memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); + } else { + trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); + QGles2CommandBuffer::Command cmd; + cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; + cmd.args.bufferSubData.target = bufD->targetForDataOps; + cmd.args.bufferSubData.buffer = bufD->buffer; + cmd.args.bufferSubData.offset = u.offset; + cmd.args.bufferSubData.size = u.data.size(); + cmd.args.bufferSubData.data = cbD->retainData(u.data); + cbD->commands.append(cmd); + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); + 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.data() + u.offset, u.data.constData(), size_t(u.data.size())); + } else { + trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); + QGles2CommandBuffer::Command cmd; + cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; + cmd.args.bufferSubData.target = bufD->targetForDataOps; + cmd.args.bufferSubData.buffer = bufD->buffer; + cmd.args.bufferSubData.offset = u.offset; + cmd.args.bufferSubData.size = u.data.size(); + cmd.args.bufferSubData.data = cbD->retainData(u.data); + cbD->commands.append(cmd); + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + 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.constData() + u.offset, size_t(u.readSize)); + if (u.result->completed) + u.result->completed(); + } else { + QGles2CommandBuffer::Command cmd; + cmd.cmd = QGles2CommandBuffer::Command::GetBufferSubData; + cmd.args.getBufferSubData.result = u.result; + cmd.args.getBufferSubData.target = bufD->targetForDataOps; + cmd.args.getBufferSubData.buffer = bufD->buffer; + cmd.args.getBufferSubData.offset = u.offset; + cmd.args.getBufferSubData.size = u.readSize; + cbD->commands.append(cmd); + } } } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QGles2Texture *texD = QRHI_RES(QGles2Texture, u.upload.tex); + QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) enqueueSubresUpload(texD, cbD, layer, level, subresDesc); } } texD->specified = true; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.copy.src); - QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.copy.dst); + Q_ASSERT(u.src && u.dst); + QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.src); + QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.dst); trackedImageBarrier(cbD, srcD, QGles2Texture::AccessRead); trackedImageBarrier(cbD, dstD, QGles2Texture::AccessUpdate); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); + const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); // do not translate coordinates, even if sp is bottom-left from gl's pov - const QPoint sp = u.copy.desc.sourceTopLeft(); - const QPoint dp = u.copy.desc.destinationTopLeft(); + const QPoint sp = u.desc.sourceTopLeft(); + const QPoint dp = u.desc.destinationTopLeft(); const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target; @@ -1485,16 +1506,16 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::CopyTex; - cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.copy.desc.sourceLayer()); + cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.desc.sourceLayer()); cmd.args.copyTex.srcTexture = srcD->texture; - cmd.args.copyTex.srcLevel = u.copy.desc.sourceLevel(); + cmd.args.copyTex.srcLevel = u.desc.sourceLevel(); cmd.args.copyTex.srcX = sp.x(); cmd.args.copyTex.srcY = sp.y(); cmd.args.copyTex.dstTarget = dstD->target; cmd.args.copyTex.dstTexture = dstD->texture; - cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.copy.desc.destinationLayer()); - cmd.args.copyTex.dstLevel = u.copy.desc.destinationLevel(); + cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.desc.destinationLayer()); + cmd.args.copyTex.dstLevel = u.desc.destinationLevel(); cmd.args.copyTex.dstX = dp.x(); cmd.args.copyTex.dstY = dp.y(); @@ -1505,8 +1526,8 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::ReadPixels; - cmd.args.readPixels.result = u.read.result; - QGles2Texture *texD = QRHI_RES(QGles2Texture, u.read.rb.texture()); + cmd.args.readPixels.result = u.result; + QGles2Texture *texD = QRHI_RES(QGles2Texture, u.rb.texture()); if (texD) trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead); cmd.args.readPixels.texture = texD ? texD->texture : 0; @@ -1516,12 +1537,12 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate cmd.args.readPixels.format = texD->m_format; const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; - cmd.args.readPixels.readTarget = faceTargetBase + uint(u.read.rb.layer()); - cmd.args.readPixels.level = u.read.rb.level(); + cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer()); + cmd.args.readPixels.level = u.rb.level(); } cbD->commands.append(cmd); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - QGles2Texture *texD = QRHI_RES(QGles2Texture, u.mipgen.tex); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); trackedImageBarrier(cbD, texD, QGles2Texture::AccessFramebuffer); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::GenMip; @@ -2080,6 +2101,32 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size, cmd.args.bufferSubData.data); break; + case QGles2CommandBuffer::Command::GetBufferSubData: + { + QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result; + f->glBindBuffer(cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer); + if (caps.gles) { + if (caps.mapBuffer) { + void *p = f->glMapBuffer(cmd.args.getBufferSubData.target, GL_READ_ONLY); + if (p) { + result->data.resize(cmd.args.getBufferSubData.size); + memcpy(result->data.data(), + reinterpret_cast(p) + cmd.args.getBufferSubData.offset, + size_t(cmd.args.getBufferSubData.size)); + f->glUnmapBuffer(cmd.args.getBufferSubData.target); + } + } + } else { + result->data.resize(cmd.args.getBufferSubData.size); + f->glGetBufferSubData(cmd.args.getBufferSubData.target, + cmd.args.getBufferSubData.offset, + cmd.args.getBufferSubData.size, + result->data.data()); + } + if (result->completed) + result->completed(); + } + break; case QGles2CommandBuffer::Command::CopyTex: { GLuint fbo; diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index 646836a699e..a4a9c15ce7c 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -312,8 +312,8 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer BindShaderResources, BindFramebuffer, Clear, - BufferData, BufferSubData, + GetBufferSubData, CopyTex, ReadPixels, SubImage, @@ -401,6 +401,13 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer int size; const void *data; // must come from retainData() } bufferSubData; + struct { + QRhiBufferReadbackResult *result; + GLenum target; + GLuint buffer; + int offset; + int size; + } getBufferSubData; struct { GLenum srcFaceTarget; GLuint srcTexture; @@ -744,7 +751,9 @@ public: rgba8Format(false), instancing(false), baseVertex(false), - compute(false) + compute(false), + textureCompareMode(false), + mapBuffer(false) { } int ctxMajor; int ctxMinor; @@ -775,6 +784,7 @@ public: uint baseVertex : 1; uint compute : 1; uint textureCompareMode : 1; + uint mapBuffer : 1; } caps; QGles2SwapChain *currentSwapChain = nullptr; QVector supportedCompressedFormats; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index f9d9cdc01a1..e55513277b9 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -205,7 +205,7 @@ struct QRhiMetalData QMetalCommandBuffer cbWrapper; } ofr; - struct ActiveReadback { + struct TextureReadback { int activeFrameSlot = -1; QRhiReadbackDescription desc; QRhiReadbackResult *result; @@ -214,7 +214,7 @@ struct QRhiMetalData QSize pixelSize; QRhiTexture::Format format; }; - QVector activeReadbacks; + QVector activeTextureReadbacks; API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr; API_AVAILABLE(macos(10.13), ios(11.0)) id captureScope = nil; @@ -225,14 +225,14 @@ struct QRhiMetalData }; Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiMetalData::TextureReadback, Q_MOVABLE_TYPE); struct QMetalBufferData { bool managed; bool slotted; id buf[QMTL_FRAMES_IN_FLIGHT]; - QVector pendingUpdates[QMTL_FRAMES_IN_FLIGHT]; + QVarLengthArray pendingUpdates[QMTL_FRAMES_IN_FLIGHT]; }; struct QMetalRenderBufferData @@ -552,6 +552,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::TriangleFanTopology: return false; + case QRhi::ReadBackNonUniformBuffer: + return true; default: Q_UNREACHABLE(); return false; @@ -1541,21 +1543,33 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) - bufD->d->pendingUpdates[i].append(u); - } - - // Due to the Metal API the handling of static and dynamic buffers is - // basically the same. So go through the same pendingUpdates machinery. - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); - Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); - Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); - for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i) - bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() }); + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) + bufD->d->pendingUpdates[i].append(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + // Due to the Metal API the handling of static and dynamic buffers is + // basically the same. So go through the same pendingUpdates machinery. + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i) + bufD->d->pendingUpdates[i].append( + QRhiResourceUpdateBatchPrivate::BufferOp::dynamicUpdate(u.buf, u.offset, u.data.size(), u.data.constData())); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); + executeBufferHostWritesForCurrentFrame(bufD); + const int idx = bufD->d->slotted ? currentFrameSlot : 0; + char *p = reinterpret_cast([bufD->d->buf[idx] contents]); + if (p) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize)); + } + if (u.result->completed) + u.result->completed(); + } } id blitEnc = nil; @@ -1569,11 +1583,11 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.upload.tex); + QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst); qsizetype stagingSize = 0; for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) stagingSize += subresUploadByteSize(subresDesc); } } @@ -1588,7 +1602,7 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate qsizetype curOfs = 0; for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs); } } @@ -1603,32 +1617,32 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate d->releaseQueue.append(e); QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot)); } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.copy.src); - QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.copy.dst); - const QPoint dp = u.copy.desc.destinationTopLeft(); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); - const QPoint sp = u.copy.desc.sourceTopLeft(); + Q_ASSERT(u.src && u.dst); + QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src); + QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst); + const QPoint dp = u.desc.destinationTopLeft(); + const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); + const QPoint sp = u.desc.sourceTopLeft(); ensureBlit(); [blitEnc copyFromTexture: srcD->d->tex - sourceSlice: NSUInteger(u.copy.desc.sourceLayer()) - sourceLevel: NSUInteger(u.copy.desc.sourceLevel()) + sourceSlice: NSUInteger(u.desc.sourceLayer()) + sourceLevel: NSUInteger(u.desc.sourceLevel()) sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), 0) sourceSize: MTLSizeMake(NSUInteger(size.width()), NSUInteger(size.height()), 1) toTexture: dstD->d->tex - destinationSlice: NSUInteger(u.copy.desc.destinationLayer()) - destinationLevel: NSUInteger(u.copy.desc.destinationLevel()) + destinationSlice: NSUInteger(u.desc.destinationLayer()) + destinationLevel: NSUInteger(u.desc.destinationLevel()) destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), 0)]; srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - QRhiMetalData::ActiveReadback aRb; - aRb.activeFrameSlot = currentFrameSlot; - aRb.desc = u.read.rb; - aRb.result = u.read.result; + QRhiMetalData::TextureReadback readback; + readback.activeFrameSlot = currentFrameSlot; + readback.desc = u.rb; + readback.result = u.result; - QMetalTexture *texD = QRHI_RES(QMetalTexture, u.read.rb.texture()); + QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture()); QMetalSwapChain *swapChainD = nullptr; id src; QSize srcSize; @@ -1637,17 +1651,17 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate qWarning("Multisample texture cannot be read back"); continue; } - aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) + readback.pixelSize = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize) : texD->m_pixelSize; - aRb.format = texD->m_format; + readback.format = texD->m_format; src = texD->d->tex; srcSize = texD->m_pixelSize; texD->lastActiveFrameSlot = currentFrameSlot; } else { Q_ASSERT(currentSwapChain); swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain); - aRb.pixelSize = swapChainD->pixelSize; - aRb.format = swapChainD->d->rhiColorFormat; + readback.pixelSize = swapChainD->pixelSize; + readback.format = swapChainD->d->rhiColorFormat; // Multisample swapchains need nothing special since resolving // happens when ending a renderpass. const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]); @@ -1656,28 +1670,28 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } quint32 bpl = 0; - textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize); - aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared]; + textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize); + readback.buf = [d->dev newBufferWithLength: readback.bufSize options: MTLResourceStorageModeShared]; - QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(aRb.buf)), + QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.buf)), texD ? static_cast(texD) : static_cast(swapChainD), - aRb.bufSize)); + readback.bufSize)); ensureBlit(); [blitEnc copyFromTexture: src - sourceSlice: NSUInteger(u.read.rb.layer()) - sourceLevel: NSUInteger(u.read.rb.level()) + sourceSlice: NSUInteger(u.rb.layer()) + sourceLevel: NSUInteger(u.rb.level()) sourceOrigin: MTLOriginMake(0, 0, 0) sourceSize: MTLSizeMake(NSUInteger(srcSize.width()), NSUInteger(srcSize.height()), 1) - toBuffer: aRb.buf + toBuffer: readback.buf destinationOffset: 0 destinationBytesPerRow: bpl destinationBytesPerImage: 0 options: MTLBlitOptionNone]; - d->activeReadbacks.append(aRb); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.mipgen.tex); + d->activeTextureReadbacks.append(readback); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst); ensureBlit(); [blitEnc generateMipmapsForTexture: utexD->d->tex]; utexD->lastActiveFrameSlot = currentFrameSlot; @@ -1697,14 +1711,13 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) { const int idx = bufD->d->slotted ? currentFrameSlot : 0; - QVector &updates(bufD->d->pendingUpdates[idx]); - if (updates.isEmpty()) + if (bufD->d->pendingUpdates[idx].isEmpty()) return; void *p = [bufD->d->buf[idx] contents]; int changeBegin = -1; int changeEnd = -1; - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) { + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->d->pendingUpdates[idx])) { Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf)); memcpy(static_cast(p) + u.offset, u.data.constData(), size_t(u.data.size())); if (changeBegin == -1 || u.offset < changeBegin) @@ -1715,7 +1728,7 @@ void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) if (changeBegin >= 0 && bufD->d->managed) [bufD->d->buf[idx] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))]; - updates.clear(); + bufD->d->pendingUpdates[idx].clear(); } void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -1951,22 +1964,22 @@ void QRhiMetal::finishActiveReadbacks(bool forced) QVarLengthArray, 4> completedCallbacks; QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (int i = d->activeReadbacks.count() - 1; i >= 0; --i) { - const QRhiMetalData::ActiveReadback &aRb(d->activeReadbacks[i]); - if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) { - aRb.result->format = aRb.format; - aRb.result->pixelSize = aRb.pixelSize; - aRb.result->data.resize(int(aRb.bufSize)); - void *p = [aRb.buf contents]; - memcpy(aRb.result->data.data(), p, aRb.bufSize); - [aRb.buf release]; + for (int i = d->activeTextureReadbacks.count() - 1; i >= 0; --i) { + const QRhiMetalData::TextureReadback &readback(d->activeTextureReadbacks[i]); + if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { + readback.result->format = readback.format; + readback.result->pixelSize = readback.pixelSize; + readback.result->data.resize(int(readback.bufSize)); + void *p = [readback.buf contents]; + memcpy(readback.result->data.data(), p, readback.bufSize); + [readback.buf release]; - QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(aRb.buf)))); + QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.buf)))); - if (aRb.result->completed) - completedCallbacks.append(aRb.result->completed); + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); - d->activeReadbacks.removeAt(i); + d->activeTextureReadbacks.removeAt(i); } } @@ -2042,7 +2055,6 @@ bool QMetalBuffer::build() for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) { if (i == 0 || d->slotted) { d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts]; - d->pendingUpdates[i].reserve(16); if (!m_objectName.isEmpty()) { if (!d->slotted) { d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()]; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 487afd3ed10..de6616b677c 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -389,13 +389,28 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re { Q_UNUSED(cb); QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate + || u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) + { + QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); + memcpy(bufD->data.data() + u.offset, u.data.constData(), size_t(u.data.size())); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QRhiBufferReadbackResult *result = u.result; + result->data.resize(u.readSize); + QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); + memcpy(result->data.data(), bufD->data.constData() + u.offset, size_t(u.readSize)); + if (result->completed) + result->completed(); + } + } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - QRhiReadbackResult *result = u.read.result; - QRhiTexture *tex = u.read.rb.texture(); + QRhiReadbackResult *result = u.result; + QRhiTexture *tex = u.rb.texture(); if (tex) { result->format = tex->format(); - result->pixelSize = q->sizeForMipLevel(u.read.rb.level(), tex->pixelSize()); + result->pixelSize = q->sizeForMipLevel(u.rb.level(), tex->pixelSize()); } else { Q_ASSERT(currentSwapChain); result->format = QRhiTexture::RGBA8; @@ -403,7 +418,7 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re } quint32 byteSize = 0; textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize); - result->data.fill(0, byteSize); + result->data.fill(0, int(byteSize)); if (result->completed) result->completed(); } @@ -454,14 +469,18 @@ QNullBuffer::~QNullBuffer() void QNullBuffer::release() { + data.clear(); + QRHI_PROF; QRHI_PROF_F(releaseBuffer(this)); } bool QNullBuffer::build() { + data.fill('\0', m_size); + QRHI_PROF; - QRHI_PROF_F(newBuffer(this, m_size, 1, 0)); + QRHI_PROF_F(newBuffer(this, uint(m_size), 1, 0)); return true; } diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h index ee301d247bb..93e298481c0 100644 --- a/src/gui/rhi/qrhinull_p_p.h +++ b/src/gui/rhi/qrhinull_p_p.h @@ -59,6 +59,8 @@ struct QNullBuffer : public QRhiBuffer ~QNullBuffer(); void release() override; bool build() override; + + QByteArray data; }; struct QNullRenderBuffer : public QRhiRenderBuffer diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 36a6557e048..154908b333c 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -363,6 +363,11 @@ bool QRhiVulkan::create(QRhi::Flags flags) Q_UNUSED(flags); Q_ASSERT(inst); + if (!inst->isValid()) { + qWarning("Vulkan instance is not valid"); + return false; + } + globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application f = inst->functions(); @@ -2645,100 +2650,164 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) - bufD->pendingDynamicUpdates[i].append(u); - } + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) + bufD->pendingDynamicUpdates[i].append(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); - Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); - Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + if (!bufD->stagingBuffers[currentFrameSlot]) { + VkBufferCreateInfo bufferInfo; + memset(&bufferInfo, 0, sizeof(bufferInfo)); + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + // must cover the entire buffer - this way multiple, partial updates per frame + // are supported even when the staging buffer is reused (Static) + bufferInfo.size = VkDeviceSize(bufD->m_size); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - if (!bufD->stagingBuffers[currentFrameSlot]) { - VkBufferCreateInfo bufferInfo; - memset(&bufferInfo, 0, sizeof(bufferInfo)); - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - // must cover the entire buffer - this way multiple, partial updates per frame - // are supported even when the staging buffer is reused (Static) - bufferInfo.size = VkDeviceSize(bufD->m_size); - bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + VmaAllocationCreateInfo allocInfo; + memset(&allocInfo, 0, sizeof(allocInfo)); + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); - allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + VmaAllocation allocation; + VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, + &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr); + if (err == VK_SUCCESS) { + bufD->stagingAllocations[currentFrameSlot] = allocation; + QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size))); + } else { + qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err); + continue; + } + } - VmaAllocation allocation; - VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, - &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr); - if (err == VK_SUCCESS) { - bufD->stagingAllocations[currentFrameSlot] = allocation; - QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size))); - } else { - qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err); + void *p = nullptr; + VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]); + VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); + if (err != VK_SUCCESS) { + qWarning("Failed to map buffer: %d", err); continue; } - } + memcpy(static_cast(p) + u.offset, u.data.constData(), size_t(u.data.size())); + vmaUnmapMemory(toVmaAllocator(allocator), a); + vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(u.offset), VkDeviceSize(u.data.size())); - void *p = nullptr; - VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]); - VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); - if (err != VK_SUCCESS) { - qWarning("Failed to map buffer: %d", err); - continue; - } - memcpy(static_cast(p) + u.offset, u.data.constData(), size_t(u.data.size())); - vmaUnmapMemory(toVmaAllocator(allocator), a); - vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(u.offset), VkDeviceSize(u.data.size())); + trackedBufferBarrier(cbD, bufD, 0, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - trackedBufferBarrier(cbD, bufD, 0, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + VkBufferCopy copyInfo; + memset(©Info, 0, sizeof(copyInfo)); + copyInfo.srcOffset = VkDeviceSize(u.offset); + copyInfo.dstOffset = VkDeviceSize(u.offset); + copyInfo.size = VkDeviceSize(u.data.size()); - VkBufferCopy copyInfo; - memset(©Info, 0, sizeof(copyInfo)); - copyInfo.srcOffset = VkDeviceSize(u.offset); - copyInfo.dstOffset = VkDeviceSize(u.offset); - copyInfo.size = VkDeviceSize(u.data.size()); + QVkCommandBuffer::Command cmd; + cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; + cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot]; + cmd.args.copyBuffer.dst = bufD->buffers[0]; + cmd.args.copyBuffer.desc = copyInfo; + cbD->commands.append(cmd); - QVkCommandBuffer::Command cmd; - cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; - cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot]; - cmd.args.copyBuffer.dst = bufD->buffers[0]; - cmd.args.copyBuffer.desc = copyInfo; - cbD->commands.append(cmd); + // Where's the barrier for read-after-write? (assuming the common case + // of binding this buffer as vertex/index, or, less likely, as uniform + // buffer, in a renderpass later on) That is handled by the pass + // resource tracking: the appropriate pipeline barrier will be + // generated and recorded right before the renderpass, that binds this + // buffer in one of its commands, gets its BeginRenderPass recorded. - // Where's the barrier for read-after-write? (assuming the common case - // of binding this buffer as vertex/index, or, less likely, as uniform - // buffer, in a renderpass later on) That is handled by the pass - // resource tracking: the appropriate pipeline barrier will be - // generated and recorded right before the renderpass, that binds this - // buffer in one of its commands, gets its BeginRenderPass recorded. + bufD->lastActiveFrameSlot = currentFrameSlot; - bufD->lastActiveFrameSlot = currentFrameSlot; + if (bufD->m_type == QRhiBuffer::Immutable) { + QRhiVulkan::DeferredReleaseEntry e; + e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; + e.lastActiveFrameSlot = currentFrameSlot; + e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot]; + e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot]; + bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; + bufD->stagingAllocations[currentFrameSlot] = nullptr; + releaseQueue.append(e); + QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot)); + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); + if (bufD->m_type == QRhiBuffer::Dynamic) { + executeBufferHostWritesForCurrentFrame(bufD); + void *p = nullptr; + VmaAllocation a = toVmaAllocation(bufD->allocations[currentFrameSlot]); + VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); + if (err == VK_SUCCESS) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), reinterpret_cast(p) + u.offset, size_t(u.readSize)); + vmaUnmapMemory(toVmaAllocator(allocator), a); + } + if (u.result->completed) + u.result->completed(); + } else { + // Non-Dynamic buffers may not be host visible, so have to + // create a readback buffer, enqueue a copy from + // bufD->buffers[0] to this buffer, and then once the command + // buffer completes, copy the data out of the host visible + // readback buffer. Quite similar to what we do for texture + // readbacks. + BufferReadback readback; + readback.activeFrameSlot = currentFrameSlot; + readback.result = u.result; + readback.byteSize = u.readSize; - if (bufD->m_type == QRhiBuffer::Immutable) { - QRhiVulkan::DeferredReleaseEntry e; - e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; - e.lastActiveFrameSlot = currentFrameSlot; - e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot]; - e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot]; - bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; - bufD->stagingAllocations[currentFrameSlot] = nullptr; - releaseQueue.append(e); - QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot)); + VkBufferCreateInfo bufferInfo; + memset(&bufferInfo, 0, sizeof(bufferInfo)); + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = VkDeviceSize(readback.byteSize); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VmaAllocationCreateInfo allocInfo; + memset(&allocInfo, 0, sizeof(allocInfo)); + allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; + + VmaAllocation allocation; + VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr); + if (err == VK_SUCCESS) { + readback.stagingAlloc = allocation; + QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), bufD, uint(readback.byteSize))); + } else { + qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); + continue; + } + + trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferCopy copyInfo; + memset(©Info, 0, sizeof(copyInfo)); + copyInfo.srcOffset = VkDeviceSize(u.offset); + copyInfo.size = VkDeviceSize(u.readSize); + + QVkCommandBuffer::Command cmd; + cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; + cmd.args.copyBuffer.src = bufD->buffers[0]; + cmd.args.copyBuffer.dst = readback.stagingBuf; + cmd.args.copyBuffer.desc = copyInfo; + cbD->commands.append(cmd); + + bufD->lastActiveFrameSlot = currentFrameSlot; + + activeBufferReadbacks.append(readback); + } } } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QVkTexture *utexD = QRHI_RES(QVkTexture, u.upload.tex); + QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos VkDeviceSize stagingSize = 0; for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) stagingSize += subresUploadByteSize(subresDesc); } } @@ -2776,7 +2845,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - const QVector &srd(u.upload.subresDesc[layer][level]); + const QVector &srd(u.subresDesc[layer][level]); if (srd.isEmpty()) continue; for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(srd)) { @@ -2817,34 +2886,34 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat utexD->lastActiveFrameSlot = currentFrameSlot; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - if (u.copy.src == u.copy.dst) { + Q_ASSERT(u.src && u.dst); + if (u.src == u.dst) { qWarning("Texture copy with matching source and destination is not supported"); continue; } - QVkTexture *srcD = QRHI_RES(QVkTexture, u.copy.src); - QVkTexture *dstD = QRHI_RES(QVkTexture, u.copy.dst); + QVkTexture *srcD = QRHI_RES(QVkTexture, u.src); + QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst); VkImageCopy region; memset(®ion, 0, sizeof(region)); region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.srcSubresource.mipLevel = uint32_t(u.copy.desc.sourceLevel()); - region.srcSubresource.baseArrayLayer = uint32_t(u.copy.desc.sourceLayer()); + region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel()); + region.srcSubresource.baseArrayLayer = uint32_t(u.desc.sourceLayer()); region.srcSubresource.layerCount = 1; - region.srcOffset.x = u.copy.desc.sourceTopLeft().x(); - region.srcOffset.y = u.copy.desc.sourceTopLeft().y(); + region.srcOffset.x = u.desc.sourceTopLeft().x(); + region.srcOffset.y = u.desc.sourceTopLeft().y(); region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.dstSubresource.mipLevel = uint32_t(u.copy.desc.destinationLevel()); - region.dstSubresource.baseArrayLayer = uint32_t(u.copy.desc.destinationLayer()); + region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel()); + region.dstSubresource.baseArrayLayer = uint32_t(u.desc.destinationLayer()); region.dstSubresource.layerCount = 1; - region.dstOffset.x = u.copy.desc.destinationTopLeft().x(); - region.dstOffset.y = u.copy.desc.destinationTopLeft().y(); + region.dstOffset.x = u.desc.destinationTopLeft().x(); + region.dstOffset.y = u.desc.destinationTopLeft().y(); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); + const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); region.extent.width = uint32_t(size.width()); region.extent.height = uint32_t(size.height()); region.extent.depth = 1; @@ -2865,21 +2934,21 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - ActiveReadback aRb; - aRb.activeFrameSlot = currentFrameSlot; - aRb.desc = u.read.rb; - aRb.result = u.read.result; + TextureReadback readback; + readback.activeFrameSlot = currentFrameSlot; + readback.desc = u.rb; + readback.result = u.result; - QVkTexture *texD = QRHI_RES(QVkTexture, u.read.rb.texture()); + QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture()); QVkSwapChain *swapChainD = nullptr; if (texD) { if (texD->samples > VK_SAMPLE_COUNT_1_BIT) { qWarning("Multisample texture cannot be read back"); continue; } - aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) - : texD->m_pixelSize; - aRb.format = texD->m_format; + readback.pixelSize = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize) + : texD->m_pixelSize; + readback.format = texD->m_format; texD->lastActiveFrameSlot = currentFrameSlot; } else { Q_ASSERT(currentSwapChain); @@ -2888,21 +2957,21 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat qWarning("Swapchain does not support readback"); continue; } - aRb.pixelSize = swapChainD->pixelSize; - aRb.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr); - if (aRb.format == QRhiTexture::UnknownFormat) + readback.pixelSize = swapChainD->pixelSize; + readback.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr); + if (readback.format == QRhiTexture::UnknownFormat) continue; // Multisample swapchains need nothing special since resolving // happens when ending a renderpass. } - textureFormatInfo(aRb.format, aRb.pixelSize, nullptr, &aRb.bufSize); + textureFormatInfo(readback.format, readback.pixelSize, nullptr, &readback.byteSize); - // Create a host visible buffer. + // Create a host visible readback buffer. VkBufferCreateInfo bufferInfo; memset(&bufferInfo, 0, sizeof(bufferInfo)); bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = aRb.bufSize; + bufferInfo.size = readback.byteSize; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocInfo; @@ -2910,14 +2979,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; VmaAllocation allocation; - VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &aRb.buf, &allocation, nullptr); + VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr); if (err == VK_SUCCESS) { - aRb.bufAlloc = allocation; - QRHI_PROF_F(newReadbackBuffer(qint64(aRb.buf), + readback.stagingAlloc = allocation; + QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), texD ? static_cast(texD) : static_cast(swapChainD), - aRb.bufSize)); + readback.byteSize)); } else { - qWarning("Failed to create readback buffer of size %u: %d", aRb.bufSize, err); + qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); continue; } @@ -2926,11 +2995,11 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat memset(©Desc, 0, sizeof(copyDesc)); copyDesc.bufferOffset = 0; copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copyDesc.imageSubresource.mipLevel = uint32_t(u.read.rb.level()); - copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.read.rb.layer()); + copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level()); + copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.rb.layer()); copyDesc.imageSubresource.layerCount = 1; - copyDesc.imageExtent.width = uint32_t(aRb.pixelSize.width()); - copyDesc.imageExtent.height = uint32_t(aRb.pixelSize.height()); + copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width()); + copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height()); copyDesc.imageExtent.depth = 1; if (texD) { @@ -2940,7 +3009,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; cmd.args.copyImageToBuffer.src = texD->image; cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout; - cmd.args.copyImageToBuffer.dst = aRb.buf; + cmd.args.copyImageToBuffer.dst = readback.stagingBuf; cmd.args.copyImageToBuffer.desc = copyDesc; cbD->commands.append(cmd); } else { @@ -2965,14 +3034,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; cmd.args.copyImageToBuffer.src = image; cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - cmd.args.copyImageToBuffer.dst = aRb.buf; + cmd.args.copyImageToBuffer.dst = readback.stagingBuf; cmd.args.copyImageToBuffer.desc = copyDesc; cbD->commands.append(cmd); } - activeReadbacks.append(aRb); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - QVkTexture *utexD = QRHI_RES(QVkTexture, u.mipgen.tex); + activeTextureReadbacks.append(readback); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips)); int w = utexD->m_pixelSize.width(); int h = utexD->m_pixelSize.height(); @@ -2989,14 +3058,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat origLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origAccess, VK_ACCESS_TRANSFER_READ_BIT, origStage, VK_PIPELINE_STAGE_TRANSFER_BIT, - u.mipgen.layer, 1, + u.layer, 1, level - 1, 1); } else { subresourceBarrier(cbD, utexD->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - u.mipgen.layer, 1, + u.layer, 1, level - 1, 1); } @@ -3004,7 +3073,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat origLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origAccess, VK_ACCESS_TRANSFER_WRITE_BIT, origStage, VK_PIPELINE_STAGE_TRANSFER_BIT, - u.mipgen.layer, 1, + u.layer, 1, level, 1); VkImageBlit region; @@ -3012,7 +3081,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.srcSubresource.mipLevel = uint32_t(level) - 1; - region.srcSubresource.baseArrayLayer = uint32_t(u.mipgen.layer); + region.srcSubresource.baseArrayLayer = uint32_t(u.layer); region.srcSubresource.layerCount = 1; region.srcOffsets[1].x = qMax(1, w); @@ -3021,7 +3090,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.dstSubresource.mipLevel = uint32_t(level); - region.dstSubresource.baseArrayLayer = uint32_t(u.mipgen.layer); + region.dstSubresource.baseArrayLayer = uint32_t(u.layer); region.dstSubresource.layerCount = 1; region.dstOffsets[1].x = qMax(1, w >> 1); @@ -3047,13 +3116,13 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origLayout, VK_ACCESS_TRANSFER_READ_BIT, origAccess, VK_PIPELINE_STAGE_TRANSFER_BIT, origStage, - u.mipgen.layer, 1, + u.layer, 1, 0, int(utexD->mipLevelCount) - 1); subresourceBarrier(cbD, utexD->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origLayout, VK_ACCESS_TRANSFER_WRITE_BIT, origAccess, VK_PIPELINE_STAGE_TRANSFER_BIT, origStage, - u.mipgen.layer, 1, + u.layer, 1, int(utexD->mipLevelCount) - 1, 1); } @@ -3066,8 +3135,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD) { - QVector &updates(bufD->pendingDynamicUpdates[currentFrameSlot]); - if (updates.isEmpty()) + if (bufD->pendingDynamicUpdates[currentFrameSlot].isEmpty()) return; Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); @@ -3083,7 +3151,7 @@ void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD) } int changeBegin = -1; int changeEnd = -1; - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) { + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->pendingDynamicUpdates[currentFrameSlot])) { Q_ASSERT(bufD == QRHI_RES(QVkBuffer, u.buf)); memcpy(static_cast(p) + u.offset, u.data.constData(), size_t(u.data.size())); if (changeBegin == -1 || u.offset < changeBegin) @@ -3095,7 +3163,7 @@ void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD) if (changeBegin >= 0) vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(changeBegin), VkDeviceSize(changeEnd - changeBegin)); - updates.clear(); + bufD->pendingDynamicUpdates[currentFrameSlot].clear(); } static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator) @@ -3189,29 +3257,53 @@ void QRhiVulkan::finishActiveReadbacks(bool forced) QVarLengthArray, 4> completedCallbacks; QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (int i = activeReadbacks.count() - 1; i >= 0; --i) { - const QRhiVulkan::ActiveReadback &aRb(activeReadbacks[i]); - if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) { - aRb.result->format = aRb.format; - aRb.result->pixelSize = aRb.pixelSize; - aRb.result->data.resize(int(aRb.bufSize)); + for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) { + const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]); + if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { + readback.result->format = readback.format; + readback.result->pixelSize = readback.pixelSize; + VmaAllocation a = toVmaAllocation(readback.stagingAlloc); void *p = nullptr; - VmaAllocation a = toVmaAllocation(aRb.bufAlloc); VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); - if (err != VK_SUCCESS) { - qWarning("Failed to map readback buffer: %d", err); - continue; + if (err == VK_SUCCESS && p) { + readback.result->data.resize(int(readback.byteSize)); + memcpy(readback.result->data.data(), p, readback.byteSize); + vmaUnmapMemory(toVmaAllocator(allocator), a); + } else { + qWarning("Failed to map texture readback buffer of size %u: %d", readback.byteSize, err); } - memcpy(aRb.result->data.data(), p, aRb.bufSize); - vmaUnmapMemory(toVmaAllocator(allocator), a); - vmaDestroyBuffer(toVmaAllocator(allocator), aRb.buf, a); - QRHI_PROF_F(releaseReadbackBuffer(qint64(aRb.buf))); + vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a); + QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); - if (aRb.result->completed) - completedCallbacks.append(aRb.result->completed); + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); - activeReadbacks.removeAt(i); + activeTextureReadbacks.removeAt(i); + } + } + + for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) { + const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]); + if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { + VmaAllocation a = toVmaAllocation(readback.stagingAlloc); + void *p = nullptr; + VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); + if (err == VK_SUCCESS && p) { + readback.result->data.resize(readback.byteSize); + memcpy(readback.result->data.data(), p, size_t(readback.byteSize)); + vmaUnmapMemory(toVmaAllocator(allocator), a); + } else { + qWarning("Failed to map buffer readback buffer of size %d: %d", readback.byteSize, err); + } + + vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a); + QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); + + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); + + activeBufferReadbacks.removeAt(i); } } @@ -3729,6 +3821,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::TriangleFanTopology: return true; + case QRhi::ReadBackNonUniformBuffer: + return true; default: Q_UNREACHABLE(); return false; @@ -4898,7 +4992,7 @@ bool QVkBuffer::build() allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; } else { allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; } QRHI_RES_RHI(QRhiVulkan); @@ -4912,11 +5006,7 @@ bool QVkBuffer::build() err = vmaCreateBuffer(toVmaAllocator(rhiD->allocator), &bufferInfo, &allocInfo, &buffers[i], &allocation, nullptr); if (err != VK_SUCCESS) break; - allocations[i] = allocation; - if (m_type == Dynamic) - pendingDynamicUpdates[i].reserve(16); - rhiD->setObjectName(uint64_t(buffers[i]), VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, m_objectName, m_type == Dynamic ? i : -1); } diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index 7bd20b3671c..d0e1e6758b5 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -79,7 +79,7 @@ struct QVkBuffer : public QRhiBuffer VkBuffer buffers[QVK_FRAMES_IN_FLIGHT]; QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]; - QVector pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT]; + QVarLengthArray pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT]; VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; struct UsageState { @@ -853,17 +853,25 @@ public: VkFence cmdFence = VK_NULL_HANDLE; } ofr; - struct ActiveReadback { + struct TextureReadback { int activeFrameSlot = -1; QRhiReadbackDescription desc; QRhiReadbackResult *result; - VkBuffer buf; - QVkAlloc bufAlloc; - quint32 bufSize; + VkBuffer stagingBuf; + QVkAlloc stagingAlloc; + quint32 byteSize; QSize pixelSize; QRhiTexture::Format format; }; - QVector activeReadbacks; + QVector activeTextureReadbacks; + struct BufferReadback { + int activeFrameSlot = -1; + QRhiBufferReadbackResult *result; + int byteSize; + VkBuffer stagingBuf; + QVkAlloc stagingAlloc; + }; + QVector activeBufferReadbacks; struct DeferredReleaseEntry { enum Type { @@ -933,7 +941,8 @@ public: Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(QRhiVulkan::DeferredReleaseEntry, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiVulkan::ActiveReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiVulkan::TextureReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiVulkan::BufferReadback, Q_MOVABLE_TYPE); QT_END_NAMESPACE diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index ac64cf5265d..409152db0d2 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -71,6 +71,8 @@ private slots: void create(); void nativeHandles_data(); void nativeHandles(); + void resourceUpdateBatchBuffer_data(); + void resourceUpdateBatchBuffer(); private: struct { @@ -103,9 +105,26 @@ void tst_QRhi::initTestCase() #endif #ifdef TST_VK +#ifndef Q_OS_ANDROID + vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_LUNARG_standard_validation") }); +#else + vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_GOOGLE_threading"), + QByteArrayLiteral("VK_LAYER_LUNARG_parameter_validation"), + QByteArrayLiteral("VK_LAYER_LUNARG_object_tracker"), + QByteArrayLiteral("VK_LAYER_LUNARG_core_validation"), + QByteArrayLiteral("VK_LAYER_LUNARG_image"), + QByteArrayLiteral("VK_LAYER_LUNARG_swapchain"), + QByteArrayLiteral("VK_LAYER_GOOGLE_unique_objects") }); +#endif + vulkanInstance.setExtensions(QByteArrayList() + << "VK_KHR_get_physical_device_properties2"); vulkanInstance.create(); initParams.vk.inst = &vulkanInstance; #endif + +#ifdef TST_D3D11 + initParams.d3d.enableDebugLayer = true; +#endif } void tst_QRhi::cleanupTestCase() @@ -248,7 +267,9 @@ void tst_QRhi::create() QRhi::WideLines, QRhi::VertexShaderPointSize, QRhi::BaseVertex, - QRhi::BaseInstance + QRhi::BaseInstance, + QRhi::TriangleFanTopology, + QRhi::ReadBackNonUniformBuffer }; for (size_t i = 0; i isFeatureSupported(features[i]); @@ -484,5 +505,91 @@ void tst_QRhi::nativeHandles() } } +void tst_QRhi::resourceUpdateBatchBuffer_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchBuffer() +{ + 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 resource updates"); + + const int bufferSize = 23; + const QByteArray a(bufferSize, 'A'); + const QByteArray b(bufferSize, 'B'); + + // dynamic buffer, updates, readback + { + QScopedPointer dynamicBuffer(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, bufferSize)); + QVERIFY(dynamicBuffer->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + batch->updateDynamicBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); + batch->updateDynamicBuffer(dynamicBuffer.data(), 0, 12, b.constData()); + + QRhiBufferReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); + + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + QVERIFY(result == QRhi::FrameOpSuccess); + QVERIFY(cb); + cb->resourceUpdate(batch); + rhi->endOffscreenFrame(); + + // Offscreen frames are synchronous, so the readback must have + // completed at this point. With swapchain frames this would not be the + // case. + QVERIFY(readCompleted); + QVERIFY(readResult.data.size() == 10); + QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB")); + QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA")); + } + + // static buffer, updates, readback + { + QScopedPointer dynamicBuffer(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::VertexBuffer, bufferSize)); + QVERIFY(dynamicBuffer->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + batch->uploadStaticBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); + batch->uploadStaticBuffer(dynamicBuffer.data(), 0, 12, b.constData()); + + QRhiBufferReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + + if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) + batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); + + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + QVERIFY(result == QRhi::FrameOpSuccess); + QVERIFY(cb); + cb->resourceUpdate(batch); + rhi->endOffscreenFrame(); + + if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) { + QVERIFY(readCompleted); + QVERIFY(readResult.data.size() == 10); + QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB")); + QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA")); + } else { + qDebug("Skipping verifying buffer contents because readback is not supported"); + } + } +} + #include QTEST_MAIN(tst_QRhi) From e22399af826b582e330a2772460d3bc0d98eb558 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Thu, 3 Oct 2019 17:33:23 +0200 Subject: [PATCH 09/23] Make QRhi::create() return false when there is no MTLDevice The interesting part here is that sending messages to a null object is valid in Objective-C, so without an explicit check it is not necessarily straightforward to discover that we do not have working rendering. (because the application won't just simply crash) Do the right thing now and return false like other backends do. Task-number: QTBUG-78994 Change-Id: I0d3c4a49a3fc78f9149f8af4fe67d581e74daae7 Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhimetal.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index e55513277b9..9c579ebfe1e 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -367,6 +367,11 @@ bool QRhiMetal::create(QRhi::Flags flags) else d->dev = MTLCreateSystemDefaultDevice(); + if (!d->dev) { + qWarning("No MTLDevice"); + return false; + } + qCDebug(QRHI_LOG_INFO, "Metal device: %s", qPrintable(QString::fromNSString([d->dev name]))); if (importedCmdQueue) From 9c466946d0c5b4e319f1c953caeea63f0d453138 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 4 Oct 2019 15:46:49 +0200 Subject: [PATCH 10/23] rhi: Autotest basic texture operations ...and make the Null backend able to deal with these, for RGBA8 textures at least. Naturally it is all QImage and QPainter under the hood. Also fix a bug in the OpenGL backend, as discovered by the autotest: the size from the readback did not reflect the mip level. Task-number: QTBUG-78971 Change-Id: Ie424b268bf5feb09021099b67068f4418a9b583e Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhigles2.cpp | 5 +- src/gui/rhi/qrhinull.cpp | 116 +++++++- src/gui/rhi/qrhinull_p_p.h | 5 + tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 408 ++++++++++++++++++++++++++- 4 files changed, 509 insertions(+), 25 deletions(-) diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 13dc016fd6d..a6094183fb7 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -1532,8 +1532,9 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead); cmd.args.readPixels.texture = texD ? texD->texture : 0; if (texD) { - cmd.args.readPixels.w = texD->m_pixelSize.width(); - cmd.args.readPixels.h = texD->m_pixelSize.height(); + const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); + cmd.args.readPixels.w = readImageSize.width(); + cmd.args.readPixels.h = readImageSize.height(); cmd.args.readPixels.format = texD->m_format; const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index de6616b677c..d78b5e20c52 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -36,6 +36,7 @@ #include "qrhinull_p_p.h" #include +#include QT_BEGIN_NAMESPACE @@ -385,6 +386,67 @@ QRhi::FrameOpResult QRhiNull::finish() return QRhi::FrameOpSuccess; } +void QRhiNull::simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u) +{ + QNullTexture *texD = QRHI_RES(QNullTexture, u.dst); + for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { + for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) { + if (!subresDesc.image().isNull()) { + const QImage src = subresDesc.image(); + QPainter painter(&texD->image[layer][level]); + const QSize srcSize = subresDesc.sourceSize().isEmpty() + ? src.size() : subresDesc.sourceSize(); + painter.drawImage(subresDesc.destinationTopLeft(), src, + QRect(subresDesc.sourceTopLeft(), srcSize)); + } else if (!subresDesc.data().isEmpty()) { + const QSize subresSize = q->sizeForMipLevel(level, texD->pixelSize()); + int w = subresSize.width(); + int h = subresSize.height(); + if (!subresDesc.sourceSize().isEmpty()) { + w = subresDesc.sourceSize().width(); + h = subresDesc.sourceSize().height(); + } + // sourceTopLeft is not supported on this path as per QRhi docs + const char *src = subresDesc.data().constData(); + const int srcBpl = w * 4; + const QPoint dstOffset = subresDesc.destinationTopLeft(); + uchar *dst = texD->image[layer][level].bits(); + const int dstBpl = texD->image[layer][level].bytesPerLine(); + for (int y = 0; y < h; ++y) { + memcpy(dst + dstOffset.x() * 4 + (y + dstOffset.y()) * dstBpl, + src + y * srcBpl, + size_t(srcBpl)); + } + } + } + } + } +} + +void QRhiNull::simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u) +{ + QNullTexture *srcD = QRHI_RES(QNullTexture, u.src); + QNullTexture *dstD = QRHI_RES(QNullTexture, u.dst); + const QImage &srcImage(srcD->image[u.desc.sourceLayer()][u.desc.sourceLevel()]); + QImage &dstImage(dstD->image[u.desc.destinationLayer()][u.desc.destinationLevel()]); + const QPoint dstPos = u.desc.destinationTopLeft(); + const QSize size = u.desc.pixelSize().isEmpty() ? srcD->pixelSize() : u.desc.pixelSize(); + const QPoint srcPos = u.desc.sourceTopLeft(); + + QPainter painter(&dstImage); + painter.drawImage(QRect(dstPos, size), srcImage, QRect(srcPos, size)); +} + +void QRhiNull::simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u) +{ + QNullTexture *texD = QRHI_RES(QNullTexture, u.dst); + const QSize baseSize = texD->pixelSize(); + const int levelCount = q->mipLevelsForSize(baseSize); + for (int level = 1; level < levelCount; ++level) + texD->image[0][level] = texD->image[0][0].scaled(q->sizeForMipLevel(level, baseSize)); +} + void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { Q_UNUSED(cb); @@ -405,22 +467,42 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re } } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { - if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { + if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { + if (u.dst->format() == QRhiTexture::RGBA8) + simulateTextureUpload(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { + if (u.src->format() == QRhiTexture::RGBA8 && u.dst->format() == QRhiTexture::RGBA8) + simulateTextureCopy(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { QRhiReadbackResult *result = u.result; - QRhiTexture *tex = u.rb.texture(); - if (tex) { - result->format = tex->format(); - result->pixelSize = q->sizeForMipLevel(u.rb.level(), tex->pixelSize()); + QNullTexture *texD = QRHI_RES(QNullTexture, u.rb.texture()); + if (texD) { + result->format = texD->format(); + result->pixelSize = q->sizeForMipLevel(u.rb.level(), texD->pixelSize()); } else { Q_ASSERT(currentSwapChain); result->format = QRhiTexture::RGBA8; result->pixelSize = currentSwapChain->currentPixelSize(); } + quint32 bytesPerLine = 0; quint32 byteSize = 0; - textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize); - result->data.fill(0, int(byteSize)); + textureFormatInfo(result->format, result->pixelSize, &bytesPerLine, &byteSize); + if (result->format == QRhiTexture::RGBA8) { + result->data.resize(int(byteSize)); + const QImage &src(texD->image[u.rb.layer()][u.rb.level()]); + char *dst = result->data.data(); + for (int y = 0, h = src.height(); y < h; ++y) { + memcpy(dst, src.constScanLine(y), bytesPerLine); + dst += bytesPerLine; + } + } else { + result->data.fill(0, int(byteSize)); + } if (result->completed) result->completed(); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + if (u.dst->format() == QRhiTexture::RGBA8) + simulateTextureGenMips(u); } } ud->free(); @@ -532,22 +614,36 @@ void QNullTexture::release() bool QNullTexture::build() { + QRHI_RES_RHI(QRhiNull); const bool isCube = m_flags.testFlag(CubeMap); const bool hasMipMaps = m_flags.testFlag(MipMapped); QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; - const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1; + const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1; + const int layerCount = isCube ? 6 : 1; + + if (m_format == RGBA8) { + for (int layer = 0; layer < layerCount; ++layer) { + for (int level = 0; level < mipLevelCount; ++level) { + image[layer][level] = QImage(rhiD->q->sizeForMipLevel(level, size), + QImage::Format_RGBA8888_Premultiplied); + image[layer][level].fill(Qt::yellow); + } + } + } + QRHI_PROF; - QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1)); + QRHI_PROF_F(newTexture(this, true, mipLevelCount, layerCount, 1)); return true; } bool QNullTexture::buildFrom(const QRhiNativeHandles *src) { Q_UNUSED(src); + QRHI_RES_RHI(QRhiNull); const bool isCube = m_flags.testFlag(CubeMap); const bool hasMipMaps = m_flags.testFlag(MipMapped); QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; - const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1; + const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1; QRHI_PROF; QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : 1, 1)); return true; diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h index 93e298481c0..ce517bfa634 100644 --- a/src/gui/rhi/qrhinull_p_p.h +++ b/src/gui/rhi/qrhinull_p_p.h @@ -84,6 +84,7 @@ struct QNullTexture : public QRhiTexture const QRhiNativeHandles *nativeHandles() override; QRhiNullTextureNativeHandles nativeHandlesStruct; + QImage image[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS]; }; struct QNullSampler : public QRhiSampler @@ -288,6 +289,10 @@ public: void releaseCachedResources() override; bool isDeviceLost() const override; + void simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + void simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + void simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + QRhiNullNativeHandles nativeHandlesStruct; QRhiSwapChain *currentSwapChain = nullptr; QNullCommandBuffer offscreenCommandBuffer; diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 409152db0d2..a17495f317e 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -30,6 +30,8 @@ #include #include #include +#include + #include #include @@ -73,6 +75,12 @@ private slots: void nativeHandles(); void resourceUpdateBatchBuffer_data(); void resourceUpdateBatchBuffer(); + void resourceUpdateBatchRGBATextureUpload_data(); + void resourceUpdateBatchRGBATextureUpload(); + void resourceUpdateBatchRGBATextureCopy_data(); + void resourceUpdateBatchRGBATextureCopy(); + void resourceUpdateBatchRGBATextureMip_data(); + void resourceUpdateBatchRGBATextureMip(); private: struct { @@ -505,6 +513,23 @@ void tst_QRhi::nativeHandles() } } +static bool submitResourceUpdates(QRhi *rhi, QRhiResourceUpdateBatch *batch) +{ + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + if (result != QRhi::FrameOpSuccess) { + qWarning("beginOffscreenFrame returned %d", result); + return false; + } + if (!cb) { + qWarning("No command buffer from beginOffscreenFrame"); + return false; + } + cb->resourceUpdate(batch); + rhi->endOffscreenFrame(); + return true; +} + void tst_QRhi::resourceUpdateBatchBuffer_data() { rhiTestData(); @@ -517,7 +542,7 @@ void tst_QRhi::resourceUpdateBatchBuffer() QScopedPointer rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); if (!rhi) - QSKIP("QRhi could not be created, skipping testing resource updates"); + QSKIP("QRhi could not be created, skipping testing buffer resource updates"); const int bufferSize = 23; const QByteArray a(bufferSize, 'A'); @@ -539,12 +564,7 @@ void tst_QRhi::resourceUpdateBatchBuffer() readResult.completed = [&readCompleted] { readCompleted = true; }; batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); - QRhiCommandBuffer *cb = nullptr; - QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); - QVERIFY(result == QRhi::FrameOpSuccess); - QVERIFY(cb); - cb->resourceUpdate(batch); - rhi->endOffscreenFrame(); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); // Offscreen frames are synchronous, so the readback must have // completed at this point. With swapchain frames this would not be the @@ -573,12 +593,7 @@ void tst_QRhi::resourceUpdateBatchBuffer() if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); - QRhiCommandBuffer *cb = nullptr; - QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); - QVERIFY(result == QRhi::FrameOpSuccess); - QVERIFY(cb); - cb->resourceUpdate(batch); - rhi->endOffscreenFrame(); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) { QVERIFY(readCompleted); @@ -591,5 +606,372 @@ void tst_QRhi::resourceUpdateBatchBuffer() } } +inline bool imageRGBAEquals(const QImage &a, const QImage &b) +{ + const int maxFuzz = 1; + + if (a.size() != b.size()) + return false; + + const QImage image0 = a.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + const QImage image1 = b.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + + const int width = image0.width(); + const int height = image0.height(); + for (int y = 0; y < height; ++y) { + const quint32 *p0 = reinterpret_cast(image0.constScanLine(y)); + const quint32 *p1 = reinterpret_cast(image1.constScanLine(y)); + int x = width - 1; + while (x-- >= 0) { + const QRgb c0(*p0++); + const QRgb c1(*p1++); + const int red = qAbs(qRed(c0) - qRed(c1)); + const int green = qAbs(qGreen(c0) - qGreen(c1)); + const int blue = qAbs(qBlue(c0) - qBlue(c1)); + const int alpha = qAbs(qAlpha(c0) - qAlpha(c1)); + if (red > maxFuzz || green > maxFuzz || blue > maxFuzz || alpha > maxFuzz) + return false; + } + } + + return true; +} + +void tst_QRhi::resourceUpdateBatchRGBATextureUpload_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureUpload() +{ + 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 texture resource updates"); + + QImage image(234, 123, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::red); + QPainter painter; + const QPoint greenRectPos(35, 50); + const QSize greenRectSize(100, 50); + painter.begin(&image); + painter.fillRect(QRect(greenRectPos, greenRectSize), Qt::green); + painter.end(); + + // simple image upload; uploading and reading back RGBA8 is supported by the Null backend even + { + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), image); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + // like with buffers, the readback is now complete due to endOffscreenFrame() + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(imageRGBAEquals(image, wrapperImage)); + } + + // the same with raw data + { + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QRhiTextureUploadEntry upload(0, 0, { image.constBits(), int(image.sizeInBytes()) }); + QRhiTextureUploadDescription uploadDesc(upload); + batch->uploadTexture(texture.data(), uploadDesc); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(imageRGBAEquals(image, wrapperImage)); + } + + // partial image upload at a non-zero destination position + { + const QSize copySize(30, 40); + const int gap = 10; + const QSize fullSize(copySize.width() + gap, copySize.height() + gap); + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize, + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QImage clearImage(fullSize, image.format()); + clearImage.fill(Qt::black); + batch->uploadTexture(texture.data(), clearImage); + + // copy green pixels of copySize to (gap, gap), leaving a black bar of + // gap pixels on the left and top + QRhiTextureSubresourceUploadDescription desc; + desc.setImage(image); + desc.setSourceSize(copySize); + desc.setDestinationTopLeft(QPoint(gap, gap)); + desc.setSourceTopLeft(greenRectPos); + + batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc })); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, clearImage.size()); + + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); + + QImage expectedImage = clearImage; + QPainter painter(&expectedImage); + painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } + + // the same (partial upload) with raw data as source + { + const QSize copySize(30, 40); + const int gap = 10; + const QSize fullSize(copySize.width() + gap, copySize.height() + gap); + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize, + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QImage clearImage(fullSize, image.format()); + clearImage.fill(Qt::black); + batch->uploadTexture(texture.data(), clearImage); + + // SourceTopLeft is not supported for non-QImage-based uploads. + const QImage im = image.copy(QRect(greenRectPos, copySize)); + QRhiTextureSubresourceUploadDescription desc; + desc.setData(QByteArray::fromRawData(reinterpret_cast(im.constBits()), + int(im.sizeInBytes()))); + desc.setSourceSize(copySize); + desc.setDestinationTopLeft(QPoint(gap, gap)); + + batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc })); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, clearImage.size()); + + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); + + QImage expectedImage = clearImage; + QPainter painter(&expectedImage); + painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } +} + +void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureCopy() +{ + 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 texture resource updates"); + + QImage red(256, 256, QImage::Format_RGBA8888_Premultiplied); + red.fill(Qt::red); + + QImage green(35, 73, red.format()); + green.fill(Qt::green); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QScopedPointer redTexture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(redTexture->build()); + batch->uploadTexture(redTexture.data(), red); + + QScopedPointer greenTexture(rhi->newTexture(QRhiTexture::RGBA8, green.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(greenTexture->build()); + batch->uploadTexture(greenTexture.data(), green); + + // 1. simple copy red -> texture; 2. subimage copy green -> texture; 3. partial subimage copy green -> texture + { + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + // 1. + batch->copyTexture(texture.data(), redTexture.data()); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + QVERIFY(imageRGBAEquals(red, wrapperImage)); + + batch = rhi->nextResourceUpdateBatch(); + readCompleted = false; + + // 2. + QRhiTextureCopyDescription copyDesc; + copyDesc.setDestinationTopLeft(QPoint(15, 23)); + batch->copyTexture(texture.data(), greenTexture.data(), copyDesc); + + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + wrapperImage = QImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + + QImage expectedImage = red; + QPainter painter(&expectedImage); + painter.drawImage(copyDesc.destinationTopLeft(), green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + + batch = rhi->nextResourceUpdateBatch(); + readCompleted = false; + + // 3. + copyDesc.setDestinationTopLeft(QPoint(125, 89)); + copyDesc.setSourceTopLeft(QPoint(5, 5)); + copyDesc.setPixelSize(QSize(26, 45)); + batch->copyTexture(texture.data(), greenTexture.data(), copyDesc); + + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + wrapperImage = QImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + + painter.begin(&expectedImage); + painter.drawImage(copyDesc.destinationTopLeft(), green, + QRect(copyDesc.sourceTopLeft(), copyDesc.pixelSize())); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } +} + +void tst_QRhi::resourceUpdateBatchRGBATextureMip_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureMip() +{ + 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 texture resource updates"); + + + QImage red(512, 512, QImage::Format_RGBA8888_Premultiplied); + red.fill(Qt::red); + + const QRhiTexture::Flags textureFlags = + QRhiTexture::UsedAsTransferSource + | QRhiTexture::MipMapped + | QRhiTexture::UsedWithGenerateMips; + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), 1, textureFlags)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), red); + batch->generateMips(texture.data()); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + const int levelCount = rhi->mipLevelsForSize(red.size()); + QCOMPARE(levelCount, 10); + for (int level = 0; level < levelCount; ++level) { + batch = rhi->nextResourceUpdateBatch(); + + QRhiReadbackDescription readDesc(texture.data()); + readDesc.setLevel(level); + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(readDesc, &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + + const QSize expectedSize = rhi->sizeForMipLevel(level, texture->pixelSize()); + QCOMPARE(readResult.pixelSize, expectedSize); + + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + + // Compare to a scaled version; we can do this safely only because we + // only have plain red pixels in the source image. + QImage expectedImage = red.scaled(expectedSize); + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } +} + #include QTEST_MAIN(tst_QRhi) From 32924110ce69498ab5b05d00728f77affd6817d9 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Sat, 5 Oct 2019 15:31:12 +0200 Subject: [PATCH 11/23] rhi: gl: Switch GetBufferSubData emulation to MapBufferRange Use only APIs that are in GLES 3.0. glMapBuffer() is an old OES extension, stop bothering with that. Not the least because ANGLE claims supporting it and then fails the map. (not that we care much about ANGLE, but, for instance, the qrhi autotest is run with ANGLE configurations as well in the CI, so have to still take care of it for the duration of Qt 5.x) Change-Id: I29140402cedffe0430f920ee0c061673257c3aa1 Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhi.cpp | 7 +++---- src/gui/rhi/qrhigles2.cpp | 21 ++++++++++++++------- src/gui/rhi/qrhigles2_p_p.h | 4 ++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 8ec8ae9208e..bea88902341 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -568,11 +568,10 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") supports QRhiGraphicsPipeline::TriangleFan. \value ReadBackNonUniformBuffer Indicates that - {QRhiResourceUpdateBatch::readBackBuffer()}{reading buffer contents} is + \l{QRhiResourceUpdateBatch::readBackBuffer()}{reading buffer contents} is supported for QRhiBuffer instances with a usage different than - UniformBuffer. While this is supported in the majority of cases, it can be - unsupported, for example, on OpenGL ES 2.0 implementations without the - MapBuffer extension. + UniformBuffer. While this is supported in the majority of cases, it will be + unsupported with OpenGL ES older than 3.0. */ /*! diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index a6094183fb7..fa99ee5e251 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -276,6 +276,10 @@ QT_BEGIN_NAMESPACE #define GL_POINT_SPRITE 0x8861 #endif +#ifndef GL_MAP_READ_BIT +#define GL_MAP_READ_BIT 0x0001 +#endif + Q_DECLARE_LOGGING_CATEGORY(lcOpenGLProgramDiskCache) /*! @@ -492,7 +496,9 @@ bool QRhiGles2::create(QRhi::Flags flags) else caps.textureCompareMode = true; - caps.mapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBuffer); + // proper as in ES 3.0 (glMapBufferRange), not the old glMapBuffer + // extension(s) (which is not in ES 3.0...messy) + caps.properMapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBufferRange); if (!caps.gles) { f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); @@ -737,7 +743,7 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const case QRhi::TriangleFanTopology: return true; case QRhi::ReadBackNonUniformBuffer: - return !caps.gles || caps.mapBuffer; + return !caps.gles || caps.properMapBuffer; default: Q_UNREACHABLE(); return false; @@ -2107,13 +2113,14 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result; f->glBindBuffer(cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer); if (caps.gles) { - if (caps.mapBuffer) { - void *p = f->glMapBuffer(cmd.args.getBufferSubData.target, GL_READ_ONLY); + if (caps.properMapBuffer) { + void *p = f->glMapBufferRange(cmd.args.getBufferSubData.target, + cmd.args.getBufferSubData.offset, + cmd.args.getBufferSubData.size, + GL_MAP_READ_BIT); if (p) { result->data.resize(cmd.args.getBufferSubData.size); - memcpy(result->data.data(), - reinterpret_cast(p) + cmd.args.getBufferSubData.offset, - size_t(cmd.args.getBufferSubData.size)); + memcpy(result->data.data(), p, size_t(cmd.args.getBufferSubData.size)); f->glUnmapBuffer(cmd.args.getBufferSubData.target); } } diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index a4a9c15ce7c..29cccb0c9c4 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -753,7 +753,7 @@ public: baseVertex(false), compute(false), textureCompareMode(false), - mapBuffer(false) + properMapBuffer(false) { } int ctxMajor; int ctxMinor; @@ -784,7 +784,7 @@ public: uint baseVertex : 1; uint compute : 1; uint textureCompareMode : 1; - uint mapBuffer : 1; + uint properMapBuffer : 1; } caps; QGles2SwapChain *currentSwapChain = nullptr; QVector supportedCompressedFormats; From 9baf69c765c446b7c8f069ebdd50910877a7d0f8 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Sat, 5 Oct 2019 16:05:06 +0200 Subject: [PATCH 12/23] rhi: gl: Add a feature flag for reading back non-zero mip levels The joys of "level - Specifies the mipmap level of the texture image to be attached, which must be 0." for glFramebufferTexture2D in OpenGL ES 2.0. Change-Id: Iaf19502f48d7ba73b26abb72535bfa6696a1e182 Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhi.cpp | 8 ++++++- src/gui/rhi/qrhi_p.h | 3 ++- src/gui/rhi/qrhid3d11.cpp | 2 ++ src/gui/rhi/qrhigles2.cpp | 31 +++++++++++++++++++++------- src/gui/rhi/qrhigles2_p_p.h | 4 +++- src/gui/rhi/qrhimetal.mm | 2 ++ src/gui/rhi/qrhivulkan.cpp | 2 ++ tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 17 ++++++++++----- 8 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index bea88902341..57c466678ed 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -572,7 +572,13 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") supported for QRhiBuffer instances with a usage different than UniformBuffer. While this is supported in the majority of cases, it will be unsupported with OpenGL ES older than 3.0. -*/ + + \value ReadBackNonBaseMipLevel Indicates that specifying a mip level other + than 0 is supported when reading back texture contents. When not supported, + specifying a non-zero level in QRhiReadbackDescription leads to returning + an all-zero image. In practice this feature will be unsupported with OpenGL + ES 2.0, while it will likely be supported everywhere else. + */ /*! \enum QRhi::BeginFrameFlag diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index 993fc44b582..bfcc1d39f5f 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -1436,7 +1436,8 @@ public: BaseVertex, BaseInstance, TriangleFanTopology, - ReadBackNonUniformBuffer + ReadBackNonUniformBuffer, + ReadBackNonBaseMipLevel }; enum BeginFrameFlag { diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 7c39fe0b118..096a8c2756b 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -473,6 +473,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const return false; case QRhi::ReadBackNonUniformBuffer: return true; + case QRhi::ReadBackNonBaseMipLevel: + return true; default: Q_UNREACHABLE(); return false; diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index fa99ee5e251..ca1c91499dc 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -500,6 +500,11 @@ bool QRhiGles2::create(QRhi::Flags flags) // extension(s) (which is not in ES 3.0...messy) caps.properMapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBufferRange); + if (caps.gles) + caps.nonBaseLevelFramebufferTexture = caps.ctxMajor >= 3; // ES 3.0 + else + caps.nonBaseLevelFramebufferTexture = true; + if (!caps.gles) { f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); f->glEnable(GL_POINT_SPRITE); @@ -744,6 +749,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::ReadBackNonUniformBuffer: return !caps.gles || caps.properMapBuffer; + case QRhi::ReadBackNonBaseMipLevel: + return caps.nonBaseLevelFramebufferTexture; default: Q_UNREACHABLE(); return false; @@ -2156,23 +2163,31 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) QRhiReadbackResult *result = cmd.args.readPixels.result; GLuint tex = cmd.args.readPixels.texture; GLuint fbo = 0; + int mipLevel = 0; if (tex) { result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h); result->format = cmd.args.readPixels.format; - f->glGenFramebuffers(1, &fbo); - f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); - f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, cmd.args.readPixels.level); + mipLevel = cmd.args.readPixels.level; + if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { + f->glGenFramebuffers(1, &fbo); + f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); + f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, mipLevel); + } } else { result->pixelSize = currentSwapChain->pixelSize; result->format = QRhiTexture::RGBA8; // readPixels handles multisample resolving implicitly } result->data.resize(result->pixelSize.width() * result->pixelSize.height() * 4); - // With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it. - f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(), - GL_RGBA, GL_UNSIGNED_BYTE, - result->data.data()); + if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { + // With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it. + f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(), + GL_RGBA, GL_UNSIGNED_BYTE, + result->data.data()); + } else { + result->data.fill('\0'); + } if (fbo) { f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(1, &fbo); diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index 29cccb0c9c4..8814d9c19da 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -753,7 +753,8 @@ public: baseVertex(false), compute(false), textureCompareMode(false), - properMapBuffer(false) + properMapBuffer(false), + nonBaseLevelFramebufferTexture(false) { } int ctxMajor; int ctxMinor; @@ -785,6 +786,7 @@ public: uint compute : 1; uint textureCompareMode : 1; uint properMapBuffer : 1; + uint nonBaseLevelFramebufferTexture : 1; } caps; QGles2SwapChain *currentSwapChain = nullptr; QVector supportedCompressedFormats; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 9c579ebfe1e..694882ab93d 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -559,6 +559,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return false; case QRhi::ReadBackNonUniformBuffer: return true; + case QRhi::ReadBackNonBaseMipLevel: + return true; default: Q_UNREACHABLE(); return false; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 154908b333c..0d364dd9fc6 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -3823,6 +3823,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::ReadBackNonUniformBuffer: return true; + case QRhi::ReadBackNonBaseMipLevel: + return true; default: Q_UNREACHABLE(); return false; diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index a17495f317e..aa20b0fd4ac 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -277,7 +277,8 @@ void tst_QRhi::create() QRhi::BaseVertex, QRhi::BaseInstance, QRhi::TriangleFanTopology, - QRhi::ReadBackNonUniformBuffer + QRhi::ReadBackNonUniformBuffer, + QRhi::ReadBackNonBaseMipLevel }; for (size_t i = 0; i isFeatureSupported(features[i]); @@ -965,10 +966,16 @@ void tst_QRhi::resourceUpdateBatchRGBATextureMip() QImage wrapperImage(reinterpret_cast(readResult.data.constData()), readResult.pixelSize.width(), readResult.pixelSize.height(), red.format()); - - // Compare to a scaled version; we can do this safely only because we - // only have plain red pixels in the source image. - QImage expectedImage = red.scaled(expectedSize); + QImage expectedImage; + if (level == 0 || rhi->isFeatureSupported(QRhi::ReadBackNonBaseMipLevel)) { + // Compare to a scaled version; we can do this safely only because we + // only have plain red pixels in the source image. + expectedImage = red.scaled(expectedSize); + } else { + qDebug("Expecting all-zero image for level %d because reading back a level other than 0 is not supported", level); + expectedImage = QImage(readResult.pixelSize, red.format()); + expectedImage.fill(0); + } QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); } } From dd105fab8d8b4bd39654e7e268e6782e53cce2de Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Sun, 6 Oct 2019 17:25:06 +0200 Subject: [PATCH 13/23] rhi: Autotest rendering a triangle Also improve (docs and runtime checks) and test the minimum set of required data to create a graphics pipeline. Task-number: QTBUG-78971 Change-Id: If5c14f1ab1ff3cf70f168fde585f05fc9d28ec91 Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhi.cpp | 48 +++- src/gui/rhi/qrhi_p.h | 1 - src/gui/rhi/qrhi_p_p.h | 2 + src/gui/rhi/qrhid3d11.cpp | 2 + src/gui/rhi/qrhigles2.cpp | 3 + src/gui/rhi/qrhimetal.mm | 2 + src/gui/rhi/qrhinull.cpp | 4 + src/gui/rhi/qrhivulkan.cpp | 3 + tests/auto/gui/rhi/qrhi/data/simple.frag | 8 + tests/auto/gui/rhi/qrhi/data/simple.frag.qsb | Bin 0 -> 773 bytes tests/auto/gui/rhi/qrhi/data/simple.vert | 10 + tests/auto/gui/rhi/qrhi/data/simple.vert.qsb | Bin 0 -> 851 bytes tests/auto/gui/rhi/qrhi/data/texture.frag | 12 - tests/auto/gui/rhi/qrhi/data/texture.vert | 18 -- tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 221 +++++++++++++++++++ 15 files changed, 300 insertions(+), 34 deletions(-) create mode 100644 tests/auto/gui/rhi/qrhi/data/simple.frag create mode 100644 tests/auto/gui/rhi/qrhi/data/simple.frag.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/simple.vert create mode 100644 tests/auto/gui/rhi/qrhi/data/simple.vert.qsb delete mode 100644 tests/auto/gui/rhi/qrhi/data/texture.frag delete mode 100644 tests/auto/gui/rhi/qrhi/data/texture.vert diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 57c466678ed..dabad356887 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -3084,9 +3084,13 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) \inmodule QtGui \brief Graphics pipeline state resource. + \note Setting the shader stages is mandatory. There must be at least one + stage, and there must be a vertex stage. + \note Setting the shader resource bindings is mandatory. The referenced QRhiShaderResourceBindings must already be built by the time build() is - called. + called. Associating with a QRhiShaderResourceBindings that has no bindings + is also valid, as long as no shader in any stage expects any resources. \note Setting the render pass descriptor is mandatory. To obtain a QRhiRenderPassDescriptor that can be passed to setRenderPassDescriptor(), @@ -3095,8 +3099,6 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) \note Setting the vertex input layout is mandatory. - \note Setting the shader stages is mandatory. - \note sampleCount() defaults to 1 and must match the sample count of the render target's color and depth stencil attachments. @@ -3912,6 +3914,46 @@ quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format, return approxSize; } +bool QRhiImplementation::sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps) +{ + if (ps->cbeginShaderStages() == ps->cendShaderStages()) { + qWarning("Cannot build a graphics pipeline without any stages"); + return false; + } + + bool hasVertexStage = false; + for (auto it = ps->cbeginShaderStages(), itEnd = ps->cendShaderStages(); it != itEnd; ++it) { + if (!it->shader().isValid()) { + qWarning("Empty shader passed to graphics pipeline"); + return false; + } + if (it->type() == QRhiShaderStage::Vertex) { + hasVertexStage = true; + const QRhiVertexInputLayout inputLayout = ps->vertexInputLayout(); + if (inputLayout.cbeginAttributes() == inputLayout.cendAttributes()) { + qWarning("Vertex stage present without any vertex inputs"); + return false; + } + } + } + if (!hasVertexStage) { + qWarning("Cannot build a graphics pipeline without a vertex stage"); + return false; + } + + if (!ps->renderPassDescriptor()) { + qWarning("Cannot build a graphics pipeline without a QRhiRenderPassDescriptor"); + return false; + } + + if (!ps->shaderResourceBindings()) { + qWarning("Cannot build a graphics pipeline without QRhiShaderResourceBindings"); + return false; + } + + return true; +} + /*! \internal */ diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index bfcc1d39f5f..907924c788a 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -72,7 +72,6 @@ class QRhiCommandBuffer; class QRhiResourceUpdateBatch; class QRhiResourceUpdateBatchPrivate; class QRhiProfiler; -class QRhiShaderResourceBindingPrivate; class Q_GUI_EXPORT QRhiDepthStencilClearValue { diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h index cc142935809..baffe28202f 100644 --- a/src/gui/rhi/qrhi_p_p.h +++ b/src/gui/rhi/qrhi_p_p.h @@ -205,6 +205,8 @@ public: cleanupCallbacks.append(callback); } + bool sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps); + QRhi *q; static const int MAX_SHADER_CACHE_ENTRIES = 128; diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 096a8c2756b..57ebb2909f4 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -3491,6 +3491,8 @@ bool QD3D11GraphicsPipeline::build() release(); QRHI_RES_RHI(QRhiD3D11); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; D3D11_RASTERIZER_DESC rastDesc; memset(&rastDesc, 0, sizeof(rastDesc)); diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index ca1c91499dc..4163ab1e79d 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -3690,6 +3690,9 @@ bool QGles2GraphicsPipeline::build() if (!rhiD->ensureContext()) return false; + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; + drawMode = toGlTopology(m_topology); program = rhiD->f->glCreateProgram(); diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 694882ab93d..ef5ab696d78 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -3139,6 +3139,8 @@ bool QMetalGraphicsPipeline::build() release(); QRHI_RES_RHI(QRhiMetal); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; // same binding space for vertex and constant buffers - work it around const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index d78b5e20c52..2eb55e705b8 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -805,6 +805,10 @@ void QNullGraphicsPipeline::release() bool QNullGraphicsPipeline::build() { + QRHI_RES_RHI(QRhiNull); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; + return true; } diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 0d364dd9fc6..fca2125f100 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -5886,6 +5886,9 @@ bool QVkGraphicsPipeline::build() release(); QRHI_RES_RHI(QRhiVulkan); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; + if (!rhiD->ensurePipelineCache()) return false; diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag b/tests/auto/gui/rhi/qrhi/data/simple.frag new file mode 100644 index 00000000000..2aa500e09a9 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.frag @@ -0,0 +1,8 @@ +#version 440 + +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..bf0c2692af4802768958aab97f7ae106d47c34d9 GIT binary patch literal 773 zcmV+g1N!^`00+@{oZVF2Y7;>eo;2IWOzJ=AML|a6MS~@7ODe^*6|Go=fTa;{YFVed zlg-x6Zn`^LYD+;1zJL^;#YgbcOCLo9!Ek7FYE>()4H!^ zkPn4aZ>4FbbUG)?TzFHQsFo0Z2`XRI)~nDRa!`Q~+}B&}Ry~$Zy|IYU$t6-uqd_i% zkDMlO97Pp7lhK zi%s8VYdS|}M_FM<5QTQo>RI9yU2Ar}Zq6}t@Bp=z`bR=0QGj7km?DvrDz(1Nh1^Ik z#S-S<9%(-V+TM7+`N~?0!Z@~GsDIUYh##`{E9)hXTp`UA@qXwz%E7|=1DxZrbQF%k z6D>8>|7ZtnB%Lmh;vW+LFkNA#Uxm~uZy9Dc;~+=oaDro+8#SD}PdQHSRQE*`!$*b9 zFPbo49Y=K7ddqse`NWF5+=02Wd>*(7W^62&FkWwIRUAkWsHi`<0?(1yOT0)xP~do- z-%E%-kGK>nfrzYdXII!V9#>(!=V$?n^N63kp#f31d+x=e`}h$}M>5Zzqjy|~8GEKl z`;o9crFA{G+fT=(Ib!w#Ikq9!x1K>yPr2k<=JsE9b&$K$Ygtzd|H+;HE4F?C+KSGn DK&5-n literal 0 HcmV?d00001 diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert b/tests/auto/gui/rhi/qrhi/data/simple.vert new file mode 100644 index 00000000000..16ee61beca3 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.vert @@ -0,0 +1,10 @@ +#version 440 + +layout(location = 0) in vec4 position; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + gl_Position = position; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb new file mode 100644 index 0000000000000000000000000000000000000000..1e010ebac3cdc0c7b9d87a53fe9784c87e121159 GIT binary patch literal 851 zcmV-Z1FZZ200|9voZVI3P7^^Cp3;`W0P^Es42fA&FG@^mCB+yDB#5SxkU*#s6Kpoy z?z9YUcgyZ9K?yfRU%(ij#Ro9)#!DZD8xzl&nPs;>L@&H@$?0~^Ip25Ad}oFdLQ+IM zKaU!R0WN-kWx>9I-AEHMO1Rq%Mc}}C1ndCp4Ok9r2kaB%j1k9cT7r8nxqRkF3CR%G z>M)FVy@0Fu{5bl>yDr1`F>96ynS}iNU@KrBp^49Pgp5m0r^eqiQX<%g%DL(}2i!R{ zc%D{#AC!AkhwV5sb&(#)=Wy#e=xk1VXJL1^{|SKLySi1|Dh9$XmY1+Rxkl0o4K^)z zkNp$UvAEndl{o~m(o(0dDbz93lamGRF?hEfbBFnR%oprLHL3hW$0D(^z(ut`3xCKT zD@n);@I|-@{jLILOwXzc)e&=7$F74H{r6;-D(4n_Ly-)y+mN5rqGcpbaphz@12Z8K zhrWtlQ|-G6>j}++8a>eKNnIQ9Q#zK@_$jSfO2=n37semM-bQpEMEftmgJ*M9_c^Wc z>)@S{GgSRx%{4&G>YQIt;fXrx89j?kqBZPbJ4UYHQ1?3&7Q8t`g$nHr#U&y&YCD^|d*+7k1%bJV z|B9}VW!ZT7{Po86_U^M%kD5vRb|?l2lDk|sYI}*+q^=&f4HVbJ%@rcudbNqW1|bFu zN|g%(8!@7B!1su^8?v-+gKZ2bK-D#dIvU9U^0Su>_aqEzPqN!CZ;@Ln@5MXGbCT z8Gu}yxB5%4uH{>TNdo4ZUgMB8MKG+w@XWyi6s`>ZJ|)kjJPm()5c<@zN&IvBDpsph dqV)1Y|I>meiSgWt3oiaA rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing empty shader"); + + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(256, 256), 1, QRhiTexture::RenderTarget)); + QVERIFY(texture->build()); + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + + // no stages + QScopedPointer pipeline(rhi->newGraphicsPipeline()); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + QShader vs; + QShader fs; + + // no shaders in the stages + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + + // no vertex stage + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + // no vertex inputs + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setRenderPassDescriptor(rpDesc.data()); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(!pipeline->build()); + + // no renderpass descriptor + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(!pipeline->build()); + + // no shader resource bindings + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + // correct + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setRenderPassDescriptor(rpDesc.data()); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(pipeline->build()); +} + +void tst_QRhi::renderToTextureSimple_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureSimple() +{ + 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"); + + const QSize outputSize(1920, 1080); + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float vertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + 0.0f, 1.0f + }; + QScopedPointer vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); // non-owning, no copy needed because readResult outlives result + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + // Offscreen frames are synchronous, so the readback is guaranteed to + // complete at this point. This would not be the case with swapchain-based + // frames. + QCOMPARE(result.size(), texture->pixelSize()); + + if (impl == QRhi::Null) + return; + + // Now we have a red rectangle on blue background. + const int y = 100; + const quint32 *p = reinterpret_cast(result.constScanLine(y)); + int x = result.width() - 1; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + while (x-- >= 0) { + const QRgb c(*p++); + if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0) + ++redCount; + else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz)) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + + QCOMPARE(redCount + blueCount, texture->pixelSize().width()); + + // The triangle is "pointing up" in the resulting image with OpenGL + // (because Y is up both in normalized device coordinates and in images) + // and Vulkan (because Y is down in both and the vertex data was specified + // with Y up in mind), but "pointing down" with D3D (because Y is up in NDC + // but down in images). + if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) + QVERIFY(redCount < blueCount); + else + QVERIFY(redCount > blueCount); +} + #include QTEST_MAIN(tst_QRhi) From 59604405c98cc8d55e924573859dd64d94d5d877 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 7 Oct 2019 10:12:48 +0200 Subject: [PATCH 14/23] rhi: Fix non-base level copy and readback wrt source size When the source size is not explicitly specified, we take the entire subresource. However, just using the texture's size is wrong: when the source level in a copy or readback is not 0, the size for the corresponding mip level has to be used instead. This fixes occasional crashes with Metal in the autotest. Change-Id: I99f689feef93ec86dffdc9e82d6bfdaf5c1eb041 Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhid3d11.cpp | 9 +++++---- src/gui/rhi/qrhigles2.cpp | 7 ++++--- src/gui/rhi/qrhimetal.mm | 10 +++++----- src/gui/rhi/qrhivulkan.cpp | 10 +++++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 57ebb2909f4..717f3e6d6c2 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -1416,15 +1416,16 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate UINT srcSubRes = D3D11CalcSubresource(UINT(u.desc.sourceLevel()), UINT(u.desc.sourceLayer()), srcD->mipLevelCount); UINT dstSubRes = D3D11CalcSubresource(UINT(u.desc.destinationLevel()), UINT(u.desc.destinationLayer()), dstD->mipLevelCount); const QPoint dp = u.desc.destinationTopLeft(); - const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); const QPoint sp = u.desc.sourceTopLeft(); D3D11_BOX srcBox; srcBox.left = UINT(sp.x()); srcBox.top = UINT(sp.y()); srcBox.front = 0; // back, right, bottom are exclusive - srcBox.right = srcBox.left + UINT(size.width()); - srcBox.bottom = srcBox.top + UINT(size.height()); + srcBox.right = srcBox.left + UINT(copySize.width()); + srcBox.bottom = srcBox.top + UINT(copySize.height()); srcBox.back = 1; QD3D11CommandBuffer::Command cmd; cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes; @@ -1457,7 +1458,7 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } src = texD->tex; dxgiFormat = texD->dxgiFormat; - pixelSize = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize) : texD->m_pixelSize; + pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); format = texD->m_format; subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(u.rb.layer()), texD->mipLevelCount); } else { diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 4163ab1e79d..e355979626b 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -1506,7 +1506,8 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate trackedImageBarrier(cbD, srcD, QGles2Texture::AccessRead); trackedImageBarrier(cbD, dstD, QGles2Texture::AccessUpdate); - const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); // do not translate coordinates, even if sp is bottom-left from gl's pov const QPoint sp = u.desc.sourceTopLeft(); const QPoint dp = u.desc.destinationTopLeft(); @@ -1532,8 +1533,8 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate cmd.args.copyTex.dstX = dp.x(); cmd.args.copyTex.dstY = dp.y(); - cmd.args.copyTex.w = size.width(); - cmd.args.copyTex.h = size.height(); + cmd.args.copyTex.w = copySize.width(); + cmd.args.copyTex.h = copySize.height(); cbD->commands.append(cmd); } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index ef5ab696d78..5f14d917b8e 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -1628,7 +1628,8 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src); QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst); const QPoint dp = u.desc.destinationTopLeft(); - const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); const QPoint sp = u.desc.sourceTopLeft(); ensureBlit(); @@ -1636,7 +1637,7 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate sourceSlice: NSUInteger(u.desc.sourceLayer()) sourceLevel: NSUInteger(u.desc.sourceLevel()) sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), 0) - sourceSize: MTLSizeMake(NSUInteger(size.width()), NSUInteger(size.height()), 1) + sourceSize: MTLSizeMake(NSUInteger(copySize.width()), NSUInteger(copySize.height()), 1) toTexture: dstD->d->tex destinationSlice: NSUInteger(u.desc.destinationLayer()) destinationLevel: NSUInteger(u.desc.destinationLevel()) @@ -1658,11 +1659,10 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate qWarning("Multisample texture cannot be read back"); continue; } - readback.pixelSize = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize) - : texD->m_pixelSize; + readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); readback.format = texD->m_format; src = texD->d->tex; - srcSize = texD->m_pixelSize; + srcSize = readback.pixelSize; texD->lastActiveFrameSlot = currentFrameSlot; } else { Q_ASSERT(currentSwapChain); diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index fca2125f100..103fea627a5 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -2913,9 +2913,10 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat region.dstOffset.x = u.desc.destinationTopLeft().x(); region.dstOffset.y = u.desc.destinationTopLeft().y(); - const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize(); - region.extent.width = uint32_t(size.width()); - region.extent.height = uint32_t(size.height()); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); + region.extent.width = uint32_t(copySize.width()); + region.extent.height = uint32_t(copySize.height()); region.extent.depth = 1; trackedImageBarrier(cbD, srcD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, @@ -2946,8 +2947,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat qWarning("Multisample texture cannot be read back"); continue; } - readback.pixelSize = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize) - : texD->m_pixelSize; + readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); readback.format = texD->m_format; texD->lastActiveFrameSlot = currentFrameSlot; } else { From 8f44da1f551a94d99b6772c8c459436e455ca8cd Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 7 Oct 2019 14:12:41 +0200 Subject: [PATCH 15/23] rhi: Enable the qrhi autotest on WinRT There is no onscreen support for WinRT in the D3D11 backend yet. However, offscreen operations (rendering into a texture) should work. One catch is that there is no D3DCompile available for deployed WinRT apps. So ship the intermediate format (DXBC output from fxc) in the .qsb files. Change-Id: Ic0aba4b817c27d13dcf3af41bf7612d799382655 Reviewed-by: Qt CI Bot Reviewed-by: Laszlo Agocs --- tests/auto/gui/rhi/qrhi/data/compile.bat | 44 +++++++++++++++++++ tests/auto/gui/rhi/qrhi/data/simple.frag.qsb | Bin 773 -> 908 bytes tests/auto/gui/rhi/qrhi/data/simple.vert.qsb | Bin 851 -> 967 bytes 3 files changed, 44 insertions(+) create mode 100644 tests/auto/gui/rhi/qrhi/data/compile.bat diff --git a/tests/auto/gui/rhi/qrhi/data/compile.bat b/tests/auto/gui/rhi/qrhi/data/compile.bat new file mode 100644 index 00000000000..2c97b02180f --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/compile.bat @@ -0,0 +1,44 @@ +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Copyright (C) 2019 The Qt Company Ltd. +:: Contact: https://www.qt.io/licensing/ +:: +:: This file is part of the QtQuick module of the Qt Toolkit. +:: +:: $QT_BEGIN_LICENSE:LGPL$ +:: Commercial License Usage +:: Licensees holding valid commercial Qt licenses may use this file in +:: accordance with the commercial license agreement provided with the +:: Software or, alternatively, in accordance with the terms contained in +:: a written agreement between you and The Qt Company. For licensing terms +:: and conditions see https://www.qt.io/terms-conditions. For further +:: information use the contact form at https://www.qt.io/contact-us. +:: +:: GNU Lesser General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU Lesser +:: General Public License version 3 as published by the Free Software +:: Foundation and appearing in the file LICENSE.LGPL3 included in the +:: packaging of this file. Please review the following information to +:: ensure the GNU Lesser General Public License version 3 requirements +:: will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +:: +:: GNU General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU +:: General Public License version 2.0 or (at your option) the GNU General +:: Public license version 3 or any later version approved by the KDE Free +:: Qt Foundation. The licenses are as published by the Free Software +:: Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +:: included in the packaging of this file. Please review the following +:: information to ensure the GNU General Public License requirements will +:: be met: https://www.gnu.org/licenses/gpl-2.0.html and +:: https://www.gnu.org/licenses/gpl-3.0.html. +:: +:: $QT_END_LICENSE$ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: Note the -c argument: we do not want runtime HLSL compilation since that is +:: not an option on UWP (WinRT). This means that running qsb must happen on Windows. + +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb index bf0c2692af4802768958aab97f7ae106d47c34d9..264b71ec0f52a346e22efda1ea21e0116560c7b5 100644 GIT binary patch literal 908 zcmV;719SWU00>=poZVGTYurW{e%HJHlJnuV&=vw^8t1U4X00{0DYauau^p$xO@h}H zgJV=#JG(1aD{Z7%lO_~Qpgr^$OwYZRTuM&;0Xg*8Q^*hSEjjfR0wsCg(X6$xorFM6 z9r0-9edqm}XGS9;$`MfkczknfJ7M7;pcnWX*vk_QQV@A=Q1Nd|vZ3n(&e7}JT?Dz? z1HgB0h^U{UwraIig1G>|6g7Yw#B)T~fqSqW(0P;}g;}gKeOz4VHYL|li{1J{6htwd z8Dzcsu_d|Rk=`WH2~*cB@N+JeSJE(jCR{a(ocliCGt$q???>j7M*9J(V-AMk!u|3} zWu=s;YH4zcmrkcBpLzqQVN)>VHCI=+1Aq0cjr6ukjXa& zFZMfOY=#VYJdJvLOdc}vo~?9Om*7x5kK^Tc2$bmrc`d= zfa1X4nzJ%};y0_~{3~Yo z<sA-%?>lYXPxVLd- z{lTN9r~dxdlV5)sXG$OD(w`FUdHNmr6W9le(625oUFy2ob(_^U?28w?A3R=NESI_e z)LfiJN`DreTj^Whi=!l}DN$S<6_;`Ci;Ct!EJV$gA7D_nOJ(||vV3*G_;7y>@|fB7 z0Ta8^@}@rxRK9c8+_)|k-M?SCytK}>TsL#u1C+ymEdQ?<*7nl~)fxAE&F|g$Eb8w3 i9MVeo;2IWOzJ=AML|a6MS~@7ODe^*6|Go=fTa;{YFVed zlg-x6Zn`^LYD+;1zJL^;#YgbcOCLo9!Ek7FYE>()4H!^ zkPn4aZ>4FbbUG)?TzFHQsFo0Z2`XRI)~nDRa!`Q~+}B&}Ry~$Zy|IYU$t6-uqd_i% zkDMlO97Pp7lhK zi%s8VYdS|}M_FM<5QTQo>RI9yU2Ar}Zq6}t@Bp=z`bR=0QGj7km?DvrDz(1Nh1^Ik z#S-S<9%(-V+TM7+`N~?0!Z@~GsDIUYh##`{E9)hXTp`UA@qXwz%E7|=1DxZrbQF%k z6D>8>|7ZtnB%Lmh;vW+LFkNA#Uxm~uZy9Dc;~+=oaDro+8#SD}PdQHSRQE*`!$*b9 zFPbo49Y=K7ddqse`NWF5+=02Wd>*(7W^62&FkWwIRUAkWsHi`<0?(1yOT0)xP~do- z-%E%-kGK>nfrzYdXII!V9#>(!=V$?n^N63kp#f31d+x=e`}h$}M>5Zzqjy|~8GEKl z`;o9crFA{G+fT=(Ib!w#Ikq9!x1K>yPr2k<=JsE9b&$K$Ygtzd|H+;HE4F?C+KSGn DK&5-n diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb index 1e010ebac3cdc0c7b9d87a53fe9784c87e121159..31941d18aa53fe090abd4cfbc9747e387c8a8257 100644 GIT binary patch literal 967 zcmV;&133Hu00}a9oZVGVPZU`YuVH2wY*a@49}|6MJPgsDogqk!0ucdW6I{p)Ye>*^ zn&~&g!=CP;`!(@TybwL=!5Gh8Jn;qm03JPh*-x++PM*jnidC;)(*vt+Ox(-9NzLn5 zRlln0s(RG`kb-D^SX*8V=_WafaBFI zDLfZ;*O_jB47gT{lf3N(B9fmuKz#Y2&1qlf^&xK>B!!bh)pnhEgO<|{iCqX{Q#q3m2%lIEGd1l97@h88Wx9o%`nzI zVY8Mc+~R=o$Y8jdjybR8Y_vo5v0(UNl7@AI!H%SDO)lP;`1AR>o!T#dzaM#XZ)@|}g}*4XTdAl+v2oxv zwJuT*^5&7MlT$bCeVsvF1=-I#R2F)!PF5unDi#77jjC%B2eYR-69)lCE}-wiU0m-uwCh7N!;_=H{xCu(`Q8_YF*{6N6N{ zy_+(q%}gy`2fA%3$m71+11S^LKj!mFzlk~g$nv{(hVgCV^Ubr-K8B*&!uY~Vh;DJb zI-g_wkE|cn|IUj0fL2EdQ`_mTLXJG;Fk}6idjumV-@SOjX`jX_l>%v(hAz{vfWtbQ zBL@&wH?aQMSYI%0O1d5Sx(~*>71psJg;q$d>G%YOUD74zl|Kar=vQ~ zK8+toZeGxSE`Gg*>M4c7)M5ZG&ieS7T+`1FmoQ<@&v>X#l?}+6wBjE?4 zW&cWlrIj(m2m>6PjJ`$zPE5`e3Wrr7{je_8xGI}oDD}i@IG!bk_6)A2rMQ-N@8am% zj}`7-Xgk5}j>=+rAjYn%Zc6+{PsP|469fz-4S9hM<4Kz66OFP*Xp8uGD(#)J!Yw6( pmQ;K$L*FIMc<^SGHC@nG9og+qK3A9jC!Z_K=TG@geF08Z#2d4M>52dV literal 851 zcmV-Z1FZZ200|9voZVI3P7^^Cp3;`W0P^Es42fA&FG@^mCB+yDB#5SxkU*#s6Kpoy z?z9YUcgyZ9K?yfRU%(ij#Ro9)#!DZD8xzl&nPs;>L@&H@$?0~^Ip25Ad}oFdLQ+IM zKaU!R0WN-kWx>9I-AEHMO1Rq%Mc}}C1ndCp4Ok9r2kaB%j1k9cT7r8nxqRkF3CR%G z>M)FVy@0Fu{5bl>yDr1`F>96ynS}iNU@KrBp^49Pgp5m0r^eqiQX<%g%DL(}2i!R{ zc%D{#AC!AkhwV5sb&(#)=Wy#e=xk1VXJL1^{|SKLySi1|Dh9$XmY1+Rxkl0o4K^)z zkNp$UvAEndl{o~m(o(0dDbz93lamGRF?hEfbBFnR%oprLHL3hW$0D(^z(ut`3xCKT zD@n);@I|-@{jLILOwXzc)e&=7$F74H{r6;-D(4n_Ly-)y+mN5rqGcpbaphz@12Z8K zhrWtlQ|-G6>j}++8a>eKNnIQ9Q#zK@_$jSfO2=n37semM-bQpEMEftmgJ*M9_c^Wc z>)@S{GgSRx%{4&G>YQIt;fXrx89j?kqBZPbJ4UYHQ1?3&7Q8t`g$nHr#U&y&YCD^|d*+7k1%bJV z|B9}VW!ZT7{Po86_U^M%kD5vRb|?l2lDk|sYI}*+q^=&f4HVbJ%@rcudbNqW1|bFu zN|g%(8!@7B!1su^8?v-+gKZ2bK-D#dIvU9U^0Su>_aqEzPqN!CZ;@Ln@5MXGbCT z8Gu}yxB5%4uH{>TNdo4ZUgMB8MKG+w@XWyi6s`>ZJ|)kjJPm()5c<@zN&IvBDpsph dqV)1Y|I>meiSgWt3oiaA Date: Mon, 7 Oct 2019 17:12:59 +0200 Subject: [PATCH 16/23] rhi: Autotest rendering a textured quad Task-number: QTBUG-78971 Change-Id: I0e7e0f3c00f9509031f7b4a8a389e51c915f01c2 Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhinull.cpp | 2 + tests/auto/gui/rhi/qrhi/data/compile.bat | 2 + tests/auto/gui/rhi/qrhi/data/qt256.png | Bin 0 -> 6208 bytes tests/auto/gui/rhi/qrhi/data/simple.frag.qsb | Bin 908 -> 899 bytes tests/auto/gui/rhi/qrhi/data/simple.vert.qsb | Bin 967 -> 961 bytes .../gui/rhi/qrhi/data/simpletextured.frag | 13 ++ .../gui/rhi/qrhi/data/simpletextured.frag.qsb | Bin 0 -> 1487 bytes .../gui/rhi/qrhi/data/simpletextured.vert | 14 ++ .../gui/rhi/qrhi/data/simpletextured.vert.qsb | Bin 0 -> 1195 bytes tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 159 ++++++++++++++++++ 10 files changed, 190 insertions(+) create mode 100644 tests/auto/gui/rhi/qrhi/data/qt256.png create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletextured.frag create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletextured.vert create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 2eb55e705b8..3027faa397b 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -397,6 +397,7 @@ void QRhiNull::simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::Textu QPainter painter(&texD->image[layer][level]); const QSize srcSize = subresDesc.sourceSize().isEmpty() ? src.size() : subresDesc.sourceSize(); + painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(subresDesc.destinationTopLeft(), src, QRect(subresDesc.sourceTopLeft(), srcSize)); } else if (!subresDesc.data().isEmpty()) { @@ -435,6 +436,7 @@ void QRhiNull::simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::Texture const QPoint srcPos = u.desc.sourceTopLeft(); QPainter painter(&dstImage); + painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(QRect(dstPos, size), srcImage, QRect(srcPos, size)); } diff --git a/tests/auto/gui/rhi/qrhi/data/compile.bat b/tests/auto/gui/rhi/qrhi/data/compile.bat index 2c97b02180f..3dafa33eab7 100644 --- a/tests/auto/gui/rhi/qrhi/data/compile.bat +++ b/tests/auto/gui/rhi/qrhi/data/compile.bat @@ -42,3 +42,5 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.frag diff --git a/tests/auto/gui/rhi/qrhi/data/qt256.png b/tests/auto/gui/rhi/qrhi/data/qt256.png new file mode 100644 index 0000000000000000000000000000000000000000..30c621c9c669457e8fb338a79d25a86501916de1 GIT binary patch literal 6208 zcmcIoc|4Tu*S`tb5sqpy7(Sg#a0NJG&vw5YDbd-%f-Y z0GzmjvarTm!;Ms2Fy2zm$2?L2-ab@m08oPl_&B?GBJiMF2sacOBDhxHEC@omLIkbA za2dFd7Q!8+e;bQ1zin*ca@*5I*;Np#4pIwHp#pd#@XnwBZ!a`XB>*D$2d)a0J|319 z1pUE+_k;-kODSvkb&wVYivWS86eL|_ugZZGm8E3C%5qBb5+FGlSp{hsd1+Z?N!hC^ z)LUK#^yei=oek@XR58=m{c|qr8${3@kM~iLmL?DgQiQ8g7_1vrR#{nEMowBzPLj$Y zi6f%%&H<8WoX}qg+6bHr7UhFSVbGvsMCV%=KRiT`s_DN>@b>u|7LEJUO;m?T2RQpk z%Sy={oAehW9R9zvdVBxPjl-KE{#)OF3yiZM`XHpu5IBq<)`hxoNTK7Yd{nfs2xmM7 zYk|Rd{pH2$?if4<=Z^6KX=xpM4J-u)U4}cmpwP#|Vt>fN;VK4b9Nro2f-umA2vU)x zP$*Xw9bI`HSzWL$SVl`hR#rzrNm*M-Q%PP%39O;5ET=2~m#j9%#m^go#{VVj`nRlt z%s34lOTqaI5QZ;vxWLjb^8W}vNM5iq`z#gt|_Oe8ml{0a^`m*Sl2 z&93pd24L`c9><{hmx&3pN>U2n)k#M?YoZ8*sz(l-@!nITos)>r4cDIF)f zYs8paT5!%zVDlpnCRta8rjR+QHP!y3`(N|sn<*1}jg(AW|BkURZ!9x#H&LnNk5s1x z06G9*1ORpb0096D0LavgrPhG|(1&T0-+px6k@)D-$I1KIhWSE{At&W%bL5>GKLJGmRyrrir6`ixaqWZnwxi z=`+C*V&w5)LkFuy3&Lu0Nu0;n&`qiQzWL1&>C6zTYlyHcfblR)=&HI_ftzuB@uCh^ zTQ~mxHGauXUjx~r&voStJ7tJ_ULNCu=N~C_r_A+dj%X?J(6QM9TXZ37XTPa+&d>M7 zE05`Q%Zn~-6+_><$9$QLpv?Bx`E4K%R2CN&ziDdZ(6Q(9vVG8t?(iK85pbK6PO`Dl zs|^X{;x`FEtl8R_ZiTLRP4>P$LFeYL_{8_EmX+c@;_r?Cb*i*3=I}-*)5xC7l zdm|VS?Ck$bqSmABZ4>MM0kM!2HsM&IZ8+Xt^MVYpwN1CF)sj9WqZ6U}cwN=m9~-k9 z);k!8s#6h0|H75Jy@%a9-m{w(Jv*>9{aj{J|3s$CH@&>__@2A>Te9qK37bk?@NeRK z1kqakNUYwo9q?>ZhH52joUYB3p0|NvmLV%}(ENO0t8`KbAHf_H7UFm*KQ{H`rK6ah zp3L^F(~Ka21W@&Zdeg((3(Aj`-GVg_z4o6~$Y9h7(7RRu?>>~DBBZQ7|8d>nr+(&s zK+jZX`c5G`4I?PC_t380{(Q}wrY7w7j&8)wRj(%i&D|Jr=A(%7N8%heMa>6A&oD89 z_}mkZK56yMbcGCaMW=phpS(QA)We_$(CAHVPRZ}?*>4?mnD7ZJ#x-R`Gk!e*G7=4J zEh-g(sC&bj?06QF{48p&1%C=uc?ua{Jsjb>yXcEb__p77t;csN{B+yx6AsgOv(JjI ze&3h1>1i1+h<@u!cBydt*-bz>HBW8F2TfG!R8@H;CRE84FHS6%Q;s&_8+7MV>_K_v zx=O*D26951jOl6q*3Enru#i-I_3~)FI4cbeUBtH<*II0wj_gr3g;zc42Kaf0sxf;p z9UDV0tHp%n66tf19p%R8Y+J+E*`7CrO4ze!UVR^WA?lesAbx&We-}1KqTp@%f+x6j4R)`SN1m6G*m7|D<=|w>}3-T1tBI zLopmK?vf@u89IQ!IdqjxQQRc#FwTK+&C}$OXup+t2*hsd$&NL((1s`+ZO&z+R5y4e zdwK3m49)L5ExJ@xzv?S%NVlOnh=vRVz1!!jA12iyM)PL7)k6AI*j`j(^KFPKBFgu7 z&alIPj3XCwy>#`1<$B&A-_`L`@3$B%dpgr~lBn>$M!$?+Rn+B8ZWzs8naZq>GcA+h zV^O+-D^X464K)B2(q8nu3{3i$w!59jHd*YI*vqe|C06{rkt?M?IGPizk$9hTOJE>! zxG8$Kah^L9eX~%~n6ea}0cLF36y-E{SiG3n+Z8x)kEgM$@pp%)qDLvVxft`^E5M{K z+&rG5_^Kif4;`_z|MAN3L?Xzl~47$0EZp%~F6ck`?z1 zZq=PaH+qYe|0s453HJr)rc=4eT+6)b>)wzB?9D6zcWk2ejH~9j(i3``fSgX0KkeDb zWKxRtg%FPPFb?wQKsYqRs-Jce+Kh_-+eK#`a zjYPy=Ewh6GFF9acms_WQGy^|}lZRQ`2gHO#)X$i-fq*r+D+oGC^1*hGJ5Ayxrk5X^ z&a=aME*o7yT8{8|i`*$noQ#;4{rv@rh9>Q*+}k;ZVGG9;77qqXAU~T^0tA@jnP1!x z?4J2G*kV5oiI|rYM#*r50SyLC&fK38U`)9a<#8MU?fMn^h!Ymc{NOvaEjO~j$i0)C zAaV;w+Az5`%D=dDU!BI?Q9jk#!L898dQV-WMA|Nrr~11u!D#6qHqEJmkar0^If;gn zOZ_4NH6vtDfsM`U2;x=)%-bp}3aFU5di!8%iA{HRQL>KMjT;KVb-XK@+VL4PR~v5~ zq3yqU5)dHBqH-=Wp zo{E`cwL!t>a(8i+{?!aig4Mr1W|<&0FPT!!)3XgKF;M1*WHt5D{w#Et<+Mez`r9(l z`E*!&(|jHr;;o7CA{txnaMREPB>c+$)skhdXL6E~Bp=$sPDJHUPGDcC{G?g0C-gMFXHRc|!GdCdQUPYxtB11p)#t4vq{V&5S+l z4cF@cj)FB>2lD$WK;j*cu|Nv~qz6$yccmoBYqdi&Lp@!$9B63Qg)PI(EzP4LB0wt| zuE$9O0!|M)rM(q(Kcf#;%PEkm0~lMv_ps7tRndddB>|pqO2TCaKygBl$*}nuH8bjSR6U%T={B z>MY$f+%|B$r|Ktfx||w}_C=!2jgz!6;6lNZ)&edWVKJzTSUaNs$`B=h>L5Tdyjj2SvA= z)Xz|>vh|XCVugnaMUIJ)Cg4%O9bt&}M{70heC4EK{lsF=PqC@M^45*isYo5KJB5p{IsTbHF!($WY%=O%KO6U(1}{)thZNm zBQSoy3Q=uD3I&3?>LO0}bY)mU-CT^9{n6WHrdG6la9N%|k10gaEY%fWA zcwEs1jQAL0WGft!9I`})bB$2;74O#>9=;)Nh@Qou%0HDoE4y7h)!%vL>qDgGVduwX zc(t|`pgtS5!G*kbmm;DpWk8bI2*e(J)4{sVeL3(~|6%-Md8O@M;lAb}cD++}C+=Q% zj(=j=FGW>bLV9w(c?}QGf&JWUpMN;uXyW=N0{27%YH-RT*!EpQZ*PvWL)=lyYrnNL z?;B0h^af$G-x`S6_UZ6d=)35IUuL!VO+VRq{0I zVKIFr0$(V?b;zcW|_@Uk8z3Wl^SuMmmJZr6Pv~CmVPC> zD5tv_?rKvuTTQLH+S^SgI`toARM%WxWJ6}xug+Z+2~IGA=SU1?akoDubzR3}euSOi z-j6Py<4bT82eNvJu5%U?+zUGI){objV1&wp8;if8uYu;cmeiDyNH_ty7pFt56gAI9 z{OU@XANQhedFC$m79Tz65)&6_4c=WW)h(^v5B@S%!h_I)_Uu8~>oPW}T_1#S8s!Pl zY@cnc^~+d+Nv!qyW?$G%U-_b)H?DTGVLqM)8b>*=zTtX6cLVTs<{!rWo_*$Ru~Am+^$Oa{PmK>7IUemBgG!F*pln`N=two z9Po;iwO{hnSMca)n=;v)RJ}B{`9bzsxr1j4=&mI|yDfA<((zLFm7LST>r>_p)+DRO zaooH7=SwM8&o(A}cO+$5X_Wv<>b`AunKefjZnXOJQN!|0A{s#MZE-g$Z0XiRhNy}- zexeMUt~qi-g&_mk?=uNnR(TVZ2Zh-yZfh~ddWSo!C)okoNXhXJ4FgP(hlYXRhYMa+ z-PIGIiwdNfn6JTk=qxq+U-HK$>HOwsUp0HL0=ahn4n!~bZK1hMsfuTi-^A%uh{)*_ z^$6gW_Wnol7_=UZ;CBfB#_dVfRf`V>i(eTFekDks+-$pOIzNm0w2Xe_TNfn}ac_C#UGyFVtyj*39KM)mITUJII<9dzU zAJ9=fka{vFS)gh{ymP&bL2aSSmxK?L={;GrN5|kS7^(iId~DZ+!2aR6`$i&B`4!?z z?v9*f-eqKWaJTA}Ns%s9J3@C{ibDmpd3q$qM059{MiddNxynb~8d)o=xgwEdR2lBaoK!8bL_)ynn93gqokE2dP zg>|T%qeUb4kqLD}Hm4R`P!ki=uHrxsWzxW`#3&wt-ec4)Sa;$o{Uk%|h5H=Vrue_d zkE|-7zN33j__XWFJ#+Di6wFFr)p-Htn;hn7$A11UCS%jr!t+2mUOungZ$~7r4 zn~$=XXnM!LpOK=j6W5vTe7i?wnz%QA)b&;iU<8dUjFYl-OIQpmn=_WwVrPVoju--8)tjwo6+C6R^@FON$WyD&a0;@Oq;`~x`%T!&hzHA0*oZOX&m0P z|3KtgPdDU2EzM)<2s?0da<9P4y>im{S~X}CZk&FeePx=unf>y4GG>zNZpuPmTjA-O z`m+p5+Z-_1dphn1snUwll<42rYCa`t$%I{`p^JPco?>7V7{0h2I;YDzl+EPKLK3-C z$IL)&5V4paA)QZtK$V|jslQ=Yo8irhiX{YU30-jO1m8X*$f?Y88+KT@>Ybj7o6ztR z$}SJEyjenotK?-_AQ?lc=nwZ|Qac80^tMz2A0nAZ6+z*T1bh0^Lm#ibc|c9u)&0Es zMf)28KWL8Xw@#g(jf$S#(D?e!!dCxSwIoDxb7DAg^T=K90oy{OB_(CAZM3+Jef8MH z+3pn$3cO+H!0sfY)o@u#K3Bhijh+Y>Ctpccf0Xh1#h*qpbnMshET#F$;`z_&;n~Hu zvvVBe7WyM5>Q}Riab+!lz$sFA6t%B9MWO@PXQ;4%0Na0-6gd-{z|IINdr$kHKeGEb fr+-Sok$4Nh7btU1@5BdBfO;9|7;BenI)(lhGcL*Y literal 0 HcmV?d00001 diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb index 264b71ec0f52a346e22efda1ea21e0116560c7b5..0b46de14537252a4b852d82052b416831cfa344e 100644 GIT binary patch literal 899 zcmV-}1AP1d00>=poZVDQYZOZqK9k8~(#gHPKm|cc<6?qg>f#S@cU`#>LG(M_HR&-%MQ~*`Uss(| z=ha`GE+WbhQ77=``tnM|!fT)lcm=FyiF(Nk9NR0owqD#O-*yiFq$_@N9*1m2o&bRB5>!^fwbu#dRkoNVmUfpa+ zo>!$aLe#J88UyZS5_vWW!zCjF4X2SaFQ*pHOg?$I_|L-g|EAx$ZkqFE@?`O?h4Iqk#qMaV;x99T}rZjG#-w*jTg*1R27rJ|=# zOXsE+#x%#gx00g_D9gSV+hhA<2-RA5oZ|P|L>hH%VjSs8^QY(6^|@`Zw~2$z^3TSv z+W%m5)@`@dF>Obaxti@5=;2H~VCyff-GQ&*+`dc%}d)r{c zKL(?f18qm!VZ%D^-7FsF?2m zz@GDT>pBG9IZDo(^`lzu)w;uw+17}ouT8Y%KAmp}UhLPeZTd8LeGK(>=scw3eSn7l zg>gu|+V2zCIi8$gi~CMW+ndM70oQlDW?71{x>UAjfnv{H9yd~bHAA1>kl0`%yW-HgiypPL zA5pjgew3XSf1@yn|4)NBf}=BxMmE<8%p}I*H28v z2^4d^>4p+ce%Y;TP9Yn1Xe${9kf9YUuSiElTV>eV^4kOkbH~?jdKTb*HE_!@zZ+z? z6UydW)0xoMYO0L>p<8#wt&}~}G#a6F+*qo))mkGtrqOTIuCKN%sOni~QPY&I>_v0w Z7puC_cBVVis)qlNGyPp`eF3+bn-)~E*H!=k literal 908 zcmV;719SWU00>=poZVGTYurW{e%HJHlJnuV&=vw^8t1U4X00{0DYauau^p$xO@h}H zgJV=#JG(1aD{Z7%lO_~Qpgr^$OwYZRTuM&;0Xg*8Q^*hSEjjfR0wsCg(X6$xorFM6 z9r0-9edqm}XGS9;$`MfkczknfJ7M7;pcnWX*vk_QQV@A=Q1Nd|vZ3n(&e7}JT?Dz? z1HgB0h^U{UwraIig1G>|6g7Yw#B)T~fqSqW(0P;}g;}gKeOz4VHYL|li{1J{6htwd z8Dzcsu_d|Rk=`WH2~*cB@N+JeSJE(jCR{a(ocliCGt$q???>j7M*9J(V-AMk!u|3} zWu=s;YH4zcmrkcBpLzqQVN)>VHCI=+1Aq0cjr6ukjXa& zFZMfOY=#VYJdJvLOdc}vo~?9Om*7x5kK^Tc2$bmrc`d= zfa1X4nzJ%};y0_~{3~Yo z<sA-%?>lYXPxVLd- z{lTN9r~dxdlV5)sXG$OD(w`FUdHNmr6W9le(625oUFy2ob(_^U?28w?A3R=NESI_e z)LfiJN`DreTj^Whi=!l}DN$S<6_;`Ci;Ct!EJV$gA7D_nOJ(||vV3*G_;7y>@|fB7 z0Ta8^@}@rxRK9c8+_)|k-M?SCytK}>TsL#u1C+ymEdQ?<*7nl~)fxAE&F|g$Eb8w3 i9MV9B(g&l zsYKSsJ9Z{r@4CCQQJ~@y!4VFq;>?9pz5pM9V~@S?3Ap6cQ-uWWdo#1yO=&@udSasI z*?I5%-h10N)u1vJZnu8vDgFq*q@J1={ zk#cJ@*C;)_0eOQ)8P~g~`T+)x)VpTd2hc08dHOX+JQhy*6ft%g=f2n35aDeRDsf6+39yQ~1UF{qCSuCAS53@Lm`m37CTdClfv(c{T);0PiKD!O z{v}IVLE}uUlC~GHC!lepo3(3Zb#LQ(%*G*(KD6tzHaFqVSzF2C&solL)?Tr($bKF7 zHe%}ltABzTI-B#h&T)%hL~TX)(9}aY7Z7pY`uv3%ZkVH9u)Qc`N}~!EC#B!@Vps6F zfl!Xu!Pa|nXMv^qNDkbO=%=>3z*rnfzt8D>M?Xb0_KL_Vz6M=6S62PeX?(&QU6qJFybg`c#75*QY7XYrd*UEG`KcDW@-R zwY@!MgQxx^^ZY)>B0a8Z*<$B0KrJc3xP(H*p--Mz0VCdvI+|_hPZ@S6Cb7JT;xw++ zkl_V%TOT5cV+MS z6k_OvjuJY62s^>fp6IIRMHyb~`OgFl`J~H>r<}0;K)RaHYQpYm22)ou+lnWsn(di9|A_fu*MeYchy4VvGzw`mp3%a>eN zpUG|*L_tsSYHNzG;ycCLnhw4c42IIfk4C-LfKS`a&#%~Smu;+M2O*3$ZV zYZ;D?j@Dm-$z?Gp?c3=q)m~lM`UL2|ft-W!^gaG66ZSZHd)`g jWab4lwKo?xAAot4l9ByR@UJpn)&F0HJ_pYL6v)IIupRhm literal 967 zcmV;&133Hu00}a9oZVGVPZU`YuVH2wY*a@49}|6MJPgsDogqk!0ucdW6I{p)Ye>*^ zn&~&g!=CP;`!(@TybwL=!5Gh8Jn;qm03JPh*-x++PM*jnidC;)(*vt+Ox(-9NzLn5 zRlln0s(RG`kb-D^SX*8V=_WafaBFI zDLfZ;*O_jB47gT{lf3N(B9fmuKz#Y2&1qlf^&xK>B!!bh)pnhEgO<|{iCqX{Q#q3m2%lIEGd1l97@h88Wx9o%`nzI zVY8Mc+~R=o$Y8jdjybR8Y_vo5v0(UNl7@AI!H%SDO)lP;`1AR>o!T#dzaM#XZ)@|}g}*4XTdAl+v2oxv zwJuT*^5&7MlT$bCeVsvF1=-I#R2F)!PF5unDi#77jjC%B2eYR-69)lCE}-wiU0m-uwCh7N!;_=H{xCu(`Q8_YF*{6N6N{ zy_+(q%}gy`2fA%3$m71+11S^LKj!mFzlk~g$nv{(hVgCV^Ubr-K8B*&!uY~Vh;DJb zI-g_wkE|cn|IUj0fL2EdQ`_mTLXJG;Fk}6idjumV-@SOjX`jX_l>%v(hAz{vfWtbQ zBL@&wH?aQMSYI%0O1d5Sx(~*>71psJg;q$d>G%YOUD74zl|Kar=vQ~ zK8+toZeGxSE`Gg*>M4c7)M5ZG&ieS7T+`1FmoQ<@&v>X#l?}+6wBjE?4 zW&cWlrIj(m2m>6PjJ`$zPE5`e3Wrr7{je_8xGI}oDD}i@IG!bk_6)A2rMQ-N@8am% zj}`7-Xgk5}j>=+rAjYn%Zc6+{PsP|469fz-4S9hM<4Kz66OFP*Xp8uGD(#)J!Yw6( pmQ;K$L*FIMc<^SGHC@nG9og+qK3A9jC!Z_K=TG@geF08Z#2d4M>52dV diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag new file mode 100644 index 00000000000..630df7b8079 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag @@ -0,0 +1,13 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(binding = 0) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, uv); + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..876290cbc72b5897191aceef71c5dc9e9b3d6f86 GIT binary patch literal 1487 zcmV;=1u*&m01nM~oZVMjZyQAvKJg`XwzmLHn>MAL5+0ml8OKels<}vQZmkpuic^S4 zBX_f2J6r5scP}Ps0ig&U0Ko%__yIgrk@5%nhJ^SNh(CbT7gQnC7gWxfIqO~AD-S%t zB;%QLoo~)LyE6*_JphmbNMvuV-}H!p1VA4Szaf#z0u12X00)||4i?y8LKUter9&O( zWgMH(f(FiA@KE-0N8rd1W1B!Zjx@6=AP-t z_4bjTztu9yr>8et4bv^nz}e9p$U!i3+07^=T{43=O_el5VJ^)WQWia3r+T>ZrREnKw$4j zy%g^=iGQ^BduWtKZd^Z&GO8zo=Lc}4ilI6A3fja^OP^5`5EptO-4J(3pN`RFe=;H;PNA4UBP2QDRfDU6cZ5A|b+T4Q-nR~>}(5c>NJkPNIQ|ym; z=P~99-a9bwA#&2JOR?x<+exHpPX~EFlf0Lg8Si^&m60)5I>eviyc6#d%8y8l%I5(4 zAgqJTpX7WTXPreva7@Nn@s7u0Pq@E9pCmuQI)gDEuXcR&N%|!7PVj!yu{eb|PsZ#| zvpvZK(|_X17gXj`T-)R-OgSO@b=b0vMo=}yMAP*3MgoSM-$j;4M+FKMCU0HtE&c$x#Gy$+PL;{ zx20vr4ND?uqebAEZVq2Zc4R~jo2DIGP|b3o8-f3;tDdjdO{CV=@&cb=KXA=bbwc`{ zA_l53TLQ`pe&Lg>j5ET^EU7TU=%Q7rPl`K;DYjh$S?HmlOmm^LJc23&?U9)7Sy3rn zKy9P2xpiBd!SO>SB^SzD4mAp!c~UHFso{2>6xk0VC`9o^SBmYLxGkHc7%`0=_q0UK z_4r+^2<{HIP1nO$y%;@L#LU;KW=+g5R4y%Du6P?dourk9Zr1|}=2)qSiT^+w#<-cb zw0e%JxQJkG1(s`~5uVGn?j_8Y?&`iN9hh##S-)u-zW1aRo~-%!0$M}g!ky>%9)M9P zj)=gvYL43!okN{+gtdf~Lgy{k+PFsPqysFfSX5{`R#m1v7v^88aF%8D;FuSk)S~#A zi6!&rrL`-iDuv-=Oc|^~^j3cuWw2P(TCQnWlG?E98!c5wW#`AMr20!_0{6q(_*q2tLiVKn3Vn}RsFOI{tsn|&aYCGrNr*c z%H`>!-{*IKSUNj+>zDgGKi@BEl*-+NI`ruB`#bg=GOyD`68TFrvvc<#<^|M66Vi_! zQ5}2@q%%4?!G|mzhDHAVM^j;d3I%badBye zY!+X@hAueecPq=fTgQ)@y}k04*_B;Tm-;iEi$Z literal 0 HcmV?d00001 diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert new file mode 100644 index 00000000000..1dd204f84d2 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert @@ -0,0 +1,14 @@ +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoord; + +layout(location = 0) out vec2 uv; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + uv = texcoord; + gl_Position = position; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb new file mode 100644 index 0000000000000000000000000000000000000000..e4f12bfb9e70f6109c56e2cabaf6f8237f7f920b GIT binary patch literal 1195 zcmV;c1XTL~01PR3oZVK{@7SGS@4EY8rBu08;*P3_6Nd`8g*zNLaNvlUzxU?Nn|Wr8#h4ngZ|)x+Ik@-|e#YVZIecdmjEymA_C44633y4MAAlOn*VzKP z2{ggFR!euKWimFOLOoOO3FN!txMf+k0sIN%yAS#TzXQ=KGnN6{H-RcZcL;MA=y75g zGZ^z@QS83uNb;E;!LIn&*MM#TeFl3;0ps*7#?lJ6TbG{-R%Ez8U++cZ5!gP813^ru z?_i9U=VC%Y2HRpFVjqv>3$0Hy4a*zNDZd6gzZ>jvnBQU%Jn8lW$Ws`f z?z*--^jt9-=8)jujP^KE4SQj<<^M@F{Kqm5x0{v2jK>?*{o89lzAtw&)4zPxU;J9% z`R-?RT^sN?r06a`0s0N73WD!}YSr}(W8|&Dw~D3y_!G|*=e267gx@Pb94Jq3Lx%J} zlr7tGthUSZwFSPlUEk()l}o;6_4=|4lTxuzVjt8uKdRDO1y2rJjQC8@H=FhOu?QdM z379n2_t$oIYE^c2cD4gpyRX1XK!B@3Jw~vyEP_?vJI^)Z*0x2hQaw_xVUSAcKKygY=OX=xki|Wh%;>BXQ$Mj+{x1 zz21nwrv6JtP;WRQr4s)(5zmcdWaDrGSBQHNJY%er&?4}=3Dr<=T%%CAj%|)zpk4as zkoFw_1wPWsmNCrEEW?~6)LyVwQ=l2uO@R#cy#dZs$}a5m$QuKHa5iwAguN$h5F&q! z_>5%1@gv?e@evRGhut0a0^ZTzRno!rB&~5jTVNC5PM+%|6faKtvl06l;3EHRwM$=T z4rbU~InZU$A7_5EjVoSK)jH}Te~H#<;-OrlSOU35vBU^(itv&u4nMXjvdt3C1+vZ3 zIUw&1SYM>L5bIr9U!uLB{S3*b$bN>-Fh=q@!b15C$W@$$b&mYsfrIbVGVMD0c(DXW)I&zdb=6Ct3J(VRTP&(rk4-L-1QY;p*KclxtTW zu4p0Okv-!*{3+a9(KOG&E`a}^9B4&?D=T>09oP0+u6o^zP5A3T4}808=`J=5esJJ9 z!p_4#GPAgF_wG_Ct_;2T2M0mfq0-4Hv^1m0VS53)^Tv}OaS(Q=A+6Fv;9;PyM;N?6 z%Lg90M0m%=7AzsGpnHY-NXz`;iSHCv1=ZQ4Sr~3^C>d^Wpc3{tRI!EY*(PcRJujvF J`~^txNDnT;T3G-9 literal 0 HcmV?d00001 diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 65561595a1d..ddead9aa447 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -85,6 +85,8 @@ private slots: void invalidPipeline(); void renderToTextureSimple_data(); void renderToTextureSimple(); + void renderToTextureTexturedQuad_data(); + void renderToTextureTexturedQuad(); private: struct { @@ -818,6 +820,34 @@ void tst_QRhi::resourceUpdateBatchRGBATextureUpload() QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); } + + // now a QImage from an actual file + { + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + inputImage = std::move(inputImage).convertToFormat(image.format()); + + QScopedPointer texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), inputImage); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + inputImage.format()); + + QVERIFY(imageRGBAEquals(inputImage, wrapperImage)); + } } void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data() @@ -1201,5 +1231,134 @@ void tst_QRhi::renderToTextureSimple() QVERIFY(redCount > blueCount); } +void tst_QRhi::renderToTextureTexturedQuad_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureTexturedQuad() +{ + 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->build()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + QScopedPointer inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->build()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->build()); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::sampledTexture(0, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(srb->build()); + + QScopedPointer pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/simpletextured.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simpletextured.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->build()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + 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 readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast(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; + + // Flip with D3D and Metal because these have Y down in images. Vulkan does + // not need this because there Y is down both in images and in NDC, which + // just happens to give correct results with our OpenGL-targeted vertex and + // UV data. + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // check a few points that are expected to match regardless of the implementation + QRgb white = qRgba(255, 255, 255, 255); + QCOMPARE(result.pixel(79, 77), white); + QCOMPARE(result.pixel(124, 81), white); + QCOMPARE(result.pixel(128, 149), white); + QCOMPARE(result.pixel(120, 189), white); + QCOMPARE(result.pixel(116, 185), white); + + 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); + + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qRed(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qBlue(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qRed(result.pixel(214, 191))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); +} + #include QTEST_MAIN(tst_QRhi) From df0b1836b5b7c14095258a510ec27758a921eb0a Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 8 Oct 2019 11:55:56 +0200 Subject: [PATCH 17/23] rhi: Autotest rendering with uniform buffer Change-Id: I4251f31494680c78e90a08a2b471cb1af08ecd81 Reviewed-by: Paul Olav Tvete --- tests/auto/gui/rhi/qrhi/data/compile.bat | 2 + tests/auto/gui/rhi/qrhi/data/simple.frag.qsb | Bin 899 -> 908 bytes tests/auto/gui/rhi/qrhi/data/simple.vert.qsb | Bin 961 -> 958 bytes .../gui/rhi/qrhi/data/simpletextured.frag.qsb | Bin 1487 -> 1479 bytes tests/auto/gui/rhi/qrhi/data/textured.frag | 19 ++ .../auto/gui/rhi/qrhi/data/textured.frag.qsb | Bin 0 -> 1997 bytes tests/auto/gui/rhi/qrhi/data/textured.vert | 19 ++ .../auto/gui/rhi/qrhi/data/textured.vert.qsb | Bin 0 -> 1708 bytes tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 206 ++++++++++++++++++ 9 files changed, 246 insertions(+) create mode 100644 tests/auto/gui/rhi/qrhi/data/textured.frag create mode 100644 tests/auto/gui/rhi/qrhi/data/textured.frag.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/textured.vert create mode 100644 tests/auto/gui/rhi/qrhi/data/textured.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/compile.bat b/tests/auto/gui/rhi/qrhi/data/compile.bat index 3dafa33eab7..5b8a77b8337 100644 --- a/tests/auto/gui/rhi/qrhi/data/compile.bat +++ b/tests/auto/gui/rhi/qrhi/data/compile.bat @@ -44,3 +44,5 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.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 diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb index 0b46de14537252a4b852d82052b416831cfa344e..264b71ec0f52a346e22efda1ea21e0116560c7b5 100644 GIT binary patch literal 908 zcmV;719SWU00>=poZVGTYurW{e%HJHlJnuV&=vw^8t1U4X00{0DYauau^p$xO@h}H zgJV=#JG(1aD{Z7%lO_~Qpgr^$OwYZRTuM&;0Xg*8Q^*hSEjjfR0wsCg(X6$xorFM6 z9r0-9edqm}XGS9;$`MfkczknfJ7M7;pcnWX*vk_QQV@A=Q1Nd|vZ3n(&e7}JT?Dz? z1HgB0h^U{UwraIig1G>|6g7Yw#B)T~fqSqW(0P;}g;}gKeOz4VHYL|li{1J{6htwd z8Dzcsu_d|Rk=`WH2~*cB@N+JeSJE(jCR{a(ocliCGt$q???>j7M*9J(V-AMk!u|3} zWu=s;YH4zcmrkcBpLzqQVN)>VHCI=+1Aq0cjr6ukjXa& zFZMfOY=#VYJdJvLOdc}vo~?9Om*7x5kK^Tc2$bmrc`d= zfa1X4nzJ%};y0_~{3~Yo z<sA-%?>lYXPxVLd- z{lTN9r~dxdlV5)sXG$OD(w`FUdHNmr6W9le(625oUFy2ob(_^U?28w?A3R=NESI_e z)LfiJN`DreTj^Whi=!l}DN$S<6_;`Ci;Ct!EJV$gA7D_nOJ(||vV3*G_;7y>@|fB7 z0Ta8^@}@rxRK9c8+_)|k-M?SCytK}>TsL#u1C+ymEdQ?<*7nl~)fxAE&F|g$Eb8w3 i9MV=poZVDQYZOZqK9k8~(#gHPKm|cc<6?qg>f#S@cU`#>LG(M_HR&-%MQ~*`Uss(| z=ha`GE+WbhQ77=``tnM|!fT)lcm=FyiF(Nk9NR0owqD#O-*yiFq$_@N9*1m2o&bRB5>!^fwbu#dRkoNVmUfpa+ zo>!$aLe#J88UyZS5_vWW!zCjF4X2SaFQ*pHOg?$I_|L-g|EAx$ZkqFE@?`O?h4Iqk#qMaV;x99T}rZjG#-w*jTg*1R27rJ|=# zOXsE+#x%#gx00g_D9gSV+hhA<2-RA5oZ|P|L>hH%VjSs8^QY(6^|@`Zw~2$z^3TSv z+W%m5)@`@dF>Obaxti@5=;2H~VCyff-GQ&*+`dc%}d)r{c zKL(?f18qm!VZ%D^-7FsF?2m zz@GDT>pBG9IZDo(^`lzu)w;uw+17}ouT8Y%KAmp}UhLPeZTd8LeGK(>=scw3eSn7l zg>gu|+V2zCIi8$gi~CMW+ndM70oQlDW?71{x>UAjfnv{H9yd~bHAA1>kl0`%yW-HgiypPL zA5pjgew3XSf1@yn|4)NBf}=BxMmE<8%p}I*H28v z2^4d^>4p+ce%Y;TP9Yn1Xe${9kf9YUuSiElTV>eV^4kOkbH~?jdKTb*HE_!@zZ+z? z6UydW)0xoMYO0L>p<8#wt&}~}G#a6F+*qo))mkGtrqOTIuCKN%sOni~QPY&I>_v0w Z7puC_cBVVis)qlNGyPp`eF3+bn-)~E*H!=k diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb index 8e44f2a1e307890e3cca3a81c00f57f0e611d302..59080b60c696a01fa13278ef898e8f138ac8f8d6 100644 GIT binary patch literal 958 zcmV;v13~-%00}a9oZVGVZxcrpe~#@qOrSBLrLB0CcR4t%F0NZ$=2Y4qn#kX-%H6}0Y%<^;e=v_FqLh5QQ^@njg_gpTRe<&Od? zKy?_KchMgD?O}m?SrhMPBPO>T7?GvS?5OmcUhE1!)e*|^8j*56xjDm9c_cgTW%^g% znqe%Cq~GTBEm7BL3a-5-8H-dHHx=(V(yugvSg{9;BelH1Q4_rFHCF9s^SgHw1U5D% z*-yuPrGM15oBBCQg>o}iqEs16e0M~s#BQ3`>`F;2F3B-cPFvtww^ic`H(K}cJ!Xz|76#%%3+ zBieFYgtFl|emkb}CdMm>=sh+nc9rn8*LzI)H(1R5ScU?fd{?%Prx0Bybd=BmMA!&6 zw?$J$Z_Ds@%YP?eh$mfCo?^mw0_kc%O9{G15ll@DTsg9}Xt|L7?!HUWs#F*|?Dj`Z zxZBg7FVlqM|C9E7e9&-)z<3X62lMOK=l*>3)$97DSI?$?zjv_z@cdtd>_Ns%OB`bG z8;w!q0hFyG*X9>)xW}dxO*qK^^P1X1``Uc9O6wQM9JyqtTr#e1$z~WtK}+#cZHzB1 z*Oz%+!@=i*PFH%ksaDEWxLIGkebIJ%z{W}_YuBM6fVGA7xs{dLJnZl9ue`%2)9Zs= zzjFu~)Rz|4zXh8787ShqJph>on!hL(wS5~ge8G!{ajxRmCg&R?W}QJ*eQkE_378ip zq27Js{~`3D_Wwc2Js|37VdiDFZ?r&}3W(USm7d1RDfb{gHnc8cm&*90lqasxe1uOd znxq@j@1}W@k{nFi5awBp!5{ig<&N0oF*fyxuCEC1==o3bek%v7OZt?@Sj@`v_($>6 zHyB_EWr^RQT^G@ZWPN&{(>^0scLt>-|5{gRY|i42o>z|iIocN>eQrl=tb*3(@HIDg zie!`a%&hD)JP+D9gwX|iPJRRAf6D3#*8h~nEMxUW8;kT;@!R@r9bn}zQ9{4dX9B(g&l zsYKSsJ9Z{r@4CCQQJ~@y!4VFq;>?9pz5pM9V~@S?3Ap6cQ-uWWdo#1yO=&@udSasI z*?I5%-h10N)u1vJZnu8vDgFq*q@J1={ zk#cJ@*C;)_0eOQ)8P~g~`T+)x)VpTd2hc08dHOX+JQhy*6ft%g=f2n35aDeRDsf6+39yQ~1UF{qCSuCAS53@Lm`m37CTdClfv(c{T);0PiKD!O z{v}IVLE}uUlC~GHC!lepo3(3Zb#LQ(%*G*(KD6tzHaFqVSzF2C&solL)?Tr($bKF7 zHe%}ltABzTI-B#h&T)%hL~TX)(9}aY7Z7pY`uv3%ZkVH9u)Qc`N}~!EC#B!@Vps6F zfl!Xu!Pa|nXMv^qNDkbO=%=>3z*rnfzt8D>M?Xb0_KL_Vz6M=6S62PeX?(&QU6qJFybg`c#75*QY7XYrd*UEG`KcDW@-R zwY@!MgQxx^^ZY)>B0a8Z*<$B0KrJc3xP(H*p--Mz0VCdvI+|_hPZ@S6Cb7JT;xw++ zkl_V%TOT5cV+MS z6k_OvjuJY62s^>fp6IIRMHyb~`OgFl`J~H>r<}0;K)RaHYQpYm22)ou+lnWsn(di9|A_fu*MeYchy4VvGzw`mp3%a>eN zpUG|*L_tsSYHNzG;ycCLnhw4c42IIfk4C-LfKS`a&#%~Smu;+M2O*3$ZV zYZ;D?j@Dm-$z?Gp?c3=q)m~lM`UL2|ft-W!^gaG66ZSZHd)`g jWab4lwKo?xAAot4l9ByR@UJpn)&F0HJ_pYL6v)IIupRhm diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb index 876290cbc72b5897191aceef71c5dc9e9b3d6f86..f302702aa99bf01f67e0422b4f96a57ef29abe6c 100644 GIT binary patch literal 1479 zcmV;&1vvTu01nM~oZVMTZyQwZ`SO9_riueI6sz~_*-60|V1mX`Mb%QE|x`oChGIm0c>D&YJ4rMMx=3}Z(Hfa#kwGQ822Q66 z=scb^YETulO%7~73qsJRm@WmeN|@iqiF#?xv`VH`q4pT=VE~q1Gl=5k8XIhzWZE&@ zsNFucb2l3X$8_{sqiWc>8F~r2*D=py&SQQK+=q@4^^j>bJl81+o|~9U#2ARK3Z5iY z>qXr)>(<_QV&pZ4lkIi8sc95*)LL^~*4s`oCXQ9jn|BP#60aZFJZ)^jZwtV-fMYw$ zxaywEfT61j=5@>)g87{n#N0<^Tdz#lt96@(`#P|Xx3OOo?9BhfdjakG4d6pZPxvYs zUB8Z)R{G~;Dql)xpN7l zq1(D^_yG*NP+z-c6kTVp7527#asjQXZ(`>u`7R+V*-_JFc ztj49>!AY5r!}xd16ofbUV1s;dW_Ip=6EK?>pDZ{9MCcHOC$U6ooKRafWp^D6HbR87}w4U^7O*O;tcyNyns(XX8zs6l5SUU$=cn`U!7gv zq2Q8n8uM0?y548=n*+Z6@({Mx2C)1+rwm~S8Vcf|BzP(6bG2<#bqc>7fNo0R?VJPOpB=sRl_^23OS z{}X&W_7C4-mQ4u1Uu3*qzr>4){Xi?vf&GBwmt}+C4^jJldPDd@3f?RBKP*^#B>qvM zL;XpidnNu!iN~^Y$azfmP9l2*NkVKn7k%P837+?KK=v~!dwE&#z6Y;fKj(oB%crF7 zEV~5#VIN}ob{^>ZA^jzx0MxW7S6)*ly}p-7BZTQT~4{)EVm h%YGA)JcT$MAL5+0ml8OKels<}vQZmkpuic^S4 zBX_f2J6r5scP}Ps0ig&U0Ko%__yIgrk@5%nhJ^SNh(CbT7gQnC7gWxfIqO~AD-S%t zB;%QLoo~)LyE6*_JphmbNMvuV-}H!p1VA4Szaf#z0u12X00)||4i?y8LKUter9&O( zWgMH(f(FiA@KE-0N8rd1W1B!Zjx@6=AP-t z_4bjTztu9yr>8et4bv^nz}e9p$U!i3+07^=T{43=O_el5VJ^)WQWia3r+T>ZrREnKw$4j zy%g^=iGQ^BduWtKZd^Z&GO8zo=Lc}4ilI6A3fja^OP^5`5EptO-4J(3pN`RFe=;H;PNA4UBP2QDRfDU6cZ5A|b+T4Q-nR~>}(5c>NJkPNIQ|ym; z=P~99-a9bwA#&2JOR?x<+exHpPX~EFlf0Lg8Si^&m60)5I>eviyc6#d%8y8l%I5(4 zAgqJTpX7WTXPreva7@Nn@s7u0Pq@E9pCmuQI)gDEuXcR&N%|!7PVj!yu{eb|PsZ#| zvpvZK(|_X17gXj`T-)R-OgSO@b=b0vMo=}yMAP*3MgoSM-$j;4M+FKMCU0HtE&c$x#Gy$+PL;{ zx20vr4ND?uqebAEZVq2Zc4R~jo2DIGP|b3o8-f3;tDdjdO{CV=@&cb=KXA=bbwc`{ zA_l53TLQ`pe&Lg>j5ET^EU7TU=%Q7rPl`K;DYjh$S?HmlOmm^LJc23&?U9)7Sy3rn zKy9P2xpiBd!SO>SB^SzD4mAp!c~UHFso{2>6xk0VC`9o^SBmYLxGkHc7%`0=_q0UK z_4r+^2<{HIP1nO$y%;@L#LU;KW=+g5R4y%Du6P?dourk9Zr1|}=2)qSiT^+w#<-cb zw0e%JxQJkG1(s`~5uVGn?j_8Y?&`iN9hh##S-)u-zW1aRo~-%!0$M}g!ky>%9)M9P zj)=gvYL43!okN{+gtdf~Lgy{k+PFsPqysFfSX5{`R#m1v7v^88aF%8D;FuSk)S~#A zi6!&rrL`-iDuv-=Oc|^~^j3cuWw2P(TCQnWlG?E98!c5wW#`AMr20!_0{6q(_*q2tLiVKn3Vn}RsFOI{tsn|&aYCGrNr*c z%H`>!-{*IKSUNj+>zDgGKi@BEl*-+NI`ruB`#bg=GOyD`68TFrvvc<#<^|M66Vi_! zQ5}2@q%%4?!G|mzhDHAVM^j;d3I%badBye zY!+X@hAueecPq=fTgQ)@y}k04*_B;Tm-;iEi$Z diff --git a/tests/auto/gui/rhi/qrhi/data/textured.frag b/tests/auto/gui/rhi/qrhi/data/textured.frag new file mode 100644 index 00000000000..605410b0283 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/textured.frag @@ -0,0 +1,19 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 matrix; + float opacity; +} ubuf; + +layout(binding = 1) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, uv); + c.a *= ubuf.opacity; + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..0a039137ec436807ee374cfb2736fb786b7c93a6 GIT binary patch literal 1997 zcmV;;2Qv5o02JtWob6Z5ZyU!IfBcr#PAt3rh%L3*L`h8AGEJ#W5Lh3Ltq&`J69=Z; zs<8!nNiL;z$R)E}s*=+bfqf24yrdypNW%C*IqS(8Ak}40LSgKjH}fR8L;?^F>i>^81rk7hXMQG3xqWy%jVsK=&IR`Y@*LzmM@3G2SN%W7>W+cBb7)v)u{{zsevIRO!}1 zzdg+SO}ANb13uTEb&}DSk{|ozj3?rZtLcP+w^hPl4S4TF@**vJhlY6Gs@Ef_aYDtX z!)~Y1{n7J6M$T(Xtd=DQZb&||Pe&7dJBUHsse0kI=ry)Jpooo#Pg7KHw4AW#(?Z0r zse?p^a`)%)2NAq?&JbDD34?AmWXC0`s~i`N(F}CGhJHP!_43|l=o6SX(7%i+wVZA| z`*q(^JTtxtzavxZ$>$5!xL2>ab^GF_^7$(l%AE~|gII1j{(9G0cgw}80?z)1HSTVO zuCJsX!fKzh1NY6Y7q}2YO4Rq7aNADcgsxKH2IW@erdtg=gDec@yx)MOePzQ-)?1d1 z#zD4qc1$aRiij<7oVMPcwk%0*ma^HwAT^wAJZ@P@Wqa11%-M#m_inNAHUh8b+x*zu z-OXtWLcA#2y@w18n=P*f44$9O2`a@>g<5C`yMddE&CMniV%pbH;er|wDGX5hg%cOCdzxrSP2|pS)9ELPg0~NBM(3Br%0h- zwF9^6snv$JzR}jcoW76j`5^wsLSaZ+_g?33I>9zas271p@b=Wgf79RVjq(y)zTkw; zwZhtioW9k6E@NNyoj&#d=W<{&{4b^fE*hHZr$OTG!s>NRal z-bOFUx+rV58y#0;nkOlG8V}&(0GD;{=?l8JRb=~doI0tb2e)iYWK9*IZ}(mHR?NII z2Q(L3uXxp<)oIm3J9{N(FJCHMvPW9m&BP>u z$;#sD{N>A678v*CN02#yUix8q#R=9?ZtU)smKRs=il1LVzd3}LJzTf+o|fj=zRuz| zMwH&wC*YX2o?pF6R2iYf)X0cFANanu^&I6|oOu?Be*Oodcjm7ip|45rEeZe9&$J$+ z`$fRs5uS50TJLBaNxh#5ME_q8h>o}(fjzZje^Yi2t|+aXU%4&kFp-H%H|@a@8@%^z z{QqV%&pfwUg#3?0d_2RJGKl0^p&kTkw!IHk9FG@~J^4;p%NL*Lijp3eDD&O{NV3nk z8yl!J36&(glTdX2Tu9)@zTkI7O0wSYr?AK6Z&l{1yvJk8bJo?})O7yj<3~4*)e*re zk5!Mxn9SqCc8(EA5z9JK3RnMw1ZM*e+k0B{ne|hmi`R_dd$PZbwXvO2>pqt89#5Z^ zQGV#tV#4>5(yh-F7dyxH;5a5Y%=%0O$9EtVSNjPS4W1vxjO%V2{TTS!c3NU%<9|r- za6b-@Thy=9qs^>Gp^ra_ERi?wYo<-h`Z1xgy#t~zF8m{8azyj<`k>JG@jfhAlImRA zuZKkk%O4e8M@0UpvSVn;-hIOQPRshZtXckq@TP_TyVQSIot8EGGa>v>3ZAr_=Q)|1 z{Rx?81P`yz2<-*LH!f!(q5Tb%l<>0ehedW4Yxd#e;=ft(-68SMk06y%G1fdxpOa^V zY3H$jLd8e>_0O=@3TUm zmE1ciJeP5L_GMORC&foGiO+k`$@@9sc{ZZsR8PmSTAvnLPJEq;#K`#QbVNQc^1OFO z*8ha%7j>4K_adt@>CLFFk9&T#(XF}mOw$dWMj5ZahF6)h`t*+1texe*{Kgy##SWK) z{Mp{Y+sCz))S#~tQvf`y7!^UrS198%?;GNgkE(0esyt>3xwW+clAa$G$DjNp&y~8(2^iDorcsD zGR()D@?hpoc^{J^vLSJ&22EoOxNzmBx-~9bn#dpE(uGT6Tr@0<8^zyw_q=&;+5(yw z6BBQ9`tCj7=iGDeJtKsO2yZAWxAHe^S}ZC;B=J3sZx0|L40GLe?C*e!0loz+1DXH} zfKz~GQLYwpj!`v*7>E;}nXBl8cl3=ywQ3cCZzjBRdtIk*O)rcK(F)mvfHdF=>s$nU ztTgKM$3P;id%bEKY%{q-cUtN`0+W7TyiA)p~F0g|}3BPV~w|EO?l&&tUW&I$RwiAkGqO`}w`Dznge(-xmaq}@;P zeLq5UA)JV`+a&UaSv1VGPz(&(H^GNwj zaxDxffgR+2Jy$TC+pO2O^+1GdglZu!O66+Kspm8mu7^$`x<*j>BIGQ_gOI-u-w;9- zwqv;khY}YPu9vuQZB0VgBH&6y>LuEbRM(i_m#2$LjAow%Rw56EYREaNLmNAuQiqIE zQ7>up7qT-;vswFkjy5n`&Y5d&ZcWdo$5V*=raGi=IJ)Ucy?|9arCItd*RXU5p&Uv( zP3Y@6E9dB*0^Q11^EdQ@WA9~QZ_eEYRGB9Y8B*`48U}5!CM#pYLwLKP@g7d7cPCVp z3!9`gN-j`1UmBeS&8i6awAVwr?y4#k6Di|eD~kFpDIN#AmP-9|5!Z6oZR}zYb6(h1Ju5oeDEhyrDJf#KQhD&o}V}v`vH~TQ(maT00ir9NZ80d;6 zo+uaIVun%wG~CWVs+`=;|E{X ze~{!uvqfo+)US)(qZ6dJjDl6Qt0hMpUK-IBE@Uof8LvXMGu6tvQO0B}H998FXXY=? zah!OR#L18zg}?3X5+4cSX|7n1sgQO^GHY+Oa!_zm+O^B zdNT`WSDNVs1m`UHZZwO?+erJl4>eB@WiSg>PdYlhHOhXMeyYNIS5zMv#Vq8@pt*3GyJ=NMlnDu+e&n(i7YjtIyA<1A`4`@1<}xV z9+f(3JH^rTacZQ*KCT>DBS~oh;M+(F*+Eiv2FMEjD6ij8_r<5n=Z%9#{BDiraZfJz z?LfE$?4eF@@#)rO-2##D3C0Vi`K3wvIv7v$9wG0Q1Xewa=VM>TQ5^npCja&e!Tp}} ziDC$Tb08n!A)gPkK3cc%nshBd#`mqI&ZBs^s7H_0eTg%_zaHXm7}vzPTnPC~e5YA& z59^(0-(Ly&MeCPC>w%Ep-H?iUbj90In(xPqYFGpGfxk(}nvQz7n0JRL?9&9VlZ+#} z%KrIum2(hbeQm5S=J_FW(#EpwtY;s~wsYKs>&3d0{UTjQdELda9KgDpbxGYa$K7m? z^!IWcJ*>aii#Ld^kLTUI?q^$V%unk9#u5E6`$qH=*qf*b&O70M1BUi@iu2gYxtQjf zr}Ydt`hy_XWw)qnlNH^`m9seVWg|bW26)@36yKzOqt~a=2-(#0&@VI_r$^U3sfj(h zD*u6fQD?=1Z`QXRoB+7Vf>yoQzKWeh7_e6TBFrf|GW~@F4czF;8?wL zf?T~n8{T0cXyO{U#zuS#HScZ;oR_m<-=b!adfTCn*MM8cv_jRy()1Gbn%B#L0sl*g zkc^Mids1H`!IXCAu5=5lQLjZb%+ba!ktomW6d@2aizHy_j%%4D=QodW&+4E1;z`Zr C6;~tx literal 0 HcmV?d00001 diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index ddead9aa447..73043dde8a9 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -87,6 +87,8 @@ private slots: void renderToTextureSimple(); void renderToTextureTexturedQuad_data(); void renderToTextureTexturedQuad(); + void renderToTextureTexturedQuadAndUniformBuffer_data(); + void renderToTextureTexturedQuadAndUniformBuffer(); private: struct { @@ -1360,5 +1362,209 @@ void tst_QRhi::renderToTextureTexturedQuad() QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); } +void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer() +{ + 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->build()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + // There will be two renderpasses. One renders with no transformation and + // an opacity of 0.5, the second has a rotation. Bake the uniform data for + // both into a single buffer. + + 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->build()); + + QMatrix4x4 matrix; + updates->updateDynamicBuffer(ubuf.data(), 0, 64, matrix.constData()); + float opacity = 0.5f; + updates->updateDynamicBuffer(ubuf.data(), 64, 4, &opacity); + + // rotation by 45 degrees around the Z axis + matrix.rotate(45, 0, 0, 1); + updates->updateDynamicBuffer(ubuf.data(), secondUbufOffset, 64, matrix.constData()); + updates->updateDynamicBuffer(ubuf.data(), secondUbufOffset + 64, 4, &opacity); + + QScopedPointer inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->build()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->build()); + + 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->build()); + + 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->build()); + 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->build()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + 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); +} + #include QTEST_MAIN(tst_QRhi) From b62b80706e6b81cd3b5e18d7ffdac2bc630b151e Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Wed, 9 Oct 2019 10:23:12 +0200 Subject: [PATCH 18/23] rhi: Autotest for rendering a triangle into a window Change-Id: Id1562ff8cf7c6bc7e5bd147bb628f3d9dd57f2b5 Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhinull.cpp | 2 +- tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 159 +++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 3027faa397b..fe606f971fc 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -489,7 +489,7 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re quint32 bytesPerLine = 0; quint32 byteSize = 0; textureFormatInfo(result->format, result->pixelSize, &bytesPerLine, &byteSize); - if (result->format == QRhiTexture::RGBA8) { + if (texD && texD->format() == QRhiTexture::RGBA8) { result->data.resize(int(byteSize)); const QImage &src(texD->image[u.rb.layer()][u.rb.level()]); char *dst = result->data.data(); diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 73043dde8a9..768b227ecd6 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -89,6 +89,8 @@ private slots: void renderToTextureTexturedQuad(); void renderToTextureTexturedQuadAndUniformBuffer_data(); void renderToTextureTexturedQuadAndUniformBuffer(); + void renderToWindowSimple_data(); + void renderToWindowSimple(); private: struct { @@ -1566,5 +1568,162 @@ void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer() QCOMPARE(result1.pixel(28, 178), empty); } +void tst_QRhi::renderToWindowSimple_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToWindowSimple() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + +#ifdef Q_OS_WINRT + if (impl == QRhi::D3D11) + QSKIP("Skipping window-based QRhi rendering on WinRT as the platform and the D3D11 backend are not prepared for this yet"); +#endif + + QScopedPointer rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QScopedPointer window(new QWindow); + switch (impl) { + case QRhi::OpenGLES2: + Q_FALLTHROUGH(); + case QRhi::D3D11: + window->setSurfaceType(QSurface::OpenGLSurface); + break; + case QRhi::Metal: + window->setSurfaceType(QSurface::MetalSurface); + break; + case QRhi::Vulkan: + window->setSurfaceType(QSurface::VulkanSurface); +#if QT_CONFIG(vulkan) + window->setVulkanInstance(&vulkanInstance); +#endif + break; + default: + break; + } + + window->setGeometry(0, 0, 640, 480); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QScopedPointer swapChain(rhi->newSwapChain()); + swapChain->setWindow(window.data()); + swapChain->setFlags(QRhiSwapChain::UsedAsTransferSource); + QScopedPointer rpDesc(swapChain->newCompatibleRenderPassDescriptor()); + swapChain->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(swapChain->buildOrResize()); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float vertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + 0.0f, 1.0f + }; + QScopedPointer vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight); + QVERIFY(framesInFlight >= 1); + const int FRAME_COUNT = framesInFlight + 1; + bool readCompleted = false; + QRhiReadbackResult readResult; + QImage result; + int readbackWidth = 0; + + for (int frameNo = 0; frameNo < FRAME_COUNT; ++frameNo) { + QVERIFY(rhi->beginFrame(swapChain.data()) == QRhi::FrameOpSuccess); + QRhiCommandBuffer *cb = swapChain->currentFrameCommandBuffer(); + QRhiRenderTarget *rt = swapChain->currentFrameRenderTarget(); + const QSize outputSize = swapChain->currentPixelSize(); + QCOMPARE(rt->pixelSize(), outputSize); + QRhiViewport viewport(0, 0, float(outputSize.width()), float(outputSize.height())); + + cb->beginPass(rt, Qt::blue, { 1.0f, 0 }, updates); + updates = nullptr; + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport(viewport); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + if (frameNo == 0) { + readResult.completed = [&readCompleted, &readResult, &result, &rhi] { + readCompleted = true; + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_ARGB32_Premultiplied); + if (readResult.format == QRhiTexture::RGBA8) + wrapperImage = wrapperImage.rgbSwapped(); + if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) + result = wrapperImage.mirrored(); + else + result = wrapperImage.copy(); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({}, &readResult); // read back the current backbuffer + readbackWidth = outputSize.width(); + cb->endPass(readbackBatch); + } else { + cb->endPass(); + } + + rhi->endFrame(swapChain.data()); + } + + // The readback is asynchronous here. However it is guaranteed that it + // finished at latest after rendering QRhi::FramesInFlight frames after the + // one that enqueues the readback. + QVERIFY(readCompleted); + QVERIFY(readbackWidth > 0); + + if (impl == QRhi::Null) + return; + + // Now we have a red rectangle on blue background. + const int y = 50; + const quint32 *p = reinterpret_cast(result.constScanLine(y)); + int x = result.width() - 1; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + while (x-- >= 0) { + const QRgb c(*p++); + if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0) + ++redCount; + else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz)) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + + QCOMPARE(redCount + blueCount, readbackWidth); + QVERIFY(redCount < blueCount); +} + #include QTEST_MAIN(tst_QRhi) From 5e7365573e703bceb99da525725cd63350987c7a Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 9 Oct 2019 15:17:05 +0200 Subject: [PATCH 19/23] Remove a new-line that was accidentally introduced this week. Change-Id: I0731c7c64e51b40f230b42694603056d89a05712 Reviewed-by: Edward Welbourne --- src/network/kernel/qauthenticator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index 858a5bc2deb..33a30eb1cd6 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -1,4 +1,3 @@ - /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. From d6734e8ab756fc56b46f6ff6b7a423d455068f27 Mon Sep 17 00:00:00 2001 From: Robert Loehning Date: Fri, 4 Oct 2019 14:59:02 +0200 Subject: [PATCH 20/23] Fuzzing: Don't copy input data to QByteArray Change-Id: I603413805dca46a85709c2ab6ff573687849572e Reviewed-by: Albert Astals Cid --- .../serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp | 2 +- tests/libfuzzer/gui/iccparser/main.cpp | 2 +- tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp | 2 +- tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp b/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp index 5a60c78cb54..7b73e6e9528 100644 --- a/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp +++ b/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp @@ -29,7 +29,7 @@ #include extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) { - QXmlStreamReader reader(QByteArray(Data, Size)); + QXmlStreamReader reader(QByteArray::fromRawData(Data, Size)); while (!reader.atEnd()) reader.readNext(); return 0; diff --git a/tests/libfuzzer/gui/iccparser/main.cpp b/tests/libfuzzer/gui/iccparser/main.cpp index ba4f70ef3ba..1db43d2e257 100644 --- a/tests/libfuzzer/gui/iccparser/main.cpp +++ b/tests/libfuzzer/gui/iccparser/main.cpp @@ -32,6 +32,6 @@ extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) { static int c = 0; static QGuiApplication a(c, nullptr); - QColorSpace cs = QColorSpace::fromIccProfile(QByteArray(data, size)); + QColorSpace cs = QColorSpace::fromIccProfile(QByteArray::fromRawData(data, size)); return 0; } diff --git a/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp b/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp index c9b33d0f888..51fa3c9e0fb 100644 --- a/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp +++ b/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp @@ -32,6 +32,6 @@ extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) { static int c = 0; static QApplication a(c, nullptr); - QTextDocument().setHtml(QByteArray(Data, Size)); + QTextDocument().setHtml(QByteArray::fromRawData(Data, Size)); return 0; } diff --git a/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp b/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp index acdd91e06ef..66ddf738f20 100644 --- a/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp +++ b/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp @@ -29,6 +29,6 @@ #include extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) { - QTextDocument().setMarkdown(QByteArray(Data, Size)); + QTextDocument().setMarkdown(QByteArray::fromRawData(Data, Size)); return 0; } From 22891dd897b37be03222ec4881629628fb312442 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Fri, 4 Oct 2019 11:00:07 +0200 Subject: [PATCH 21/23] Explain an usage of volatile It's to work around QTBUG-45307. Change-Id: Ib6076223013e5b1ccfa6ec25e2d040daaa2f19dd Reviewed-by: Oswald Buddenhagen --- src/corelib/global/qlibraryinfo.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/corelib/global/qlibraryinfo.cpp b/src/corelib/global/qlibraryinfo.cpp index 6476b7404ab..ebacd8a0c6b 100644 --- a/src/corelib/global/qlibraryinfo.cpp +++ b/src/corelib/global/qlibraryinfo.cpp @@ -728,6 +728,11 @@ QLibraryInfo::rawLocation(LibraryLocation loc, PathGroup group) #ifndef QT_BUILD_QMAKE_BOOTSTRAP if (!fromConf) { + // "volatile" here is a hack to prevent compilers from doing a + // compile-time strlen() on "path". The issue is that Qt installers + // will binary-patch the Qt installation paths -- in such scenarios, Qt + // will be built with a dummy path, thus the compile-time result of + // strlen is meaningless. const char * volatile path = 0; if (loc == PrefixPath) { path = getPrefix( From 18aa8390ce83b4aa9cabe5609b8f830f86e475e5 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 3 Oct 2019 08:58:06 +0200 Subject: [PATCH 22/23] Add qt.qpa.input.events logging to evdevtouch The xcb platform plugin uses this category for detailed input event logging, so we might as well be consistent in evdevtouch. When hardware supports pressure sensing, it's likely to need extra debugging. Task-number: QTBUG-77142 Change-Id: I7682bb5d49e669054523f9cf556715e511bcd572 Reviewed-by: Laszlo Agocs --- .../input/evdevtouch/qevdevtouchhandler.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp index 2802c9b6473..c51db59e1ff 100644 --- a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp +++ b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp @@ -69,6 +69,7 @@ extern "C" { QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcEvdevTouch, "qt.qpa.input") +Q_LOGGING_CATEGORY(qLcEvents, "qt.qpa.input.events") /* android (and perhaps some other linux-derived stuff) don't define everything * in linux/input.h, so we'll need to do that ourselves. @@ -539,6 +540,9 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) if (m_typeB) m_contacts[m_currentSlot].maj = m_currentData.maj; } else if (data->code == ABS_PRESSURE || data->code == ABS_MT_PRESSURE) { + if (Q_UNLIKELY(qLcEvents().isDebugEnabled())) + qCDebug(qLcEvents, "EV_ABS code 0x%x: pressure %d; bounding to [%d,%d]", + data->code, data->value, hw_pressure_min, hw_pressure_max); m_currentData.pressure = qBound(hw_pressure_min, data->value, hw_pressure_max); if (m_typeB || m_singleTouch) m_contacts[m_currentSlot].pressure = m_currentData.pressure; @@ -781,6 +785,9 @@ void QEvdevTouchScreenData::reportPoints() tp.pressure = tp.state == Qt::TouchPointReleased ? 0 : 1; else tp.pressure = (tp.pressure - hw_pressure_min) / qreal(hw_pressure_max - hw_pressure_min); + + if (Q_UNLIKELY(qLcEvents().isDebugEnabled())) + qCDebug(qLcEvents) << "reporting" << tp; } // Let qguiapp pick the target window. From 5e421957de544224511c4823a4829ed2a34317d0 Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Thu, 10 Oct 2019 14:37:45 +0200 Subject: [PATCH 23/23] Fix strange qmake syntax It seems like qmake is happy with the comment, followed by a continuation and then another comment, but having the continuation at the end of the line makes more sense. This will make it easier to port to CMake. Change-Id: I20c964e8c3b6fea4745095783503045b191b000b Reviewed-by: Alexandru Croitor --- tests/auto/other/other.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/other/other.pro b/tests/auto/other/other.pro index c5426202e88..4206852e4c1 100644 --- a/tests/auto/other/other.pro +++ b/tests/auto/other/other.pro @@ -11,7 +11,7 @@ SUBDIRS=\ macplist \ networkselftest \ qaccessibility \ - # qaccessibilitylinux \ # QTBUG-44434 + # qaccessibilitylinux # QTBUG-44434 \ qaccessibilitymac \ qcomplextext \ qfocusevent \