diff options
Diffstat (limited to 'tests/auto/gui/rhi')
44 files changed, 2694 insertions, 134 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..5b07c7bf2b 100644 --- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat +++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat @@ -11,3 +11,18 @@ 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 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 Binary files differnew file mode 100644 index 0000000000..fb8680024a --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/half.vert.qsb 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 Binary files differnew file mode 100644 index 0000000000..0f42103ac5 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb 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 Binary files differnew file mode 100644 index 0000000000..8c98d92c46 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb 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 Binary files differnew file mode 100644 index 0000000000..8aa7632717 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb 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 Binary files differnew file mode 100644 index 0000000000..ee90983e0b --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsb 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..fd6cabebc5 --- /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) 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 Binary files differnew file mode 100644 index 0000000000..77887ed941 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb 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 Binary files differnew file mode 100644 index 0000000000..b4c43ecc9b --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb 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 Binary files differnew file mode 100644 index 0000000000..53fc9a1906 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb 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 Binary files differnew file mode 100644 index 0000000000..e48aa0269c --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb 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 Binary files differnew file mode 100644 index 0000000000..23a433b5ae --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb 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 Binary files differnew file mode 100644 index 0000000000..8b1cff52fd --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb Binary files differnew file mode 100644 index 0000000000..7eda4bed2d --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb 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 Binary files differnew file mode 100644 index 0000000000..b503d596c6 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb 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 Binary files differnew file mode 100644 index 0000000000..898bda454a --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb 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 Binary files differnew file mode 100644 index 0000000000..07384d643c --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 6409c43bc4..914bc0ec7c 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -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> # 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(); @@ -104,6 +104,8 @@ private slots: void renderToTextureTexturedQuadAllDynamicBuffers(); void renderToTextureDeferredSrb_data(); void renderToTextureDeferredSrb(); + void renderToTextureDeferredUpdateSamplerInSrb_data(); + void renderToTextureDeferredUpdateSamplerInSrb(); void renderToTextureMultipleUniformBuffersAndDynamicOffset_data(); void renderToTextureMultipleUniformBuffersAndDynamicOffset(); void renderToTextureSrbReuse_data(); @@ -121,12 +123,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 +137,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 +165,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 +184,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 +199,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 +207,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 +233,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 +279,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()); @@ -383,7 +408,12 @@ void tst_QRhi::create() QRhi::Tessellation, QRhi::GeometryShader, QRhi::TextureArrayRange, - QRhi::NonFillPolygonMode + QRhi::NonFillPolygonMode, + QRhi::OneDimensionalTextures, + QRhi::OneDimensionalTextureMipmaps, + QRhi::HalfAttributes, + QRhi::RenderToOneDimensionalTexture, + QRhi::ThreeDimensionalTextureMipmaps }; for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i) rhi->isFeatureSupported(features[i]); @@ -399,6 +429,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 +487,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 +520,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 +575,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 +638,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 +709,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 +721,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 +736,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 +756,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 +821,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 +904,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 +980,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 +1007,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 +1116,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 +1204,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 +1529,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 +3097,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 +3555,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 @@ -3629,7 +3949,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 +4223,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 +4241,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 +4267,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 +4582,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,21 +4752,13 @@ void tst_QRhi::pipelineCache() } } -void tst_QRhi::textureImportOpenGL_data() -{ - rhiTestDataOpenGL(); -} - void tst_QRhi::textureImportOpenGL() { - 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"); @@ -4433,21 +4798,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 +4896,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 +4941,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 +4961,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 +5513,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 +5580,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 +5789,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::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 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[] = { + -1.0, -1.0, 0.0, 0.0, + 1.0, -1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 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 Binary files differnew file mode 100644 index 0000000000..4d49ede3ff --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb 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 Binary files differnew file mode 100644 index 0000000000..ea68da7eb4 --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb 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 Binary files differnew file mode 100644 index 0000000000..41005f76bc --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb 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 Binary files differnew file mode 100644 index 0000000000..39734b6d5d --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb 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 Binary files differnew file mode 100644 index 0000000000..edcd84cbe6 --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data/storage_buffer_info_v8.comp.qsb 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 cd883b34d9..fed2e883e0 100644 --- a/tests/auto/gui/rhi/qshader/tst_qshader.cpp +++ b/tests/auto/gui/rhi/qshader/tst_qshader.cpp @@ -5,8 +5,8 @@ #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 { @@ -18,12 +18,15 @@ private slots: void genVariants(); void shaderDescImplicitSharing(); void bakedShaderImplicitSharing(); + void sortedKeys(); void mslResourceMapping(); void serializeShaderDesc(); void comparison(); void loadV4(); void manualShaderPackCreation(); void loadV6WithSeparateImagesAndSamplers(); + void loadV7(); + void loadV8(); }; static QShader getShader(const QString &name) @@ -56,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))); @@ -65,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: @@ -81,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: @@ -93,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: @@ -118,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; } } @@ -131,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; @@ -154,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(); @@ -192,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); @@ -222,22 +225,32 @@ 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); } } +void tst_QShader::sortedKeys() +{ + QShader s = getShader(QLatin1String(":/data/texture_all_v4.frag.qsb")); + QVERIFY(s.isValid()); + QList<QShaderKey> availableShaders = s.availableShaders(); + QCOMPARE(availableShaders.size(), 7); + std::sort(availableShaders.begin(), availableShaders.end()); + QCOMPARE(availableShaders, s.availableShaders()); +} + void tst_QShader::mslResourceMapping() { QShader s = getShader(QLatin1String(":/data/texture_all_v4.frag.qsb")); @@ -245,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)))); @@ -264,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 } @@ -281,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()); @@ -306,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()); @@ -366,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)))); @@ -377,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: @@ -389,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: @@ -401,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: @@ -426,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; } } @@ -537,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); } @@ -553,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)))); @@ -563,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 : { @@ -575,8 +588,112 @@ 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> |