summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2019-10-10 09:10:26 +0200
committerQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2019-10-10 09:10:27 +0200
commit68375b4f3dc9fef5412343ac4496a4af0db43839 (patch)
tree019e4756c61bba7aead031eed2dc4f4fafd99294 /tests
parent22891dd897b37be03222ec4881629628fb312442 (diff)
parent18aa8390ce83b4aa9cabe5609b8f830f86e475e5 (diff)
Merge remote-tracking branch 'origin/5.14' into 5.15
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/corelib/text/qstring/tst_qstring.cpp3
-rw-r--r--tests/auto/gui/rhi/qrhi/data/compile.bat48
-rw-r--r--tests/auto/gui/rhi/qrhi/data/qt256.pngbin0 -> 6208 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.frag8
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.frag.qsbbin0 -> 908 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.vert10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.vert.qsbbin0 -> 958 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.frag13
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsbbin0 -> 1479 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.vert14
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsbbin0 -> 1195 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/texture.frag12
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.frag19
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.frag.qsbbin0 -> 1997 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.vert (renamed from tests/auto/gui/rhi/qrhi/data/texture.vert)9
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.vert.qsbbin0 -> 1708 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp1487
-rw-r--r--tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp2
-rw-r--r--tests/libfuzzer/gui/iccparser/main.cpp2
-rw-r--r--tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp2
-rw-r--r--tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp2
21 files changed, 1609 insertions, 22 deletions
diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp
index cce3e601cd..c96210f53d 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()
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 0000000000..5b8a77b833
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/compile.bat
@@ -0,0 +1,48 @@
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::
+:: 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
+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/qt256.png b/tests/auto/gui/rhi/qrhi/data/qt256.png
new file mode 100644
index 0000000000..30c621c9c6
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/qt256.png
Binary files differ
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 0000000000..2aa500e09a
--- /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 0000000000..264b71ec0f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb
Binary files differ
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 0000000000..16ee61beca
--- /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 0000000000..59080b60c6
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb
Binary files differ
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 0000000000..630df7b807
--- /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 0000000000..f302702aa9
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb
Binary files differ
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 0000000000..1dd204f84d
--- /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 0000000000..e4f12bfb9e
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/texture.frag b/tests/auto/gui/rhi/qrhi/data/texture.frag
deleted file mode 100644
index e6021fe905..0000000000
--- a/tests/auto/gui/rhi/qrhi/data/texture.frag
+++ /dev/null
@@ -1,12 +0,0 @@
-#version 440
-
-layout(location = 0) in vec2 v_texcoord;
-
-layout(location = 0) out vec4 fragColor;
-
-layout(binding = 1) uniform sampler2D tex;
-
-void main()
-{
- fragColor = texture(tex, v_texcoord);
-}
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 0000000000..605410b028
--- /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 0000000000..0a039137ec
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/texture.vert b/tests/auto/gui/rhi/qrhi/data/textured.vert
index de486cb772..f1ccf2ee50 100644
--- a/tests/auto/gui/rhi/qrhi/data/texture.vert
+++ b/tests/auto/gui/rhi/qrhi/data/textured.vert
@@ -3,16 +3,17 @@
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texcoord;
-layout(location = 0) out vec2 v_texcoord;
+layout(location = 0) out vec2 uv;
layout(std140, binding = 0) uniform buf {
- mat4 mvp;
+ mat4 matrix;
+ float opacity;
} ubuf;
out gl_PerVertex { vec4 gl_Position; };
void main()
{
- v_texcoord = texcoord;
- gl_Position = ubuf.mvp * position;
+ uv = texcoord;
+ gl_Position = ubuf.matrix * position;
}
diff --git a/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb
new file mode 100644
index 0000000000..7853f77943
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index 897613d525..768b227ecd 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -30,10 +30,13 @@
#include <QThread>
#include <QFile>
#include <QOffscreenSurface>
+#include <QPainter>
+
#include <QtGui/private/qrhi_p.h>
#include <QtGui/private/qrhinull_p.h>
#if QT_CONFIG(opengl)
+# include <QOpenGLContext>
# include <QtGui/private/qrhigles2_p.h>
# define TST_GL
#endif
@@ -65,8 +68,29 @@ private slots:
void initTestCase();
void cleanupTestCase();
+ void rhiTestData();
void create_data();
void create();
+ void nativeHandles_data();
+ void nativeHandles();
+ void resourceUpdateBatchBuffer_data();
+ void resourceUpdateBatchBuffer();
+ void resourceUpdateBatchRGBATextureUpload_data();
+ void resourceUpdateBatchRGBATextureUpload();
+ void resourceUpdateBatchRGBATextureCopy_data();
+ void resourceUpdateBatchRGBATextureCopy();
+ void resourceUpdateBatchRGBATextureMip_data();
+ void resourceUpdateBatchRGBATextureMip();
+ void invalidPipeline_data();
+ void invalidPipeline();
+ void renderToTextureSimple_data();
+ void renderToTextureSimple();
+ void renderToTextureTexturedQuad_data();
+ void renderToTextureTexturedQuad();
+ void renderToTextureTexturedQuadAndUniformBuffer_data();
+ void renderToTextureTexturedQuadAndUniformBuffer();
+ void renderToWindowSimple_data();
+ void renderToWindowSimple();
private:
struct {
@@ -99,9 +123,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()
@@ -113,7 +154,7 @@ void tst_QRhi::cleanupTestCase()
delete fallbackSurface;
}
-void tst_QRhi::create_data()
+void tst_QRhi::rhiTestData()
{
QTest::addColumn<QRhi::Implementation>("impl");
QTest::addColumn<QRhiInitParams *>("initParams");
@@ -134,6 +175,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 +200,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 +259,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 +280,1450 @@ void tst_QRhi::create()
QRhi::NonFourAlignedEffectiveIndexBufferOffset,
QRhi::NPOTTextureRepeat,
QRhi::RedOrAlpha8IsRed,
- QRhi::ElementIndexUint
+ QRhi::ElementIndexUint,
+ QRhi::Compute,
+ QRhi::WideLines,
+ QRhi::VertexShaderPointSize,
+ QRhi::BaseVertex,
+ QRhi::BaseInstance,
+ QRhi::TriangleFanTopology,
+ QRhi::ReadBackNonUniformBuffer,
+ QRhi::ReadBackNonBaseMipLevel
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->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<QRhi> 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<const QRhiVulkanNativeHandles *>(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<const QRhiGles2NativeHandles *>(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<const QRhiD3D11NativeHandles *>(rhiHandles);
+ QVERIFY(d3dHandles->dev);
+ QVERIFY(d3dHandles->context);
+ }
+ break;
+#endif
+#ifdef TST_MTL
+ case QRhi::Metal:
+ {
+ const QRhiMetalNativeHandles *mtlHandles = static_cast<const QRhiMetalNativeHandles *>(rhiHandles);
+ QVERIFY(mtlHandles->dev);
+ QVERIFY(mtlHandles->cmdQueue);
+ }
+ break;
+#endif
+ default:
+ Q_ASSERT(false);
+ }
+ }
+
+ // QRhiTexture::nativeHandles()
+ {
+ QScopedPointer<QRhiTexture> 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<const QRhiVulkanTextureNativeHandles *>(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<const QRhiGles2TextureNativeHandles *>(texHandles);
+ QVERIFY(glHandles->texture);
+ }
+ break;
+#endif
+#ifdef TST_D3D11
+ case QRhi::D3D11:
+ {
+ const QRhiD3D11TextureNativeHandles *d3dHandles = static_cast<const QRhiD3D11TextureNativeHandles *>(texHandles);
+ QVERIFY(d3dHandles->texture);
+ }
+ break;
+#endif
+#ifdef TST_MTL
+ case QRhi::Metal:
+ {
+ const QRhiMetalTextureNativeHandles *mtlHandles = static_cast<const QRhiMetalTextureNativeHandles *>(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<const QRhiVulkanCommandBufferNativeHandles *>(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<const QRhiMetalCommandBufferNativeHandles *>(cbHandles);
+ QVERIFY(mtlHandles);
+ QVERIFY(mtlHandles->commandBuffer);
+ QVERIFY(!mtlHandles->encoder);
+
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget));
+ QVERIFY(tex->build());
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ QVERIFY(rpDesc);
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->build());
+ cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 });
+ QVERIFY(static_cast<const QRhiMetalCommandBufferNativeHandles *>(cb->nativeHandles())->encoder);
+ cb->endPass();
+ }
+ break;
+#endif
+ default:
+ Q_ASSERT(false);
+ }
+
+ rhi->endOffscreenFrame();
+ }
+
+ // QRhiRenderPassDescriptor::nativeHandles()
+ {
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget));
+ QVERIFY(tex->build());
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> 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<const QRhiVulkanRenderPassNativeHandles *>(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);
+ }
+ }
+}
+
+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();
+}
+
+void tst_QRhi::resourceUpdateBatchBuffer()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing buffer resource updates");
+
+ const int bufferSize = 23;
+ const QByteArray a(bufferSize, 'A');
+ const QByteArray b(bufferSize, 'B');
+
+ // dynamic buffer, updates, readback
+ {
+ QScopedPointer<QRhiBuffer> 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);
+
+ 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
+ // 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<QRhiBuffer> 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);
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+
+ 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");
+ }
+ }
+}
+
+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<const quint32 *>(image0.constScanLine(y));
+ const quint32 *p1 = reinterpret_cast<const quint32 *>(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<QRhi> 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<QRhiTexture> 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<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ image.format());
+
+ QVERIFY(imageRGBAEquals(image, wrapperImage));
+ }
+
+ // the same with raw data
+ {
+ QScopedPointer<QRhiTexture> 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<const uchar *>(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<QRhiTexture> 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<const uchar *>(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<QRhiTexture> 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<const char *>(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<const uchar *>(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));
+ }
+
+ // 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<QRhiTexture> 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<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ inputImage.format());
+
+ QVERIFY(imageRGBAEquals(inputImage, wrapperImage));
+ }
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureCopy()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing 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<QRhiTexture> redTexture(rhi->newTexture(QRhiTexture::RGBA8, red.size(),
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(redTexture->build());
+ batch->uploadTexture(redTexture.data(), red);
+
+ QScopedPointer<QRhiTexture> 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<QRhiTexture> 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<const uchar *>(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<const uchar *>(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<const uchar *>(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<QRhi> 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<QRhiTexture> 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<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ red.format());
+ 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));
+ }
+}
+
+static QShader loadShader(const char *name)
+{
+ QFile f(QString::fromUtf8(name));
+ if (f.open(QIODevice::ReadOnly)) {
+ const QByteArray contents = f.readAll();
+ return QShader::fromSerialized(contents);
+ }
+ return QShader();
+}
+
+void tst_QRhi::invalidPipeline_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::invalidPipeline()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing empty shader");
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(256, 256), 1, QRhiTexture::RenderTarget));
+ QVERIFY(texture->build());
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->build());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->build());
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 2 * sizeof(float) } });
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } });
+
+ // no stages
+ QScopedPointer<QRhiGraphicsPipeline> 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<QRhi> 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<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> 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<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ QVERIFY(vbuf->build());
+ updates->uploadStaticBuffer(vbuf.data(), vertices);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->build());
+
+ QScopedPointer<QRhiGraphicsPipeline> 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<const uchar *>(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<const quint32 *>(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);
+}
+
+void tst_QRhi::renderToTextureTexturedQuad_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureTexturedQuad()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing rendering");
+
+ QImage inputImage;
+ inputImage.load(QLatin1String(":/data/qt256.png"));
+ QVERIFY(!inputImage.isNull());
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> 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<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs)));
+ QVERIFY(vbuf->build());
+ updates->uploadStaticBuffer(vbuf.data(), verticesUvs);
+
+ QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
+ QVERIFY(inputTexture->build());
+ updates->uploadTexture(inputTexture.data(), inputImage);
+
+ QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
+ QVERIFY(sampler->build());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({
+ QRhiShaderResourceBinding::sampledTexture(0, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data())
+ });
+ QVERIFY(srb->build());
+
+ QScopedPointer<QRhiGraphicsPipeline> 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<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ QVERIFY(!result.isNull());
+
+ if (impl == QRhi::Null)
+ return;
+
+ // 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)));
+}
+
+void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing rendering");
+
+ QImage inputImage;
+ inputImage.load(QLatin1String(":/data/qt256.png"));
+ QVERIFY(!inputImage.isNull());
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> 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<QRhiBuffer> 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<QRhiBuffer> 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<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
+ QVERIFY(inputTexture->build());
+ updates->uploadTexture(inputTexture.data(), inputImage);
+
+ QScopedPointer<QRhiSampler> 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<QRhiShaderResourceBindings> srb0(rhi->newShaderResourceBindings());
+ srb0->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), 0, UNIFORM_BLOCK_SIZE),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data())
+ });
+ QVERIFY(srb0->build());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
+ srb1->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), secondUbufOffset, UNIFORM_BLOCK_SIZE),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data())
+ });
+ QVERIFY(srb1->build());
+ QVERIFY(srb1->isLayoutCompatible(srb0.data())); // hence no need for a second pipeline
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
+ QShader vs = loadShader(":/data/textured.vert.qsb");
+ QVERIFY(vs.isValid());
+ QShaderDescription shaderDesc = vs.description();
+ QVERIFY(!shaderDesc.uniformBlocks().isEmpty());
+ QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE);
+
+ QShader fs = loadShader(":/data/textured.frag.qsb");
+ QVERIFY(fs.isValid());
+ shaderDesc = fs.description();
+ QVERIFY(!shaderDesc.uniformBlocks().isEmpty());
+ QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE);
+
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 4 * sizeof(float) } });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
+ });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb0.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->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<const uchar *>(readResult0.data.constData()),
+ readResult0.pixelSize.width(), readResult0.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult0);
+ cb->endPass(readbackBatch);
+
+ // second pass (rotated)
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 });
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setShaderResources(srb1.data()); // sources data from a different offset in ubuf
+ cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) });
+ cb->setVertexInput(0, 1, &vbindings);
+ cb->draw(4);
+
+ QRhiReadbackResult readResult1;
+ QImage result1;
+ readResult1.completed = [&readResult1, &result1] {
+ result1 = QImage(reinterpret_cast<const uchar *>(readResult1.data.constData()),
+ readResult1.pixelSize.width(), readResult1.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ };
+ readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult1);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ QVERIFY(!result0.isNull());
+ QVERIFY(!result1.isNull());
+
+ if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) {
+ result0 = std::move(result0).mirrored();
+ result1 = std::move(result1).mirrored();
+ }
+
+ if (impl == QRhi::Null)
+ return;
+
+ // opacity 0.5 (premultiplied)
+ static const auto checkSemiWhite = [](const QRgb &c) {
+ QRgb semiWhite127 = qPremultiply(qRgba(255, 255, 255, 127));
+ QRgb semiWhite128 = qPremultiply(qRgba(255, 255, 255, 128));
+ return c == semiWhite127 || c == semiWhite128;
+ };
+ QVERIFY(checkSemiWhite(result0.pixel(79, 77)));
+ QVERIFY(checkSemiWhite(result0.pixel(124, 81)));
+ QVERIFY(checkSemiWhite(result0.pixel(128, 149)));
+ QVERIFY(checkSemiWhite(result0.pixel(120, 189)));
+ QVERIFY(checkSemiWhite(result0.pixel(116, 185)));
+ QVERIFY(checkSemiWhite(result0.pixel(191, 172)));
+
+ QRgb empty = qRgba(0, 0, 0, 0);
+ QCOMPARE(result0.pixel(11, 45), empty);
+ QCOMPARE(result0.pixel(246, 202), empty);
+ QCOMPARE(result0.pixel(130, 18), empty);
+ QCOMPARE(result0.pixel(4, 227), empty);
+
+ // also rotated 45 degrees around Z
+ QRgb black = qRgba(0, 0, 0, 255);
+ QCOMPARE(result1.pixel(20, 23), black);
+ QCOMPARE(result1.pixel(47, 5), black);
+ QCOMPARE(result1.pixel(238, 22), black);
+ QCOMPARE(result1.pixel(250, 203), black);
+ QCOMPARE(result1.pixel(224, 237), black);
+ QCOMPARE(result1.pixel(12, 221), black);
+
+ QVERIFY(checkSemiWhite(result1.pixel(142, 67)));
+ QVERIFY(checkSemiWhite(result1.pixel(81, 79)));
+ QVERIFY(checkSemiWhite(result1.pixel(79, 168)));
+ QVERIFY(checkSemiWhite(result1.pixel(146, 204)));
+ QVERIFY(checkSemiWhite(result1.pixel(186, 156)));
+
+ QCOMPARE(result1.pixel(204, 45), empty);
+ QCOMPARE(result1.pixel(28, 178), empty);
+}
+
+void tst_QRhi::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<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing rendering");
+
+ QScopedPointer<QWindow> 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<QRhiSwapChain> swapChain(rhi->newSwapChain());
+ swapChain->setWindow(window.data());
+ swapChain->setFlags(QRhiSwapChain::UsedAsTransferSource);
+ QScopedPointer<QRhiRenderPassDescriptor> 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<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ QVERIFY(vbuf->build());
+ updates->uploadStaticBuffer(vbuf.data(), vertices);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->build());
+
+ QScopedPointer<QRhiGraphicsPipeline> 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<const uchar *>(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<const quint32 *>(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 <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)
diff --git a/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp b/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp
index 5a60c78cb5..7b73e6e952 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 <QXmlStreamReader>
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 ba4f70ef3b..1db43d2e25 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 c9b33d0f88..51fa3c9e0f 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 acdd91e06e..66ddf738f2 100644
--- a/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp
+++ b/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp
@@ -29,6 +29,6 @@
#include <QTextDocument>
extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) {
- QTextDocument().setMarkdown(QByteArray(Data, Size));
+ QTextDocument().setMarkdown(QByteArray::fromRawData(Data, Size));
return 0;
}