summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/rhi
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/gui/rhi')
-rw-r--r--tests/auto/gui/rhi/CMakeLists.txt3
-rw-r--r--tests/auto/gui/rhi/qrhi/BLACKLIST2
-rw-r--r--tests/auto/gui/rhi/qrhi/CMakeLists.txt11
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat19
-rw-r--r--tests/auto/gui/rhi/qrhi/data/half.vert10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/half.vert.qsbbin0 -> 815 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.frag10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.frag.qsbbin0 -> 2425 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.vert18
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.vert.qsbbin0 -> 3644 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.frag10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsbbin0 -> 591 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tesc22
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsbbin0 -> 1402 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tese17
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsbbin0 -> 1401 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.vert12
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsbbin0 -> 936 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer.comp28
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsbbin0 -> 1262 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp25
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsbbin0 -> 1415 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag33
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsbbin0 -> 1259 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc42
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsbbin0 -> 2024 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese39
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsbbin0 -> 1854 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert48
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsbbin0 -> 2047 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsbbin0 -> 590 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc56
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsbbin0 -> 2873 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese96
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsbbin0 -> 4526 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert20
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsbbin0 -> 1306 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp2334
-rw-r--r--tests/auto/gui/rhi/qshader/CMakeLists.txt11
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsbbin0 -> 729 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsbbin0 -> 1749 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsbbin0 -> 2390 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsbbin0 -> 1106 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data/storage_buffer_info_v8.comp.qsbbin0 -> 902 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data_src/color.vert (renamed from tests/auto/gui/rhi/qshader/data/color.vert)0
-rw-r--r--tests/auto/gui/rhi/qshader/data_src/texture.frag (renamed from tests/auto/gui/rhi/qshader/data/texture.frag)0
-rw-r--r--tests/auto/gui/rhi/qshader/data_src/texture_sep.frag (renamed from tests/auto/gui/rhi/qshader/data/texture_sep.frag)0
-rw-r--r--tests/auto/gui/rhi/qshader/tst_qshader.cpp228
48 files changed, 2957 insertions, 137 deletions
diff --git a/tests/auto/gui/rhi/CMakeLists.txt b/tests/auto/gui/rhi/CMakeLists.txt
index 786e121f00..898a67d2dc 100644
--- a/tests/auto/gui/rhi/CMakeLists.txt
+++ b/tests/auto/gui/rhi/CMakeLists.txt
@@ -1,4 +1,5 @@
-# Generated from rhi.pro.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qshader)
add_subdirectory(qrhi)
diff --git a/tests/auto/gui/rhi/qrhi/BLACKLIST b/tests/auto/gui/rhi/qrhi/BLACKLIST
index dc2bc57077..b3284f8979 100644
--- a/tests/auto/gui/rhi/qrhi/BLACKLIST
+++ b/tests/auto/gui/rhi/qrhi/BLACKLIST
@@ -17,3 +17,5 @@ android
android
[renderToRgb10Texture]
android
+[tessellation vulkan]
+android
diff --git a/tests/auto/gui/rhi/qrhi/CMakeLists.txt b/tests/auto/gui/rhi/qrhi/CMakeLists.txt
index e0e97f6509..3b0d643060 100644
--- a/tests/auto/gui/rhi/qrhi/CMakeLists.txt
+++ b/tests/auto/gui/rhi/qrhi/CMakeLists.txt
@@ -1,9 +1,16 @@
-# Generated from qrhi.pro.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qrhi Test:
#####################################################################
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qrhi LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
# Resources:
file(GLOB_RECURSE qrhi_resource_files
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
@@ -13,7 +20,7 @@ file(GLOB_RECURSE qrhi_resource_files
qt_internal_add_test(tst_qrhi
SOURCES
tst_qrhi.cpp
- PUBLIC_LIBRARIES
+ LIBRARIES
Qt::Gui
Qt::GuiPrivate
TESTDATA ${qrhi_resource_files}
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 68d9bb8ae7..fe40459719 100644
--- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
+++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
@@ -1,5 +1,5 @@
:: Copyright (C) 2019 The Qt Company Ltd.
-:: SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+:: SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
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
@@ -11,3 +11,20 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.vert.qsb textured_multiubuf.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.frag.qsb textured_multiubuf.frag
+qsb --glsl 320es,410 --msl 12 --msltess simpletess.vert -o simpletess.vert.qsb
+qsb --glsl 320es,410 --msl 12 --tess-mode triangles simpletess.tesc -o simpletess.tesc.qsb
+qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 simpletess.tese -o simpletess.tese.qsb
+qsb --glsl 320es,410 --msl 12 simpletess.frag -o simpletess.frag.qsb
+qsb --glsl 310es,430 --msl 12 --hlsl 50 storagebuffer.comp -o storagebuffer.comp.qsb
+qsb --glsl 320es,430 --msl 12 --msltess storagebuffer_runtime.vert -o storagebuffer_runtime.vert.qsb
+qsb --glsl 320es,430 --msl 12 --tess-mode triangles storagebuffer_runtime.tesc -o storagebuffer_runtime.tesc.qsb
+qsb --glsl 320es,430 --msl 12 --tess-vertex-count 3 storagebuffer_runtime.tese -o storagebuffer_runtime.tese.qsb
+qsb --glsl 320es,430 --msl 12 storagebuffer_runtime.frag -o storagebuffer_runtime.frag.qsb
+qsb --glsl 320es,430 --hlsl 50 -c --msl 12 storagebuffer_runtime.comp -o storagebuffer_runtime.comp.qsb
+qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o half.vert.qsb half.vert
+qsb --glsl 320es,430 --msl 21 --msltess tessinterfaceblocks.vert -o tessinterfaceblocks.vert.qsb
+qsb --glsl 320es,430 --msl 21 --tess-mode triangles tessinterfaceblocks.tesc -o tessinterfaceblocks.tesc.qsb
+qsb --glsl 320es,430 --msl 21 --tess-vertex-count 3 tessinterfaceblocks.tese -o tessinterfaceblocks.tese.qsb
+qsb --glsl 320es,430 --msl 21 simpletess.frag -o tessinterfaceblocks.frag.qsb
+qsb --view-count 2 --glsl "300 es,330" --hlsl 61 -c --msl 12 multiview.vert -o multiview.vert.qsb
+qsb --glsl "300 es,330" --hlsl 61 -c --msl 12 multiview.frag -o multiview.frag.qsb
diff --git a/tests/auto/gui/rhi/qrhi/data/half.vert b/tests/auto/gui/rhi/qrhi/data/half.vert
new file mode 100644
index 0000000000..b503201351
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/half.vert
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 position;
+
+out gl_PerVertex { vec4 gl_Position; };
+
+void main()
+{
+ gl_Position = vec4(position, 1.0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/half.vert.qsb b/tests/auto/gui/rhi/qrhi/data/half.vert.qsb
new file mode 100644
index 0000000000..fb8680024a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/half.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.frag b/tests/auto/gui/rhi/qrhi/data/multiview.frag
new file mode 100644
index 0000000000..375587662f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.frag
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 v_color;
+
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(v_color, 1.0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb b/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb
new file mode 100644
index 0000000000..db8133f12e
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.vert b/tests/auto/gui/rhi/qrhi/data/multiview.vert
new file mode 100644
index 0000000000..b9c9e5a704
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.vert
@@ -0,0 +1,18 @@
+#version 440
+#extension GL_EXT_multiview : require
+
+layout(location = 0) in vec4 pos;
+layout(location = 1) in vec3 color;
+
+layout(location = 0) out vec3 v_color;
+
+layout(std140, binding = 0) uniform buf
+{
+ mat4 mvp[2];
+};
+
+void main()
+{
+ v_color = color;
+ gl_Position = mvp[gl_ViewIndex] * pos;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb b/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb
new file mode 100644
index 0000000000..cf1f67f58f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.frag b/tests/auto/gui/rhi/qrhi/data/simpletess.frag
new file mode 100644
index 0000000000..375587662f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.frag
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 v_color;
+
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(v_color, 1.0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb
new file mode 100644
index 0000000000..0f42103ac5
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tesc b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc
new file mode 100644
index 0000000000..e192fc77c7
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc
@@ -0,0 +1,22 @@
+#version 440
+
+layout(vertices = 3) out;
+
+layout(location = 0) in vec3 inColor[];
+layout(location = 0) out vec3 outColor[];
+layout(location = 1) patch out float a_per_patch_output_variable;
+
+void main()
+{
+ if (gl_InvocationID == 0) {
+ gl_TessLevelOuter[0] = 4.0;
+ gl_TessLevelOuter[1] = 4.0;
+ gl_TessLevelOuter[2] = 4.0;
+
+ gl_TessLevelInner[0] = 4.0;
+ }
+
+ gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+ outColor[gl_InvocationID] = inColor[gl_InvocationID];
+ a_per_patch_output_variable = 1.0;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb
new file mode 100644
index 0000000000..8c98d92c46
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tese b/tests/auto/gui/rhi/qrhi/data/simpletess.tese
new file mode 100644
index 0000000000..17b348635a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tese
@@ -0,0 +1,17 @@
+#version 440
+
+layout(triangles, fractional_odd_spacing, ccw) in;
+
+layout(location = 0) in vec3 inColor[];
+layout(location = 0) out vec3 outColor;
+layout(location = 1) patch in float a_per_patch_output_variable;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+};
+
+void main()
+{
+ gl_Position = mvp * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position));
+ outColor = gl_TessCoord.x * inColor[0] + gl_TessCoord.y * inColor[1] + gl_TessCoord.z * inColor[2] * a_per_patch_output_variable;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb
new file mode 100644
index 0000000000..8aa7632717
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.vert b/tests/auto/gui/rhi/qrhi/data/simpletess.vert
new file mode 100644
index 0000000000..3838d2f3bb
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.vert
@@ -0,0 +1,12 @@
+#version 440
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 color;
+
+layout(location = 0) out vec3 v_color;
+
+void main()
+{
+ gl_Position = vec4(position, 1.0);
+ v_color = color;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsb
new file mode 100644
index 0000000000..ee90983e0b
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp
new file mode 100644
index 0000000000..ffa0bc7004
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp
@@ -0,0 +1,28 @@
+#version 430
+layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+
+layout (binding = 0, std430) readonly buffer toGpu
+{
+ float _float;
+ vec2 _vec2;
+ vec3 _vec3;
+ vec4 _vec4;
+};
+
+layout (binding = 1, std140) buffer fromGpu
+{
+ int _int;
+ ivec2 _ivec2;
+ ivec3 _ivec3;
+ ivec4 _ivec4;
+};
+
+void main()
+{
+ _int = int(_float);
+ _ivec2 = ivec2(_vec2);
+ _ivec3 = ivec3(_vec3);
+ _ivec4 = ivec4(_vec4);
+}
+
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb
new file mode 100644
index 0000000000..b02f541cc5
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp
new file mode 100644
index 0000000000..d36f5426bc
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp
@@ -0,0 +1,25 @@
+#version 430
+
+layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+layout (binding = 0, std430) buffer toGpu
+{
+ float _float[];
+};
+
+
+layout (binding = 1, std140) buffer fromGpu
+{
+ int _int[];
+};
+
+void main()
+{
+ int length = min(_float.length(), _int.length());
+
+ for (int i = 0; i < length; ++i)
+ _int[i] = int(_float[i]);
+
+}
+
+
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb
new file mode 100644
index 0000000000..b4c43ecc9b
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag
new file mode 100644
index 0000000000..2e45a5f62a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag
@@ -0,0 +1,33 @@
+#version 450
+
+layout (location = 0) out vec4 fragColor;
+
+layout (std430, binding = 1) readonly buffer ssboG
+{
+ float g[];
+};
+
+layout (std430, binding = 2) readonly buffer ssboB
+{
+ float b[];
+};
+
+layout (std430, binding = 6) readonly buffer ssboR
+{
+ float r[];
+};
+
+layout (std430, binding = 3) readonly buffer ssbo3
+{
+ vec4 _vec4;
+};
+
+void main()
+{
+
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0.
+ float a = (r[0]+g[0]+b[0])>0?1:1;
+
+ fragColor = a * vec4(r.length(), g.length(), b.length(), 255)/vec4(255);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb
new file mode 100644
index 0000000000..53fc9a1906
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc
new file mode 100644
index 0000000000..56060285d2
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc
@@ -0,0 +1,42 @@
+#version 450
+
+layout(vertices = 3) out;
+
+
+layout (std430, binding = 7) readonly buffer ssbo7
+{
+ float float7[];
+};
+
+layout (std430, binding = 8) readonly buffer ssbo8
+{
+ float float8[];
+};
+
+layout (std430, binding = 9) readonly buffer ssbo9
+{
+ float float9[];
+};
+
+layout (std430, binding = 10) readonly buffer ssbo10
+{
+ float float10[];
+};
+
+void main()
+{
+
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0
+ float a = float7[0] == 0 && float8[0] == 0 && float9[0] == 0 && float10[0] == 0 ? 1 : 1;
+
+ if (gl_InvocationID == 0) {
+ gl_TessLevelOuter[0] = float7.length() * a;
+ gl_TessLevelOuter[1] = float8.length() * a;
+ gl_TessLevelOuter[2] = float9.length() * a;
+ gl_TessLevelInner[0] = float10.length() * a;
+ }
+
+ gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb
new file mode 100644
index 0000000000..e48aa0269c
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese
new file mode 100644
index 0000000000..a8bec13561
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese
@@ -0,0 +1,39 @@
+#version 450
+
+layout(triangles, fractional_odd_spacing, ccw) in;
+
+layout (std140, binding = 6) uniform unused0
+{
+ int unused;
+}u0;
+
+layout (binding = 0) uniform u
+{
+ mat4 matrix;
+};
+
+layout (std430, binding = 5) readonly buffer ssbo5
+{
+ float _float[];
+};
+
+layout (std430, binding = 8) readonly buffer ssbo8
+{
+ float float8[];
+};
+
+layout (std430, binding = 1) readonly buffer unused1
+{
+ int unused[];
+}u1;
+
+
+void main()
+{
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0
+ float a = _float[0] == 0 && float8[0] == 1 ? 1 : 1;
+
+ if(_float.length() == 64)
+ gl_Position = a * matrix * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position)) * (float8.length()==2?1:0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb
new file mode 100644
index 0000000000..23a433b5ae
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert
new file mode 100644
index 0000000000..b3ac10efea
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert
@@ -0,0 +1,48 @@
+#version 450
+
+layout (location = 0) in vec3 position;
+
+layout (std140, binding = 6) uniform unused0
+{
+ int unused;
+}u0;
+
+layout (binding = 0) uniform u
+{
+ mat4 matrix;
+};
+
+layout (std430, binding = 5) readonly buffer ssbo5
+{
+ float _float[];
+};
+
+layout (std140, binding = 3) readonly buffer ssbo3
+{
+ vec4 _vec4;
+};
+
+layout (std430, binding = 4) readonly buffer ssbo1
+{
+ bool _bool[];
+};
+
+layout (std430, binding = 1) readonly buffer unused1
+{
+ int unused[];
+}u1;
+
+
+void main()
+{
+
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0
+ float a = _float[0] == 0 && _bool[0] ? 1 : 1;
+
+ gl_Position = vec4(0);
+
+ if(_bool.length() == 32)
+ gl_Position = a * matrix * vec4(position*_vec4.xyz, _float.length() == 64 ? 1.0 : 0.0);
+
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb
new file mode 100644
index 0000000000..8b1cff52fd
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb
new file mode 100644
index 0000000000..7eda4bed2d
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc
new file mode 100644
index 0000000000..92a2dc28fa
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc
@@ -0,0 +1,56 @@
+#version 440
+
+layout(vertices = 3) out;
+
+layout(location = 4) in VertOut
+{
+ vec3 v_color;
+ int a;
+ float b;
+}vOut[];
+
+layout(location = 5) out TescOutA {
+ vec3 color;
+ int id;
+}tcOutA[];
+
+layout(location = 10) out TescOutB {
+ vec2 some;
+ int other[3];
+ vec3 variables;
+}tcOutB[];
+
+layout(location = 2) patch out TescOutC {
+ vec3 stuff;
+ float more_stuff;
+}tcOutC;
+
+void main()
+{
+ // tesc builtin outputs
+ gl_TessLevelOuter[0] = 1.0;
+ gl_TessLevelOuter[1] = 2.0;
+ gl_TessLevelOuter[2] = 3.0;
+ gl_TessLevelOuter[3] = 4.0;
+ gl_TessLevelInner[0] = 5.0;
+ gl_TessLevelInner[1] = 6.0;
+
+ gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+ gl_out[gl_InvocationID].gl_PointSize = 10 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[0] = 20.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[1] = 40.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[2] = 60.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[3] = 80.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[4] = 100.0 + gl_InvocationID;
+
+ // outputs
+ tcOutA[gl_InvocationID].color = vOut[gl_InvocationID].v_color;
+ tcOutA[gl_InvocationID].id = gl_InvocationID + 91;
+ tcOutB[gl_InvocationID].some = vec2(gl_InvocationID, vOut[gl_InvocationID].a);
+ tcOutB[gl_InvocationID].other[0] = gl_PrimitiveID + 10;
+ tcOutB[gl_InvocationID].other[1] = gl_PrimitiveID + 20;
+ tcOutB[gl_InvocationID].other[2] = gl_PrimitiveID + 30;
+ tcOutB[gl_InvocationID].variables = vec3(3.0f, vOut[gl_InvocationID].b, 17.0f);
+ tcOutC.stuff = vec3(1.0, 2.0, 3.0);
+ tcOutC.more_stuff = 4.0;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb
new file mode 100644
index 0000000000..b503d596c6
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese
new file mode 100644
index 0000000000..05430a5f63
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese
@@ -0,0 +1,96 @@
+#version 440
+
+layout(triangles, fractional_odd_spacing, ccw) in;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+};
+
+layout(location = 5) in TescOutA {
+ vec3 color;
+ int id;
+}tcOutA[];
+
+layout(location = 10) in TescOutB {
+ vec2 some;
+ int other[3];
+ vec3 variables;
+}tcOutB[];
+
+layout(location = 2) patch in TescOutC {
+ vec3 stuff;
+ float more_stuff;
+}tcOutC;
+
+layout(location = 0) out vec3 outColor;
+
+struct A {
+ vec3 color;
+ int id;
+};
+
+struct B {
+ vec2 some;
+ int other[3];
+ vec3 variables;
+};
+
+struct C {
+ vec3 stuff;
+ float more_stuff;
+};
+
+struct Element {
+ A a[3];
+ B b[3];
+ C c;
+ vec4 tesslevelOuter;
+ vec2 tessLevelInner;
+ float pointSize[3];
+ float clipDistance[3][5];
+ vec3 tessCoord;
+ int patchVerticesIn;
+ int primitiveID;
+};
+
+layout(std430, binding = 1) buffer result {
+ int count;
+ Element elements[];
+};
+
+void main()
+{
+ gl_Position = mvp * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position));
+ outColor = gl_TessCoord.x * tcOutA[0].color + gl_TessCoord.y * tcOutA[1].color + gl_TessCoord.z * tcOutA[2].color;
+
+ count = 1;
+
+ elements[gl_PrimitiveID].c.stuff = tcOutC.stuff;
+ elements[gl_PrimitiveID].c.more_stuff = tcOutC.more_stuff;
+ elements[gl_PrimitiveID].tesslevelOuter = vec4(gl_TessLevelOuter[0], gl_TessLevelOuter[1], gl_TessLevelOuter[2], gl_TessLevelOuter[3]);
+ elements[gl_PrimitiveID].tessLevelInner = vec2(gl_TessLevelInner[0], gl_TessLevelInner[1]);
+
+ for (int i = 0; i < 3; ++i) {
+
+ elements[gl_PrimitiveID].a[i].color = tcOutA[i].color;
+ elements[gl_PrimitiveID].a[i].id = tcOutA[i].id;
+
+ elements[gl_PrimitiveID].b[i].some = tcOutB[i].some;
+ elements[gl_PrimitiveID].b[i].other = tcOutB[i].other;
+ elements[gl_PrimitiveID].b[i].variables = tcOutB[i].variables;
+
+ elements[gl_PrimitiveID].pointSize[i] = gl_in[i].gl_PointSize;
+ elements[gl_PrimitiveID].clipDistance[i][0] = gl_in[i].gl_ClipDistance[0];
+ elements[gl_PrimitiveID].clipDistance[i][1] = gl_in[i].gl_ClipDistance[1];
+ elements[gl_PrimitiveID].clipDistance[i][2] = gl_in[i].gl_ClipDistance[2];
+ elements[gl_PrimitiveID].clipDistance[i][3] = gl_in[i].gl_ClipDistance[3];
+ elements[gl_PrimitiveID].clipDistance[i][4] = gl_in[i].gl_ClipDistance[4];
+
+ }
+
+ elements[gl_PrimitiveID].tessCoord = gl_TessCoord;
+ elements[gl_PrimitiveID].patchVerticesIn = 3;
+ elements[gl_PrimitiveID].primitiveID = gl_PrimitiveID;
+
+}
+
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb
new file mode 100644
index 0000000000..898bda454a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert
new file mode 100644
index 0000000000..7c722bb374
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert
@@ -0,0 +1,20 @@
+#version 440
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 color;
+
+
+layout(location = 4) out VertOut
+{
+ vec3 v_color;
+ int a;
+ float b;
+};
+
+void main()
+{
+ gl_Position = vec4(position, 1.0);
+ v_color = color;
+ a = gl_VertexIndex;
+ b = 13.0f + gl_VertexIndex;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb
new file mode 100644
index 0000000000..07384d643c
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.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 6409c43bc4..8929b69cec 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2019 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QThread>
@@ -9,31 +9,28 @@
#include <qrgbafloat.h>
#include <qrgba64.h>
-#include <QtGui/private/qrhi_p.h>
-#include <QtGui/private/qrhi_p_p.h>
-#include <QtGui/private/qrhinull_p.h>
+#include <private/qrhi_p.h>
#if QT_CONFIG(opengl)
# include <QOpenGLContext>
# include <QOpenGLFunctions>
-# include <QtGui/private/qrhigles2_p.h>
+# include <QtGui/private/qguiapplication_p.h>
+# include <qpa/qplatformintegration.h>
# define TST_GL
#endif
#if QT_CONFIG(vulkan)
# include <QVulkanInstance>
# include <QVulkanFunctions>
-# include <QtGui/private/qrhivulkan_p.h>
# define TST_VK
#endif
#ifdef Q_OS_WIN
-#include <QtGui/private/qrhid3d11_p.h>
# define TST_D3D11
+# define TST_D3D12
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
-# include <QtGui/private/qrhimetal_p.h>
+#if QT_CONFIG(metal)
# define TST_MTL
#endif
@@ -49,9 +46,10 @@ private slots:
void cleanupTestCase();
void rhiTestData();
- void rhiTestDataOpenGL();
void create_data();
void create();
+ void stats_data();
+ void stats();
void nativeHandles_data();
void nativeHandles();
void nativeHandlesImportVulkan();
@@ -73,6 +71,8 @@ private slots:
void resourceUpdateBatchTextureRawDataStride();
void resourceUpdateBatchLotsOfResources_data();
void resourceUpdateBatchLotsOfResources();
+ void resourceUpdateBatchBetweenFrames_data();
+ void resourceUpdateBatchBetweenFrames();
void invalidPipeline_data();
void invalidPipeline();
void srbLayoutCompatibility_data();
@@ -83,6 +83,8 @@ private slots:
void renderPassDescriptorCompatibility();
void renderPassDescriptorClone_data();
void renderPassDescriptorClone();
+ void textureWithSampleCount_data();
+ void textureWithSampleCount();
void renderToTextureSimple_data();
void renderToTextureSimple();
@@ -104,12 +106,16 @@ private slots:
void renderToTextureTexturedQuadAllDynamicBuffers();
void renderToTextureDeferredSrb_data();
void renderToTextureDeferredSrb();
+ void renderToTextureDeferredUpdateSamplerInSrb_data();
+ void renderToTextureDeferredUpdateSamplerInSrb();
void renderToTextureMultipleUniformBuffersAndDynamicOffset_data();
void renderToTextureMultipleUniformBuffersAndDynamicOffset();
void renderToTextureSrbReuse_data();
void renderToTextureSrbReuse();
void renderToTextureIndexedDraw_data();
void renderToTextureIndexedDraw();
+ void renderToTextureArrayMultiView_data();
+ void renderToTextureArrayMultiView();
void renderToWindowSimple_data();
void renderToWindowSimple();
void finishWithinSwapchainFrame_data();
@@ -121,12 +127,12 @@ private slots:
void pipelineCache_data();
void pipelineCache();
- void textureImportOpenGL_data();
void textureImportOpenGL();
- void renderbufferImportOpenGL_data();
void renderbufferImportOpenGL();
void threeDimTexture_data();
void threeDimTexture();
+ void oneDimTexture_data();
+ void oneDimTexture();
void leakedResourceDestroy_data();
void leakedResourceDestroy();
@@ -135,6 +141,22 @@ private slots:
void renderToRgb10Texture_data();
void renderToRgb10Texture();
+ void tessellation_data();
+ void tessellation();
+
+ void tessellationInterfaceBlocks_data();
+ void tessellationInterfaceBlocks();
+
+ void storageBuffer_data();
+ void storageBuffer();
+ void storageBufferRuntimeSizeCompute_data();
+ void storageBufferRuntimeSizeCompute();
+ void storageBufferRuntimeSizeGraphics_data();
+ void storageBufferRuntimeSizeGraphics();
+
+ void halfPrecisionAttributes_data();
+ void halfPrecisionAttributes();
+
private:
void setWindowType(QWindow *window, QRhi::Implementation impl);
@@ -147,7 +169,10 @@ private:
QRhiVulkanInitParams vk;
#endif
#ifdef TST_D3D11
- QRhiD3D11InitParams d3d;
+ QRhiD3D11InitParams d3d11;
+#endif
+#ifdef TST_D3D12
+ QRhiD3D12InitParams d3d12;
#endif
#ifdef TST_MTL
QRhiMetalInitParams mtl;
@@ -163,6 +188,12 @@ private:
void tst_QRhi::initTestCase()
{
#ifdef TST_GL
+ QSurfaceFormat fmt;
+ fmt.setDepthBufferSize(24);
+ fmt.setStencilBufferSize(8);
+ QSurfaceFormat::setDefaultFormat(fmt);
+
+ initParams.gl.format = QSurfaceFormat::defaultFormat();
fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
initParams.gl.fallbackSurface = fallbackSurface;
#endif
@@ -172,7 +203,7 @@ void tst_QRhi::initTestCase()
if (supportedVersion >= QVersionNumber(1, 2))
vulkanInstance.setApiVersion(QVersionNumber(1, 2));
else if (supportedVersion >= QVersionNumber(1, 1))
- vulkanInstance.setApiVersion(QVersionNumber(1, 2));
+ vulkanInstance.setApiVersion(QVersionNumber(1, 1));
vulkanInstance.setLayers({ "VK_LAYER_KHRONOS_validation" });
vulkanInstance.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
vulkanInstance.create();
@@ -180,7 +211,10 @@ void tst_QRhi::initTestCase()
#endif
#ifdef TST_D3D11
- initParams.d3d.enableDebugLayer = true;
+ initParams.d3d11.enableDebugLayer = true;
+#endif
+#ifdef TST_D3D12
+ initParams.d3d12.enableDebugLayer = true;
#endif
}
@@ -203,30 +237,24 @@ void tst_QRhi::rhiTestData()
QTest::newRow("Null") << QRhi::Null << static_cast<QRhiInitParams *>(&initParams.null);
#endif
#ifdef TST_GL
- QTest::newRow("OpenGL") << QRhi::OpenGLES2 << static_cast<QRhiInitParams *>(&initParams.gl);
+ if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL))
+ QTest::newRow("OpenGL") << QRhi::OpenGLES2 << static_cast<QRhiInitParams *>(&initParams.gl);
#endif
#ifdef TST_VK
if (vulkanInstance.isValid())
QTest::newRow("Vulkan") << QRhi::Vulkan << static_cast<QRhiInitParams *>(&initParams.vk);
#endif
#ifdef TST_D3D11
- QTest::newRow("Direct3D 11") << QRhi::D3D11 << static_cast<QRhiInitParams *>(&initParams.d3d);
+ QTest::newRow("Direct3D 11") << QRhi::D3D11 << static_cast<QRhiInitParams *>(&initParams.d3d11);
+#endif
+#ifdef TST_D3D12
+ QTest::newRow("Direct3D 12") << QRhi::D3D12 << static_cast<QRhiInitParams *>(&initParams.d3d12);
#endif
#ifdef TST_MTL
QTest::newRow("Metal") << QRhi::Metal << static_cast<QRhiInitParams *>(&initParams.mtl);
#endif
}
-void tst_QRhi::rhiTestDataOpenGL()
-{
- QTest::addColumn<QRhi::Implementation>("impl");
- QTest::addColumn<QRhiInitParams *>("initParams");
-
-#ifdef TST_GL
- QTest::newRow("OpenGL") << QRhi::OpenGLES2 << static_cast<QRhiInitParams *>(&initParams.gl);
-#endif
-}
-
void tst_QRhi::create_data()
{
rhiTestData();
@@ -255,6 +283,7 @@ void tst_QRhi::create()
QCOMPARE(rhi->backend(), impl);
QVERIFY(strcmp(rhi->backendName(), ""));
+ QVERIFY(!strcmp(rhi->backendName(), QRhi::backendName(rhi->backend())));
QVERIFY(!rhi->driverInfo().deviceName.isEmpty());
QCOMPARE(rhi->thread(), QThread::currentThread());
@@ -289,8 +318,13 @@ void tst_QRhi::create()
QVERIFY(resUpd);
resUpd->release();
- QVERIFY(!rhi->supportedSampleCounts().isEmpty());
- QVERIFY(rhi->supportedSampleCounts().contains(1));
+ const QVector<int> supportedSampleCounts = rhi->supportedSampleCounts();
+ QVERIFY(!supportedSampleCounts.isEmpty());
+ QVERIFY(supportedSampleCounts.contains(1));
+ for (int i = 1; i < supportedSampleCounts.count(); ++i) {
+ // Verify the list is sorted. Internally the backends rely on this.
+ QVERIFY(supportedSampleCounts[i] > supportedSampleCounts[i - 1]);
+ }
QVERIFY(rhi->ubufAlignment() > 0);
QCOMPARE(rhi->ubufAligned(123), aligned(123, rhi->ubufAlignment()));
@@ -383,7 +417,15 @@ void tst_QRhi::create()
QRhi::Tessellation,
QRhi::GeometryShader,
QRhi::TextureArrayRange,
- QRhi::NonFillPolygonMode
+ QRhi::NonFillPolygonMode,
+ QRhi::OneDimensionalTextures,
+ QRhi::OneDimensionalTextureMipmaps,
+ QRhi::HalfAttributes,
+ QRhi::RenderToOneDimensionalTexture,
+ QRhi::ThreeDimensionalTextureMipmaps,
+ QRhi::MultiView,
+ QRhi::TextureViewFormat,
+ QRhi::ResolveDepthStencil
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->isFeatureSupported(features[i]);
@@ -399,6 +441,38 @@ void tst_QRhi::create()
}
}
+void tst_QRhi::stats_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::stats()
+{
+ 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 statistics getter");
+
+ QRhiStats stats = rhi->statistics();
+ qDebug() << stats;
+ QCOMPARE(stats.totalPipelineCreationTime, 0);
+
+ if (impl == QRhi::Vulkan) {
+ QScopedPointer<QRhiBuffer> buf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 32768));
+ QVERIFY(buf->create());
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(1024, 1024)));
+ QVERIFY(tex->create());
+
+ stats = rhi->statistics();
+ qDebug() << stats;
+ QVERIFY(stats.allocCount > 0);
+ QVERIFY(stats.blockCount > 0);
+ QVERIFY(stats.usedBytes > 0);
+ }
+}
+
void tst_QRhi::nativeHandles_data()
{
rhiTestData();
@@ -425,10 +499,10 @@ void tst_QRhi::nativeHandles()
case QRhi::Vulkan:
{
const QRhiVulkanNativeHandles *vkHandles = static_cast<const QRhiVulkanNativeHandles *>(rhiHandles);
+ QVERIFY(vkHandles->inst);
+ QCOMPARE(vkHandles->inst, &vulkanInstance);
QVERIFY(vkHandles->physDev);
QVERIFY(vkHandles->dev);
- QVERIFY(vkHandles->gfxQueueFamilyIdx >= 0);
- QVERIFY(vkHandles->gfxQueueIdx >= 0);
QVERIFY(vkHandles->gfxQueue);
QVERIFY(vkHandles->vmemAllocator);
}
@@ -458,6 +532,17 @@ void tst_QRhi::nativeHandles()
}
break;
#endif
+#ifdef TST_D3D12
+ case QRhi::D3D12:
+ {
+ const QRhiD3D12NativeHandles *d3dHandles = static_cast<const QRhiD3D12NativeHandles *>(rhiHandles);
+ QVERIFY(d3dHandles->dev);
+ QVERIFY(d3dHandles->minimumFeatureLevel > 0);
+ QVERIFY(d3dHandles->adapterLuidLow || d3dHandles->adapterLuidHigh);
+ QVERIFY(d3dHandles->commandQueue);
+ }
+ break;
+#endif
#ifdef TST_MTL
case QRhi::Metal:
{
@@ -502,6 +587,10 @@ void tst_QRhi::nativeHandles()
case QRhi::D3D11:
break;
#endif
+#ifdef TST_D3D12
+ case QRhi::D3D12:
+ break;
+#endif
#ifdef TST_MTL
case QRhi::Metal:
{
@@ -561,6 +650,10 @@ void tst_QRhi::nativeHandles()
case QRhi::D3D11:
break;
#endif
+#ifdef TST_D3D12
+ case QRhi::D3D12:
+ break;
+#endif
#ifdef TST_MTL
case QRhi::Metal:
break;
@@ -628,7 +721,7 @@ void tst_QRhi::nativeHandlesImportVulkan()
void tst_QRhi::nativeHandlesImportD3D11()
{
#ifdef TST_D3D11
- QScopedPointer<QRhi> rhi(QRhi::create(QRhi::D3D11, &initParams.d3d, QRhi::Flags(), nullptr));
+ QScopedPointer<QRhi> rhi(QRhi::create(QRhi::D3D11, &initParams.d3d11, QRhi::Flags(), nullptr));
if (!rhi)
QSKIP("QRhi could not be created, skipping testing D3D11 native handle import");
@@ -640,7 +733,7 @@ void tst_QRhi::nativeHandlesImportD3D11()
h.featureLevel = 0; // see if these are queried as expected, even when not provided
h.adapterLuidLow = 0;
h.adapterLuidHigh = 0;
- QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d, QRhi::Flags(), &h));
+ QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d11, QRhi::Flags(), &h));
QVERIFY(adoptingRhi);
const QRhiD3D11NativeHandles *newNativeHandles = static_cast<const QRhiD3D11NativeHandles *>(adoptingRhi->nativeHandles());
QCOMPARE(newNativeHandles->dev, nativeHandles->dev);
@@ -655,7 +748,7 @@ void tst_QRhi::nativeHandlesImportD3D11()
QRhiD3D11NativeHandles h = *nativeHandles;
h.dev = nullptr;
h.context = nullptr;
- QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d, QRhi::Flags(), &h));
+ QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d11, QRhi::Flags(), &h));
QVERIFY(adoptingRhi);
const QRhiD3D11NativeHandles *newNativeHandles = static_cast<const QRhiD3D11NativeHandles *>(adoptingRhi->nativeHandles());
QVERIFY(newNativeHandles->dev != nativeHandles->dev);
@@ -675,7 +768,6 @@ void tst_QRhi::nativeHandlesImportOpenGL()
#ifdef TST_GL
QRhiGles2NativeHandles h;
QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext);
- ctx->setFormat(QRhiGles2InitParams::adjustedFormat());
if (!ctx->create())
QSKIP("No OpenGL context, skipping OpenGL-specific test");
h.context = ctx.data();
@@ -741,6 +833,14 @@ void tst_QRhi::nativeTexture()
}
break;
#endif
+#ifdef TST_D3D12
+ case QRhi::D3D12:
+ {
+ auto *texture = reinterpret_cast<void *>(nativeTex.object);
+ QVERIFY(texture);
+ }
+ break;
+#endif
#ifdef TST_MTL
case QRhi::Metal:
{
@@ -816,6 +916,18 @@ void tst_QRhi::nativeBuffer()
}
break;
#endif
+ #ifdef TST_D3D12
+ case QRhi::D3D12:
+ {
+ QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers
+ for (int i = 0; i < nativeBuf.slotCount; ++i) {
+ auto *buffer = static_cast<void * const *>(nativeBuf.objects[i]);
+ QVERIFY(buffer);
+ QVERIFY(*buffer);
+ }
+ }
+ break;
+ #endif
#ifdef TST_MTL
case QRhi::Metal:
{
@@ -880,7 +992,7 @@ void tst_QRhi::resourceUpdateBatchBuffer()
batch->updateDynamicBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData());
batch->updateDynamicBuffer(dynamicBuffer.data(), 0, 12, b.constData());
- QRhiBufferReadbackResult readResult;
+ QRhiReadbackResult readResult;
bool readCompleted = false;
readResult.completed = [&readCompleted] { readCompleted = true; };
batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult);
@@ -907,7 +1019,7 @@ void tst_QRhi::resourceUpdateBatchBuffer()
batch->uploadStaticBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData());
batch->uploadStaticBuffer(dynamicBuffer.data(), 0, 12, b.constData());
- QRhiBufferReadbackResult readResult;
+ QRhiReadbackResult readResult;
bool readCompleted = false;
readResult.completed = [&readCompleted] { readCompleted = true; };
@@ -1016,7 +1128,7 @@ void tst_QRhi::resourceUpdateBatchRGBATextureUpload()
QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
- QRhiTextureUploadEntry upload(0, 0, { image.constBits(), int(image.sizeInBytes()) });
+ QRhiTextureUploadEntry upload(0, 0, { image.constBits(), quint32(image.sizeInBytes()) });
QRhiTextureUploadDescription uploadDesc(upload);
batch->uploadTexture(texture.data(), uploadDesc);
@@ -1104,8 +1216,8 @@ void tst_QRhi::resourceUpdateBatchRGBATextureUpload()
// 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.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(im.constBits()), im.sizeInBytes()));
+
desc.setSourceSize(copySize);
desc.setDestinationTopLeft(QPoint(gap, gap));
@@ -1429,6 +1541,86 @@ void tst_QRhi::resourceUpdateBatchLotsOfResources()
submitResourceUpdates(rhi.data(), b);
}
+void tst_QRhi::resourceUpdateBatchBetweenFrames_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::resourceUpdateBatchBetweenFrames()
+{
+ 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 resource updates");
+
+ QImage image(128, 128, QImage::Format_RGBA8888_Premultiplied);
+ image.fill(Qt::red);
+ static const float bufferData[64] = {};
+
+ QRhiCommandBuffer *cb = nullptr;
+ QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb);
+ QVERIFY(result == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ static const int TEXTURE_COUNT = 123;
+ static const int BUFFER_COUNT = 456;
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ std::vector<std::unique_ptr<QRhiTexture>> textures;
+ std::vector<std::unique_ptr<QRhiBuffer>> buffers;
+
+ for (int i = 0; i < TEXTURE_COUNT; ++i) {
+ std::unique_ptr<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8,
+ image.size(),
+ 1,
+ QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+ u->uploadTexture(texture.get(), image);
+ textures.push_back(std::move(texture));
+ }
+
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ std::unique_ptr<QRhiBuffer> buffer(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 256));
+ QVERIFY(buffer->create());
+ u->uploadStaticBuffer(buffer.get(), bufferData);
+ buffers.push_back(std::move(buffer));
+ }
+
+ rhi->endOffscreenFrame();
+ cb = nullptr;
+
+ // 'u' stays valid, commit it in another frame
+
+ result = rhi->beginOffscreenFrame(&cb);
+ QVERIFY(result == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ cb->resourceUpdate(u); // this should work
+
+ rhi->endOffscreenFrame();
+
+ u = rhi->nextResourceUpdateBatch();
+ QRhiReadbackResult readResult;
+ bool readCompleted = false;
+ readResult.completed = [&readCompleted] { readCompleted = true; };
+ u->readBackTexture(textures[5].get(), &readResult);
+
+ QVERIFY(submitResourceUpdates(rhi.data(), u));
+ 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(),
+ QImage::Format_RGBA8888_Premultiplied);
+ for (int y = 0; y < image.height(); ++y) {
+ for (int x = 0; x < image.width(); ++x)
+ QCOMPARE(wrapperImage.pixel(x, y), qRgba(255, 0, 0, 255));
+ }
+}
+
static QShader loadShader(const char *name)
{
QFile f(QString::fromUtf8(name));
@@ -2917,6 +3109,147 @@ void tst_QRhi::renderToTextureDeferredSrb()
QCOMPARE(result.pixel(4, 227), empty);
}
+void tst_QRhi::renderToTextureDeferredUpdateSamplerInSrb_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureDeferredUpdateSamplerInSrb()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing rendering");
+
+ QImage inputImage;
+ inputImage.load(QLatin1String(":/data/qt256.png"));
+ QVERIFY(!inputImage.isNull());
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVerticesUvs)));
+ QVERIFY(vbuf->create());
+ updates->uploadStaticBuffer(vbuf.data(), quadVerticesUvs);
+
+ QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
+ QVERIFY(inputTexture->create());
+ updates->uploadTexture(inputTexture.data(), inputImage);
+
+ QScopedPointer<QRhiSampler> sampler1(rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
+ QRhiSampler::Repeat, QRhiSampler::Repeat));
+ QVERIFY(sampler1->create());
+ QScopedPointer<QRhiSampler> sampler2(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
+ QVERIFY(sampler2->create());
+
+ QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4));
+ QVERIFY(ubuf->create());
+
+ QMatrix4x4 matrix;
+ updates->updateDynamicBuffer(ubuf.data(), 0, 64, matrix.constData());
+ float opacity = 0.5f;
+ updates->updateDynamicBuffer(ubuf.data(), 64, 4, &opacity);
+
+ const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data()),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler1.data())
+ });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
+ QShader vs = loadShader(":/data/textured.vert.qsb");
+ QVERIFY(vs.isValid());
+ QShader fs = loadShader(":/data/textured.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->create());
+
+ // Now update the sampler to a different one, so if the pipeline->create()
+ // baked in static samplers somewhere (with 3D APIs where that's a thing),
+ // based on sampler1, that's now all invalid.
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data()),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler2.data())
+ });
+ srb->updateResources(); // now it references sampler2, not sampler1
+
+ 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;
+
+ if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC())
+ result = std::move(result).mirrored();
+
+ // 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(result.pixel(79, 77)));
+ QVERIFY(checkSemiWhite(result.pixel(124, 81)));
+ QVERIFY(checkSemiWhite(result.pixel(128, 149)));
+ QVERIFY(checkSemiWhite(result.pixel(120, 189)));
+ QVERIFY(checkSemiWhite(result.pixel(116, 185)));
+ QVERIFY(checkSemiWhite(result.pixel(191, 172)));
+
+ 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);
+}
+
void tst_QRhi::renderToTextureMultipleUniformBuffersAndDynamicOffset_data()
{
rhiTestData();
@@ -3234,7 +3567,6 @@ void tst_QRhi::setWindowType(QWindow *window, QRhi::Implementation impl)
switch (impl) {
#ifdef TST_GL
case QRhi::OpenGLES2:
- window->setFormat(QRhiGles2InitParams::adjustedFormat());
window->setSurfaceType(QSurface::OpenGLSurface);
break;
#endif
@@ -3375,6 +3707,182 @@ void tst_QRhi::renderToTextureIndexedDraw()
QVERIFY(redCount > blueCount);
}
+void tst_QRhi::renderToTextureArrayMultiView_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureArrayMultiView()
+{
+ 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");
+
+ if (!rhi->isFeatureSupported(QRhi::MultiView))
+ QSKIP("Multiview not supported, skipping testing on this backend");
+
+ if (rhi->backend() == QRhi::Vulkan && rhi->driverInfo().deviceType == QRhiDriverInfo::CpuDevice)
+ QSKIP("lavapipe does not like multiview, skip for now");
+
+ for (int sampleCount : rhi->supportedSampleCounts()) {
+ const QSize outputSize(1920, 1080);
+ QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget;
+ if (sampleCount <= 1)
+ textureFlags |= QRhiTexture::UsedAsTransferSource;
+ QScopedPointer<QRhiTexture> texture(rhi->newTextureArray(QRhiTexture::RGBA8, 2, outputSize, sampleCount, textureFlags));
+ QVERIFY(texture->create());
+
+ // exercise a depth-stencil buffer as well, not that the triangle needs it; note that this also needs to be a two-layer texture array
+ QScopedPointer<QRhiTexture> ds(rhi->newTextureArray(QRhiTexture::D24S8, 2, outputSize, sampleCount, QRhiTexture::RenderTarget));
+ QVERIFY(ds->create());
+
+ QScopedPointer<QRhiTexture> resolveTexture;
+ if (sampleCount > 1) {
+ resolveTexture.reset(rhi->newTextureArray(QRhiTexture::RGBA8, 2, outputSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(resolveTexture->create());
+ }
+
+ QRhiColorAttachment multiViewAtt(texture.get());
+ multiViewAtt.setMultiViewCount(2);
+ if (sampleCount > 1)
+ multiViewAtt.setResolveTexture(resolveTexture.get());
+
+ QRhiTextureRenderTargetDescription rtDesc(multiViewAtt);
+ rtDesc.setDepthTexture(ds.get());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ static float triangleData[] = {
+ 0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f
+ };
+
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleData)));
+ QVERIFY(vbuf->create());
+ updates->uploadStaticBuffer(vbuf.data(), triangleData);
+
+ QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 128)); // mat4 mvp[2]
+ QVERIFY(ubuf->create());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf.get())
+ });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> ps(rhi->newGraphicsPipeline());
+ ps->setShaderStages({
+ { QRhiShaderStage::Vertex, loadShader(":/data/multiview.vert.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/multiview.frag.qsb") }
+ });
+ ps->setMultiViewCount(2); // the view count must be set both on the render target and the pipeline
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, quint32(2 * sizeof(float)) }
+ });
+ ps->setDepthTest(true);
+ ps->setDepthWrite(true);
+ ps->setSampleCount(sampleCount);
+ ps->setVertexInputLayout(inputLayout);
+ ps->setShaderResourceBindings(srb.get());
+ ps->setRenderPassDescriptor(rpDesc.get());
+ QVERIFY(ps->create());
+
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ mvp.perspective(45.0f, outputSize.width() / float(outputSize.height()), 0.01f, 1000.0f);
+ mvp.translate(0, 0, -2);
+ mvp.rotate(90, 0, 0, 1); // point left
+ updates->updateDynamicBuffer(ubuf.get(), 0, 64, mvp.constData());
+ mvp.rotate(-180, 0, 0, 1); // point right
+ updates->updateDynamicBuffer(ubuf.get(), 64, 64, mvp.constData());
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates);
+ cb->setGraphicsPipeline(ps.data());
+ cb->setShaderResources();
+ cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) });
+ QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbindings);
+ cb->draw(3);
+
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ QRhiReadbackResult readResult[2];
+ QRhiReadbackDescription readbackDesc;
+ if (sampleCount > 1)
+ readbackDesc.setTexture(resolveTexture.get());
+ else
+ readbackDesc.setTexture(texture.get());
+ readbackDesc.setLayer(0);
+ readbackBatch->readBackTexture(readbackDesc, &readResult[0]);
+ readbackDesc.setLayer(1);
+ readbackBatch->readBackTexture(readbackDesc, &readResult[1]);
+
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ if (rhi->backend() == QRhi::Null)
+ QSKIP("No real content with Null backend, skipping multiview content check");
+
+ // both readbacks should be finished now due to using offscreen frames
+
+ QImage image0 = QImage(reinterpret_cast<const uchar *>(readResult[0].data.constData()),
+ readResult[0].pixelSize.width(), readResult[0].pixelSize.height(),
+ QImage::Format_RGBA8888);
+ if (rhi->isYUpInFramebuffer()) // note that we used clipSpaceCorrMatrix
+ image0 = image0.mirrored();
+
+ QImage image1 = QImage(reinterpret_cast<const uchar *>(readResult[1].data.constData()),
+ readResult[1].pixelSize.width(), readResult[1].pixelSize.height(),
+ QImage::Format_RGBA8888);
+ if (rhi->isYUpInFramebuffer())
+ image1 = image1.mirrored();
+
+ QVERIFY(!image0.isNull());
+ QVERIFY(!image1.isNull());
+
+ // image0 should have a triangle rotated so that it points left with the red
+ // tip. image1 should have a triangle rotated so that it points right with
+ // the red tip. Both are centered, so we will check in range 0..width/2 for
+ // image0 and width/2..width-1 for image1 to see if the red-enough pixels
+ // are present.
+
+ int y = image0.height() / 2;
+ int n = 0;
+ for (int x = 0; x < image0.width() / 2; ++x) {
+ QRgb c = image0.pixel(x, y);
+ if (qRed(c) > 250 && qGreen(c) < 10 && qBlue(c) < 10)
+ ++n;
+ }
+ QVERIFY(n >= 10);
+
+ y = image1.height() / 2;
+ n = 0;
+ for (int x = image1.width() / 2; x < image1.width(); ++x) {
+ QRgb c = image1.pixel(x, y);
+ if (qRed(c) > 250 && qGreen(c) < 10 && qBlue(c) < 10)
+ ++n;
+ }
+ QVERIFY(n >= 10);
+ }
+}
+
void tst_QRhi::renderToWindowSimple_data()
{
rhiTestData();
@@ -3629,7 +4137,7 @@ void tst_QRhi::resourceUpdateBatchBufferTextureWithSwapchainFrames()
const char *b = "abcdefghi";
bool readCompleted = false;
- QRhiBufferReadbackResult readResult;
+ QRhiReadbackResult readResult;
readResult.completed = [&readCompleted] { readCompleted = true; };
QRhiReadbackResult texReadResult;
texReadResult.completed = [&readCompleted] { readCompleted = true; };
@@ -3903,7 +4411,7 @@ void tst_QRhi::srbLayoutCompatibility()
QVERIFY(srb2->isLayoutCompatible(srb1.data()));
QCOMPARE(srb1->serializedLayoutDescription(), srb2->serializedLayoutDescription());
- QVERIFY(srb1->serializedLayoutDescription().count() == 0);
+ QVERIFY(srb1->serializedLayoutDescription().size() == 0);
}
// different count (not compatible)
@@ -3921,8 +4429,8 @@ void tst_QRhi::srbLayoutCompatibility()
QVERIFY(!srb2->isLayoutCompatible(srb1.data()));
QVERIFY(srb1->serializedLayoutDescription() != srb2->serializedLayoutDescription());
- QVERIFY(srb1->serializedLayoutDescription().count() == 0);
- QVERIFY(srb2->serializedLayoutDescription().count() == 1 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING);
+ QVERIFY(srb1->serializedLayoutDescription().size() == 0);
+ QVERIFY(srb2->serializedLayoutDescription().size() == 1 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING);
}
// full match (compatible)
@@ -3947,7 +4455,7 @@ void tst_QRhi::srbLayoutCompatibility()
QVERIFY(!srb1->serializedLayoutDescription().isEmpty());
QVERIFY(!srb2->serializedLayoutDescription().isEmpty());
QCOMPARE(srb1->serializedLayoutDescription(), srb2->serializedLayoutDescription());
- QVERIFY(srb1->serializedLayoutDescription().count() == 2 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING);
+ QVERIFY(srb1->serializedLayoutDescription().size() == 2 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING);
// see what we would get if a binding list got serialized "manually", without pulling it out from the srb after building
// (the results should be identical)
@@ -4262,6 +4770,59 @@ void tst_QRhi::renderPassDescriptorCompatibility()
} else {
qDebug("Skipping texture format dependent tests");
}
+
+ if (rhi->isFeatureSupported(QRhi::MultiView)) {
+ {
+ QScopedPointer<QRhiTexture> texArr(rhi->newTextureArray(QRhiTexture::RGBA8, 2, QSize(512, 512), 1, QRhiTexture::RenderTarget));
+ QVERIFY(texArr->create());
+ QRhiColorAttachment multiViewAtt(texArr.data());
+ multiViewAtt.setMultiViewCount(2);
+ QRhiTextureRenderTargetDescription rtDesc(multiViewAtt);
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor());
+ rt2->setRenderPassDescriptor(rpDesc2.data());
+ QVERIFY(rt2->create());
+
+ QVERIFY(rpDesc->isCompatible(rpDesc2.data()));
+ QVERIFY(rpDesc2->isCompatible(rpDesc.data()));
+ QCOMPARE(rpDesc->serializedFormat(), rpDesc2->serializedFormat());
+
+ QScopedPointer<QRhiRenderPassDescriptor> rpDescClone(rpDesc->newCompatibleRenderPassDescriptor());
+ QVERIFY(rpDesc->isCompatible(rpDescClone.data()));
+ QVERIFY(rpDesc2->isCompatible(rpDescClone.data()));
+ QCOMPARE(rpDesc->serializedFormat(), rpDescClone->serializedFormat());
+
+ // With Vulkan the multiViewCount really matters since it is baked
+ // in to underlying native object (VkRenderPass). Verify that the
+ // compatibility check fails when the view count differs. Other
+ // backends cannot do this test since they will likely report the
+ // rps being compatible regardless.
+ if (impl == QRhi::Vulkan) {
+ QRhiColorAttachment nonMultiViewAtt(texArr.data());
+ QRhiTextureRenderTargetDescription rtDesc3(nonMultiViewAtt);
+ QScopedPointer<QRhiTextureRenderTarget> rt3(rhi->newTextureRenderTarget(rtDesc3));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc3(rt3->newCompatibleRenderPassDescriptor());
+ rt3->setRenderPassDescriptor(rpDesc3.data());
+ QVERIFY(rt3->create());
+
+ QVERIFY(!rpDesc->isCompatible(rpDesc3.data()));
+ QVERIFY(!rpDesc2->isCompatible(rpDesc3.data()));
+ QVERIFY(rpDesc->serializedFormat() != rpDesc3->serializedFormat());
+
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc3Clone(rpDesc3->newCompatibleRenderPassDescriptor());
+ QVERIFY(!rpDesc->isCompatible(rpDesc3Clone.data()));
+ QVERIFY(!rpDesc2->isCompatible(rpDesc3Clone.data()));
+ QVERIFY(rpDesc->serializedFormat() != rpDesc3Clone->serializedFormat());
+ }
+ }
+ } else {
+ qDebug("Skipping multiview dependent tests");
+ }
}
void tst_QRhi::renderPassDescriptorClone_data()
@@ -4379,22 +4940,67 @@ void tst_QRhi::pipelineCache()
}
}
-void tst_QRhi::textureImportOpenGL_data()
+void tst_QRhi::textureWithSampleCount_data()
{
- rhiTestDataOpenGL();
+ rhiTestData();
}
-void tst_QRhi::textureImportOpenGL()
+void tst_QRhi::textureWithSampleCount()
{
QFETCH(QRhi::Implementation, impl);
- if (impl != QRhi::OpenGLES2)
- QSKIP("Skipping OpenGL-dependent test");
-
-#ifdef TST_GL
QFETCH(QRhiInitParams *, initParams);
QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing renderpass descriptors");
+
+ if (!rhi->isFeatureSupported(QRhi::MultisampleTexture))
+ QSKIP("No multisample texture support with this backend, skipping");
+
+ {
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1));
+ QVERIFY(tex->create());
+ }
+
+ // Ensure 0 is accepted the same way as 1.
+ {
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 0));
+ QVERIFY(tex->create());
+ }
+
+ // Note that we intentionally do not pass in RenderTarget in flags. Where
+ // matters for create(), the backend is expected to act as if it was
+ // specified whenever samples > 1. (in practice it does not make sense to not
+ // have the flag for an msaa texture, but we only care about create() here)
+
+ // Pick the commonly supported sample count of 4.
+ {
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 4));
+ QVERIFY(tex->create());
+ }
+
+ // Now a bogus value that is typically in-between the supported values.
+ {
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 3));
+ QVERIFY(tex->create());
+ }
+
+ // Now a bogus value that is out of range.
+ {
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 123));
+ QVERIFY(tex->create());
+ }
+}
+
+
+void tst_QRhi::textureImportOpenGL()
+{
+#ifdef TST_GL
+ if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL))
+ QSKIP("Skipping OpenGL-dependent test");
+
+ QScopedPointer<QRhi> rhi(QRhi::create(QRhi::OpenGLES2, &initParams.gl, QRhi::Flags(), nullptr));
+ if (!rhi)
QSKIP("QRhi could not be created, skipping testing native texture");
QVERIFY(rhi->makeThreadLocalNativeContextCurrent());
@@ -4433,21 +5039,13 @@ void tst_QRhi::textureImportOpenGL()
#endif
}
-void tst_QRhi::renderbufferImportOpenGL_data()
-{
- rhiTestDataOpenGL();
-}
-
void tst_QRhi::renderbufferImportOpenGL()
{
- QFETCH(QRhi::Implementation, impl);
- if (impl != QRhi::OpenGLES2)
- QSKIP("Skipping OpenGL-dependent test");
-
#ifdef TST_GL
- QFETCH(QRhiInitParams *, initParams);
+ if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL))
+ QSKIP("Skipping OpenGL-dependent test");
- QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ QScopedPointer<QRhi> rhi(QRhi::create(QRhi::OpenGLES2, &initParams.gl, QRhi::Flags(), nullptr));
if (!rhi)
QSKIP("QRhi could not be created, skipping testing native texture");
@@ -4539,7 +5137,7 @@ void tst_QRhi::threeDimTexture()
}
// mipmaps
- {
+ if (rhi->isFeatureSupported(QRhi::ThreeDimensionalTextureMipmaps)) {
QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, WIDTH, HEIGHT, DEPTH,
1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips));
QVERIFY(texture->create());
@@ -4584,6 +5182,8 @@ void tst_QRhi::threeDimTexture()
// problems with this.
if (impl != QRhi::Null && impl != QRhi::OpenGLES2)
QVERIFY(imageRGBAEquals(result, referenceImage, 2));
+ } else {
+ qDebug("Skipping 3D texture mipmap generation test because it is reported as unsupported");
}
// render target (one slice)
@@ -4602,11 +5202,531 @@ void tst_QRhi::threeDimTexture()
rt->setRenderPassDescriptor(rp.data());
QVERIFY(rt->create());
+ // render to slice 23
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+ cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 });
+ // slice 23 is now blue
+ cb->endPass();
+ rhi->endOffscreenFrame();
+
+ // Fill all other slices with some color. We should be free to do this
+ // step *before* the "render to slice 23" block above as well. However,
+ // as QTBUG-111772 shows, some Vulkan implementations have problems
+ // then. (or it could be QRhi is doing something wrong, but there is no
+ // evidence of that yet) For now, keep the order of first rendering to
+ // a slice and then uploading data for the rest.
QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
QVERIFY(batch);
-
for (int i = 0; i < DEPTH; ++i) {
- QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888);
+ if (i != SLICE) {
+ QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888);
+ img.fill(QColor::fromRgb(i * 2, 0, 0));
+ QRhiTextureUploadEntry sliceUpload(i, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(texture.data(), sliceUpload);
+ }
+ }
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+
+ // read back slice 23 (blue)
+ batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+ 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);
+ };
+ QRhiReadbackDescription readbackDescription(texture.data());
+ readbackDescription.setLayer(23);
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH, HEIGHT, result.format());
+ referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 1.0f));
+ // the Null backend does not render so skip the verification for that
+ if (impl != QRhi::Null)
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+
+ // read back slice 0 (black)
+ batch = rhi->nextResourceUpdateBatch();
+ result = QImage();
+ readbackDescription.setLayer(0);
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 0.0f));
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+
+ // read back slice 127 (almost red)
+ batch = rhi->nextResourceUpdateBatch();
+ result = QImage();
+ readbackDescription.setLayer(127);
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ referenceImage.fill(QColor::fromRgb(254, 0, 0));
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+ }
+}
+void tst_QRhi::oneDimTexture_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::oneDimTexture()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing 1D textures");
+
+ if (!rhi->isFeatureSupported(QRhi::OneDimensionalTextures))
+ QSKIP("Skipping testing 1D textures because they are reported as unsupported");
+
+ const int WIDTH = 512;
+ const int LAYERS = 128;
+
+ {
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, WIDTH, 0, 0));
+ QVERIFY(texture->create());
+
+ QVERIFY(texture->flags().testFlag(QRhiTexture::Flag::OneDimensional));
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
+ img.fill(QColor::fromRgb(255, 0, 0));
+
+ QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(texture.data(), upload);
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ }
+
+ {
+ QScopedPointer<QRhiTexture> texture(
+ rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0)));
+ QVERIFY(texture->create());
+
+ QVERIFY(texture->flags().testFlag(QRhiTexture::Flag::OneDimensional));
+ QVERIFY(texture->flags().testFlag(QRhiTexture::Flag::TextureArray));
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ for (int i = 0; i < LAYERS; ++i) {
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
+ img.fill(QColor::fromRgb(i * 2, 0, 0));
+ QRhiTextureUploadEntry layerUpload(i, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(texture.data(), layerUpload);
+ }
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ }
+
+ // Copy from 2D texture to 1D texture
+ {
+ const int WIDTH = 256;
+ const int HEIGHT = 256;
+
+ QScopedPointer<QRhiTexture> srcTexture(rhi->newTexture(
+ QRhiTexture::RGBA8, WIDTH, HEIGHT, 0, 1, QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(srcTexture->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888);
+ for (int x = 0; x < WIDTH; ++x) {
+ for (int y = 0; y < HEIGHT; ++y) {
+ img.setPixelColor(x, y, QColor::fromRgb(x, y, 0));
+ }
+ }
+ QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(srcTexture.data(), upload);
+
+ QScopedPointer<QRhiTexture> dstTexture(rhi->newTexture(
+ QRhiTexture::RGBA8, WIDTH, 0, 0, 1, QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(dstTexture->create());
+
+ QRhiTextureCopyDescription copy;
+ copy.setPixelSize(QSize(WIDTH / 2, 1));
+ copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0));
+ copy.setSourceTopLeft(QPoint(33, 67));
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ copy.setDestinationTopLeft(QPoint(0, 0));
+ copy.setSourceTopLeft(QPoint(99, 12));
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ 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);
+ };
+
+ QRhiReadbackDescription readbackDescription(dstTexture.data());
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH, 1, result.format());
+ for (int i = 0; i < WIDTH / 2; ++i) {
+ referenceImage.setPixelColor(i, 0, img.pixelColor(99 + i, 12));
+ referenceImage.setPixelColor(WIDTH / 2 + i, 0, img.pixelColor(33 + i, 67));
+ }
+
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+ }
+
+ // Copy from 2D texture to 1D texture array
+ {
+ const int WIDTH = 256;
+ const int HEIGHT = 256;
+ const int LAYERS = 64;
+
+ QScopedPointer<QRhiTexture> srcTexture(rhi->newTexture(
+ QRhiTexture::RGBA8, WIDTH, HEIGHT, 0, 1, QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(srcTexture->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888);
+ for (int x = 0; x < WIDTH; ++x) {
+ for (int y = 0; y < HEIGHT; ++y) {
+ img.setPixelColor(x, y, QColor::fromRgb(x, y, 0));
+ }
+ }
+ QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(srcTexture.data(), upload);
+
+ QScopedPointer<QRhiTexture> dstTexture(
+ rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1,
+ QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(dstTexture->create());
+
+ QRhiTextureCopyDescription copy;
+ copy.setPixelSize(QSize(WIDTH / 2, 1));
+ copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0));
+ copy.setSourceTopLeft(QPoint(33, 67));
+ copy.setDestinationLayer(12);
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ copy.setDestinationTopLeft(QPoint(0, 0));
+ copy.setSourceTopLeft(QPoint(99, 12));
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ 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);
+ };
+
+ QRhiReadbackDescription readbackDescription(dstTexture.data());
+ readbackDescription.setLayer(12);
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH, 1, result.format());
+ for (int i = 0; i < WIDTH / 2; ++i) {
+ referenceImage.setPixelColor(i, 0, img.pixelColor(99 + i, 12));
+ referenceImage.setPixelColor(WIDTH / 2 + i, 0, img.pixelColor(33 + i, 67));
+ }
+
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+ }
+
+ // Copy from 1D texture array to 1D texture
+ {
+ const int WIDTH = 256;
+ const int LAYERS = 256;
+
+ QScopedPointer<QRhiTexture> srcTexture(
+ rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1,
+ QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(srcTexture->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ for (int y = 0; y < LAYERS; ++y) {
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
+ for (int x = 0; x < WIDTH; ++x) {
+ img.setPixelColor(x, 0, QColor::fromRgb(x, y, 0));
+ }
+ QRhiTextureUploadEntry upload(y, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(srcTexture.data(), upload);
+ }
+
+ QScopedPointer<QRhiTexture> dstTexture(rhi->newTexture(
+ QRhiTexture::RGBA8, WIDTH, 0, 0, 1, QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(dstTexture->create());
+
+ QRhiTextureCopyDescription copy;
+ copy.setPixelSize(QSize(WIDTH / 2, 1));
+ copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0));
+ copy.setSourceLayer(67);
+ copy.setSourceTopLeft(QPoint(33, 0));
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ copy.setDestinationTopLeft(QPoint(0, 0));
+ copy.setSourceLayer(12);
+ copy.setSourceTopLeft(QPoint(99, 0));
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ 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);
+ };
+
+ QRhiReadbackDescription readbackDescription(dstTexture.data());
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH, 1, result.format());
+ for (int i = 0; i < WIDTH / 2; ++i) {
+ referenceImage.setPixelColor(i, 0, QColor::fromRgb(99 + i, 12, 0));
+ referenceImage.setPixelColor(WIDTH / 2 + i, 0, QColor::fromRgb(33 + i, 67, 0));
+ }
+
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+ }
+
+ // Copy from 1D texture to 1D texture array
+ {
+ const int WIDTH = 256;
+ const int LAYERS = 256;
+
+ QScopedPointer<QRhiTexture> srcTexture(rhi->newTexture(
+ QRhiTexture::RGBA8, WIDTH, 0, 0, 1, QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(srcTexture->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
+ for (int x = 0; x < WIDTH; ++x) {
+ img.setPixelColor(x, 0, QColor::fromRgb(x, 0, 0));
+ }
+ QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(srcTexture.data(), upload);
+
+ QScopedPointer<QRhiTexture> dstTexture(
+ rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1,
+ QRhiTexture::Flag::UsedAsTransferSource));
+ QVERIFY(dstTexture->create());
+
+ QRhiTextureCopyDescription copy;
+ copy.setPixelSize(QSize(WIDTH / 2, 1));
+ copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0));
+ copy.setDestinationLayer(67);
+ copy.setSourceTopLeft(QPoint(33, 0));
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ copy.setDestinationTopLeft(QPoint(0, 0));
+ copy.setSourceTopLeft(QPoint(99, 0));
+ batch->copyTexture(dstTexture.data(), srcTexture.data(), copy);
+
+ 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);
+ };
+
+ QRhiReadbackDescription readbackDescription(dstTexture.data());
+ readbackDescription.setLayer(67);
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH, 1, result.format());
+ for (int i = 0; i < WIDTH / 2; ++i) {
+ referenceImage.setPixelColor(i, 0, QColor::fromRgb(99 + i, 0, 0));
+ referenceImage.setPixelColor(WIDTH / 2 + i, 0, QColor::fromRgb(33 + i, 0, 0));
+ }
+
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+ }
+
+ // mipmaps and 1D render target
+ if (!rhi->isFeatureSupported(QRhi::OneDimensionalTextureMipmaps)
+ || !rhi->isFeatureSupported(QRhi::RenderToOneDimensionalTexture))
+ {
+ QSKIP("Skipping testing 1D texture mipmaps and 1D render target because they are reported as unsupported");
+ }
+
+ {
+ QScopedPointer<QRhiTexture> texture(
+ rhi->newTexture(QRhiTexture::RGBA8, WIDTH, 0, 0, 1,
+ QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips));
+ QVERIFY(texture->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
+ img.fill(QColor::fromRgb(128, 0, 0));
+ QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(texture.data(), upload);
+
+ batch->generateMips(texture.data());
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+
+ // read back level 1 (256x1, #800000ff)
+ batch = rhi->nextResourceUpdateBatch();
+ 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);
+ };
+ QRhiReadbackDescription readbackDescription(texture.data());
+ readbackDescription.setLevel(1);
+ readbackDescription.setLayer(0);
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH / 2, 1, result.format());
+ referenceImage.fill(QColor::fromRgb(128, 0, 0));
+
+ QVERIFY(imageRGBAEquals(result, referenceImage, 2));
+ }
+
+ {
+ QScopedPointer<QRhiTexture> texture(
+ rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1,
+ QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips));
+ QVERIFY(texture->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ for (int i = 0; i < LAYERS; ++i) {
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
+ img.fill(QColor::fromRgb(i * 2, 0, 0));
+ QRhiTextureUploadEntry sliceUpload(i, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(texture.data(), sliceUpload);
+ }
+
+ batch->generateMips(texture.data());
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+
+ // read back slice 63 of level 1 (256x1, #7E0000FF)
+ batch = rhi->nextResourceUpdateBatch();
+ 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);
+ };
+ QRhiReadbackDescription readbackDescription(texture.data());
+ readbackDescription.setLevel(1);
+ readbackDescription.setLayer(63);
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH / 2, 1, result.format());
+ referenceImage.fill(QColor::fromRgb(126, 0, 0));
+
+ // Now restrict the test a bit. The Null QRhi backend has broken support for
+ // mipmap generation of 1D texture arrays.
+ if (impl != QRhi::Null)
+ QVERIFY(imageRGBAEquals(result, referenceImage, 2));
+ }
+
+ // 1D texture render target
+ // NB with Vulkan we require Vulkan 1.1 for this to work.
+ // Metal does not allow 1D texture render targets
+ {
+ QScopedPointer<QRhiTexture> texture(
+ rhi->newTexture(QRhiTexture::RGBA8, WIDTH, 0, 0, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QRhiColorAttachment att(texture.data());
+ QRhiTextureRenderTargetDescription rtDesc(att);
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rp.data());
+ QVERIFY(rt->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
+ img.fill(QColor::fromRgb(128, 0, 0));
+ QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img));
+ batch->uploadTexture(texture.data(), upload);
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+ cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, batch);
+ // texture is now blue
+ cb->endPass();
+ rhi->endOffscreenFrame();
+
+ // read back texture (blue)
+ batch = rhi->nextResourceUpdateBatch();
+ 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);
+ };
+ QRhiReadbackDescription readbackDescription(texture.data());
+ batch->readBackTexture(readbackDescription, &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(!result.isNull());
+ QImage referenceImage(WIDTH, 1, result.format());
+ referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 1.0f));
+ // the Null backend does not render so skip the verification for that
+ if (impl != QRhi::Null)
+ QVERIFY(imageRGBAEquals(result, referenceImage));
+ }
+
+ // 1D array texture render target (one slice)
+ // NB with Vulkan we require Vulkan 1.1 for this to work.
+ // Metal does not allow 1D texture render targets
+ {
+ const int SLICE = 23;
+ QScopedPointer<QRhiTexture> texture(rhi->newTextureArray(
+ QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QRhiColorAttachment att(texture.data());
+ att.setLayer(SLICE);
+ QRhiTextureRenderTargetDescription rtDesc(att);
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rp.data());
+ QVERIFY(rt->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ QVERIFY(batch);
+
+ for (int i = 0; i < LAYERS; ++i) {
+ QImage img(WIDTH, 1, QImage::Format_RGBA8888);
img.fill(QColor::fromRgb(i * 2, 0, 0));
QRhiTextureUploadEntry sliceUpload(i, 0, QRhiTextureSubresourceUploadDescription(img));
batch->uploadTexture(texture.data(), sliceUpload);
@@ -4634,7 +5754,7 @@ void tst_QRhi::threeDimTexture()
batch->readBackTexture(readbackDescription, &readResult);
QVERIFY(submitResourceUpdates(rhi.data(), batch));
QVERIFY(!result.isNull());
- QImage referenceImage(WIDTH, HEIGHT, result.format());
+ QImage referenceImage(WIDTH, 1, result.format());
referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 1.0f));
// the Null backend does not render so skip the verification for that
if (impl != QRhi::Null)
@@ -4701,12 +5821,36 @@ void tst_QRhi::leakedResourceDestroy()
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->create());
+ QRhiRenderBuffer *rb = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(512, 512));
+ QVERIFY(rb->create());
+
+ QRhiShaderResourceBindings *srb = rhi->newShaderResourceBindings();
+ QVERIFY(srb->create());
+
if (impl == QRhi::Vulkan)
qDebug("Vulkan validation layer warnings may be printed below - this is expected");
+ if (impl == QRhi::D3D12)
+ qDebug("QD3D12CpuDescriptorPool warnings may be printed below - this is expected");
+
+ qDebug("QRhi resource leak check warnings may be printed below - this is expected");
+
+ // make the QRhi go away early
rhi.reset();
- // let the scoped ptr do its job with the resources
+ // see if the internal rhi backpointer got nulled out
+ QVERIFY(buffer->rhi() == nullptr);
+ QVERIFY(texture->rhi() == nullptr);
+ QVERIFY(rt->rhi() == nullptr);
+ QVERIFY(rpDesc->rhi() == nullptr);
+ QVERIFY(rb->rhi() == nullptr);
+ QVERIFY(srb->rhi() == nullptr);
+
+ // test out deleteLater on some of the resources
+ rb->deleteLater();
+ srb->deleteLater();
+
+ // let the scoped ptr do its job with the rest
}
void tst_QRhi::renderToFloatTexture_data()
@@ -4886,5 +6030,1055 @@ void tst_QRhi::renderToRgb10Texture()
QVERIFY(redCount > blueCount); // 1742 > 178
}
+void tst_QRhi::tessellation_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::tessellation()
+{
+#ifdef Q_OS_ANDROID
+ if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31)
+ QSKIP("Fails on Android 12 (QTBUG-108844)");
+#endif
+ 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");
+
+ if (!rhi->isFeatureSupported(QRhi::Tessellation)) {
+ // From a Vulkan or Metal implementation we expect tessellation to work,
+ // even though it is optional (as per spec) for Vulkan.
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QSKIP("Tessellation is not supported with this graphics API, skipping test");
+ }
+
+ if (rhi->backend() == QRhi::D3D11 || rhi->backend() == QRhi::D3D12)
+ QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet");
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ static const float triangleVertices[] = {
+ 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
+ };
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices)));
+ QVERIFY(vbuf->create());
+ u->uploadStaticBuffer(vbuf.data(), triangleVertices);
+
+ QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
+ QVERIFY(ubuf->create());
+
+ // Use the 3D API specific correction matrix that flips Y, so we can use
+ // the OpenGL-targeted vertex data and the tessellation winding order of
+ // counter-clockwise to get uniform results.
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()),
+ });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+
+ pipeline->setTopology(QRhiGraphicsPipeline::Patches);
+ pipeline->setPatchControlPointCount(3);
+
+ pipeline->setShaderStages({
+ { QRhiShaderStage::Vertex, loadShader(":/data/simpletess.vert.qsb") },
+ { QRhiShaderStage::TessellationControl, loadShader(":/data/simpletess.tesc.qsb") },
+ { QRhiShaderStage::TessellationEvaluation, loadShader(":/data/simpletess.tese.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/simpletess.frag.qsb") }
+ });
+
+ pipeline->setCullMode(QRhiGraphicsPipeline::Back); // to ensure the winding order is correct
+
+ // won't get the wireframe with OpenGL ES
+ if (rhi->isFeatureSupported(QRhi::NonFillPolygonMode))
+ pipeline->setPolygonMode(QRhiGraphicsPipeline::Line);
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 6 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) }
+ });
+
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess);
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ cb->setShaderResources();
+ QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ 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);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ if (rhi->isYUpInFramebuffer()) // we used clipSpaceCorrMatrix so this is different from many other tests
+ result = std::move(result).mirrored();
+
+ QCOMPARE(result.size(), rt->pixelSize());
+
+ // cannot check rendering results with Null, because there is no rendering there
+ if (impl == QRhi::Null)
+ return;
+
+ int redCount = 0, greenCount = 0, blueCount = 0;
+ for (int y = 0; y < result.height(); ++y) {
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ const int red = qRed(c);
+ const int green = qGreen(c);
+ const int blue = qBlue(c);
+ // just count the color components that are above a certain threshold
+ if (red > 240)
+ ++redCount;
+ if (green > 240)
+ ++greenCount;
+ if (blue > 240)
+ ++blueCount;
+ }
+ }
+
+ // Line drawing can be different between the 3D APIs. What we will check if
+ // the number of strong-enough r/g/b components above a certain threshold.
+ // That is good enough to ensure that something got rendered, i.e. that
+ // tessellation is not completely broken.
+ //
+ // For the record the actual values are something like:
+ // OpenGL (NVIDIA, Windows) 59 82 82
+ // Metal (Intel, macOS 12.5) 59 79 79
+ // Vulkan (NVIDIA, Windows) 71 85 85
+
+ QVERIFY(redCount > 50);
+ QVERIFY(blueCount > 50);
+ QVERIFY(greenCount > 50);
+}
+
+void tst_QRhi::tessellationInterfaceBlocks_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::tessellationInterfaceBlocks()
+{
+#ifdef Q_OS_ANDROID
+ if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31)
+ QSKIP("Fails on Android 12 (QTBUG-108844)");
+#endif
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ // This test is intended for Metal, but will run on other tessellation render pipelines
+ //
+ // Metal tessellation uses a combination of compute pipelines for the vert and tesc, and a
+ // render pipeline for the tese and frag. This test uses input output interface blocks between
+ // the tesc and tese, and all tese stage builtin inputs to check that the Metal tese-frag
+ // pipeline vertex inputs are correctly configured. The tese writes the values to a storage
+ // buffer whose values are checked by the unit test. MSL 2.1 is required for this test.
+ // (requires support for writing to a storage buffer in the vertex shader within a render
+ // pipeline)
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing rendering");
+
+ if (!rhi->isFeatureSupported(QRhi::Tessellation)) {
+ // From a Vulkan or Metal implementation we expect tessellation to work,
+ // even though it is optional (as per spec) for Vulkan.
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QSKIP("Tessellation is not supported with this graphics API, skipping test");
+ }
+
+ if (rhi->backend() == QRhi::D3D11 || rhi->backend() == QRhi::D3D12)
+ QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet");
+
+ if (rhi->backend() == QRhi::OpenGLES2)
+ QSKIP("Skipping test on OpenGL as gl_ClipDistance[] support inconsistent");
+
+ QScopedPointer<QRhiTexture> texture(
+ rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ static const float triangleVertices[] = {
+ 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
+ };
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
+ sizeof(triangleVertices)));
+ QVERIFY(vbuf->create());
+ u->uploadStaticBuffer(vbuf.data(), triangleVertices);
+
+ QScopedPointer<QRhiBuffer> ubuf(
+ rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
+ QVERIFY(ubuf->create());
+
+ // Use the 3D API specific correction matrix that flips Y, so we can use
+ // the OpenGL-targeted vertex data and the tessellation winding order of
+ // counter-clockwise to get uniform results.
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData());
+
+ QScopedPointer<QRhiBuffer> buffer(
+ rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::UsageFlag::StorageBuffer, 1024));
+ QVERIFY(buffer->create());
+
+ u->uploadStaticBuffer(buffer.data(), 0, 1024, QByteArray(1024, 0).constData());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings(
+ { QRhiShaderResourceBinding::uniformBuffer(
+ 0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()),
+ QRhiShaderResourceBinding::bufferLoadStore(
+ 1, QRhiShaderResourceBinding::TessellationEvaluationStage, buffer.data()) });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+
+ pipeline->setTopology(QRhiGraphicsPipeline::Patches);
+ pipeline->setPatchControlPointCount(3);
+
+ pipeline->setShaderStages(
+ { { QRhiShaderStage::Vertex, loadShader(":/data/tessinterfaceblocks.vert.qsb") },
+ { QRhiShaderStage::TessellationControl,
+ loadShader(":/data/tessinterfaceblocks.tesc.qsb") },
+ { QRhiShaderStage::TessellationEvaluation,
+ loadShader(":/data/tessinterfaceblocks.tese.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/tessinterfaceblocks.frag.qsb") } });
+
+ pipeline->setCullMode(QRhiGraphicsPipeline::Back); // to ensure the winding order is correct
+
+ // won't get the wireframe with OpenGL ES
+ if (rhi->isFeatureSupported(QRhi::NonFillPolygonMode))
+ pipeline->setPolygonMode(QRhiGraphicsPipeline::Line);
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 6 * sizeof(float) } });
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) } });
+
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess);
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ cb->setShaderResources();
+ QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ 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);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+
+ QRhiReadbackResult bufferReadResult;
+ bufferReadResult.completed = []() {};
+ readbackBatch->readBackBuffer(buffer.data(), 0, 1024, &bufferReadResult);
+
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ if (rhi->isYUpInFramebuffer()) // we used clipSpaceCorrMatrix so this is different from many
+ // other tests
+ result = std::move(result).mirrored();
+
+ QCOMPARE(result.size(), rt->pixelSize());
+
+ // cannot check rendering results with Null, because there is no rendering there
+ if (impl == QRhi::Null)
+ return;
+
+ int redCount = 0, greenCount = 0, blueCount = 0;
+ for (int y = 0; y < result.height(); ++y) {
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ const int red = qRed(c);
+ const int green = qGreen(c);
+ const int blue = qBlue(c);
+ // just count the color components that are above a certain threshold
+ if (red > 240)
+ ++redCount;
+ if (green > 240)
+ ++greenCount;
+ if (blue > 240)
+ ++blueCount;
+ }
+ }
+
+ // make sure we drew something
+ QVERIFY(redCount > 50);
+ QVERIFY(blueCount > 50);
+ QVERIFY(greenCount > 50);
+
+ // StorageBlock("result" "" knownSize=16 binding=1 set=0 runtimeArrayStride=336 QList(
+ // BlockVariable("int" "count" offset=0 size=4),
+ // BlockVariable("struct" "elements" offset=16 size=0 array=QList(0) structMembers=QList(
+ // BlockVariable("struct" "a" offset=0 size=48 array=QList(3) structMembers=QList(
+ // BlockVariable("vec3" "color" offset=0 size=12),
+ // BlockVariable("int" "id" offset=12 size=4))),
+ // BlockVariable("struct" "b" offset=48 size=144 array=QList(3) structMembers=QList(
+ // BlockVariable("vec2" "some" offset=0 size=8),
+ // BlockVariable("int" "other" offset=8 size=12 array=QList(3)),
+ // BlockVariable("vec3" "variables" offset=32 size=12))),
+ // BlockVariable("struct" "c" offset=192 size=16 structMembers=QList(
+ // BlockVariable("vec3" "stuff" offset=0 size=12),
+ // BlockVariable("float" "more_stuff" offset=12 size=4))),
+ // BlockVariable("vec4" "tesslevelOuter" offset=208 size=16),
+ // BlockVariable("vec2" "tessLevelInner" offset=224 size=8),
+ // BlockVariable("float" "pointSize" offset=232 size=12 array=QList(3)),
+ // BlockVariable("float" "clipDistance" offset=244 size=60 array=QList(5, 3)),
+ // BlockVariable("vec3" "tessCoord" offset=304 size=12),
+ // BlockVariable("int" "patchVerticesIn" offset=316 size=4),
+ // BlockVariable("int" "primitiveID" offset=320 size=4)))))
+
+ // int count
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[0])[0], 1);
+
+ // a[0].color
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[0], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[1], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[2], 1.0f);
+
+ // a[0].id
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 0 + 12])[0], 91);
+
+ // a[1].color
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[1], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[2], 0.0f);
+
+ // a[1].id
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 16 + 12])[0], 92);
+
+ // a[2].color
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[0], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[1], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[2], 0.0f);
+
+ // a[2].id
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 32 + 12])[0], 93);
+
+ // b[0].some
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 0])[0], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 0])[1], 0.0f);
+
+ // b[0].other[0]
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[1], 20.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[2], 30.0f);
+
+ // b[0].variables
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[0], 3.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[1], 13.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[2], 17.0f);
+
+ // b[1].some
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 0])[1], 1.0f);
+
+ // b[1].other[0]
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[1], 20.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[2], 30.0f);
+
+ // b[1].variables
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[0], 3.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[1], 14.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[2], 17.0f);
+
+ // b[2].some
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 0])[0], 2.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 0])[1], 2.0f);
+
+ // b[2].other[0]
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[1], 20.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[2], 30.0f);
+
+ // b[2].variables
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[0], 3.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[1], 15.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[2], 17.0f);
+
+ // c.stuff
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[1], 2.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[2], 3.0f);
+
+ // c.more_stuff
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 12])[0], 4.0f);
+
+ // tessLevelOuter
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[1], 2.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[2], 3.0f);
+
+ // tessLevelInner
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 224 + 0])[0], 5.0f);
+
+ // pointSize[0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[1], 11.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[2], 12.0f);
+
+ // clipDistance[0][0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[0], 20.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[1], 40.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[2], 60.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[3], 80.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[4], 100.0f);
+
+ // clipDistance[1][0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[0], 21.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[1], 41.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[2], 61.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[3], 81.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[4], 101.0f);
+
+ // clipDistance[2][0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[0], 22.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[1], 42.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[2], 62.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[3], 82.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[4], 102.0f);
+
+ // patchVerticesIn
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 316 + 0])[0], 3);
+
+ // primitiveID
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 320 + 0])[0], 0);
+}
+
+void tst_QRhi::storageBuffer_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::storageBuffer()
+{
+ // Use a compute shader to copy from one storage buffer of float types to
+ // another of int types. We fill the "toGpu" buffer with known float type
+ // data generated and uploaded from the CPU, then dispatch a compute shader
+ // to copy from the "toGpu" buffer to the "fromGpu" buffer. We then
+ // readback the "fromGpu" buffer and verify that the results are as
+ // expected.
+
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ // we can't test with Null as there is no compute
+ if (impl == QRhi::Null)
+ return;
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing");
+
+ if (!rhi->isFeatureSupported(QRhi::Feature::Compute))
+ QSKIP("Compute is not supported with this graphics API, skipping test");
+
+ QShader s = loadShader(":/data/storagebuffer.comp.qsb");
+ QVERIFY(s.isValid());
+ QCOMPARE(s.description().storageBlocks().size(), 2);
+
+ QMap<QByteArray, QShaderDescription::StorageBlock> blocks;
+ for (const QShaderDescription::StorageBlock &block : s.description().storageBlocks())
+ blocks[block.blockName] = block;
+
+ QMap<QByteArray, QShaderDescription::BlockVariable> toGpuMembers;
+ for (const QShaderDescription::BlockVariable &member: blocks["toGpu"].members)
+ toGpuMembers[member.name] = member;
+
+ QMap<QByteArray, QShaderDescription::BlockVariable> fromGpuMembers;
+ for (const QShaderDescription::BlockVariable &member: blocks["fromGpu"].members)
+ fromGpuMembers[member.name] = member;
+
+ for (QRhiBuffer::Type type : {QRhiBuffer::Type::Immutable, QRhiBuffer::Type::Static}) {
+
+ QRhiCommandBuffer *cb = nullptr;
+ rhi->beginOffscreenFrame(&cb);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QVERIFY(u);
+
+ QScopedPointer<QRhiBuffer> toGpuBuffer(rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer, blocks["toGpu"].knownSize));
+ QVERIFY(toGpuBuffer->create());
+
+ QScopedPointer<QRhiBuffer> fromGpuBuffer(rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer, blocks["fromGpu"].knownSize));
+ QVERIFY(fromGpuBuffer->create());
+
+ QByteArray toGpuData(blocks["toGpu"].knownSize, 0);
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_float"].offset])[0] = 1.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec2"].offset])[0] = 2.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec2"].offset])[1] = 3.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec3"].offset])[0] = 4.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec3"].offset])[1] = 5.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec3"].offset])[2] = 6.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[0] = 7.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[1] = 8.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[2] = 9.0f;
+ reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[3] = 10.0f;
+
+ u->uploadStaticBuffer(toGpuBuffer.data(), 0, toGpuData.size(), toGpuData.constData());
+ u->uploadStaticBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize, QByteArray(blocks["fromGpu"].knownSize, 0).constData());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({QRhiShaderResourceBinding::bufferLoad(blocks["toGpu"].binding, QRhiShaderResourceBinding::ComputeStage, toGpuBuffer.data()),
+ QRhiShaderResourceBinding::bufferLoadStore(blocks["fromGpu"].binding, QRhiShaderResourceBinding::ComputeStage, fromGpuBuffer.data())});
+
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiComputePipeline> pipeline(rhi->newComputePipeline());
+ pipeline->setShaderStage({QRhiShaderStage::Compute, s});
+ pipeline->setShaderResourceBindings(srb.data());
+ QVERIFY(pipeline->create());
+
+ cb->beginComputePass(u);
+
+ cb->setComputePipeline(pipeline.data());
+ cb->setShaderResources();
+ cb->dispatch(1, 1, 1);
+
+ u = rhi->nextResourceUpdateBatch();
+ QVERIFY(u);
+
+ int readCompletedNotifications = 0;
+ QRhiReadbackResult result;
+ result.completed = [&readCompletedNotifications]() { readCompletedNotifications++; };
+ u->readBackBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize, &result);
+
+ cb->endComputePass(u);
+
+ rhi->endOffscreenFrame();
+
+ QCOMPARE(readCompletedNotifications, 1);
+
+ QCOMPARE(result.data.size(), blocks["fromGpu"].knownSize);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_int"].offset])[0], 1);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec2"].offset])[0], 2);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec2"].offset])[1], 3);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec3"].offset])[0], 4);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec3"].offset])[1], 5);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec3"].offset])[2], 6);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[0], 7);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[1], 8);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[2], 9);
+ QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[3], 10);
+
+ }
+}
+
+ void tst_QRhi::storageBufferRuntimeSizeCompute_data()
+{
+ rhiTestData();
+}
+
+ void tst_QRhi::storageBufferRuntimeSizeCompute()
+{
+ // Use a compute shader to copy from one storage buffer with std430 runtime
+ // float array to another with std140 runtime int array. We fill the
+ // "toGpu" buffer with known float data generated and uploaded from the
+ // CPU, then dispatch a compute shader to copy from the "toGpu" buffer to
+ // the "fromGpu" buffer. We then readback the "fromGpu" buffer and verify
+ // that the results are as expected. This is primarily to test Metal
+ // SPIRV-Cross buffer size buffers.
+
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ // we can't test with Null as there is no compute
+ if (impl == QRhi::Null)
+ return;
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing");
+
+ if (!rhi->isFeatureSupported(QRhi::Feature::Compute))
+ QSKIP("Compute is not supported with this graphics API, skipping test");
+
+ QShader s = loadShader(":/data/storagebuffer_runtime.comp.qsb");
+ QVERIFY(s.isValid());
+ QCOMPARE(s.description().storageBlocks().size(), 2);
+
+ QMap<QByteArray, QShaderDescription::StorageBlock> blocks;
+ for (const QShaderDescription::StorageBlock &block : s.description().storageBlocks())
+ blocks[block.blockName] = block;
+
+ QMap<QByteArray, QShaderDescription::BlockVariable> toGpuMembers;
+ for (const QShaderDescription::BlockVariable &member : blocks["toGpu"].members)
+ toGpuMembers[member.name] = member;
+
+ QMap<QByteArray, QShaderDescription::BlockVariable> fromGpuMembers;
+ for (const QShaderDescription::BlockVariable &member : blocks["fromGpu"].members)
+ fromGpuMembers[member.name] = member;
+
+ for (QRhiBuffer::Type type : { QRhiBuffer::Type::Immutable, QRhiBuffer::Type::Static }) {
+ QRhiCommandBuffer *cb = nullptr;
+
+ rhi->beginOffscreenFrame(&cb);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QVERIFY(u);
+
+ const int stride430 = sizeof(float);
+ const int stride140 = 4 * sizeof(float);
+ const int length = 32;
+
+ QScopedPointer<QRhiBuffer> toGpuBuffer(
+ rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer,
+ blocks["toGpu"].knownSize + length * stride430));
+ QVERIFY(toGpuBuffer->create());
+
+ QScopedPointer<QRhiBuffer> fromGpuBuffer(
+ rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer,
+ blocks["fromGpu"].knownSize + length * stride140));
+ QVERIFY(fromGpuBuffer->create());
+
+ QByteArray toGpuData(toGpuBuffer->size(), 0);
+ for (int i = 0; i < length; ++i)
+ reinterpret_cast<float &>(toGpuData.data()[toGpuMembers["_float"].offset + i * stride430]) = float(i);
+
+ u->uploadStaticBuffer(toGpuBuffer.data(), 0, toGpuData.size(), toGpuData.constData());
+ u->uploadStaticBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize,
+ QByteArray(fromGpuBuffer->size(), 0).constData());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings(
+ { QRhiShaderResourceBinding::bufferLoadStore(
+ blocks["toGpu"].binding, QRhiShaderResourceBinding::ComputeStage,
+ toGpuBuffer.data()),
+ QRhiShaderResourceBinding::bufferLoadStore(
+ blocks["fromGpu"].binding, QRhiShaderResourceBinding::ComputeStage,
+ fromGpuBuffer.data()) });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiComputePipeline> pipeline(rhi->newComputePipeline());
+ pipeline->setShaderStage({ QRhiShaderStage::Compute, s });
+ pipeline->setShaderResourceBindings(srb.data());
+ QVERIFY(pipeline->create());
+
+ cb->beginComputePass(u);
+
+ cb->setComputePipeline(pipeline.data());
+ cb->setShaderResources();
+ cb->dispatch(1, 1, 1);
+
+ u = rhi->nextResourceUpdateBatch();
+ QVERIFY(u);
+ int readbackCompleted = 0;
+ QRhiReadbackResult result;
+ result.completed = [&readbackCompleted]() { readbackCompleted++; };
+ u->readBackBuffer(fromGpuBuffer.data(), 0, fromGpuBuffer->size(), &result);
+
+ cb->endComputePass(u);
+
+ rhi->endOffscreenFrame();
+
+ QVERIFY(readbackCompleted > 0);
+ QCOMPARE(result.data.size(), fromGpuBuffer->size());
+
+ for (int i = 0; i < length; ++i)
+ QCOMPARE(reinterpret_cast<const int &>(result.data.constData()[fromGpuMembers["_int"].offset + i * stride140]), i);
+
+ QCOMPARE(readbackCompleted, 1);
+
+ }
+
+}
+
+void tst_QRhi::storageBufferRuntimeSizeGraphics_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::storageBufferRuntimeSizeGraphics()
+{
+#ifdef Q_OS_ANDROID
+ if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31)
+ QSKIP("Fails on Android 12 (QTBUG-108844)");
+#endif
+ // Draws a tessellated triangle with color determined by the length of
+ // buffers bound to shader stages. This is primarily to test Metal
+ // SPIRV-Cross buffer size buffers.
+
+ 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");
+
+ if (!rhi->isFeatureSupported(QRhi::Tessellation)) {
+ // From a Vulkan or Metal implementation we expect tessellation to work,
+ // even though it is optional (as per spec) for Vulkan.
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QSKIP("Tessellation is not supported with this graphics API, skipping test");
+ }
+
+ if (rhi->backend() == QRhi::D3D11 || rhi->backend() == QRhi::D3D12)
+ QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet");
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(64, 64), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ static const float triangleVertices[] = {
+ 0.0f, 0.5f, 0.0f,
+ -0.5f, -0.5f, 0.0f,
+ 0.5f, -0.5f, 0.0f,
+ };
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices)));
+ QVERIFY(vbuf->create());
+ u->uploadStaticBuffer(vbuf.data(), triangleVertices);
+
+ QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
+ QVERIFY(ubuf->create());
+
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData());
+
+ QScopedPointer<QRhiBuffer> ssbo5(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 256));
+ QVERIFY(ssbo5->create());
+
+ QScopedPointer<QRhiBuffer> ssbo3(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 16));
+ QVERIFY(ssbo3->create());
+
+ u->uploadStaticBuffer(ssbo3.data(), QVector<float>({ 1.0f, 1.0f, 1.0f, 1.0f }).constData());
+
+ QScopedPointer<QRhiBuffer> ssbo4(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 128));
+ QVERIFY(ssbo4->create());
+
+ const int red = 79;
+ const int green = 43;
+ const int blue = 251;
+
+ QScopedPointer<QRhiBuffer> ssboR(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, red * sizeof(float)));
+ QVERIFY(ssboR->create());
+
+ QScopedPointer<QRhiBuffer> ssboG(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, green * sizeof(float)));
+ QVERIFY(ssboG->create());
+
+ QScopedPointer<QRhiBuffer> ssboB(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, blue * sizeof(float)));
+ QVERIFY(ssboB->create());
+
+ const int tessOuter0 = 1;
+ const int tessOuter1 = 2;
+ const int tessOuter2 = 3;
+ const int tessInner0 = 4;
+
+ QScopedPointer<QRhiBuffer> ssboTessOuter0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter0 * sizeof(float)));
+ QVERIFY(ssboTessOuter0->create());
+
+ QScopedPointer<QRhiBuffer> ssboTessOuter1(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter1 * sizeof(float)));
+ QVERIFY(ssboTessOuter1->create());
+
+ QScopedPointer<QRhiBuffer> ssboTessOuter2(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter2 * sizeof(float)));
+ QVERIFY(ssboTessOuter2->create());
+
+ QScopedPointer<QRhiBuffer> ssboTessInner0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessInner0 * sizeof(float)));
+ QVERIFY(ssboTessInner0->create());
+
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()),
+ QRhiShaderResourceBinding::bufferLoad(5, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssbo5.data()),
+ QRhiShaderResourceBinding::bufferLoad(3, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage | QRhiShaderResourceBinding::FragmentStage, ssbo3.data()),
+ QRhiShaderResourceBinding::bufferLoad(4, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssbo4.data()),
+ QRhiShaderResourceBinding::bufferLoad(7, QRhiShaderResourceBinding::TessellationControlStage, ssboTessOuter0.data()),
+ QRhiShaderResourceBinding::bufferLoad(8, QRhiShaderResourceBinding::TessellationControlStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssboTessOuter1.data()),
+ QRhiShaderResourceBinding::bufferLoad(9, QRhiShaderResourceBinding::TessellationControlStage, ssboTessOuter2.data()),
+ QRhiShaderResourceBinding::bufferLoad(10, QRhiShaderResourceBinding::TessellationControlStage, ssboTessInner0.data()),
+ QRhiShaderResourceBinding::bufferLoad(1, QRhiShaderResourceBinding::FragmentStage, ssboG.data()),
+ QRhiShaderResourceBinding::bufferLoad(2, QRhiShaderResourceBinding::FragmentStage, ssboB.data()),
+ QRhiShaderResourceBinding::bufferLoad(6, QRhiShaderResourceBinding::FragmentStage, ssboR.data()) });
+
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+
+ pipeline->setTopology(QRhiGraphicsPipeline::Patches);
+ pipeline->setPatchControlPointCount(3);
+
+ pipeline->setShaderStages({
+ { QRhiShaderStage::Vertex, loadShader(":/data/storagebuffer_runtime.vert.qsb") },
+ { QRhiShaderStage::TessellationControl, loadShader(":/data/storagebuffer_runtime.tesc.qsb") },
+ { QRhiShaderStage::TessellationEvaluation, loadShader(":/data/storagebuffer_runtime.tese.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/storagebuffer_runtime.frag.qsb") }
+ });
+
+ pipeline->setCullMode(QRhiGraphicsPipeline::None);
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 3 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ });
+
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess);
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ cb->setShaderResources();
+ QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ 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);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ QCOMPARE(result.size(), rt->pixelSize());
+
+ // cannot check rendering results with Null, because there is no rendering there
+ if (impl == QRhi::Null)
+ return;
+
+ QCOMPARE(result.pixel(32, 32), qRgb(red, green, blue));
+}
+
+void tst_QRhi::halfPrecisionAttributes_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::halfPrecisionAttributes()
+{
+#ifdef Q_OS_ANDROID
+ if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31)
+ QSKIP("Fails on Android 12 (QTBUG-108844)");
+#endif
+ 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");
+
+ if (!rhi->isFeatureSupported(QRhi::HalfAttributes)) {
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QVERIFY(rhi->backend() != QRhi::D3D11);
+ QVERIFY(rhi->backend() != QRhi::D3D12);
+ QSKIP("Half precision vertex attributes are not supported with this graphics API, skipping test");
+ }
+
+ const QSize outputSize(1920, 1080);
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ //
+ // This test uses half3 vertices
+ //
+ // Note: D3D does not support half3 - rhi passes it through as half4. Because of this, D3D will
+ // report the following warning and error if we don't take precautions:
+ //
+ // D3D11 WARNING: ID3D11DeviceContext::Draw: Input vertex slot 0 has stride 6 which is less than
+ // the minimum stride logically expected from the current Input Layout (8 bytes). This is OK, as
+ // hardware is perfectly capable of reading overlapping data. However the developer probably did
+ // not intend to make use of this behavior. [ EXECUTION WARNING #355:
+ // DEVICE_DRAW_VERTEX_BUFFER_STRIDE_TOO_SMALL]
+ //
+ // D3D11 ERROR: ID3D11DeviceContext::Draw: Vertex Buffer Stride (6) at the input vertex slot 0
+ // is not aligned properly. The current Input Layout imposes an alignment of (4) because of the
+ // Formats used with this slot. [ EXECUTION ERROR #367: DEVICE_DRAW_VERTEX_STRIDE_UNALIGNED]
+ //
+ // The same warning and error are produced for D3D12. The rendered output is correct despite
+ // the warning and error.
+ //
+ // To avoid these errors, we pad the vertices to 8 byte stride.
+ //
+ static const qfloat16 vertices[] = {
+ qfloat16(-1.0), qfloat16(-1.0), qfloat16(0.0), qfloat16(0.0),
+ qfloat16(1.0), qfloat16(-1.0), qfloat16(0.0), qfloat16(0.0),
+ qfloat16(0.0), qfloat16(1.0), qfloat16(0.0), qfloat16(0.0),
+ };
+
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ QVERIFY(vbuf->create());
+ updates->uploadStaticBuffer(vbuf.data(), vertices);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ QShader vs = loadShader(":/data/half.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({ { 4 * sizeof(qfloat16) } }); // 8 byte vertex stride for D3D
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Half3, 0 } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(pipeline->create());
+
+ 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());
+ QVERIFY(redCount != 0);
+ QVERIFY(blueCount != 0);
+
+ // 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 <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)
diff --git a/tests/auto/gui/rhi/qshader/CMakeLists.txt b/tests/auto/gui/rhi/qshader/CMakeLists.txt
index 9807b19e53..09bf0d585d 100644
--- a/tests/auto/gui/rhi/qshader/CMakeLists.txt
+++ b/tests/auto/gui/rhi/qshader/CMakeLists.txt
@@ -1,9 +1,16 @@
-# Generated from qshader.pro.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qshader Test:
#####################################################################
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qshader LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
# Resources:
file(GLOB_RECURSE qshader_resource_files
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
@@ -13,7 +20,7 @@ file(GLOB_RECURSE qshader_resource_files
qt_internal_add_test(tst_qshader
SOURCES
tst_qshader.cpp
- PUBLIC_LIBRARIES
+ LIBRARIES
Qt::Gui
Qt::GuiPrivate
TESTDATA ${qshader_resource_files}
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb
new file mode 100644
index 0000000000..4d49ede3ff
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb
new file mode 100644
index 0000000000..ea68da7eb4
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb
new file mode 100644
index 0000000000..41005f76bc
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb
new file mode 100644
index 0000000000..39734b6d5d
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/storage_buffer_info_v8.comp.qsb b/tests/auto/gui/rhi/qshader/data/storage_buffer_info_v8.comp.qsb
new file mode 100644
index 0000000000..edcd84cbe6
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/storage_buffer_info_v8.comp.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/color.vert b/tests/auto/gui/rhi/qshader/data_src/color.vert
index c92f71b9e1..c92f71b9e1 100644
--- a/tests/auto/gui/rhi/qshader/data/color.vert
+++ b/tests/auto/gui/rhi/qshader/data_src/color.vert
diff --git a/tests/auto/gui/rhi/qshader/data/texture.frag b/tests/auto/gui/rhi/qshader/data_src/texture.frag
index bd22f817e0..bd22f817e0 100644
--- a/tests/auto/gui/rhi/qshader/data/texture.frag
+++ b/tests/auto/gui/rhi/qshader/data_src/texture.frag
diff --git a/tests/auto/gui/rhi/qshader/data/texture_sep.frag b/tests/auto/gui/rhi/qshader/data_src/texture_sep.frag
index 368e851bb4..368e851bb4 100644
--- a/tests/auto/gui/rhi/qshader/data/texture_sep.frag
+++ b/tests/auto/gui/rhi/qshader/data_src/texture_sep.frag
diff --git a/tests/auto/gui/rhi/qshader/tst_qshader.cpp b/tests/auto/gui/rhi/qshader/tst_qshader.cpp
index 40aa9d9a87..9e179c95c3 100644
--- a/tests/auto/gui/rhi/qshader/tst_qshader.cpp
+++ b/tests/auto/gui/rhi/qshader/tst_qshader.cpp
@@ -1,12 +1,12 @@
// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QFile>
#include <QBuffer>
-#include <QtGui/private/qshaderdescription_p_p.h>
-#include <QtGui/private/qshader_p_p.h>
+#include <private/qshaderdescription_p.h>
+#include <private/qshader_p.h>
class tst_QShader : public QObject
{
@@ -25,6 +25,8 @@ private slots:
void loadV4();
void manualShaderPackCreation();
void loadV6WithSeparateImagesAndSamplers();
+ void loadV7();
+ void loadV8();
};
static QShader getShader(const QString &name)
@@ -57,7 +59,7 @@ void tst_QShader::simpleCompileCheckResults()
QShader s = getShader(QLatin1String(":/data/color_spirv_v5.vert.qsb"));
QVERIFY(s.isValid());
QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 5);
- QCOMPARE(s.availableShaders().count(), 1);
+ QCOMPARE(s.availableShaders().size(), 1);
const QShaderCode shader = s.shader(QShaderKey(QShader::SpirvShader,
QShaderVersion(100)));
@@ -66,7 +68,7 @@ void tst_QShader::simpleCompileCheckResults()
const QShaderDescription desc = s.description();
QVERIFY(desc.isValid());
- QCOMPARE(desc.inputVariables().count(), 2);
+ QCOMPARE(desc.inputVariables().size(), 2);
for (const QShaderDescription::InOutVariable &v : desc.inputVariables()) {
switch (v.location) {
case 0:
@@ -82,7 +84,7 @@ void tst_QShader::simpleCompileCheckResults()
break;
}
}
- QCOMPARE(desc.outputVariables().count(), 1);
+ QCOMPARE(desc.outputVariables().size(), 1);
for (const QShaderDescription::InOutVariable &v : desc.outputVariables()) {
switch (v.location) {
case 0:
@@ -94,15 +96,15 @@ void tst_QShader::simpleCompileCheckResults()
break;
}
}
- QCOMPARE(desc.uniformBlocks().count(), 1);
+ QCOMPARE(desc.uniformBlocks().size(), 1);
const QShaderDescription::UniformBlock blk = desc.uniformBlocks().first();
QCOMPARE(blk.blockName, QByteArrayLiteral("buf"));
QCOMPARE(blk.structName, QByteArrayLiteral("ubuf"));
QCOMPARE(blk.size, 68);
QCOMPARE(blk.binding, 0);
QCOMPARE(blk.descriptorSet, 0);
- QCOMPARE(blk.members.count(), 2);
- for (int i = 0; i < blk.members.count(); ++i) {
+ QCOMPARE(blk.members.size(), 2);
+ for (int i = 0; i < blk.members.size(); ++i) {
const QShaderDescription::BlockVariable v = blk.members[i];
switch (i) {
case 0:
@@ -119,7 +121,7 @@ void tst_QShader::simpleCompileCheckResults()
QCOMPARE(v.type, QShaderDescription::Float);
break;
default:
- QFAIL(qPrintable(QStringLiteral("Too many blocks: %1").arg(blk.members.count())));
+ QFAIL(qPrintable(QStringLiteral("Too many blocks: %1").arg(blk.members.size())));
break;
}
}
@@ -132,7 +134,7 @@ void tst_QShader::genVariants()
// + batchable variants
QVERIFY(s.isValid());
QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 5);
- QCOMPARE(s.availableShaders().count(), 2 * 6);
+ QCOMPARE(s.availableShaders().size(), 2 * 6);
int batchableVariantCount = 0;
int batchableGlslVariantCount = 0;
@@ -155,33 +157,33 @@ void tst_QShader::shaderDescImplicitSharing()
QShader s = getShader(QLatin1String(":/data/color_spirv_v5.vert.qsb"));
QVERIFY(s.isValid());
QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 5);
- QCOMPARE(s.availableShaders().count(), 1);
+ QCOMPARE(s.availableShaders().size(), 1);
QVERIFY(s.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
QShaderDescription d0 = s.description();
QVERIFY(d0.isValid());
- QCOMPARE(d0.inputVariables().count(), 2);
- QCOMPARE(d0.outputVariables().count(), 1);
- QCOMPARE(d0.uniformBlocks().count(), 1);
+ QCOMPARE(d0.inputVariables().size(), 2);
+ QCOMPARE(d0.outputVariables().size(), 1);
+ QCOMPARE(d0.uniformBlocks().size(), 1);
QShaderDescription d1 = d0;
QVERIFY(QShaderDescriptionPrivate::get(&d0) == QShaderDescriptionPrivate::get(&d1));
- QCOMPARE(d0.inputVariables().count(), 2);
- QCOMPARE(d0.outputVariables().count(), 1);
- QCOMPARE(d0.uniformBlocks().count(), 1);
- QCOMPARE(d1.inputVariables().count(), 2);
- QCOMPARE(d1.outputVariables().count(), 1);
- QCOMPARE(d1.uniformBlocks().count(), 1);
+ QCOMPARE(d0.inputVariables().size(), 2);
+ QCOMPARE(d0.outputVariables().size(), 1);
+ QCOMPARE(d0.uniformBlocks().size(), 1);
+ QCOMPARE(d1.inputVariables().size(), 2);
+ QCOMPARE(d1.outputVariables().size(), 1);
+ QCOMPARE(d1.uniformBlocks().size(), 1);
QCOMPARE(d0, d1);
d1.detach();
QVERIFY(QShaderDescriptionPrivate::get(&d0) != QShaderDescriptionPrivate::get(&d1));
- QCOMPARE(d0.inputVariables().count(), 2);
- QCOMPARE(d0.outputVariables().count(), 1);
- QCOMPARE(d0.uniformBlocks().count(), 1);
- QCOMPARE(d1.inputVariables().count(), 2);
- QCOMPARE(d1.outputVariables().count(), 1);
- QCOMPARE(d1.uniformBlocks().count(), 1);
+ QCOMPARE(d0.inputVariables().size(), 2);
+ QCOMPARE(d0.outputVariables().size(), 1);
+ QCOMPARE(d0.uniformBlocks().size(), 1);
+ QCOMPARE(d1.inputVariables().size(), 2);
+ QCOMPARE(d1.outputVariables().size(), 1);
+ QCOMPARE(d1.uniformBlocks().size(), 1);
QCOMPARE(d0, d1);
d1 = QShaderDescription();
@@ -193,24 +195,24 @@ void tst_QShader::bakedShaderImplicitSharing()
QShader s0 = getShader(QLatin1String(":/data/color_spirv_v5.vert.qsb"));
QVERIFY(s0.isValid());
QCOMPARE(QShaderPrivate::get(&s0)->qsbVersion, 5);
- QCOMPARE(s0.availableShaders().count(), 1);
+ QCOMPARE(s0.availableShaders().size(), 1);
QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
{
QShader s1 = s0;
QVERIFY(QShaderPrivate::get(&s0) == QShaderPrivate::get(&s1));
- QCOMPARE(s0.availableShaders().count(), 1);
+ QCOMPARE(s0.availableShaders().size(), 1);
QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
- QCOMPARE(s1.availableShaders().count(), 1);
+ QCOMPARE(s1.availableShaders().size(), 1);
QVERIFY(s1.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
QCOMPARE(s0.stage(), s1.stage());
QCOMPARE(s0, s1);
s1.detach();
QVERIFY(QShaderPrivate::get(&s0) != QShaderPrivate::get(&s1));
- QCOMPARE(s0.availableShaders().count(), 1);
+ QCOMPARE(s0.availableShaders().size(), 1);
QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
- QCOMPARE(s1.availableShaders().count(), 1);
+ QCOMPARE(s1.availableShaders().size(), 1);
QVERIFY(s1.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
QCOMPARE(s0.stage(), s1.stage());
QCOMPARE(s0, s1);
@@ -223,18 +225,18 @@ void tst_QShader::bakedShaderImplicitSharing()
s1.setStage(QShader::FragmentStage); // call a setter to trigger a detach
QVERIFY(QShaderPrivate::get(&s0) != QShaderPrivate::get(&s1));
- QCOMPARE(s0.availableShaders().count(), 1);
+ QCOMPARE(s0.availableShaders().size(), 1);
QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
- QCOMPARE(s1.availableShaders().count(), 1);
+ QCOMPARE(s1.availableShaders().size(), 1);
QVERIFY(s1.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
QShaderDescription d0 = s0.description();
- QCOMPARE(d0.inputVariables().count(), 2);
- QCOMPARE(d0.outputVariables().count(), 1);
- QCOMPARE(d0.uniformBlocks().count(), 1);
+ QCOMPARE(d0.inputVariables().size(), 2);
+ QCOMPARE(d0.outputVariables().size(), 1);
+ QCOMPARE(d0.uniformBlocks().size(), 1);
QShaderDescription d1 = s1.description();
- QCOMPARE(d1.inputVariables().count(), 2);
- QCOMPARE(d1.outputVariables().count(), 1);
- QCOMPARE(d1.uniformBlocks().count(), 1);
+ QCOMPARE(d1.inputVariables().size(), 2);
+ QCOMPARE(d1.outputVariables().size(), 1);
+ QCOMPARE(d1.uniformBlocks().size(), 1);
QVERIFY(s0 != s1);
}
}
@@ -244,7 +246,7 @@ void tst_QShader::sortedKeys()
QShader s = getShader(QLatin1String(":/data/texture_all_v4.frag.qsb"));
QVERIFY(s.isValid());
QList<QShaderKey> availableShaders = s.availableShaders();
- QCOMPARE(availableShaders.count(), 7);
+ QCOMPARE(availableShaders.size(), 7);
std::sort(availableShaders.begin(), availableShaders.end());
QCOMPARE(availableShaders, s.availableShaders());
}
@@ -256,7 +258,7 @@ void tst_QShader::mslResourceMapping()
QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 4);
const QList<QShaderKey> availableShaders = s.availableShaders();
- QCOMPARE(availableShaders.count(), 7);
+ QCOMPARE(availableShaders.size(), 7);
QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50))));
@@ -275,7 +277,7 @@ void tst_QShader::mslResourceMapping()
resMap = s.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12)));
QVERIFY(!resMap.isEmpty());
- QCOMPARE(resMap.count(), 2);
+ QCOMPARE(resMap.size(), 2);
QCOMPARE(resMap.value(0).first, 0); // mapped to native buffer index 0
QCOMPARE(resMap.value(1), qMakePair(0, 0)); // mapped to native texture index 0 and sampler index 0
}
@@ -292,7 +294,7 @@ void tst_QShader::serializeShaderDesc()
QBuffer buf(&data);
QDataStream ds(&buf);
QVERIFY(buf.open(QIODevice::WriteOnly));
- desc.serialize(&ds);
+ desc.serialize(&ds, QShaderPrivate::QSB_VERSION);
}
QVERIFY(!data.isEmpty());
@@ -317,7 +319,7 @@ void tst_QShader::serializeShaderDesc()
QBuffer buf(&data);
QDataStream ds(&buf);
QVERIFY(buf.open(QIODevice::WriteOnly));
- desc.serialize(&ds);
+ desc.serialize(&ds, QShaderPrivate::QSB_VERSION);
}
QVERIFY(!data.isEmpty());
@@ -377,7 +379,7 @@ void tst_QShader::loadV4()
QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 4);
const QList<QShaderKey> availableShaders = s.availableShaders();
- QCOMPARE(availableShaders.count(), 7);
+ QCOMPARE(availableShaders.size(), 7);
QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50))));
@@ -388,7 +390,7 @@ void tst_QShader::loadV4()
const QShaderDescription desc = s.description();
QVERIFY(desc.isValid());
- QCOMPARE(desc.inputVariables().count(), 1);
+ QCOMPARE(desc.inputVariables().size(), 1);
for (const QShaderDescription::InOutVariable &v : desc.inputVariables()) {
switch (v.location) {
case 0:
@@ -400,7 +402,7 @@ void tst_QShader::loadV4()
break;
}
}
- QCOMPARE(desc.outputVariables().count(), 1);
+ QCOMPARE(desc.outputVariables().size(), 1);
for (const QShaderDescription::InOutVariable &v : desc.outputVariables()) {
switch (v.location) {
case 0:
@@ -412,15 +414,15 @@ void tst_QShader::loadV4()
break;
}
}
- QCOMPARE(desc.uniformBlocks().count(), 1);
+ QCOMPARE(desc.uniformBlocks().size(), 1);
const QShaderDescription::UniformBlock blk = desc.uniformBlocks().first();
QCOMPARE(blk.blockName, QByteArrayLiteral("buf"));
QCOMPARE(blk.structName, QByteArrayLiteral("ubuf"));
QCOMPARE(blk.size, 68);
QCOMPARE(blk.binding, 0);
QCOMPARE(blk.descriptorSet, 0);
- QCOMPARE(blk.members.count(), 2);
- for (int i = 0; i < blk.members.count(); ++i) {
+ QCOMPARE(blk.members.size(), 2);
+ for (int i = 0; i < blk.members.size(); ++i) {
const QShaderDescription::BlockVariable v = blk.members[i];
switch (i) {
case 0:
@@ -437,7 +439,7 @@ void tst_QShader::loadV4()
QCOMPARE(v.type, QShaderDescription::Float);
break;
default:
- QFAIL(qPrintable(QStringLiteral("Bad many blocks: %1").arg(blk.members.count())));
+ QFAIL(qPrintable(QStringLiteral("Bad many blocks: %1").arg(blk.members.size())));
break;
}
}
@@ -548,11 +550,11 @@ void tst_QShader::manualShaderPackCreation()
const QByteArray serialized = shaderPack.serialized();
QShader newShaderPack = QShader::fromSerialized(serialized);
- QCOMPARE(newShaderPack.availableShaders().count(), 2);
- QCOMPARE(newShaderPack.description().inputVariables().count(), 1);
- QCOMPARE(newShaderPack.description().outputVariables().count(), 1);
- QCOMPARE(newShaderPack.description().uniformBlocks().count(), 1);
- QCOMPARE(newShaderPack.description().combinedImageSamplers().count(), 1);
+ QCOMPARE(newShaderPack.availableShaders().size(), 2);
+ QCOMPARE(newShaderPack.description().inputVariables().size(), 1);
+ QCOMPARE(newShaderPack.description().outputVariables().size(), 1);
+ QCOMPARE(newShaderPack.description().uniformBlocks().size(), 1);
+ QCOMPARE(newShaderPack.description().combinedImageSamplers().size(), 1);
QCOMPARE(newShaderPack.shader(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs))).shader(), fs_gles);
QCOMPARE(newShaderPack.shader(QShaderKey(QShader::GlslShader, QShaderVersion(120))).shader(), fs_gl);
}
@@ -564,7 +566,7 @@ void tst_QShader::loadV6WithSeparateImagesAndSamplers()
QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 6);
const QList<QShaderKey> availableShaders = s.availableShaders();
- QCOMPARE(availableShaders.count(), 6);
+ QCOMPARE(availableShaders.size(), 6);
QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50))));
@@ -574,10 +576,10 @@ void tst_QShader::loadV6WithSeparateImagesAndSamplers()
QShader::NativeResourceBindingMap resMap =
s.nativeResourceBindingMap(QShaderKey(QShader::HlslShader, QShaderVersion(50)));
- QVERIFY(resMap.count() == 4);
+ QVERIFY(resMap.size() == 4);
QVERIFY(s.separateToCombinedImageSamplerMappingList(QShaderKey(QShader::HlslShader, QShaderVersion(50))).isEmpty());
resMap = s.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12)));
- QVERIFY(resMap.count() == 4);
+ QVERIFY(resMap.size() == 4);
QVERIFY(s.separateToCombinedImageSamplerMappingList(QShaderKey(QShader::MslShader, QShaderVersion(12))).isEmpty());
for (auto key : {
@@ -586,9 +588,113 @@ void tst_QShader::loadV6WithSeparateImagesAndSamplers()
QShaderKey(QShader::GlslShader, QShaderVersion(150)) })
{
auto list = s.separateToCombinedImageSamplerMappingList(key);
- QCOMPARE(list.count(), 2);
+ QCOMPARE(list.size(), 2);
}
}
+void tst_QShader::loadV7()
+{
+ QShader vert = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.vert.qsb"));
+ QVERIFY(vert.isValid());
+ QCOMPARE(QShaderPrivate::get(&vert)->qsbVersion, 7);
+ QCOMPARE(vert.availableShaders().size(), 8);
+
+ QCOMPARE(vert.description().inputVariables().size(), 2);
+ QCOMPARE(vert.description().outputBuiltinVariables().size(), 1);
+ QCOMPARE(vert.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(vert.description().outputVariables().size(), 1);
+ QCOMPARE(vert.description().outputVariables()[0].name, QByteArrayLiteral("v_color"));
+
+ QVERIFY(vert.availableShaders().contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
+ QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::NonIndexedVertexAsComputeShader)).shader().isEmpty());
+ QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt16IndexedVertexAsComputeShader)).shader().isEmpty());
+ QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt32IndexedVertexAsComputeShader)).shader().isEmpty());
+
+ QShader tesc = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tesc.qsb"));
+ QVERIFY(tesc.isValid());
+ QCOMPARE(QShaderPrivate::get(&tesc)->qsbVersion, 7);
+ QCOMPARE(tesc.availableShaders().size(), 5);
+ QCOMPARE(tesc.description().tessellationOutputVertexCount(), 3u);
+
+ QCOMPARE(tesc.description().inputBuiltinVariables().size(), 2);
+ QCOMPARE(tesc.description().outputBuiltinVariables().size(), 3);
+ // builtins must be sorted based on the type
+ QCOMPARE(tesc.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(tesc.description().inputBuiltinVariables()[1].type, QShaderDescription::InvocationIdBuiltin);
+ QCOMPARE(tesc.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(tesc.description().outputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
+ QCOMPARE(tesc.description().outputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
+
+ QCOMPARE(tesc.description().outputVariables().size(), 3);
+ for (const QShaderDescription::InOutVariable &v : tesc.description().outputVariables()) {
+ switch (v.location) {
+ case 0:
+ QCOMPARE(v.name, QByteArrayLiteral("outColor"));
+ QCOMPARE(v.type, QShaderDescription::Vec3);
+ QCOMPARE(v.perPatch, false);
+ break;
+ case 1:
+ QCOMPARE(v.name, QByteArrayLiteral("stuff"));
+ QCOMPARE(v.type, QShaderDescription::Vec3);
+ QCOMPARE(v.perPatch, true);
+ break;
+ case 2:
+ QCOMPARE(v.name, QByteArrayLiteral("more_stuff"));
+ QCOMPARE(v.type, QShaderDescription::Float);
+ QCOMPARE(v.perPatch, true);
+ break;
+ default:
+ QFAIL(qPrintable(QStringLiteral("Bad location: %1").arg(v.location)));
+ break;
+ }
+ }
+
+ QVERIFY(!tesc.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().isEmpty());
+ QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::SpirvShader, QShaderVersion(100))).extraBufferBindings.size(), 0);
+ QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::MslShader, QShaderVersion(12))).extraBufferBindings.size(), 5);
+
+ QShader tese = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tese.qsb"));
+ QVERIFY(tese.isValid());
+ QCOMPARE(QShaderPrivate::get(&tese)->qsbVersion, 7);
+ QCOMPARE(tese.availableShaders().size(), 5);
+ QCOMPARE(tese.description().tessellationMode(), QShaderDescription::TrianglesTessellationMode);
+ QCOMPARE(tese.description().tessellationWindingOrder(), QShaderDescription::CcwTessellationWindingOrder);
+ QCOMPARE(tese.description().tessellationPartitioning(), QShaderDescription::FractionalOddTessellationPartitioning);
+
+ QCOMPARE(tese.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(tese.description().inputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
+ QCOMPARE(tese.description().inputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
+ QCOMPARE(tese.description().inputBuiltinVariables()[3].type, QShaderDescription::TessCoordBuiltin);
+
+ QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).size(), 1);
+ QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).value(0), qMakePair(0, -1));
+
+ QShader frag = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.frag.qsb"));
+ QVERIFY(frag.isValid());
+ QCOMPARE(QShaderPrivate::get(&frag)->qsbVersion, 7);
+}
+
+void tst_QShader::loadV8()
+{
+ QShader s = getShader(QLatin1String(":/data/storage_buffer_info_v8.comp.qsb"));
+ QVERIFY(s.isValid());
+ QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 8);
+
+ const QList<QShaderKey> availableShaders = s.availableShaders();
+ QCOMPARE(availableShaders.size(), 5);
+ QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
+ QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
+ QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50))));
+ QVERIFY(availableShaders.contains(
+ QShaderKey(QShader::GlslShader, QShaderVersion(310, QShaderVersion::GlslEs))));
+ QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(430))));
+
+ QCOMPARE(s.description().storageBlocks().size(), 1);
+ QCOMPARE(s.description().storageBlocks().last().runtimeArrayStride, 4);
+ QCOMPARE(s.description().storageBlocks().last().qualifierFlags,
+ QShaderDescription::QualifierFlags(QShaderDescription::QualifierWriteOnly
+ | QShaderDescription::QualifierRestrict));
+}
+
#include <tst_qshader.moc>
QTEST_MAIN(tst_QShader)