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.txt2
-rw-r--r--tests/auto/gui/rhi/qrhi/CMakeLists.txt8
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat15
-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/storagebuffer.comp2
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsbbin1239 -> 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.cpp1269
-rw-r--r--tests/auto/gui/rhi/qshader/CMakeLists.txt8
-rw-r--r--tests/auto/gui/rhi/qshader/tst_qshader.cpp6
31 files changed, 1656 insertions, 51 deletions
diff --git a/tests/auto/gui/rhi/CMakeLists.txt b/tests/auto/gui/rhi/CMakeLists.txt
index dd920cf511..898a67d2dc 100644
--- a/tests/auto/gui/rhi/CMakeLists.txt
+++ b/tests/auto/gui/rhi/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from rhi.pro.
-
add_subdirectory(qshader)
add_subdirectory(qrhi)
diff --git a/tests/auto/gui/rhi/qrhi/CMakeLists.txt b/tests/auto/gui/rhi/qrhi/CMakeLists.txt
index d7594ef180..3b0d643060 100644
--- a/tests/auto/gui/rhi/qrhi/CMakeLists.txt
+++ b/tests/auto/gui/rhi/qrhi/CMakeLists.txt
@@ -1,12 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from qrhi.pro.
-
#####################################################################
## 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}"
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 1b5d4f28a6..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
@@ -16,4 +16,15 @@ qsb --glsl 320es,410 --msl 12 --tess-mode triangles simpletess.tesc -o simpletes
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/storagebuffer.comp b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp
index fd6cabebc5..ffa0bc7004 100644
--- a/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp
@@ -2,7 +2,7 @@
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-layout (binding = 0, std430) buffer toGpu
+layout (binding = 0, std430) readonly buffer toGpu
{
float _float;
vec2 _vec2;
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb
index 77887ed941..b02f541cc5 100644
--- a/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb
+++ 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 a298a9f545..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,14 +9,11 @@
#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
@@ -25,19 +22,15 @@
#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>
-#include <QtGui/private/qrhid3d12_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
@@ -78,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();
@@ -88,6 +83,8 @@ private slots:
void renderPassDescriptorCompatibility();
void renderPassDescriptorClone_data();
void renderPassDescriptorClone();
+ void textureWithSampleCount_data();
+ void textureWithSampleCount();
void renderToTextureSimple_data();
void renderToTextureSimple();
@@ -117,6 +114,8 @@ private slots:
void renderToTextureSrbReuse();
void renderToTextureIndexedDraw_data();
void renderToTextureIndexedDraw();
+ void renderToTextureArrayMultiView_data();
+ void renderToTextureArrayMultiView();
void renderToWindowSimple_data();
void renderToWindowSimple();
void finishWithinSwapchainFrame_data();
@@ -145,8 +144,18 @@ private slots:
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);
@@ -309,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()));
@@ -403,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]);
@@ -477,6 +499,8 @@ 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->gfxQueue);
@@ -968,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);
@@ -995,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; };
@@ -1517,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));
@@ -3603,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();
@@ -3857,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; };
@@ -4490,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()
@@ -4607,6 +4940,59 @@ void tst_QRhi::pipelineCache()
}
}
+void tst_QRhi::textureWithSampleCount_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::textureWithSampleCount()
+{
+ 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 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
@@ -4751,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());
@@ -4794,11 +5180,10 @@ void tst_QRhi::threeDimTexture()
// Some software-based OpenGL implementations, such as Mesa llvmpipe builds that are
// used both in Qt CI and are shipped with the official Qt binaries also seem to have
// problems with this.
- if (impl != QRhi::Null && impl != QRhi::OpenGLES2) {
- // temporarily skip for D3D12 as well since 3D texture mipmap generation is not implemented there
- if (impl != QRhi::D3D12)
- QVERIFY(imageRGBAEquals(result, referenceImage, 2));
- }
+ 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)
@@ -4817,26 +5202,36 @@ void tst_QRhi::threeDimTexture()
rt->setRenderPassDescriptor(rp.data());
QVERIFY(rt->create());
- QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
- QVERIFY(batch);
-
- for (int i = 0; i < DEPTH; ++i) {
- 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);
- }
-
+ // render to slice 23
QRhiCommandBuffer *cb = nullptr;
QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
QVERIFY(cb);
- cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, batch);
+ 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) {
+ 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] {
@@ -5168,9 +5563,11 @@ void tst_QRhi::oneDimTexture()
}
// mipmaps and 1D render target
- if (!rhi->isFeatureSupported(QRhi::OneDimensionalTextureMipmaps))
- QSKIP("Skipping testing 1D texture mipmaps and 1D render target because they are reported "
- "as unsupported");
+ 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(
@@ -5424,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()
@@ -5616,6 +6037,10 @@ void tst_QRhi::tessellation_data()
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);
@@ -5769,6 +6194,325 @@ void tst_QRhi::tessellation()
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();
@@ -5844,7 +6588,7 @@ void tst_QRhi::storageBuffer()
u->uploadStaticBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize, QByteArray(blocks["fromGpu"].knownSize, 0).constData());
QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
- srb->setBindings({QRhiShaderResourceBinding::bufferLoadStore(blocks["toGpu"].binding, QRhiShaderResourceBinding::ComputeStage, toGpuBuffer.data()),
+ srb->setBindings({QRhiShaderResourceBinding::bufferLoad(blocks["toGpu"].binding, QRhiShaderResourceBinding::ComputeStage, toGpuBuffer.data()),
QRhiShaderResourceBinding::bufferLoadStore(blocks["fromGpu"].binding, QRhiShaderResourceBinding::ComputeStage, fromGpuBuffer.data())});
QVERIFY(srb->create());
@@ -5864,7 +6608,7 @@ void tst_QRhi::storageBuffer()
QVERIFY(u);
int readCompletedNotifications = 0;
- QRhiBufferReadbackResult result;
+ QRhiReadbackResult result;
result.completed = [&readCompletedNotifications]() { readCompletedNotifications++; };
u->readBackBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize, &result);
@@ -5889,5 +6633,452 @@ void tst_QRhi::storageBuffer()
}
}
+ 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 454681f81d..09bf0d585d 100644
--- a/tests/auto/gui/rhi/qshader/CMakeLists.txt
+++ b/tests/auto/gui/rhi/qshader/CMakeLists.txt
@@ -1,12 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from qshader.pro.
-
#####################################################################
## 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}"
diff --git a/tests/auto/gui/rhi/qshader/tst_qshader.cpp b/tests/auto/gui/rhi/qshader/tst_qshader.cpp
index 425b5951d0..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
{