diff options
Diffstat (limited to 'tests/auto/gui/rhi')
53 files changed, 3505 insertions, 408 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 65c9e4834b..b3284f8979 100644 --- a/tests/auto/gui/rhi/qrhi/BLACKLIST +++ b/tests/auto/gui/rhi/qrhi/BLACKLIST @@ -10,3 +10,12 @@ android # Same here, GLES 3.0 features seem hopeless [renderToTextureTextureArray] android +# Ditto +[renderToTextureSampleWithSeparateTextureAndSampler] +android +[renderToFloatTexture] +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 65d072c6b9..3b0d643060 100644 --- a/tests/auto/gui/rhi/qrhi/CMakeLists.txt +++ b/tests/auto/gui/rhi/qrhi/CMakeLists.txt @@ -1,26 +1,28 @@ -# 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}" + data/* +) + qt_internal_add_test(tst_qrhi SOURCES tst_qrhi.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::GuiPrivate + TESTDATA ${qrhi_resource_files} + BUILTIN_TESTDATA ) - -# Resources: -set(qrhi_resource_files - "data" -) - -qt_internal_add_resource(tst_qrhi "qrhi" - PREFIX - "/" - FILES - ${qrhi_resource_files} -) - diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat index 0102457b8a..fe40459719 100644 --- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat +++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat @@ -1,48 +1,30 @@ -::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -:: :: Copyright (C) 2019 The Qt Company Ltd. -:: Contact: https://www.qt.io/licensing/ -:: -:: This file is part of the QtQuick module of the Qt Toolkit. -:: -:: $QT_BEGIN_LICENSE:LGPL$ -:: Commercial License Usage -:: Licensees holding valid commercial Qt licenses may use this file in -:: accordance with the commercial license agreement provided with the -:: Software or, alternatively, in accordance with the terms contained in -:: a written agreement between you and The Qt Company. For licensing terms -:: and conditions see https://www.qt.io/terms-conditions. For further -:: information use the contact form at https://www.qt.io/contact-us. -:: -:: GNU Lesser General Public License Usage -:: Alternatively, this file may be used under the terms of the GNU Lesser -:: General Public License version 3 as published by the Free Software -:: Foundation and appearing in the file LICENSE.LGPL3 included in the -:: packaging of this file. Please review the following information to -:: ensure the GNU Lesser General Public License version 3 requirements -:: will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -:: -:: GNU General Public License Usage -:: Alternatively, this file may be used under the terms of the GNU -:: General Public License version 2.0 or (at your option) the GNU General -:: Public license version 3 or any later version approved by the KDE Free -:: Qt Foundation. The licenses are as published by the Free Software -:: Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -:: included in the packaging of this file. Please review the following -:: information to ensure the GNU General Public License requirements will -:: be met: https://www.gnu.org/licenses/gpl-2.0.html and -:: https://www.gnu.org/licenses/gpl-3.0.html. -:: -:: $QT_END_LICENSE$ -:: -::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.frag qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 20 -o simpletextured_array.frag.qsb simpletextured_array.frag +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured_separate.frag.qsb simpletextured_separate.frag qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.vert.qsb textured_multiubuf.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.frag.qsb textured_multiubuf.frag +qsb --glsl 320es,410 --msl 12 --msltess simpletess.vert -o simpletess.vert.qsb +qsb --glsl 320es,410 --msl 12 --tess-mode triangles simpletess.tesc -o simpletess.tesc.qsb +qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 simpletess.tese -o simpletess.tese.qsb +qsb --glsl 320es,410 --msl 12 simpletess.frag -o simpletess.frag.qsb +qsb --glsl 310es,430 --msl 12 --hlsl 50 storagebuffer.comp -o storagebuffer.comp.qsb +qsb --glsl 320es,430 --msl 12 --msltess storagebuffer_runtime.vert -o storagebuffer_runtime.vert.qsb +qsb --glsl 320es,430 --msl 12 --tess-mode triangles storagebuffer_runtime.tesc -o storagebuffer_runtime.tesc.qsb +qsb --glsl 320es,430 --msl 12 --tess-vertex-count 3 storagebuffer_runtime.tese -o storagebuffer_runtime.tese.qsb +qsb --glsl 320es,430 --msl 12 storagebuffer_runtime.frag -o storagebuffer_runtime.frag.qsb +qsb --glsl 320es,430 --hlsl 50 -c --msl 12 storagebuffer_runtime.comp -o storagebuffer_runtime.comp.qsb +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o half.vert.qsb half.vert +qsb --glsl 320es,430 --msl 21 --msltess tessinterfaceblocks.vert -o tessinterfaceblocks.vert.qsb +qsb --glsl 320es,430 --msl 21 --tess-mode triangles tessinterfaceblocks.tesc -o tessinterfaceblocks.tesc.qsb +qsb --glsl 320es,430 --msl 21 --tess-vertex-count 3 tessinterfaceblocks.tese -o tessinterfaceblocks.tese.qsb +qsb --glsl 320es,430 --msl 21 simpletess.frag -o tessinterfaceblocks.frag.qsb +qsb --view-count 2 --glsl "300 es,330" --hlsl 61 -c --msl 12 multiview.vert -o multiview.vert.qsb +qsb --glsl "300 es,330" --hlsl 61 -c --msl 12 multiview.frag -o multiview.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/half.vert b/tests/auto/gui/rhi/qrhi/data/half.vert new file mode 100644 index 0000000000..b503201351 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/half.vert @@ -0,0 +1,10 @@ +#version 440 + +layout(location = 0) in vec3 position; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + gl_Position = vec4(position, 1.0); +} diff --git a/tests/auto/gui/rhi/qrhi/data/half.vert.qsb b/tests/auto/gui/rhi/qrhi/data/half.vert.qsb 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/multiview.frag b/tests/auto/gui/rhi/qrhi/data/multiview.frag new file mode 100644 index 0000000000..375587662f --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/multiview.frag @@ -0,0 +1,10 @@ +#version 440 + +layout(location = 0) in vec3 v_color; + +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = vec4(v_color, 1.0); +} diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb b/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb Binary files differnew file mode 100644 index 0000000000..db8133f12e --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.vert b/tests/auto/gui/rhi/qrhi/data/multiview.vert new file mode 100644 index 0000000000..b9c9e5a704 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/multiview.vert @@ -0,0 +1,18 @@ +#version 440 +#extension GL_EXT_multiview : require + +layout(location = 0) in vec4 pos; +layout(location = 1) in vec3 color; + +layout(location = 0) out vec3 v_color; + +layout(std140, binding = 0) uniform buf +{ + mat4 mvp[2]; +}; + +void main() +{ + v_color = color; + gl_Position = mvp[gl_ViewIndex] * pos; +} diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb b/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb Binary files differnew file mode 100644 index 0000000000..cf1f67f58f --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/multiview.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/simpletextured_separate.frag b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag new file mode 100644 index 0000000000..41b0e4f1c2 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag @@ -0,0 +1,14 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(binding = 3) uniform texture2D tex; +layout(binding = 5) uniform sampler samp; + +void main() +{ + vec4 c = texture(sampler2D(tex, samp), uv); + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.qsb Binary files differnew file mode 100644 index 0000000000..c5afe1a8eb --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.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..ffa0bc7004 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp @@ -0,0 +1,28 @@ +#version 430 +layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +layout (binding = 0, std430) readonly buffer toGpu +{ + float _float; + vec2 _vec2; + vec3 _vec3; + vec4 _vec4; +}; + +layout (binding = 1, std140) buffer fromGpu +{ + int _int; + ivec2 _ivec2; + ivec3 _ivec3; + ivec4 _ivec4; +}; + +void main() +{ + _int = int(_float); + _ivec2 = ivec2(_vec2); + _ivec3 = ivec3(_vec3); + _ivec4 = ivec4(_vec4); +} + diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer.comp.qsb Binary files differnew file mode 100644 index 0000000000..b02f541cc5 --- /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/qrhi.qrc b/tests/auto/gui/rhi/qrhi/qrhi.qrc deleted file mode 100644 index f161d8aad6..0000000000 --- a/tests/auto/gui/rhi/qrhi/qrhi.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>data</file> - </qresource> -</RCC> diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 5142d5566f..8929b69cec 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -1,62 +1,36 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QThread> #include <QFile> #include <QOffscreenSurface> #include <QPainter> +#include <qrgbafloat.h> +#include <qrgba64.h> -#include <QtGui/private/qrhi_p.h> -#include <QtGui/private/qrhi_p_p.h> -#include <QtGui/private/qrhinull_p.h> +#include <private/qrhi_p.h> #if QT_CONFIG(opengl) # include <QOpenGLContext> # include <QOpenGLFunctions> -# include <QtGui/private/qrhigles2_p.h> +# include <QtGui/private/qguiapplication_p.h> +# include <qpa/qplatformintegration.h> # define TST_GL #endif #if QT_CONFIG(vulkan) # include <QVulkanInstance> # include <QVulkanFunctions> -# include <QtGui/private/qrhivulkan_p.h> # define TST_VK #endif #ifdef Q_OS_WIN -#include <QtGui/private/qrhid3d11_p.h> # define TST_D3D11 +# define TST_D3D12 #endif -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) -# include <QtGui/private/qrhimetal_p.h> +#if QT_CONFIG(metal) # define TST_MTL #endif @@ -72,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(); @@ -96,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(); @@ -106,6 +83,8 @@ private slots: void renderPassDescriptorCompatibility(); void renderPassDescriptorClone_data(); void renderPassDescriptorClone(); + void textureWithSampleCount_data(); + void textureWithSampleCount(); void renderToTextureSimple_data(); void renderToTextureSimple(); @@ -117,6 +96,8 @@ private slots: void renderToTextureTextureArray(); void renderToTextureTexturedQuad_data(); void renderToTextureTexturedQuad(); + void renderToTextureSampleWithSeparateTextureAndSampler_data(); + void renderToTextureSampleWithSeparateTextureAndSampler(); void renderToTextureArrayOfTexturedQuad_data(); void renderToTextureArrayOfTexturedQuad(); void renderToTextureTexturedQuadAndUniformBuffer_data(); @@ -125,30 +106,57 @@ private slots: void renderToTextureTexturedQuadAllDynamicBuffers(); void renderToTextureDeferredSrb_data(); void renderToTextureDeferredSrb(); + void renderToTextureDeferredUpdateSamplerInSrb_data(); + void renderToTextureDeferredUpdateSamplerInSrb(); void renderToTextureMultipleUniformBuffersAndDynamicOffset_data(); void renderToTextureMultipleUniformBuffersAndDynamicOffset(); void renderToTextureSrbReuse_data(); void renderToTextureSrbReuse(); void renderToTextureIndexedDraw_data(); void renderToTextureIndexedDraw(); + void renderToTextureArrayMultiView_data(); + void renderToTextureArrayMultiView(); void renderToWindowSimple_data(); void renderToWindowSimple(); void finishWithinSwapchainFrame_data(); void finishWithinSwapchainFrame(); void resourceUpdateBatchBufferTextureWithSwapchainFrames_data(); void resourceUpdateBatchBufferTextureWithSwapchainFrames(); + void textureRenderTargetAutoRebuild_data(); + void textureRenderTargetAutoRebuild(); 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(); + void renderToFloatTexture_data(); + void renderToFloatTexture(); + 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); @@ -161,7 +169,10 @@ private: QRhiVulkanInitParams vk; #endif #ifdef TST_D3D11 - QRhiD3D11InitParams d3d; + QRhiD3D11InitParams d3d11; +#endif +#ifdef TST_D3D12 + QRhiD3D12InitParams d3d12; #endif #ifdef TST_MTL QRhiMetalInitParams mtl; @@ -177,6 +188,12 @@ private: void tst_QRhi::initTestCase() { #ifdef TST_GL + QSurfaceFormat fmt; + fmt.setDepthBufferSize(24); + fmt.setStencilBufferSize(8); + QSurfaceFormat::setDefaultFormat(fmt); + + initParams.gl.format = QSurfaceFormat::defaultFormat(); fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); initParams.gl.fallbackSurface = fallbackSurface; #endif @@ -186,7 +203,7 @@ void tst_QRhi::initTestCase() if (supportedVersion >= QVersionNumber(1, 2)) vulkanInstance.setApiVersion(QVersionNumber(1, 2)); else if (supportedVersion >= QVersionNumber(1, 1)) - vulkanInstance.setApiVersion(QVersionNumber(1, 2)); + vulkanInstance.setApiVersion(QVersionNumber(1, 1)); vulkanInstance.setLayers({ "VK_LAYER_KHRONOS_validation" }); vulkanInstance.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions()); vulkanInstance.create(); @@ -194,7 +211,10 @@ void tst_QRhi::initTestCase() #endif #ifdef TST_D3D11 - initParams.d3d.enableDebugLayer = true; + initParams.d3d11.enableDebugLayer = true; +#endif +#ifdef TST_D3D12 + initParams.d3d12.enableDebugLayer = true; #endif } @@ -212,32 +232,29 @@ void tst_QRhi::rhiTestData() QTest::addColumn<QRhi::Implementation>("impl"); QTest::addColumn<QRhiInitParams *>("initParams"); +// webOS does not support raster (software) pipeline +#ifndef Q_OS_WEBOS 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(); @@ -260,10 +277,13 @@ void tst_QRhi::create() QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); if (rhi) { + QVERIFY(QRhi::probe(impl, initParams)); + qDebug() << rhi->driverInfo(); 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()); @@ -298,8 +318,13 @@ void tst_QRhi::create() QVERIFY(resUpd); resUpd->release(); - QVERIFY(!rhi->supportedSampleCounts().isEmpty()); - QVERIFY(rhi->supportedSampleCounts().contains(1)); + const QVector<int> supportedSampleCounts = rhi->supportedSampleCounts(); + QVERIFY(!supportedSampleCounts.isEmpty()); + QVERIFY(supportedSampleCounts.contains(1)); + for (int i = 1; i < supportedSampleCounts.count(); ++i) { + // Verify the list is sorted. Internally the backends rely on this. + QVERIFY(supportedSampleCounts[i] > supportedSampleCounts[i - 1]); + } QVERIFY(rhi->ubufAlignment() > 0); QCOMPARE(rhi->ubufAligned(123), aligned(123, rhi->ubufAlignment())); @@ -342,6 +367,9 @@ void tst_QRhi::create() const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight); const int texArrayMax = rhi->resourceLimit(QRhi::TextureArraySizeMax); const int uniBufRangeMax = rhi->resourceLimit(QRhi::MaxUniformBufferRange); + const int maxVertexInputs = rhi->resourceLimit(QRhi::MaxVertexInputs); + const int maxVertexOutputs = rhi->resourceLimit(QRhi::MaxVertexOutputs); + QVERIFY(texMin >= 1); QVERIFY(texMax >= texMin); QVERIFY(maxAtt >= 1); @@ -349,9 +377,10 @@ void tst_QRhi::create() if (rhi->isFeatureSupported(QRhi::TextureArrays)) QVERIFY(texArrayMax > 1); QVERIFY(uniBufRangeMax >= 224 * 4 * 4); + QVERIFY(maxVertexInputs >= 8); + QVERIFY(maxVertexOutputs >= 8); QVERIFY(rhi->nativeHandles()); - QVERIFY(rhi->profiler()); const QRhi::Feature features[] = { QRhi::MultisampleTexture, @@ -382,7 +411,21 @@ void tst_QRhi::create() QRhi::PipelineCacheDataLoadSave, QRhi::ImageDataStride, QRhi::RenderBufferImport, - QRhi::ThreeDimensionalTextures + QRhi::ThreeDimensionalTextures, + QRhi::RenderTo3DTextureSlice, + QRhi::TextureArrays, + QRhi::Tessellation, + QRhi::GeometryShader, + QRhi::TextureArrayRange, + QRhi::NonFillPolygonMode, + QRhi::OneDimensionalTextures, + QRhi::OneDimensionalTextureMipmaps, + QRhi::HalfAttributes, + QRhi::RenderToOneDimensionalTexture, + QRhi::ThreeDimensionalTextureMipmaps, + QRhi::MultiView, + QRhi::TextureViewFormat, + QRhi::ResolveDepthStencil }; for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i) rhi->isFeatureSupported(features[i]); @@ -398,6 +441,38 @@ void tst_QRhi::create() } } +void tst_QRhi::stats_data() +{ + rhiTestData(); +} + +void tst_QRhi::stats() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing statistics getter"); + + QRhiStats stats = rhi->statistics(); + qDebug() << stats; + QCOMPARE(stats.totalPipelineCreationTime, 0); + + if (impl == QRhi::Vulkan) { + QScopedPointer<QRhiBuffer> buf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 32768)); + QVERIFY(buf->create()); + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(1024, 1024))); + QVERIFY(tex->create()); + + stats = rhi->statistics(); + qDebug() << stats; + QVERIFY(stats.allocCount > 0); + QVERIFY(stats.blockCount > 0); + QVERIFY(stats.usedBytes > 0); + } +} + void tst_QRhi::nativeHandles_data() { rhiTestData(); @@ -424,10 +499,10 @@ void tst_QRhi::nativeHandles() case QRhi::Vulkan: { const QRhiVulkanNativeHandles *vkHandles = static_cast<const QRhiVulkanNativeHandles *>(rhiHandles); + QVERIFY(vkHandles->inst); + QCOMPARE(vkHandles->inst, &vulkanInstance); QVERIFY(vkHandles->physDev); QVERIFY(vkHandles->dev); - QVERIFY(vkHandles->gfxQueueFamilyIdx >= 0); - QVERIFY(vkHandles->gfxQueueIdx >= 0); QVERIFY(vkHandles->gfxQueue); QVERIFY(vkHandles->vmemAllocator); } @@ -457,6 +532,17 @@ void tst_QRhi::nativeHandles() } break; #endif +#ifdef TST_D3D12 + case QRhi::D3D12: + { + const QRhiD3D12NativeHandles *d3dHandles = static_cast<const QRhiD3D12NativeHandles *>(rhiHandles); + QVERIFY(d3dHandles->dev); + QVERIFY(d3dHandles->minimumFeatureLevel > 0); + QVERIFY(d3dHandles->adapterLuidLow || d3dHandles->adapterLuidHigh); + QVERIFY(d3dHandles->commandQueue); + } + break; +#endif #ifdef TST_MTL case QRhi::Metal: { @@ -501,6 +587,10 @@ void tst_QRhi::nativeHandles() case QRhi::D3D11: break; #endif +#ifdef TST_D3D12 + case QRhi::D3D12: + break; +#endif #ifdef TST_MTL case QRhi::Metal: { @@ -560,6 +650,10 @@ void tst_QRhi::nativeHandles() case QRhi::D3D11: break; #endif +#ifdef TST_D3D12 + case QRhi::D3D12: + break; +#endif #ifdef TST_MTL case QRhi::Metal: break; @@ -627,7 +721,7 @@ void tst_QRhi::nativeHandlesImportVulkan() void tst_QRhi::nativeHandlesImportD3D11() { #ifdef TST_D3D11 - QScopedPointer<QRhi> rhi(QRhi::create(QRhi::D3D11, &initParams.d3d, QRhi::Flags(), nullptr)); + QScopedPointer<QRhi> rhi(QRhi::create(QRhi::D3D11, &initParams.d3d11, QRhi::Flags(), nullptr)); if (!rhi) QSKIP("QRhi could not be created, skipping testing D3D11 native handle import"); @@ -639,7 +733,7 @@ void tst_QRhi::nativeHandlesImportD3D11() h.featureLevel = 0; // see if these are queried as expected, even when not provided h.adapterLuidLow = 0; h.adapterLuidHigh = 0; - QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d, QRhi::Flags(), &h)); + QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d11, QRhi::Flags(), &h)); QVERIFY(adoptingRhi); const QRhiD3D11NativeHandles *newNativeHandles = static_cast<const QRhiD3D11NativeHandles *>(adoptingRhi->nativeHandles()); QCOMPARE(newNativeHandles->dev, nativeHandles->dev); @@ -654,7 +748,7 @@ void tst_QRhi::nativeHandlesImportD3D11() QRhiD3D11NativeHandles h = *nativeHandles; h.dev = nullptr; h.context = nullptr; - QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d, QRhi::Flags(), &h)); + QScopedPointer<QRhi> adoptingRhi(QRhi::create(QRhi::D3D11, &initParams.d3d11, QRhi::Flags(), &h)); QVERIFY(adoptingRhi); const QRhiD3D11NativeHandles *newNativeHandles = static_cast<const QRhiD3D11NativeHandles *>(adoptingRhi->nativeHandles()); QVERIFY(newNativeHandles->dev != nativeHandles->dev); @@ -674,7 +768,6 @@ void tst_QRhi::nativeHandlesImportOpenGL() #ifdef TST_GL QRhiGles2NativeHandles h; QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext); - ctx->setFormat(QRhiGles2InitParams::adjustedFormat()); if (!ctx->create()) QSKIP("No OpenGL context, skipping OpenGL-specific test"); h.context = ctx.data(); @@ -740,6 +833,14 @@ void tst_QRhi::nativeTexture() } break; #endif +#ifdef TST_D3D12 + case QRhi::D3D12: + { + auto *texture = reinterpret_cast<void *>(nativeTex.object); + QVERIFY(texture); + } + break; +#endif #ifdef TST_MTL case QRhi::Metal: { @@ -815,6 +916,18 @@ void tst_QRhi::nativeBuffer() } break; #endif + #ifdef TST_D3D12 + case QRhi::D3D12: + { + QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers + for (int i = 0; i < nativeBuf.slotCount; ++i) { + auto *buffer = static_cast<void * const *>(nativeBuf.objects[i]); + QVERIFY(buffer); + QVERIFY(*buffer); + } + } + break; + #endif #ifdef TST_MTL case QRhi::Metal: { @@ -879,7 +992,7 @@ void tst_QRhi::resourceUpdateBatchBuffer() batch->updateDynamicBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); batch->updateDynamicBuffer(dynamicBuffer.data(), 0, 12, b.constData()); - QRhiBufferReadbackResult readResult; + QRhiReadbackResult readResult; bool readCompleted = false; readResult.completed = [&readCompleted] { readCompleted = true; }; batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); @@ -906,7 +1019,7 @@ void tst_QRhi::resourceUpdateBatchBuffer() batch->uploadStaticBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); batch->uploadStaticBuffer(dynamicBuffer.data(), 0, 12, b.constData()); - QRhiBufferReadbackResult readResult; + QRhiReadbackResult readResult; bool readCompleted = false; readResult.completed = [&readCompleted] { readCompleted = true; }; @@ -1015,7 +1128,7 @@ void tst_QRhi::resourceUpdateBatchRGBATextureUpload() QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); - QRhiTextureUploadEntry upload(0, 0, { image.constBits(), int(image.sizeInBytes()) }); + QRhiTextureUploadEntry upload(0, 0, { image.constBits(), quint32(image.sizeInBytes()) }); QRhiTextureUploadDescription uploadDesc(upload); batch->uploadTexture(texture.data(), uploadDesc); @@ -1103,8 +1216,8 @@ void tst_QRhi::resourceUpdateBatchRGBATextureUpload() // SourceTopLeft is not supported for non-QImage-based uploads. const QImage im = image.copy(QRect(greenRectPos, copySize)); QRhiTextureSubresourceUploadDescription desc; - desc.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(im.constBits()), - int(im.sizeInBytes()))); + desc.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(im.constBits()), im.sizeInBytes())); + desc.setSourceSize(copySize); desc.setDestinationTopLeft(QPoint(gap, gap)); @@ -1428,6 +1541,86 @@ void tst_QRhi::resourceUpdateBatchLotsOfResources() submitResourceUpdates(rhi.data(), b); } +void tst_QRhi::resourceUpdateBatchBetweenFrames_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchBetweenFrames() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing resource updates"); + + QImage image(128, 128, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::red); + static const float bufferData[64] = {}; + + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + QVERIFY(result == QRhi::FrameOpSuccess); + QVERIFY(cb); + + static const int TEXTURE_COUNT = 123; + static const int BUFFER_COUNT = 456; + + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + std::vector<std::unique_ptr<QRhiTexture>> textures; + std::vector<std::unique_ptr<QRhiBuffer>> buffers; + + for (int i = 0; i < TEXTURE_COUNT; ++i) { + std::unique_ptr<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, + image.size(), + 1, + QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + u->uploadTexture(texture.get(), image); + textures.push_back(std::move(texture)); + } + + for (int i = 0; i < BUFFER_COUNT; ++i) { + std::unique_ptr<QRhiBuffer> buffer(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 256)); + QVERIFY(buffer->create()); + u->uploadStaticBuffer(buffer.get(), bufferData); + buffers.push_back(std::move(buffer)); + } + + rhi->endOffscreenFrame(); + cb = nullptr; + + // 'u' stays valid, commit it in another frame + + result = rhi->beginOffscreenFrame(&cb); + QVERIFY(result == QRhi::FrameOpSuccess); + QVERIFY(cb); + + cb->resourceUpdate(u); // this should work + + rhi->endOffscreenFrame(); + + u = rhi->nextResourceUpdateBatch(); + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + u->readBackTexture(textures[5].get(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), u)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); ++x) + QCOMPARE(wrapperImage.pixel(x, y), qRgba(255, 0, 0, 255)); + } +} + static QShader loadShader(const char *name) { QFile f(QString::fromUtf8(name)); @@ -1529,6 +1722,31 @@ void tst_QRhi::renderToTextureSimple_data() rhiTestData(); } +static QRhiGraphicsPipeline *createSimplePipeline(QRhi *rhi, QRhiShaderResourceBindings *srb, QRhiRenderPassDescriptor *rpDesc) +{ + std::unique_ptr<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/simple.vert.qsb"); + if (!vs.isValid()) + return nullptr; + QShader fs = loadShader(":/data/simple.frag.qsb"); + if (!fs.isValid()) + return nullptr; + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb); + pipeline->setRenderPassDescriptor(rpDesc); + return pipeline->create() ? pipeline.release() : nullptr; +} + +static const float triangleVertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + 0.0f, 1.0f +}; + void tst_QRhi::renderToTextureSimple() { QFETCH(QRhi::Implementation, impl); @@ -1554,32 +1772,15 @@ void tst_QRhi::renderToTextureSimple() QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); - static const float vertices[] = { - -1.0f, -1.0f, - 1.0f, -1.0f, - 0.0f, 1.0f - }; - QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); QVERIFY(vbuf->create()); - updates->uploadStaticBuffer(vbuf.data(), vertices); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); QVERIFY(srb->create()); - QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); - QShader vs = loadShader(":/data/simple.vert.qsb"); - QVERIFY(vs.isValid()); - QShader fs = loadShader(":/data/simple.frag.qsb"); - QVERIFY(fs.isValid()); - pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); - QRhiVertexInputLayout inputLayout; - inputLayout.setBindings({ { 2 * sizeof(float) } }); - inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); - pipeline->setVertexInputLayout(inputLayout); - pipeline->setShaderResourceBindings(srb.data()); - pipeline->setRenderPassDescriptor(rpDesc.data()); - - QVERIFY(pipeline->create()); + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); cb->setGraphicsPipeline(pipeline.data()); @@ -1681,32 +1882,15 @@ void tst_QRhi::renderToTextureMip() QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); - static const float vertices[] = { - -1.0f, -1.0f, - 1.0f, -1.0f, - 0.0f, 1.0f - }; - QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); QVERIFY(vbuf->create()); - updates->uploadStaticBuffer(vbuf.data(), vertices); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); QVERIFY(srb->create()); - QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); - QShader vs = loadShader(":/data/simple.vert.qsb"); - QVERIFY(vs.isValid()); - QShader fs = loadShader(":/data/simple.frag.qsb"); - QVERIFY(fs.isValid()); - pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); - QRhiVertexInputLayout inputLayout; - inputLayout.setBindings({ { 2 * sizeof(float) } }); - inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); - pipeline->setVertexInputLayout(inputLayout); - pipeline->setShaderResourceBindings(srb.data()); - pipeline->setRenderPassDescriptor(rpDesc.data()); - - QVERIFY(pipeline->create()); + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); cb->setGraphicsPipeline(pipeline.data()); @@ -1803,32 +1987,15 @@ void tst_QRhi::renderToTextureCubemapFace() QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); - static const float vertices[] = { - -1.0f, -1.0f, - 1.0f, -1.0f, - 0.0f, 1.0f - }; - QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); QVERIFY(vbuf->create()); - updates->uploadStaticBuffer(vbuf.data(), vertices); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); QVERIFY(srb->create()); - QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); - QShader vs = loadShader(":/data/simple.vert.qsb"); - QVERIFY(vs.isValid()); - QShader fs = loadShader(":/data/simple.frag.qsb"); - QVERIFY(fs.isValid()); - pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); - QRhiVertexInputLayout inputLayout; - inputLayout.setBindings({ { 2 * sizeof(float) } }); - inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); - pipeline->setVertexInputLayout(inputLayout); - pipeline->setShaderResourceBindings(srb.data()); - pipeline->setRenderPassDescriptor(rpDesc.data()); - - QVERIFY(pipeline->create()); + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); cb->setGraphicsPipeline(pipeline.data()); @@ -1946,32 +2113,15 @@ void tst_QRhi::renderToTextureTextureArray() QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); - static const float vertices[] = { - -1.0f, -1.0f, - 1.0f, -1.0f, - 0.0f, 1.0f - }; - QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); QVERIFY(vbuf->create()); - updates->uploadStaticBuffer(vbuf.data(), vertices); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); QVERIFY(srb->create()); - QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); - QShader vs = loadShader(":/data/simple.vert.qsb"); - QVERIFY(vs.isValid()); - QShader fs = loadShader(":/data/simple.frag.qsb"); - QVERIFY(fs.isValid()); - pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); - QRhiVertexInputLayout inputLayout; - inputLayout.setBindings({ { 2 * sizeof(float) } }); - inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); - pipeline->setVertexInputLayout(inputLayout); - pipeline->setShaderResourceBindings(srb.data()); - pipeline->setRenderPassDescriptor(rpDesc.data()); - - QVERIFY(pipeline->create()); + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); cb->setGraphicsPipeline(pipeline.data()); @@ -2156,6 +2306,131 @@ void tst_QRhi::renderToTextureTexturedQuad() QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); } +void tst_QRhi::renderToTextureSampleWithSeparateTextureAndSampler_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureSampleWithSeparateTextureAndSampler() +{ + // Same as renderToTextureTexturedQuad but the fragment shader uses a + // separate image and sampler. For Vulkan/Metal/D3D11 these are natively + // supported. For OpenGL this exercises the auto-generated combined sampler + // in the GLSL code and the mapping table that gets applied at run time by + // the backend. + + 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> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->create()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::texture(3, QRhiShaderResourceBinding::FragmentStage, inputTexture.data()), + QRhiShaderResourceBinding::sampler(5, QRhiShaderResourceBinding::FragmentStage, sampler.data()) + }); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/simpletextured.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simpletextured_separate.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()); + + 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(); + + QRgb white = qRgba(255, 255, 255, 255); + QCOMPARE(result.pixel(79, 77), white); + QCOMPARE(result.pixel(124, 81), white); + QCOMPARE(result.pixel(128, 149), white); + QCOMPARE(result.pixel(120, 189), white); + QCOMPARE(result.pixel(116, 185), white); + + QRgb empty = qRgba(0, 0, 0, 0); + QCOMPARE(result.pixel(11, 45), empty); + QCOMPARE(result.pixel(246, 202), empty); + QCOMPARE(result.pixel(130, 18), empty); + QCOMPARE(result.pixel(4, 227), empty); + + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qRed(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qBlue(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qRed(result.pixel(214, 191))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); +} + void tst_QRhi::renderToTextureArrayOfTexturedQuad_data() { rhiTestData(); @@ -2834,6 +3109,147 @@ void tst_QRhi::renderToTextureDeferredSrb() QCOMPARE(result.pixel(4, 227), empty); } +void tst_QRhi::renderToTextureDeferredUpdateSamplerInSrb_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureDeferredUpdateSamplerInSrb() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->create()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVerticesUvs))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), quadVerticesUvs); + + QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->create()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer<QRhiSampler> sampler1(rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear, + QRhiSampler::Repeat, QRhiSampler::Repeat)); + QVERIFY(sampler1->create()); + QScopedPointer<QRhiSampler> sampler2(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler2->create()); + + QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4)); + QVERIFY(ubuf->create()); + + QMatrix4x4 matrix; + updates->updateDynamicBuffer(ubuf.data(), 0, 64, matrix.constData()); + float opacity = 0.5f; + updates->updateDynamicBuffer(ubuf.data(), 64, 4, &opacity); + + const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data()), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler1.data()) + }); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/textured.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/textured.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->create()); + + // Now update the sampler to a different one, so if the pipeline->create() + // baked in static samplers somewhere (with 3D APIs where that's a thing), + // based on sampler1, that's now all invalid. + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data()), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler2.data()) + }); + srb->updateResources(); // now it references sampler2, not sampler1 + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(); + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QVERIFY(!result.isNull()); + + if (impl == QRhi::Null) + return; + + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // opacity 0.5 (premultiplied) + static const auto checkSemiWhite = [](const QRgb &c) { + QRgb semiWhite127 = qPremultiply(qRgba(255, 255, 255, 127)); + QRgb semiWhite128 = qPremultiply(qRgba(255, 255, 255, 128)); + return c == semiWhite127 || c == semiWhite128; + }; + QVERIFY(checkSemiWhite(result.pixel(79, 77))); + QVERIFY(checkSemiWhite(result.pixel(124, 81))); + QVERIFY(checkSemiWhite(result.pixel(128, 149))); + QVERIFY(checkSemiWhite(result.pixel(120, 189))); + QVERIFY(checkSemiWhite(result.pixel(116, 185))); + QVERIFY(checkSemiWhite(result.pixel(191, 172))); + + QRgb empty = qRgba(0, 0, 0, 0); + QCOMPARE(result.pixel(11, 45), empty); + QCOMPARE(result.pixel(246, 202), empty); + QCOMPARE(result.pixel(130, 18), empty); + QCOMPARE(result.pixel(4, 227), empty); +} + void tst_QRhi::renderToTextureMultipleUniformBuffersAndDynamicOffset_data() { rhiTestData(); @@ -3151,7 +3567,6 @@ void tst_QRhi::setWindowType(QWindow *window, QRhi::Implementation impl) switch (impl) { #ifdef TST_GL case QRhi::OpenGLES2: - window->setFormat(QRhiGles2InitParams::adjustedFormat()); window->setSurfaceType(QSurface::OpenGLSurface); break; #endif @@ -3202,18 +3617,13 @@ void tst_QRhi::renderToTextureIndexedDraw() QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); - static const float vertices[] = { - -1.0f, -1.0f, - 1.0f, -1.0f, - 0.0f, 1.0f - }; static const quint16 indices[] = { 0, 1, 2 }; - QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); QVERIFY(vbuf->create()); - updates->uploadStaticBuffer(vbuf.data(), vertices); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); QScopedPointer<QRhiBuffer> ibuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indices))); QVERIFY(ibuf->create()); @@ -3222,20 +3632,8 @@ void tst_QRhi::renderToTextureIndexedDraw() QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); QVERIFY(srb->create()); - QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); - QShader vs = loadShader(":/data/simple.vert.qsb"); - QVERIFY(vs.isValid()); - QShader fs = loadShader(":/data/simple.frag.qsb"); - QVERIFY(fs.isValid()); - pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); - QRhiVertexInputLayout inputLayout; - inputLayout.setBindings({ { 2 * sizeof(float) } }); - inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); - pipeline->setVertexInputLayout(inputLayout); - pipeline->setShaderResourceBindings(srb.data()); - pipeline->setRenderPassDescriptor(rpDesc.data()); - - QVERIFY(pipeline->create()); + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); @@ -3309,6 +3707,182 @@ void tst_QRhi::renderToTextureIndexedDraw() QVERIFY(redCount > blueCount); } +void tst_QRhi::renderToTextureArrayMultiView_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureArrayMultiView() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + if (!rhi->isFeatureSupported(QRhi::MultiView)) + QSKIP("Multiview not supported, skipping testing on this backend"); + + if (rhi->backend() == QRhi::Vulkan && rhi->driverInfo().deviceType == QRhiDriverInfo::CpuDevice) + QSKIP("lavapipe does not like multiview, skip for now"); + + for (int sampleCount : rhi->supportedSampleCounts()) { + const QSize outputSize(1920, 1080); + QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget; + if (sampleCount <= 1) + textureFlags |= QRhiTexture::UsedAsTransferSource; + QScopedPointer<QRhiTexture> texture(rhi->newTextureArray(QRhiTexture::RGBA8, 2, outputSize, sampleCount, textureFlags)); + QVERIFY(texture->create()); + + // exercise a depth-stencil buffer as well, not that the triangle needs it; note that this also needs to be a two-layer texture array + QScopedPointer<QRhiTexture> ds(rhi->newTextureArray(QRhiTexture::D24S8, 2, outputSize, sampleCount, QRhiTexture::RenderTarget)); + QVERIFY(ds->create()); + + QScopedPointer<QRhiTexture> resolveTexture; + if (sampleCount > 1) { + resolveTexture.reset(rhi->newTextureArray(QRhiTexture::RGBA8, 2, outputSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(resolveTexture->create()); + } + + QRhiColorAttachment multiViewAtt(texture.get()); + multiViewAtt.setMultiViewCount(2); + if (sampleCount > 1) + multiViewAtt.setResolveTexture(resolveTexture.get()); + + QRhiTextureRenderTargetDescription rtDesc(multiViewAtt); + rtDesc.setDepthTexture(ds.get()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->create()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static float triangleData[] = { + 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f + }; + + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleData))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), triangleData); + + QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 128)); // mat4 mvp[2] + QVERIFY(ubuf->create()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf.get()) + }); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> ps(rhi->newGraphicsPipeline()); + ps->setShaderStages({ + { QRhiShaderStage::Vertex, loadShader(":/data/multiview.vert.qsb") }, + { QRhiShaderStage::Fragment, loadShader(":/data/multiview.frag.qsb") } + }); + ps->setMultiViewCount(2); // the view count must be set both on the render target and the pipeline + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, quint32(2 * sizeof(float)) } + }); + ps->setDepthTest(true); + ps->setDepthWrite(true); + ps->setSampleCount(sampleCount); + ps->setVertexInputLayout(inputLayout); + ps->setShaderResourceBindings(srb.get()); + ps->setRenderPassDescriptor(rpDesc.get()); + QVERIFY(ps->create()); + + QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix(); + mvp.perspective(45.0f, outputSize.width() / float(outputSize.height()), 0.01f, 1000.0f); + mvp.translate(0, 0, -2); + mvp.rotate(90, 0, 0, 1); // point left + updates->updateDynamicBuffer(ubuf.get(), 0, 64, mvp.constData()); + mvp.rotate(-180, 0, 0, 1); // point right + updates->updateDynamicBuffer(ubuf.get(), 64, 64, mvp.constData()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(ps.data()); + cb->setShaderResources(); + cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + QRhiReadbackResult readResult[2]; + QRhiReadbackDescription readbackDesc; + if (sampleCount > 1) + readbackDesc.setTexture(resolveTexture.get()); + else + readbackDesc.setTexture(texture.get()); + readbackDesc.setLayer(0); + readbackBatch->readBackTexture(readbackDesc, &readResult[0]); + readbackDesc.setLayer(1); + readbackBatch->readBackTexture(readbackDesc, &readResult[1]); + + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + if (rhi->backend() == QRhi::Null) + QSKIP("No real content with Null backend, skipping multiview content check"); + + // both readbacks should be finished now due to using offscreen frames + + QImage image0 = QImage(reinterpret_cast<const uchar *>(readResult[0].data.constData()), + readResult[0].pixelSize.width(), readResult[0].pixelSize.height(), + QImage::Format_RGBA8888); + if (rhi->isYUpInFramebuffer()) // note that we used clipSpaceCorrMatrix + image0 = image0.mirrored(); + + QImage image1 = QImage(reinterpret_cast<const uchar *>(readResult[1].data.constData()), + readResult[1].pixelSize.width(), readResult[1].pixelSize.height(), + QImage::Format_RGBA8888); + if (rhi->isYUpInFramebuffer()) + image1 = image1.mirrored(); + + QVERIFY(!image0.isNull()); + QVERIFY(!image1.isNull()); + + // image0 should have a triangle rotated so that it points left with the red + // tip. image1 should have a triangle rotated so that it points right with + // the red tip. Both are centered, so we will check in range 0..width/2 for + // image0 and width/2..width-1 for image1 to see if the red-enough pixels + // are present. + + int y = image0.height() / 2; + int n = 0; + for (int x = 0; x < image0.width() / 2; ++x) { + QRgb c = image0.pixel(x, y); + if (qRed(c) > 250 && qGreen(c) < 10 && qBlue(c) < 10) + ++n; + } + QVERIFY(n >= 10); + + y = image1.height() / 2; + n = 0; + for (int x = image1.width() / 2; x < image1.width(); ++x) { + QRgb c = image1.pixel(x, y); + if (qRed(c) > 250 && qGreen(c) < 10 && qBlue(c) < 10) + ++n; + } + QVERIFY(n >= 10); + } +} + void tst_QRhi::renderToWindowSimple_data() { rhiTestData(); @@ -3342,32 +3916,15 @@ void tst_QRhi::renderToWindowSimple() QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); - static const float vertices[] = { - -1.0f, -1.0f, - 1.0f, -1.0f, - 0.0f, 1.0f - }; - QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); QVERIFY(vbuf->create()); - updates->uploadStaticBuffer(vbuf.data(), vertices); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); QVERIFY(srb->create()); - QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); - QShader vs = loadShader(":/data/simple.vert.qsb"); - QVERIFY(vs.isValid()); - QShader fs = loadShader(":/data/simple.frag.qsb"); - QVERIFY(fs.isValid()); - pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); - QRhiVertexInputLayout inputLayout; - inputLayout.setBindings({ { 2 * sizeof(float) } }); - inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); - pipeline->setVertexInputLayout(inputLayout); - pipeline->setShaderResourceBindings(srb.data()); - pipeline->setRenderPassDescriptor(rpDesc.data()); - - QVERIFY(pipeline->create()); + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); const int asyncReadbackFrames = rhi->resourceLimit(QRhi::MaxAsyncReadbackFrames); // one frame issues the readback, then we do MaxAsyncReadbackFrames more to ensure the readback completes @@ -3381,6 +3938,9 @@ void tst_QRhi::renderToWindowSimple() QVERIFY(rhi->beginFrame(swapChain.data()) == QRhi::FrameOpSuccess); QRhiCommandBuffer *cb = swapChain->currentFrameCommandBuffer(); QRhiRenderTarget *rt = swapChain->currentFrameRenderTarget(); + QCOMPARE(rt->resourceType(), QRhiResource::SwapChainRenderTarget); + QVERIFY(rt->renderPassDescriptor()); + QCOMPARE(static_cast<QRhiSwapChainRenderTarget *>(rt)->swapChain(), swapChain.data()); const QSize outputSize = swapChain->currentPixelSize(); QCOMPARE(rt->pixelSize(), outputSize); QRhiViewport viewport(0, 0, float(outputSize.width()), float(outputSize.height())); @@ -3481,26 +4041,10 @@ void tst_QRhi::finishWithinSwapchainFrame() QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); QVERIFY(srb->create()); - QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); - QShader vs = loadShader(":/data/simple.vert.qsb"); - QVERIFY(vs.isValid()); - QShader fs = loadShader(":/data/simple.frag.qsb"); - QVERIFY(fs.isValid()); - pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); - QRhiVertexInputLayout inputLayout; - inputLayout.setBindings({ { 2 * sizeof(float) } }); - inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); - pipeline->setVertexInputLayout(inputLayout); - pipeline->setShaderResourceBindings(srb.data()); - pipeline->setRenderPassDescriptor(rpDesc.data()); - QVERIFY(pipeline->create()); + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); - static const float vertices[] = { - -1.0f, -1.0f, - 1.0f, -1.0f, - 0.0f, 1.0f - }; - QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); QVERIFY(vbuf->create()); // exercise begin/endExternal() just a little bit, note ExternalContent for beginPass() @@ -3513,7 +4057,7 @@ void tst_QRhi::finishWithinSwapchainFrame() // times within the same frame for (int i = 0; i < 5; ++i) { QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); - updates->uploadStaticBuffer(vbuf.data(), vertices); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); cb->beginPass(rt, Qt::blue, { 1.0f, 0 }, updates, QRhiCommandBuffer::ExternalContent); @@ -3593,7 +4137,7 @@ void tst_QRhi::resourceUpdateBatchBufferTextureWithSwapchainFrames() const char *b = "abcdefghi"; bool readCompleted = false; - QRhiBufferReadbackResult readResult; + QRhiReadbackResult readResult; readResult.completed = [&readCompleted] { readCompleted = true; }; QRhiReadbackResult texReadResult; texReadResult.completed = [&readCompleted] { readCompleted = true; }; @@ -3767,6 +4311,67 @@ void tst_QRhi::resourceUpdateBatchBufferTextureWithSwapchainFrames() } } +void tst_QRhi::textureRenderTargetAutoRebuild_data() +{ + rhiTestData(); +} + +void tst_QRhi::textureRenderTargetAutoRebuild() +{ + 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"); + + // case 1: beginPass's implicit create() + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(texture->create()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ { texture.data() } })); + QScopedPointer<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.data()); + QVERIFY(rt->create()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 }); + cb->endPass(); + rhi->endOffscreenFrame(); + + texture->setPixelSize(QSize(256, 256)); + QVERIFY(texture->create()); + QCOMPARE(texture->pixelSize(), QSize(256, 256)); + + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + // no rt->create() but beginPass() does it implicitly for us + cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 }); + QCOMPARE(rt->pixelSize(), QSize(256, 256)); + cb->endPass(); + rhi->endOffscreenFrame(); + } + + // case 2: pixelSize's implicit create() + { + QSize sz(512, 512); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, sz, 1, QRhiTexture::RenderTarget)); + QVERIFY(texture->create()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ { texture.data() } })); + QScopedPointer<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.data()); + QVERIFY(rt->create()); + QCOMPARE(rt->pixelSize(), sz); + + sz = QSize(256, 256); + texture->setPixelSize(sz); + QVERIFY(texture->create()); + QCOMPARE(rt->pixelSize(), sz); + } +} + void tst_QRhi::srbLayoutCompatibility_data() { rhiTestData(); @@ -3806,7 +4411,7 @@ void tst_QRhi::srbLayoutCompatibility() QVERIFY(srb2->isLayoutCompatible(srb1.data())); QCOMPARE(srb1->serializedLayoutDescription(), srb2->serializedLayoutDescription()); - QVERIFY(srb1->serializedLayoutDescription().count() == 0); + QVERIFY(srb1->serializedLayoutDescription().size() == 0); } // different count (not compatible) @@ -3824,8 +4429,8 @@ void tst_QRhi::srbLayoutCompatibility() QVERIFY(!srb2->isLayoutCompatible(srb1.data())); QVERIFY(srb1->serializedLayoutDescription() != srb2->serializedLayoutDescription()); - QVERIFY(srb1->serializedLayoutDescription().count() == 0); - QVERIFY(srb2->serializedLayoutDescription().count() == 1 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING); + QVERIFY(srb1->serializedLayoutDescription().size() == 0); + QVERIFY(srb2->serializedLayoutDescription().size() == 1 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING); } // full match (compatible) @@ -3850,7 +4455,7 @@ void tst_QRhi::srbLayoutCompatibility() QVERIFY(!srb1->serializedLayoutDescription().isEmpty()); QVERIFY(!srb2->serializedLayoutDescription().isEmpty()); QCOMPARE(srb1->serializedLayoutDescription(), srb2->serializedLayoutDescription()); - QVERIFY(srb1->serializedLayoutDescription().count() == 2 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING); + QVERIFY(srb1->serializedLayoutDescription().size() == 2 * QRhiShaderResourceBinding::LAYOUT_DESC_ENTRIES_PER_BINDING); // see what we would get if a binding list got serialized "manually", without pulling it out from the srb after building // (the results should be identical) @@ -4165,6 +4770,59 @@ void tst_QRhi::renderPassDescriptorCompatibility() } else { qDebug("Skipping texture format dependent tests"); } + + if (rhi->isFeatureSupported(QRhi::MultiView)) { + { + QScopedPointer<QRhiTexture> texArr(rhi->newTextureArray(QRhiTexture::RGBA8, 2, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(texArr->create()); + QRhiColorAttachment multiViewAtt(texArr.data()); + multiViewAtt.setMultiViewCount(2); + QRhiTextureRenderTargetDescription rtDesc(multiViewAtt); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->create()); + + QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor()); + rt2->setRenderPassDescriptor(rpDesc2.data()); + QVERIFY(rt2->create()); + + QVERIFY(rpDesc->isCompatible(rpDesc2.data())); + QVERIFY(rpDesc2->isCompatible(rpDesc.data())); + QCOMPARE(rpDesc->serializedFormat(), rpDesc2->serializedFormat()); + + QScopedPointer<QRhiRenderPassDescriptor> rpDescClone(rpDesc->newCompatibleRenderPassDescriptor()); + QVERIFY(rpDesc->isCompatible(rpDescClone.data())); + QVERIFY(rpDesc2->isCompatible(rpDescClone.data())); + QCOMPARE(rpDesc->serializedFormat(), rpDescClone->serializedFormat()); + + // With Vulkan the multiViewCount really matters since it is baked + // in to underlying native object (VkRenderPass). Verify that the + // compatibility check fails when the view count differs. Other + // backends cannot do this test since they will likely report the + // rps being compatible regardless. + if (impl == QRhi::Vulkan) { + QRhiColorAttachment nonMultiViewAtt(texArr.data()); + QRhiTextureRenderTargetDescription rtDesc3(nonMultiViewAtt); + QScopedPointer<QRhiTextureRenderTarget> rt3(rhi->newTextureRenderTarget(rtDesc3)); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc3(rt3->newCompatibleRenderPassDescriptor()); + rt3->setRenderPassDescriptor(rpDesc3.data()); + QVERIFY(rt3->create()); + + QVERIFY(!rpDesc->isCompatible(rpDesc3.data())); + QVERIFY(!rpDesc2->isCompatible(rpDesc3.data())); + QVERIFY(rpDesc->serializedFormat() != rpDesc3->serializedFormat()); + + QScopedPointer<QRhiRenderPassDescriptor> rpDesc3Clone(rpDesc3->newCompatibleRenderPassDescriptor()); + QVERIFY(!rpDesc->isCompatible(rpDesc3Clone.data())); + QVERIFY(!rpDesc2->isCompatible(rpDesc3Clone.data())); + QVERIFY(rpDesc->serializedFormat() != rpDesc3Clone->serializedFormat()); + } + } + } else { + qDebug("Skipping multiview dependent tests"); + } } void tst_QRhi::renderPassDescriptorClone_data() @@ -4282,22 +4940,67 @@ void tst_QRhi::pipelineCache() } } -void tst_QRhi::textureImportOpenGL_data() +void tst_QRhi::textureWithSampleCount_data() { - rhiTestDataOpenGL(); + rhiTestData(); } -void tst_QRhi::textureImportOpenGL() +void tst_QRhi::textureWithSampleCount() { QFETCH(QRhi::Implementation, impl); - if (impl != QRhi::OpenGLES2) - QSKIP("Skipping OpenGL-dependent test"); - -#ifdef TST_GL QFETCH(QRhiInitParams *, initParams); QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); if (!rhi) + QSKIP("QRhi could not be created, skipping testing renderpass descriptors"); + + if (!rhi->isFeatureSupported(QRhi::MultisampleTexture)) + QSKIP("No multisample texture support with this backend, skipping"); + + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1)); + QVERIFY(tex->create()); + } + + // Ensure 0 is accepted the same way as 1. + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 0)); + QVERIFY(tex->create()); + } + + // Note that we intentionally do not pass in RenderTarget in flags. Where + // matters for create(), the backend is expected to act as if it was + // specified whenever samples > 1. (in practice it does not make sense to not + // have the flag for an msaa texture, but we only care about create() here) + + // Pick the commonly supported sample count of 4. + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 4)); + QVERIFY(tex->create()); + } + + // Now a bogus value that is typically in-between the supported values. + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 3)); + QVERIFY(tex->create()); + } + + // Now a bogus value that is out of range. + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 123)); + QVERIFY(tex->create()); + } +} + + +void tst_QRhi::textureImportOpenGL() +{ +#ifdef TST_GL + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) + QSKIP("Skipping OpenGL-dependent test"); + + QScopedPointer<QRhi> rhi(QRhi::create(QRhi::OpenGLES2, &initParams.gl, QRhi::Flags(), nullptr)); + if (!rhi) QSKIP("QRhi could not be created, skipping testing native texture"); QVERIFY(rhi->makeThreadLocalNativeContextCurrent()); @@ -4336,21 +5039,13 @@ void tst_QRhi::textureImportOpenGL() #endif } -void tst_QRhi::renderbufferImportOpenGL_data() -{ - rhiTestDataOpenGL(); -} - void tst_QRhi::renderbufferImportOpenGL() { - QFETCH(QRhi::Implementation, impl); - if (impl != QRhi::OpenGLES2) - QSKIP("Skipping OpenGL-dependent test"); - #ifdef TST_GL - QFETCH(QRhiInitParams *, initParams); + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) + QSKIP("Skipping OpenGL-dependent test"); - QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + QScopedPointer<QRhi> rhi(QRhi::create(QRhi::OpenGLES2, &initParams.gl, QRhi::Flags(), nullptr)); if (!rhi) QSKIP("QRhi could not be created, skipping testing native texture"); @@ -4442,7 +5137,7 @@ void tst_QRhi::threeDimTexture() } // mipmaps - { + if (rhi->isFeatureSupported(QRhi::ThreeDimensionalTextureMipmaps)) { QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, WIDTH, HEIGHT, DEPTH, 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); QVERIFY(texture->create()); @@ -4487,6 +5182,8 @@ void tst_QRhi::threeDimTexture() // problems with this. if (impl != QRhi::Null && impl != QRhi::OpenGLES2) QVERIFY(imageRGBAEquals(result, referenceImage, 2)); + } else { + qDebug("Skipping 3D texture mipmap generation test because it is reported as unsupported"); } // render target (one slice) @@ -4505,11 +5202,531 @@ void tst_QRhi::threeDimTexture() rt->setRenderPassDescriptor(rp.data()); QVERIFY(rt->create()); + // render to slice 23 + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }); + // slice 23 is now blue + cb->endPass(); + rhi->endOffscreenFrame(); + + // Fill all other slices with some color. We should be free to do this + // step *before* the "render to slice 23" block above as well. However, + // as QTBUG-111772 shows, some Vulkan implementations have problems + // then. (or it could be QRhi is doing something wrong, but there is no + // evidence of that yet) For now, keep the order of first rendering to + // a slice and then uploading data for the rest. QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); QVERIFY(batch); - for (int i = 0; i < DEPTH; ++i) { - QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888); + if (i != SLICE) { + QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888); + img.fill(QColor::fromRgb(i * 2, 0, 0)); + QRhiTextureUploadEntry sliceUpload(i, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(texture.data(), sliceUpload); + } + } + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + // read back slice 23 (blue) + batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + QRhiReadbackDescription readbackDescription(texture.data()); + readbackDescription.setLayer(23); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH, HEIGHT, result.format()); + referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 1.0f)); + // the Null backend does not render so skip the verification for that + if (impl != QRhi::Null) + QVERIFY(imageRGBAEquals(result, referenceImage)); + + // read back slice 0 (black) + batch = rhi->nextResourceUpdateBatch(); + result = QImage(); + readbackDescription.setLayer(0); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 0.0f)); + QVERIFY(imageRGBAEquals(result, referenceImage)); + + // read back slice 127 (almost red) + batch = rhi->nextResourceUpdateBatch(); + result = QImage(); + readbackDescription.setLayer(127); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + referenceImage.fill(QColor::fromRgb(254, 0, 0)); + QVERIFY(imageRGBAEquals(result, referenceImage)); + } +} +void tst_QRhi::oneDimTexture_data() +{ + rhiTestData(); +} + +void tst_QRhi::oneDimTexture() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing 1D textures"); + + if (!rhi->isFeatureSupported(QRhi::OneDimensionalTextures)) + QSKIP("Skipping testing 1D textures because they are reported as unsupported"); + + const int WIDTH = 512; + const int LAYERS = 128; + + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, WIDTH, 0, 0)); + QVERIFY(texture->create()); + + QVERIFY(texture->flags().testFlag(QRhiTexture::Flag::OneDimensional)); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + QImage img(WIDTH, 1, QImage::Format_RGBA8888); + img.fill(QColor::fromRgb(255, 0, 0)); + + QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(texture.data(), upload); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + } + + { + QScopedPointer<QRhiTexture> texture( + rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0))); + QVERIFY(texture->create()); + + QVERIFY(texture->flags().testFlag(QRhiTexture::Flag::OneDimensional)); + QVERIFY(texture->flags().testFlag(QRhiTexture::Flag::TextureArray)); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + for (int i = 0; i < LAYERS; ++i) { + QImage img(WIDTH, 1, QImage::Format_RGBA8888); + img.fill(QColor::fromRgb(i * 2, 0, 0)); + QRhiTextureUploadEntry layerUpload(i, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(texture.data(), layerUpload); + } + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + } + + // Copy from 2D texture to 1D texture + { + const int WIDTH = 256; + const int HEIGHT = 256; + + QScopedPointer<QRhiTexture> srcTexture(rhi->newTexture( + QRhiTexture::RGBA8, WIDTH, HEIGHT, 0, 1, QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(srcTexture->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888); + for (int x = 0; x < WIDTH; ++x) { + for (int y = 0; y < HEIGHT; ++y) { + img.setPixelColor(x, y, QColor::fromRgb(x, y, 0)); + } + } + QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(srcTexture.data(), upload); + + QScopedPointer<QRhiTexture> dstTexture(rhi->newTexture( + QRhiTexture::RGBA8, WIDTH, 0, 0, 1, QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(dstTexture->create()); + + QRhiTextureCopyDescription copy; + copy.setPixelSize(QSize(WIDTH / 2, 1)); + copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0)); + copy.setSourceTopLeft(QPoint(33, 67)); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + copy.setDestinationTopLeft(QPoint(0, 0)); + copy.setSourceTopLeft(QPoint(99, 12)); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + + QRhiReadbackDescription readbackDescription(dstTexture.data()); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH, 1, result.format()); + for (int i = 0; i < WIDTH / 2; ++i) { + referenceImage.setPixelColor(i, 0, img.pixelColor(99 + i, 12)); + referenceImage.setPixelColor(WIDTH / 2 + i, 0, img.pixelColor(33 + i, 67)); + } + + QVERIFY(imageRGBAEquals(result, referenceImage)); + } + + // Copy from 2D texture to 1D texture array + { + const int WIDTH = 256; + const int HEIGHT = 256; + const int LAYERS = 64; + + QScopedPointer<QRhiTexture> srcTexture(rhi->newTexture( + QRhiTexture::RGBA8, WIDTH, HEIGHT, 0, 1, QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(srcTexture->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + QImage img(WIDTH, HEIGHT, QImage::Format_RGBA8888); + for (int x = 0; x < WIDTH; ++x) { + for (int y = 0; y < HEIGHT; ++y) { + img.setPixelColor(x, y, QColor::fromRgb(x, y, 0)); + } + } + QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(srcTexture.data(), upload); + + QScopedPointer<QRhiTexture> dstTexture( + rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1, + QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(dstTexture->create()); + + QRhiTextureCopyDescription copy; + copy.setPixelSize(QSize(WIDTH / 2, 1)); + copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0)); + copy.setSourceTopLeft(QPoint(33, 67)); + copy.setDestinationLayer(12); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + copy.setDestinationTopLeft(QPoint(0, 0)); + copy.setSourceTopLeft(QPoint(99, 12)); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + + QRhiReadbackDescription readbackDescription(dstTexture.data()); + readbackDescription.setLayer(12); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH, 1, result.format()); + for (int i = 0; i < WIDTH / 2; ++i) { + referenceImage.setPixelColor(i, 0, img.pixelColor(99 + i, 12)); + referenceImage.setPixelColor(WIDTH / 2 + i, 0, img.pixelColor(33 + i, 67)); + } + + QVERIFY(imageRGBAEquals(result, referenceImage)); + } + + // Copy from 1D texture array to 1D texture + { + const int WIDTH = 256; + const int LAYERS = 256; + + QScopedPointer<QRhiTexture> srcTexture( + rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1, + QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(srcTexture->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + for (int y = 0; y < LAYERS; ++y) { + QImage img(WIDTH, 1, QImage::Format_RGBA8888); + for (int x = 0; x < WIDTH; ++x) { + img.setPixelColor(x, 0, QColor::fromRgb(x, y, 0)); + } + QRhiTextureUploadEntry upload(y, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(srcTexture.data(), upload); + } + + QScopedPointer<QRhiTexture> dstTexture(rhi->newTexture( + QRhiTexture::RGBA8, WIDTH, 0, 0, 1, QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(dstTexture->create()); + + QRhiTextureCopyDescription copy; + copy.setPixelSize(QSize(WIDTH / 2, 1)); + copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0)); + copy.setSourceLayer(67); + copy.setSourceTopLeft(QPoint(33, 0)); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + copy.setDestinationTopLeft(QPoint(0, 0)); + copy.setSourceLayer(12); + copy.setSourceTopLeft(QPoint(99, 0)); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + + QRhiReadbackDescription readbackDescription(dstTexture.data()); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH, 1, result.format()); + for (int i = 0; i < WIDTH / 2; ++i) { + referenceImage.setPixelColor(i, 0, QColor::fromRgb(99 + i, 12, 0)); + referenceImage.setPixelColor(WIDTH / 2 + i, 0, QColor::fromRgb(33 + i, 67, 0)); + } + + QVERIFY(imageRGBAEquals(result, referenceImage)); + } + + // Copy from 1D texture to 1D texture array + { + const int WIDTH = 256; + const int LAYERS = 256; + + QScopedPointer<QRhiTexture> srcTexture(rhi->newTexture( + QRhiTexture::RGBA8, WIDTH, 0, 0, 1, QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(srcTexture->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + QImage img(WIDTH, 1, QImage::Format_RGBA8888); + for (int x = 0; x < WIDTH; ++x) { + img.setPixelColor(x, 0, QColor::fromRgb(x, 0, 0)); + } + QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(srcTexture.data(), upload); + + QScopedPointer<QRhiTexture> dstTexture( + rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1, + QRhiTexture::Flag::UsedAsTransferSource)); + QVERIFY(dstTexture->create()); + + QRhiTextureCopyDescription copy; + copy.setPixelSize(QSize(WIDTH / 2, 1)); + copy.setDestinationTopLeft(QPoint(WIDTH / 2, 0)); + copy.setDestinationLayer(67); + copy.setSourceTopLeft(QPoint(33, 0)); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + copy.setDestinationTopLeft(QPoint(0, 0)); + copy.setSourceTopLeft(QPoint(99, 0)); + batch->copyTexture(dstTexture.data(), srcTexture.data(), copy); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + + QRhiReadbackDescription readbackDescription(dstTexture.data()); + readbackDescription.setLayer(67); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH, 1, result.format()); + for (int i = 0; i < WIDTH / 2; ++i) { + referenceImage.setPixelColor(i, 0, QColor::fromRgb(99 + i, 0, 0)); + referenceImage.setPixelColor(WIDTH / 2 + i, 0, QColor::fromRgb(33 + i, 0, 0)); + } + + QVERIFY(imageRGBAEquals(result, referenceImage)); + } + + // mipmaps and 1D render target + if (!rhi->isFeatureSupported(QRhi::OneDimensionalTextureMipmaps) + || !rhi->isFeatureSupported(QRhi::RenderToOneDimensionalTexture)) + { + QSKIP("Skipping testing 1D texture mipmaps and 1D render target because they are reported as unsupported"); + } + + { + QScopedPointer<QRhiTexture> texture( + rhi->newTexture(QRhiTexture::RGBA8, WIDTH, 0, 0, 1, + QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); + QVERIFY(texture->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + QImage img(WIDTH, 1, QImage::Format_RGBA8888); + img.fill(QColor::fromRgb(128, 0, 0)); + QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(texture.data(), upload); + + batch->generateMips(texture.data()); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + // read back level 1 (256x1, #800000ff) + batch = rhi->nextResourceUpdateBatch(); + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + QRhiReadbackDescription readbackDescription(texture.data()); + readbackDescription.setLevel(1); + readbackDescription.setLayer(0); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH / 2, 1, result.format()); + referenceImage.fill(QColor::fromRgb(128, 0, 0)); + + QVERIFY(imageRGBAEquals(result, referenceImage, 2)); + } + + { + QScopedPointer<QRhiTexture> texture( + rhi->newTextureArray(QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1, + QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); + QVERIFY(texture->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + for (int i = 0; i < LAYERS; ++i) { + QImage img(WIDTH, 1, QImage::Format_RGBA8888); + img.fill(QColor::fromRgb(i * 2, 0, 0)); + QRhiTextureUploadEntry sliceUpload(i, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(texture.data(), sliceUpload); + } + + batch->generateMips(texture.data()); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + // read back slice 63 of level 1 (256x1, #7E0000FF) + batch = rhi->nextResourceUpdateBatch(); + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + QRhiReadbackDescription readbackDescription(texture.data()); + readbackDescription.setLevel(1); + readbackDescription.setLayer(63); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH / 2, 1, result.format()); + referenceImage.fill(QColor::fromRgb(126, 0, 0)); + + // Now restrict the test a bit. The Null QRhi backend has broken support for + // mipmap generation of 1D texture arrays. + if (impl != QRhi::Null) + QVERIFY(imageRGBAEquals(result, referenceImage, 2)); + } + + // 1D texture render target + // NB with Vulkan we require Vulkan 1.1 for this to work. + // Metal does not allow 1D texture render targets + { + QScopedPointer<QRhiTexture> texture( + rhi->newTexture(QRhiTexture::RGBA8, WIDTH, 0, 0, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QRhiColorAttachment att(texture.data()); + QRhiTextureRenderTargetDescription rtDesc(att); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.data()); + QVERIFY(rt->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + QImage img(WIDTH, 1, QImage::Format_RGBA8888); + img.fill(QColor::fromRgb(128, 0, 0)); + QRhiTextureUploadEntry upload(0, 0, QRhiTextureSubresourceUploadDescription(img)); + batch->uploadTexture(texture.data(), upload); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, batch); + // texture is now blue + cb->endPass(); + rhi->endOffscreenFrame(); + + // read back texture (blue) + batch = rhi->nextResourceUpdateBatch(); + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + QRhiReadbackDescription readbackDescription(texture.data()); + batch->readBackTexture(readbackDescription, &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(!result.isNull()); + QImage referenceImage(WIDTH, 1, result.format()); + referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 1.0f)); + // the Null backend does not render so skip the verification for that + if (impl != QRhi::Null) + QVERIFY(imageRGBAEquals(result, referenceImage)); + } + + // 1D array texture render target (one slice) + // NB with Vulkan we require Vulkan 1.1 for this to work. + // Metal does not allow 1D texture render targets + { + const int SLICE = 23; + QScopedPointer<QRhiTexture> texture(rhi->newTextureArray( + QRhiTexture::RGBA8, LAYERS, QSize(WIDTH, 0), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QRhiColorAttachment att(texture.data()); + att.setLayer(SLICE); + QRhiTextureRenderTargetDescription rtDesc(att); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.data()); + QVERIFY(rt->create()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + for (int i = 0; i < LAYERS; ++i) { + QImage img(WIDTH, 1, QImage::Format_RGBA8888); img.fill(QColor::fromRgb(i * 2, 0, 0)); QRhiTextureUploadEntry sliceUpload(i, 0, QRhiTextureSubresourceUploadDescription(img)); batch->uploadTexture(texture.data(), sliceUpload); @@ -4537,7 +5754,7 @@ void tst_QRhi::threeDimTexture() batch->readBackTexture(readbackDescription, &readResult); QVERIFY(submitResourceUpdates(rhi.data(), batch)); QVERIFY(!result.isNull()); - QImage referenceImage(WIDTH, HEIGHT, result.format()); + QImage referenceImage(WIDTH, 1, result.format()); referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 1.0f)); // the Null backend does not render so skip the verification for that if (impl != QRhi::Null) @@ -4604,12 +5821,1263 @@ 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() +{ + rhiTestData(); +} + +void tst_QRhi::renderToFloatTexture() +{ + 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->isTextureFormatSupported(QRhiTexture::RGBA16F)) + QSKIP("RGBA16F is not supported, skipping test"); + + const QSize outputSize(1920, 1080); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA16F, 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(); + + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); + + 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_RGBA16FPx4); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + QCOMPARE(result.size(), texture->pixelSize()); + + if (impl == QRhi::Null) + return; + + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // Now we have a red rectangle on blue background. + const int y = 100; + const QRgbaFloat16 *p = reinterpret_cast<const QRgbaFloat16 *>(result.constScanLine(y)); + int redCount = 0; + int blueCount = 0; + int x = result.width() - 1; + while (x-- >= 0) { + QRgbaFloat16 c = *p++; + if (c.red() >= 0.95f && qFuzzyIsNull(c.green()) && qFuzzyIsNull(c.blue())) + ++redCount; + else if (qFuzzyIsNull(c.red()) && qFuzzyIsNull(c.green()) && c.blue() >= 0.95f) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + QCOMPARE(redCount + blueCount, texture->pixelSize().width()); + QVERIFY(redCount > blueCount); // 1742 > 178 +} + +void tst_QRhi::renderToRgb10Texture_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToRgb10Texture() +{ + 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->isTextureFormatSupported(QRhiTexture::RGB10A2)) + QSKIP("RGB10A2 is not supported, skipping test"); + + const QSize outputSize(1920, 1080); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGB10A2, 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(); + + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), triangleVertices); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(createSimplePipeline(rhi.data(), srb.data(), rpDesc.data())); + QVERIFY(pipeline); + + 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_A2BGR30_Premultiplied); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + QCOMPARE(result.size(), texture->pixelSize()); + + if (impl == QRhi::Null) + return; + + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // Now we have a red rectangle on blue background. + const int y = 100; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + for (int x = 0; x < result.width(); ++x) { + QRgb c = result.pixel(x, y); + 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 > blueCount); // 1742 > 178 +} + +void tst_QRhi::tessellation_data() +{ + rhiTestData(); +} + +void tst_QRhi::tessellation() +{ +#ifdef Q_OS_ANDROID + if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) + QSKIP("Fails on Android 12 (QTBUG-108844)"); +#endif + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + if (!rhi->isFeatureSupported(QRhi::Tessellation)) { + // From a Vulkan or Metal implementation we expect tessellation to work, + // even though it is optional (as per spec) for Vulkan. + QVERIFY(rhi->backend() != QRhi::Vulkan); + QVERIFY(rhi->backend() != QRhi::Metal); + QSKIP("Tessellation is not supported with this graphics API, skipping test"); + } + + if (rhi->backend() == QRhi::D3D11 || rhi->backend() == QRhi::D3D12) + QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet"); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->create()); + + static const float triangleVertices[] = { + 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, + }; + + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); + QVERIFY(vbuf->create()); + u->uploadStaticBuffer(vbuf.data(), triangleVertices); + + QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64)); + QVERIFY(ubuf->create()); + + // Use the 3D API specific correction matrix that flips Y, so we can use + // the OpenGL-targeted vertex data and the tessellation winding order of + // counter-clockwise to get uniform results. + QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix(); + u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()), + }); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + + pipeline->setTopology(QRhiGraphicsPipeline::Patches); + pipeline->setPatchControlPointCount(3); + + pipeline->setShaderStages({ + { QRhiShaderStage::Vertex, loadShader(":/data/simpletess.vert.qsb") }, + { QRhiShaderStage::TessellationControl, loadShader(":/data/simpletess.tesc.qsb") }, + { QRhiShaderStage::TessellationEvaluation, loadShader(":/data/simpletess.tese.qsb") }, + { QRhiShaderStage::Fragment, loadShader(":/data/simpletess.frag.qsb") } + }); + + pipeline->setCullMode(QRhiGraphicsPipeline::Back); // to ensure the winding order is correct + + // won't get the wireframe with OpenGL ES + if (rhi->isFeatureSupported(QRhi::NonFillPolygonMode)) + pipeline->setPolygonMode(QRhiGraphicsPipeline::Line); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 6 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) } + }); + + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->create()); + + QRhiCommandBuffer *cb = nullptr; + QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u); + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) }); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + if (rhi->isYUpInFramebuffer()) // we used clipSpaceCorrMatrix so this is different from many other tests + result = std::move(result).mirrored(); + + QCOMPARE(result.size(), rt->pixelSize()); + + // cannot check rendering results with Null, because there is no rendering there + if (impl == QRhi::Null) + return; + + int redCount = 0, greenCount = 0, blueCount = 0; + for (int y = 0; y < result.height(); ++y) { + const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); + int x = result.width() - 1; + while (x-- >= 0) { + const QRgb c(*p++); + const int red = qRed(c); + const int green = qGreen(c); + const int blue = qBlue(c); + // just count the color components that are above a certain threshold + if (red > 240) + ++redCount; + if (green > 240) + ++greenCount; + if (blue > 240) + ++blueCount; + } + } + + // Line drawing can be different between the 3D APIs. What we will check if + // the number of strong-enough r/g/b components above a certain threshold. + // That is good enough to ensure that something got rendered, i.e. that + // tessellation is not completely broken. + // + // For the record the actual values are something like: + // OpenGL (NVIDIA, Windows) 59 82 82 + // Metal (Intel, macOS 12.5) 59 79 79 + // Vulkan (NVIDIA, Windows) 71 85 85 + + QVERIFY(redCount > 50); + QVERIFY(blueCount > 50); + QVERIFY(greenCount > 50); +} + +void tst_QRhi::tessellationInterfaceBlocks_data() +{ + rhiTestData(); +} + +void tst_QRhi::tessellationInterfaceBlocks() +{ +#ifdef Q_OS_ANDROID + if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) + QSKIP("Fails on Android 12 (QTBUG-108844)"); +#endif + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + // This test is intended for Metal, but will run on other tessellation render pipelines + // + // Metal tessellation uses a combination of compute pipelines for the vert and tesc, and a + // render pipeline for the tese and frag. This test uses input output interface blocks between + // the tesc and tese, and all tese stage builtin inputs to check that the Metal tese-frag + // pipeline vertex inputs are correctly configured. The tese writes the values to a storage + // buffer whose values are checked by the unit test. MSL 2.1 is required for this test. + // (requires support for writing to a storage buffer in the vertex shader within a render + // pipeline) + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + if (!rhi->isFeatureSupported(QRhi::Tessellation)) { + // From a Vulkan or Metal implementation we expect tessellation to work, + // even though it is optional (as per spec) for Vulkan. + QVERIFY(rhi->backend() != QRhi::Vulkan); + QVERIFY(rhi->backend() != QRhi::Metal); + QSKIP("Tessellation is not supported with this graphics API, skipping test"); + } + + if (rhi->backend() == QRhi::D3D11 || rhi->backend() == QRhi::D3D12) + QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet"); + + if (rhi->backend() == QRhi::OpenGLES2) + QSKIP("Skipping test on OpenGL as gl_ClipDistance[] support inconsistent"); + + QScopedPointer<QRhiTexture> texture( + rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->create()); + + static const float triangleVertices[] = { + 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, + }; + + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, + sizeof(triangleVertices))); + QVERIFY(vbuf->create()); + u->uploadStaticBuffer(vbuf.data(), triangleVertices); + + QScopedPointer<QRhiBuffer> ubuf( + rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64)); + QVERIFY(ubuf->create()); + + // Use the 3D API specific correction matrix that flips Y, so we can use + // the OpenGL-targeted vertex data and the tessellation winding order of + // counter-clockwise to get uniform results. + QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix(); + u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData()); + + QScopedPointer<QRhiBuffer> buffer( + rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::UsageFlag::StorageBuffer, 1024)); + QVERIFY(buffer->create()); + + u->uploadStaticBuffer(buffer.data(), 0, 1024, QByteArray(1024, 0).constData()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings( + { QRhiShaderResourceBinding::uniformBuffer( + 0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()), + QRhiShaderResourceBinding::bufferLoadStore( + 1, QRhiShaderResourceBinding::TessellationEvaluationStage, buffer.data()) }); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + + pipeline->setTopology(QRhiGraphicsPipeline::Patches); + pipeline->setPatchControlPointCount(3); + + pipeline->setShaderStages( + { { QRhiShaderStage::Vertex, loadShader(":/data/tessinterfaceblocks.vert.qsb") }, + { QRhiShaderStage::TessellationControl, + loadShader(":/data/tessinterfaceblocks.tesc.qsb") }, + { QRhiShaderStage::TessellationEvaluation, + loadShader(":/data/tessinterfaceblocks.tese.qsb") }, + { QRhiShaderStage::Fragment, loadShader(":/data/tessinterfaceblocks.frag.qsb") } }); + + pipeline->setCullMode(QRhiGraphicsPipeline::Back); // to ensure the winding order is correct + + // won't get the wireframe with OpenGL ES + if (rhi->isFeatureSupported(QRhi::NonFillPolygonMode)) + pipeline->setPolygonMode(QRhiGraphicsPipeline::Line); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 6 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) } }); + + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->create()); + + QRhiCommandBuffer *cb = nullptr; + QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u); + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) }); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + + QRhiReadbackResult bufferReadResult; + bufferReadResult.completed = []() {}; + readbackBatch->readBackBuffer(buffer.data(), 0, 1024, &bufferReadResult); + + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + if (rhi->isYUpInFramebuffer()) // we used clipSpaceCorrMatrix so this is different from many + // other tests + result = std::move(result).mirrored(); + + QCOMPARE(result.size(), rt->pixelSize()); + + // cannot check rendering results with Null, because there is no rendering there + if (impl == QRhi::Null) + return; + + int redCount = 0, greenCount = 0, blueCount = 0; + for (int y = 0; y < result.height(); ++y) { + const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); + int x = result.width() - 1; + while (x-- >= 0) { + const QRgb c(*p++); + const int red = qRed(c); + const int green = qGreen(c); + const int blue = qBlue(c); + // just count the color components that are above a certain threshold + if (red > 240) + ++redCount; + if (green > 240) + ++greenCount; + if (blue > 240) + ++blueCount; + } + } + + // make sure we drew something + QVERIFY(redCount > 50); + QVERIFY(blueCount > 50); + QVERIFY(greenCount > 50); + + // StorageBlock("result" "" knownSize=16 binding=1 set=0 runtimeArrayStride=336 QList( + // BlockVariable("int" "count" offset=0 size=4), + // BlockVariable("struct" "elements" offset=16 size=0 array=QList(0) structMembers=QList( + // BlockVariable("struct" "a" offset=0 size=48 array=QList(3) structMembers=QList( + // BlockVariable("vec3" "color" offset=0 size=12), + // BlockVariable("int" "id" offset=12 size=4))), + // BlockVariable("struct" "b" offset=48 size=144 array=QList(3) structMembers=QList( + // BlockVariable("vec2" "some" offset=0 size=8), + // BlockVariable("int" "other" offset=8 size=12 array=QList(3)), + // BlockVariable("vec3" "variables" offset=32 size=12))), + // BlockVariable("struct" "c" offset=192 size=16 structMembers=QList( + // BlockVariable("vec3" "stuff" offset=0 size=12), + // BlockVariable("float" "more_stuff" offset=12 size=4))), + // BlockVariable("vec4" "tesslevelOuter" offset=208 size=16), + // BlockVariable("vec2" "tessLevelInner" offset=224 size=8), + // BlockVariable("float" "pointSize" offset=232 size=12 array=QList(3)), + // BlockVariable("float" "clipDistance" offset=244 size=60 array=QList(5, 3)), + // BlockVariable("vec3" "tessCoord" offset=304 size=12), + // BlockVariable("int" "patchVerticesIn" offset=316 size=4), + // BlockVariable("int" "primitiveID" offset=320 size=4))))) + + // int count + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[0])[0], 1); + + // a[0].color + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[0], 0.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[1], 0.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[2], 1.0f); + + // a[0].id + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 0 + 12])[0], 91); + + // a[1].color + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[0], 1.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[1], 0.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[2], 0.0f); + + // a[1].id + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 16 + 12])[0], 92); + + // a[2].color + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[0], 0.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[1], 1.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[2], 0.0f); + + // a[2].id + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 32 + 12])[0], 93); + + // b[0].some + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 0])[0], 0.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 0])[1], 0.0f); + + // b[0].other[0] + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[0], 10.0f); + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[1], 20.0f); + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[2], 30.0f); + + // b[0].variables + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[0], 3.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[1], 13.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[2], 17.0f); + + // b[1].some + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 0])[0], 1.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 0])[1], 1.0f); + + // b[1].other[0] + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[0], 10.0f); + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[1], 20.0f); + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[2], 30.0f); + + // b[1].variables + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[0], 3.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[1], 14.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[2], 17.0f); + + // b[2].some + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 0])[0], 2.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 0])[1], 2.0f); + + // b[2].other[0] + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[0], 10.0f); + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[1], 20.0f); + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[2], 30.0f); + + // b[2].variables + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[0], 3.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[1], 15.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[2], 17.0f); + + // c.stuff + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[0], 1.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[1], 2.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[2], 3.0f); + + // c.more_stuff + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 12])[0], 4.0f); + + // tessLevelOuter + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[0], 1.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[1], 2.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[2], 3.0f); + + // tessLevelInner + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 224 + 0])[0], 5.0f); + + // pointSize[0] + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[0], 10.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[1], 11.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[2], 12.0f); + + // clipDistance[0][0] + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[0], 20.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[1], 40.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[2], 60.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[3], 80.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[4], 100.0f); + + // clipDistance[1][0] + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[0], 21.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[1], 41.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[2], 61.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[3], 81.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[4], 101.0f); + + // clipDistance[2][0] + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[0], 22.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[1], 42.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[2], 62.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[3], 82.0f); + QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[4], 102.0f); + + // patchVerticesIn + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 316 + 0])[0], 3); + + // primitiveID + QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 320 + 0])[0], 0); +} + +void tst_QRhi::storageBuffer_data() +{ + rhiTestData(); +} + +void tst_QRhi::storageBuffer() +{ + // Use a compute shader to copy from one storage buffer of float types to + // another of int types. We fill the "toGpu" buffer with known float type + // data generated and uploaded from the CPU, then dispatch a compute shader + // to copy from the "toGpu" buffer to the "fromGpu" buffer. We then + // readback the "fromGpu" buffer and verify that the results are as + // expected. + + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + // we can't test with Null as there is no compute + if (impl == QRhi::Null) + return; + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing"); + + if (!rhi->isFeatureSupported(QRhi::Feature::Compute)) + QSKIP("Compute is not supported with this graphics API, skipping test"); + + QShader s = loadShader(":/data/storagebuffer.comp.qsb"); + QVERIFY(s.isValid()); + QCOMPARE(s.description().storageBlocks().size(), 2); + + QMap<QByteArray, QShaderDescription::StorageBlock> blocks; + for (const QShaderDescription::StorageBlock &block : s.description().storageBlocks()) + blocks[block.blockName] = block; + + QMap<QByteArray, QShaderDescription::BlockVariable> toGpuMembers; + for (const QShaderDescription::BlockVariable &member: blocks["toGpu"].members) + toGpuMembers[member.name] = member; + + QMap<QByteArray, QShaderDescription::BlockVariable> fromGpuMembers; + for (const QShaderDescription::BlockVariable &member: blocks["fromGpu"].members) + fromGpuMembers[member.name] = member; + + for (QRhiBuffer::Type type : {QRhiBuffer::Type::Immutable, QRhiBuffer::Type::Static}) { + + QRhiCommandBuffer *cb = nullptr; + rhi->beginOffscreenFrame(&cb); + QVERIFY(cb); + + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + QVERIFY(u); + + QScopedPointer<QRhiBuffer> toGpuBuffer(rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer, blocks["toGpu"].knownSize)); + QVERIFY(toGpuBuffer->create()); + + QScopedPointer<QRhiBuffer> fromGpuBuffer(rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer, blocks["fromGpu"].knownSize)); + QVERIFY(fromGpuBuffer->create()); + + QByteArray toGpuData(blocks["toGpu"].knownSize, 0); + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_float"].offset])[0] = 1.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec2"].offset])[0] = 2.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec2"].offset])[1] = 3.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec3"].offset])[0] = 4.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec3"].offset])[1] = 5.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec3"].offset])[2] = 6.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[0] = 7.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[1] = 8.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[2] = 9.0f; + reinterpret_cast<float *>(&toGpuData.data()[toGpuMembers["_vec4"].offset])[3] = 10.0f; + + u->uploadStaticBuffer(toGpuBuffer.data(), 0, toGpuData.size(), toGpuData.constData()); + u->uploadStaticBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize, QByteArray(blocks["fromGpu"].knownSize, 0).constData()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({QRhiShaderResourceBinding::bufferLoad(blocks["toGpu"].binding, QRhiShaderResourceBinding::ComputeStage, toGpuBuffer.data()), + QRhiShaderResourceBinding::bufferLoadStore(blocks["fromGpu"].binding, QRhiShaderResourceBinding::ComputeStage, fromGpuBuffer.data())}); + + QVERIFY(srb->create()); + + QScopedPointer<QRhiComputePipeline> pipeline(rhi->newComputePipeline()); + pipeline->setShaderStage({QRhiShaderStage::Compute, s}); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(pipeline->create()); + + cb->beginComputePass(u); + + cb->setComputePipeline(pipeline.data()); + cb->setShaderResources(); + cb->dispatch(1, 1, 1); + + u = rhi->nextResourceUpdateBatch(); + QVERIFY(u); + + int readCompletedNotifications = 0; + QRhiReadbackResult result; + result.completed = [&readCompletedNotifications]() { readCompletedNotifications++; }; + u->readBackBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize, &result); + + cb->endComputePass(u); + + rhi->endOffscreenFrame(); + + QCOMPARE(readCompletedNotifications, 1); + + QCOMPARE(result.data.size(), blocks["fromGpu"].knownSize); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_int"].offset])[0], 1); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec2"].offset])[0], 2); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec2"].offset])[1], 3); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec3"].offset])[0], 4); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec3"].offset])[1], 5); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec3"].offset])[2], 6); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[0], 7); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[1], 8); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[2], 9); + QCOMPARE(reinterpret_cast<const int *>(&result.data.constData()[fromGpuMembers["_ivec4"].offset])[3], 10); + + } +} + + void tst_QRhi::storageBufferRuntimeSizeCompute_data() +{ + rhiTestData(); +} + + void tst_QRhi::storageBufferRuntimeSizeCompute() +{ + // Use a compute shader to copy from one storage buffer with std430 runtime + // float array to another with std140 runtime int array. We fill the + // "toGpu" buffer with known float data generated and uploaded from the + // CPU, then dispatch a compute shader to copy from the "toGpu" buffer to + // the "fromGpu" buffer. We then readback the "fromGpu" buffer and verify + // that the results are as expected. This is primarily to test Metal + // SPIRV-Cross buffer size buffers. + + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + // we can't test with Null as there is no compute + if (impl == QRhi::Null) + return; + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing"); + + if (!rhi->isFeatureSupported(QRhi::Feature::Compute)) + QSKIP("Compute is not supported with this graphics API, skipping test"); + + QShader s = loadShader(":/data/storagebuffer_runtime.comp.qsb"); + QVERIFY(s.isValid()); + QCOMPARE(s.description().storageBlocks().size(), 2); + + QMap<QByteArray, QShaderDescription::StorageBlock> blocks; + for (const QShaderDescription::StorageBlock &block : s.description().storageBlocks()) + blocks[block.blockName] = block; + + QMap<QByteArray, QShaderDescription::BlockVariable> toGpuMembers; + for (const QShaderDescription::BlockVariable &member : blocks["toGpu"].members) + toGpuMembers[member.name] = member; + + QMap<QByteArray, QShaderDescription::BlockVariable> fromGpuMembers; + for (const QShaderDescription::BlockVariable &member : blocks["fromGpu"].members) + fromGpuMembers[member.name] = member; + + for (QRhiBuffer::Type type : { QRhiBuffer::Type::Immutable, QRhiBuffer::Type::Static }) { + QRhiCommandBuffer *cb = nullptr; + + rhi->beginOffscreenFrame(&cb); + QVERIFY(cb); + + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + QVERIFY(u); + + const int stride430 = sizeof(float); + const int stride140 = 4 * sizeof(float); + const int length = 32; + + QScopedPointer<QRhiBuffer> toGpuBuffer( + rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer, + blocks["toGpu"].knownSize + length * stride430)); + QVERIFY(toGpuBuffer->create()); + + QScopedPointer<QRhiBuffer> fromGpuBuffer( + rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer, + blocks["fromGpu"].knownSize + length * stride140)); + QVERIFY(fromGpuBuffer->create()); + + QByteArray toGpuData(toGpuBuffer->size(), 0); + for (int i = 0; i < length; ++i) + reinterpret_cast<float &>(toGpuData.data()[toGpuMembers["_float"].offset + i * stride430]) = float(i); + + u->uploadStaticBuffer(toGpuBuffer.data(), 0, toGpuData.size(), toGpuData.constData()); + u->uploadStaticBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize, + QByteArray(fromGpuBuffer->size(), 0).constData()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings( + { QRhiShaderResourceBinding::bufferLoadStore( + blocks["toGpu"].binding, QRhiShaderResourceBinding::ComputeStage, + toGpuBuffer.data()), + QRhiShaderResourceBinding::bufferLoadStore( + blocks["fromGpu"].binding, QRhiShaderResourceBinding::ComputeStage, + fromGpuBuffer.data()) }); + QVERIFY(srb->create()); + + QScopedPointer<QRhiComputePipeline> pipeline(rhi->newComputePipeline()); + pipeline->setShaderStage({ QRhiShaderStage::Compute, s }); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(pipeline->create()); + + cb->beginComputePass(u); + + cb->setComputePipeline(pipeline.data()); + cb->setShaderResources(); + cb->dispatch(1, 1, 1); + + u = rhi->nextResourceUpdateBatch(); + QVERIFY(u); + int readbackCompleted = 0; + QRhiReadbackResult result; + result.completed = [&readbackCompleted]() { readbackCompleted++; }; + u->readBackBuffer(fromGpuBuffer.data(), 0, fromGpuBuffer->size(), &result); + + cb->endComputePass(u); + + rhi->endOffscreenFrame(); + + QVERIFY(readbackCompleted > 0); + QCOMPARE(result.data.size(), fromGpuBuffer->size()); + + for (int i = 0; i < length; ++i) + QCOMPARE(reinterpret_cast<const int &>(result.data.constData()[fromGpuMembers["_int"].offset + i * stride140]), i); + + QCOMPARE(readbackCompleted, 1); + + } + +} + +void tst_QRhi::storageBufferRuntimeSizeGraphics_data() +{ + rhiTestData(); +} + +void tst_QRhi::storageBufferRuntimeSizeGraphics() +{ +#ifdef Q_OS_ANDROID + if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) + QSKIP("Fails on Android 12 (QTBUG-108844)"); +#endif + // Draws a tessellated triangle with color determined by the length of + // buffers bound to shader stages. This is primarily to test Metal + // SPIRV-Cross buffer size buffers. + + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + if (!rhi->isFeatureSupported(QRhi::Tessellation)) { + // From a Vulkan or Metal implementation we expect tessellation to work, + // even though it is optional (as per spec) for Vulkan. + QVERIFY(rhi->backend() != QRhi::Vulkan); + QVERIFY(rhi->backend() != QRhi::Metal); + QSKIP("Tessellation is not supported with this graphics API, skipping test"); + } + + if (rhi->backend() == QRhi::D3D11 || rhi->backend() == QRhi::D3D12) + QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet"); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(64, 64), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->create()); + + static const float triangleVertices[] = { + 0.0f, 0.5f, 0.0f, + -0.5f, -0.5f, 0.0f, + 0.5f, -0.5f, 0.0f, + }; + + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); + QVERIFY(vbuf->create()); + u->uploadStaticBuffer(vbuf.data(), triangleVertices); + + QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64)); + QVERIFY(ubuf->create()); + + QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix(); + u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData()); + + QScopedPointer<QRhiBuffer> ssbo5(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 256)); + QVERIFY(ssbo5->create()); + + QScopedPointer<QRhiBuffer> ssbo3(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 16)); + QVERIFY(ssbo3->create()); + + u->uploadStaticBuffer(ssbo3.data(), QVector<float>({ 1.0f, 1.0f, 1.0f, 1.0f }).constData()); + + QScopedPointer<QRhiBuffer> ssbo4(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 128)); + QVERIFY(ssbo4->create()); + + const int red = 79; + const int green = 43; + const int blue = 251; + + QScopedPointer<QRhiBuffer> ssboR(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, red * sizeof(float))); + QVERIFY(ssboR->create()); + + QScopedPointer<QRhiBuffer> ssboG(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, green * sizeof(float))); + QVERIFY(ssboG->create()); + + QScopedPointer<QRhiBuffer> ssboB(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, blue * sizeof(float))); + QVERIFY(ssboB->create()); + + const int tessOuter0 = 1; + const int tessOuter1 = 2; + const int tessOuter2 = 3; + const int tessInner0 = 4; + + QScopedPointer<QRhiBuffer> ssboTessOuter0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter0 * sizeof(float))); + QVERIFY(ssboTessOuter0->create()); + + QScopedPointer<QRhiBuffer> ssboTessOuter1(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter1 * sizeof(float))); + QVERIFY(ssboTessOuter1->create()); + + QScopedPointer<QRhiBuffer> ssboTessOuter2(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter2 * sizeof(float))); + QVERIFY(ssboTessOuter2->create()); + + QScopedPointer<QRhiBuffer> ssboTessInner0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessInner0 * sizeof(float))); + QVERIFY(ssboTessInner0->create()); + + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()), + QRhiShaderResourceBinding::bufferLoad(5, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssbo5.data()), + QRhiShaderResourceBinding::bufferLoad(3, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage | QRhiShaderResourceBinding::FragmentStage, ssbo3.data()), + QRhiShaderResourceBinding::bufferLoad(4, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssbo4.data()), + QRhiShaderResourceBinding::bufferLoad(7, QRhiShaderResourceBinding::TessellationControlStage, ssboTessOuter0.data()), + QRhiShaderResourceBinding::bufferLoad(8, QRhiShaderResourceBinding::TessellationControlStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssboTessOuter1.data()), + QRhiShaderResourceBinding::bufferLoad(9, QRhiShaderResourceBinding::TessellationControlStage, ssboTessOuter2.data()), + QRhiShaderResourceBinding::bufferLoad(10, QRhiShaderResourceBinding::TessellationControlStage, ssboTessInner0.data()), + QRhiShaderResourceBinding::bufferLoad(1, QRhiShaderResourceBinding::FragmentStage, ssboG.data()), + QRhiShaderResourceBinding::bufferLoad(2, QRhiShaderResourceBinding::FragmentStage, ssboB.data()), + QRhiShaderResourceBinding::bufferLoad(6, QRhiShaderResourceBinding::FragmentStage, ssboR.data()) }); + + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + + pipeline->setTopology(QRhiGraphicsPipeline::Patches); + pipeline->setPatchControlPointCount(3); + + pipeline->setShaderStages({ + { QRhiShaderStage::Vertex, loadShader(":/data/storagebuffer_runtime.vert.qsb") }, + { QRhiShaderStage::TessellationControl, loadShader(":/data/storagebuffer_runtime.tesc.qsb") }, + { QRhiShaderStage::TessellationEvaluation, loadShader(":/data/storagebuffer_runtime.tese.qsb") }, + { QRhiShaderStage::Fragment, loadShader(":/data/storagebuffer_runtime.frag.qsb") } + }); + + pipeline->setCullMode(QRhiGraphicsPipeline::None); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + }); + + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->create()); + + QRhiCommandBuffer *cb = nullptr; + QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u); + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) }); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QCOMPARE(result.size(), rt->pixelSize()); + + // cannot check rendering results with Null, because there is no rendering there + if (impl == QRhi::Null) + return; + + QCOMPARE(result.pixel(32, 32), qRgb(red, green, blue)); +} + +void tst_QRhi::halfPrecisionAttributes_data() +{ + rhiTestData(); +} + +void tst_QRhi::halfPrecisionAttributes() +{ +#ifdef Q_OS_ANDROID + if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) + QSKIP("Fails on Android 12 (QTBUG-108844)"); +#endif + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + if (!rhi->isFeatureSupported(QRhi::HalfAttributes)) { + QVERIFY(rhi->backend() != QRhi::Vulkan); + QVERIFY(rhi->backend() != QRhi::Metal); + QVERIFY(rhi->backend() != QRhi::D3D11); + QVERIFY(rhi->backend() != QRhi::D3D12); + QSKIP("Half precision vertex attributes are not supported with this graphics API, skipping test"); + } + + const QSize outputSize(1920, 1080); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->create()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + // + // This test uses half3 vertices + // + // Note: D3D does not support half3 - rhi passes it through as half4. Because of this, D3D will + // report the following warning and error if we don't take precautions: + // + // D3D11 WARNING: ID3D11DeviceContext::Draw: Input vertex slot 0 has stride 6 which is less than + // the minimum stride logically expected from the current Input Layout (8 bytes). This is OK, as + // hardware is perfectly capable of reading overlapping data. However the developer probably did + // not intend to make use of this behavior. [ EXECUTION WARNING #355: + // DEVICE_DRAW_VERTEX_BUFFER_STRIDE_TOO_SMALL] + // + // D3D11 ERROR: ID3D11DeviceContext::Draw: Vertex Buffer Stride (6) at the input vertex slot 0 + // is not aligned properly. The current Input Layout imposes an alignment of (4) because of the + // Formats used with this slot. [ EXECUTION ERROR #367: DEVICE_DRAW_VERTEX_STRIDE_UNALIGNED] + // + // The same warning and error are produced for D3D12. The rendered output is correct despite + // the warning and error. + // + // To avoid these errors, we pad the vertices to 8 byte stride. + // + static const qfloat16 vertices[] = { + qfloat16(-1.0), qfloat16(-1.0), qfloat16(0.0), qfloat16(0.0), + qfloat16(1.0), qfloat16(-1.0), qfloat16(0.0), qfloat16(0.0), + qfloat16(0.0), qfloat16(1.0), qfloat16(0.0), qfloat16(0.0), + }; + + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/half.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(qfloat16) } }); // 8 byte vertex stride for D3D + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Half3, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(pipeline->create()); + + cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); // non-owning, no copy needed because readResult outlives result + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + // Offscreen frames are synchronous, so the readback is guaranteed to + // complete at this point. This would not be the case with swapchain-based + // frames. + QCOMPARE(result.size(), texture->pixelSize()); + + if (impl == QRhi::Null) + return; + + // Now we have a red rectangle on blue background. + const int y = 100; + const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); + int x = result.width() - 1; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + while (x-- >= 0) { + const QRgb c(*p++); + if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0) + ++redCount; + else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz)) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + + QCOMPARE(redCount + blueCount, texture->pixelSize().width()); + QVERIFY(redCount != 0); + QVERIFY(blueCount != 0); + + // The triangle is "pointing up" in the resulting image with OpenGL + // (because Y is up both in normalized device coordinates and in images) + // and Vulkan (because Y is down in both and the vertex data was specified + // with Y up in mind), but "pointing down" with D3D (because Y is up in NDC + // but down in images). + if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) + QVERIFY(redCount < blueCount); + else + QVERIFY(redCount > blueCount); + } #include <tst_qrhi.moc> diff --git a/tests/auto/gui/rhi/qshader/CMakeLists.txt b/tests/auto/gui/rhi/qshader/CMakeLists.txt index 9a0e246aa4..09bf0d585d 100644 --- a/tests/auto/gui/rhi/qshader/CMakeLists.txt +++ b/tests/auto/gui/rhi/qshader/CMakeLists.txt @@ -1,26 +1,28 @@ -# 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}" + "data/*" +) + qt_internal_add_test(tst_qshader SOURCES tst_qshader.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::GuiPrivate + TESTDATA ${qshader_resource_files} + BUILTIN_TESTDATA ) - -# Resources: -set(qshader_resource_files - "data" -) - -qt_internal_add_resource(tst_qshader "qshader" - PREFIX - "/" - FILES - ${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/texture_sep_v6.frag.qsb b/tests/auto/gui/rhi/qshader/data/texture_sep_v6.frag.qsb Binary files differnew file mode 100644 index 0000000000..b654ee576d --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data/texture_sep_v6.frag.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_src/texture_sep.frag b/tests/auto/gui/rhi/qshader/data_src/texture_sep.frag new file mode 100644 index 0000000000..368e851bb4 --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data_src/texture_sep.frag @@ -0,0 +1,17 @@ +#version 440 + +layout(location = 0) in vec2 v_texcoord; + +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D combinedTexSampler; +layout(binding = 2) uniform texture2D sepTex; +layout(binding = 3) uniform sampler sepSampler; +layout(binding = 4) uniform sampler sepSampler2; + +void main() +{ + fragColor = texture(sampler2D(sepTex, sepSampler), v_texcoord); + fragColor *= texture(sampler2D(sepTex, sepSampler2), v_texcoord); + fragColor *= texture(combinedTexSampler, v_texcoord); +} diff --git a/tests/auto/gui/rhi/qshader/qshader.qrc b/tests/auto/gui/rhi/qshader/qshader.qrc deleted file mode 100644 index f161d8aad6..0000000000 --- a/tests/auto/gui/rhi/qshader/qshader.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>data</file> - </qresource> -</RCC> diff --git a/tests/auto/gui/rhi/qshader/tst_qshader.cpp b/tests/auto/gui/rhi/qshader/tst_qshader.cpp index 1b3d861756..9e179c95c3 100644 --- a/tests/auto/gui/rhi/qshader/tst_qshader.cpp +++ b/tests/auto/gui/rhi/qshader/tst_qshader.cpp @@ -1,37 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QFile> #include <QBuffer> -#include <QtGui/private/qshaderdescription_p_p.h> -#include <QtGui/private/qshader_p_p.h> +#include <private/qshaderdescription_p.h> +#include <private/qshader_p.h> class tst_QShader : public QObject { @@ -43,11 +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) @@ -80,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))); @@ -89,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: @@ -105,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: @@ -117,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: @@ -142,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; } } @@ -155,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; @@ -178,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(); @@ -216,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); @@ -246,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")); @@ -269,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)))); @@ -278,19 +267,19 @@ void tst_QShader::mslResourceMapping() QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(150)))); QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(330)))); - const QShader::NativeResourceBindingMap *resMap = + QShader::NativeResourceBindingMap resMap = s.nativeResourceBindingMap(QShaderKey(QShader::GlslShader, QShaderVersion(330))); - QVERIFY(!resMap); + QVERIFY(resMap.isEmpty()); // The Metal shader must come with a mapping table for binding points 0 // (uniform buffer) and 1 (combined image sampler mapped to a texture and // sampler in the shader). resMap = s.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))); - QVERIFY(resMap); + QVERIFY(!resMap.isEmpty()); - QCOMPARE(resMap->count(), 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 + 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 } void tst_QShader::serializeShaderDesc() @@ -305,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()); @@ -330,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()); @@ -390,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)))); @@ -401,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: @@ -413,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: @@ -425,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: @@ -450,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; } } @@ -561,14 +550,151 @@ 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); } +void tst_QShader::loadV6WithSeparateImagesAndSamplers() +{ + QShader s = getShader(QLatin1String(":/data/texture_sep_v6.frag.qsb")); + QVERIFY(s.isValid()); + QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 6); + + const QList<QShaderKey> availableShaders = s.availableShaders(); + 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)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(120)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(150)))); + + QShader::NativeResourceBindingMap resMap = + s.nativeResourceBindingMap(QShaderKey(QShader::HlslShader, QShaderVersion(50))); + QVERIFY(resMap.size() == 4); + QVERIFY(s.separateToCombinedImageSamplerMappingList(QShaderKey(QShader::HlslShader, QShaderVersion(50))).isEmpty()); + resMap = s.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))); + QVERIFY(resMap.size() == 4); + QVERIFY(s.separateToCombinedImageSamplerMappingList(QShaderKey(QShader::MslShader, QShaderVersion(12))).isEmpty()); + + for (auto key : { + QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)), + QShaderKey(QShader::GlslShader, QShaderVersion(120)), + QShaderKey(QShader::GlslShader, QShaderVersion(150)) }) + { + auto list = s.separateToCombinedImageSamplerMappingList(key); + QCOMPARE(list.size(), 2); + } +} + +void tst_QShader::loadV7() +{ + QShader vert = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.vert.qsb")); + QVERIFY(vert.isValid()); + QCOMPARE(QShaderPrivate::get(&vert)->qsbVersion, 7); + QCOMPARE(vert.availableShaders().size(), 8); + + QCOMPARE(vert.description().inputVariables().size(), 2); + QCOMPARE(vert.description().outputBuiltinVariables().size(), 1); + QCOMPARE(vert.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin); + QCOMPARE(vert.description().outputVariables().size(), 1); + QCOMPARE(vert.description().outputVariables()[0].name, QByteArrayLiteral("v_color")); + + QVERIFY(vert.availableShaders().contains(QShaderKey(QShader::MslShader, QShaderVersion(12)))); + QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::NonIndexedVertexAsComputeShader)).shader().isEmpty()); + QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt16IndexedVertexAsComputeShader)).shader().isEmpty()); + QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt32IndexedVertexAsComputeShader)).shader().isEmpty()); + + QShader tesc = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tesc.qsb")); + QVERIFY(tesc.isValid()); + QCOMPARE(QShaderPrivate::get(&tesc)->qsbVersion, 7); + QCOMPARE(tesc.availableShaders().size(), 5); + QCOMPARE(tesc.description().tessellationOutputVertexCount(), 3u); + + QCOMPARE(tesc.description().inputBuiltinVariables().size(), 2); + QCOMPARE(tesc.description().outputBuiltinVariables().size(), 3); + // builtins must be sorted based on the type + QCOMPARE(tesc.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin); + QCOMPARE(tesc.description().inputBuiltinVariables()[1].type, QShaderDescription::InvocationIdBuiltin); + QCOMPARE(tesc.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin); + QCOMPARE(tesc.description().outputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin); + QCOMPARE(tesc.description().outputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin); + + QCOMPARE(tesc.description().outputVariables().size(), 3); + for (const QShaderDescription::InOutVariable &v : tesc.description().outputVariables()) { + switch (v.location) { + case 0: + QCOMPARE(v.name, QByteArrayLiteral("outColor")); + QCOMPARE(v.type, QShaderDescription::Vec3); + QCOMPARE(v.perPatch, false); + break; + case 1: + QCOMPARE(v.name, QByteArrayLiteral("stuff")); + QCOMPARE(v.type, QShaderDescription::Vec3); + QCOMPARE(v.perPatch, true); + break; + case 2: + QCOMPARE(v.name, QByteArrayLiteral("more_stuff")); + QCOMPARE(v.type, QShaderDescription::Float); + QCOMPARE(v.perPatch, true); + break; + default: + QFAIL(qPrintable(QStringLiteral("Bad location: %1").arg(v.location))); + break; + } + } + + QVERIFY(!tesc.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().isEmpty()); + QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::SpirvShader, QShaderVersion(100))).extraBufferBindings.size(), 0); + QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::MslShader, QShaderVersion(12))).extraBufferBindings.size(), 5); + + QShader tese = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tese.qsb")); + QVERIFY(tese.isValid()); + QCOMPARE(QShaderPrivate::get(&tese)->qsbVersion, 7); + QCOMPARE(tese.availableShaders().size(), 5); + QCOMPARE(tese.description().tessellationMode(), QShaderDescription::TrianglesTessellationMode); + QCOMPARE(tese.description().tessellationWindingOrder(), QShaderDescription::CcwTessellationWindingOrder); + QCOMPARE(tese.description().tessellationPartitioning(), QShaderDescription::FractionalOddTessellationPartitioning); + + QCOMPARE(tese.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin); + QCOMPARE(tese.description().inputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin); + QCOMPARE(tese.description().inputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin); + QCOMPARE(tese.description().inputBuiltinVariables()[3].type, QShaderDescription::TessCoordBuiltin); + + QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).size(), 1); + QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).value(0), qMakePair(0, -1)); + + QShader frag = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.frag.qsb")); + QVERIFY(frag.isValid()); + QCOMPARE(QShaderPrivate::get(&frag)->qsbVersion, 7); +} + +void tst_QShader::loadV8() +{ + QShader s = getShader(QLatin1String(":/data/storage_buffer_info_v8.comp.qsb")); + QVERIFY(s.isValid()); + QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 8); + + const QList<QShaderKey> availableShaders = s.availableShaders(); + QCOMPARE(availableShaders.size(), 5); + QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50)))); + QVERIFY(availableShaders.contains( + QShaderKey(QShader::GlslShader, QShaderVersion(310, QShaderVersion::GlslEs)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(430)))); + + QCOMPARE(s.description().storageBlocks().size(), 1); + QCOMPARE(s.description().storageBlocks().last().runtimeArrayStride, 4); + QCOMPARE(s.description().storageBlocks().last().qualifierFlags, + QShaderDescription::QualifierFlags(QShaderDescription::QualifierWriteOnly + | QShaderDescription::QualifierRestrict)); +} + #include <tst_qshader.moc> QTEST_MAIN(tst_QShader) |