From bd78fcc4731bf9b1cd4c20fb52c196cfdae4277f Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 30 Jan 2026 15:32:04 +0000 Subject: [PATCH] Update multiview test to check shader exported viewport * This also has code to support the qcom implicit viewport, but we disable that path by default since it would break all the single-viewport tests. --- util/test/demos/vk/vk_multi_view.cpp | 150 +++++++++++++++++++++--- util/test/tests/Vulkan/VK_Multi_View.py | 53 ++++++++- 2 files changed, 183 insertions(+), 20 deletions(-) diff --git a/util/test/demos/vk/vk_multi_view.cpp b/util/test/demos/vk/vk_multi_view.cpp index eeaabbaea..4a348758f 100644 --- a/util/test/demos/vk/vk_multi_view.cpp +++ b/util/test/demos/vk/vk_multi_view.cpp @@ -65,6 +65,32 @@ void main() vertOut.col = vec4(0, 1, 0, 1); } +)EOSHADER"; + + const std::string multiviewViewportVertex = common + R"EOSHADER( + +#extension GL_ARB_shader_viewport_layer_array : require + +layout(location = 0) in vec3 Position; +layout(location = 1) in vec4 Color; +layout(location = 2) in vec2 UV; + +layout(location = 0) out v2f vertOut; + +void main() +{ + vertOut.pos = vec4(Position.xyz*vec3(1,-1,1), 1); + gl_Position = vertOut.pos; + vertOut.col = Color; + vertOut.uv = vec4(UV.xy, 0, 1); + + gl_ViewportIndex = gl_ViewIndex; + if (gl_ViewIndex == 0) + vertOut.col = vec4(1, 0, 0, 1); + if (gl_ViewIndex == 1) + vertOut.col = vec4(0, 1, 0, 1); +} + )EOSHADER"; const std::string multiViewGeom = common + R"EOSHADER( @@ -128,7 +154,13 @@ void main() void Prepare(int argc, char **argv) { features.geometryShader = VK_TRUE; + features.multiViewport = VK_TRUE; devExts.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); + optDevExts.push_back(VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME); + + // this extension is all-or-nothing and prevents us doing any other tests normally with single + // viewports + // optDevExts.push_back(VK_QCOM_MULTIVIEW_PER_VIEW_VIEWPORTS_EXTENSION_NAME); VulkanGraphicsTest::Prepare(argc, argv); @@ -143,6 +175,17 @@ void main() geometryTest = multiview.multiviewGeometryShader == VK_TRUE; devInfoNext = &multiview; + + static VkPhysicalDeviceMultiviewPerViewViewportsFeaturesQCOM perviewQC = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_VIEWPORTS_FEATURES_QCOM, + }; + + if(hasExt(VK_QCOM_MULTIVIEW_PER_VIEW_VIEWPORTS_EXTENSION_NAME)) + { + perviewQC.multiviewPerViewViewports = VK_TRUE; + perviewQC.pNext = (void *)devInfoNext; + devInfoNext = &perviewQC; + } } int main() @@ -228,33 +271,72 @@ void main() std::vector testNames; std::vector testPipes; - testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); - testNames.push_back("Vertex: viewIndex"); - pipeCreateInfo.stages = { - CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), - CompileShaderModule(multiViewPixel, ShaderLang::glsl, ShaderStage::frag, "main"), - }; - testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); - testNames.push_back("Fragment: viewIndex"); + size_t viewportChoosePipe = ~0U; + size_t viewportAutoPipe = ~0U; - if(geometryTest) + if(hasExt(VK_QCOM_MULTIVIEW_PER_VIEW_VIEWPORTS_EXTENSION_NAME)) { + pipeCreateInfo.stages = { + CompileShaderModule(multiviewVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + pipeCreateInfo.viewportState.viewportCount = 2; + pipeCreateInfo.viewportState.scissorCount = 2; + + viewportAutoPipe = testPipes.size(); + + testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); + testNames.push_back("viewportIndex auto"); + } + else + { + testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); + testNames.push_back("Vertex: viewIndex"); + + pipeCreateInfo.stages = { + CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(multiViewPixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); + testNames.push_back("Fragment: viewIndex"); + + if(geometryTest) + { + pipeCreateInfo.stages = { + CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), + CompileShaderModule(multiViewGeom, ShaderLang::glsl, ShaderStage::geom, "main"), + }; + testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); + testNames.push_back("Geometry: viewIndex"); + } + pipeCreateInfo.stages = { CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), - CompileShaderModule(multiViewGeom, ShaderLang::glsl, ShaderStage::geom, "main"), }; testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); - testNames.push_back("Geometry: viewIndex"); - } + testNames.push_back("No viewIndex"); - pipeCreateInfo.stages = { - CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), - CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), - }; - testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); - testNames.push_back("No viewIndex"); + if(hasExt(VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME)) + { + pipeCreateInfo.stages = { + CompileShaderModule(multiviewViewportVertex, ShaderLang::glsl, ShaderStage::vert, + "main"), + CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + pipeCreateInfo.viewportState.viewportCount = 2; + pipeCreateInfo.viewportState.scissorCount = 2; + + viewportChoosePipe = testPipes.size(); + + testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); + testNames.push_back("viewportIndex choice"); + } + } AllocatedBuffer vb( this, @@ -288,6 +370,38 @@ void main() vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, testPipes[i]); vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + + if(i == viewportChoosePipe || i == viewportAutoPipe) + { + VkViewport v = mainWindow->viewport; + VkRect2D s = mainWindow->scissor; + + VkViewport vs[2]; + + v.width /= 2.0f; + vs[0] = v; + // vkCmdSetViewport(cmd, 0, 1, &v); + v.x += v.width; + vs[1] = v; + // vkCmdSetViewport(cmd, 1, 1, &v); + vkCmdSetViewport(cmd, 0, 2, vs); + + s.extent.width /= 2; + s.extent.width -= 150; + s.offset.x += 75; + s.extent.height -= 200; + s.offset.y += 100; + + VkRect2D ss[2]; + + ss[0] = s; + // vkCmdSetScissor(cmd, 0, 1, &s); + s.offset.x += mainWindow->scissor.extent.width / 2; + ss[1] = s; + // vkCmdSetScissor(cmd, 1, 1, &s); + vkCmdSetScissor(cmd, 0, 2, ss); + } + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); setMarker(cmd, testNames[i]); vkCmdDraw(cmd, 3, 1, 0, 0); diff --git a/util/test/tests/Vulkan/VK_Multi_View.py b/util/test/tests/Vulkan/VK_Multi_View.py index eee0b22d2..d1a64b9e1 100644 --- a/util/test/tests/Vulkan/VK_Multi_View.py +++ b/util/test/tests/Vulkan/VK_Multi_View.py @@ -14,7 +14,10 @@ class VK_Multi_View(rdtest.TestCase): for test_name in ["Vertex: viewIndex", "Geometry: viewIndex", "Fragment: viewIndex", "No viewIndex"]: rdtest.log.print("Test {}".format(test_name)) - action: rd.ActionDescription = self.find_action(test_name).next + label = self.find_action(test_name) + if label is None: + continue + action = label.next self.controller.SetFrameEvent(action.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() @@ -45,6 +48,47 @@ class VK_Multi_View(rdtest.TestCase): self.check_debug(vtx, idx, inst, view, postvs) rdtest.log.print(f"View {view} Slice {slice} passed") + for test_name in ["viewportIndex choice"]: + rdtest.log.print("Test {}".format(test_name)) + label = self.find_action(test_name) + if label is None: + continue + action = label.next + self.controller.SetFrameEvent(action.eventId, True) + + pipe: rd.PipeState = self.controller.GetPipelineState() + if not pipe.GetShaderReflection(rd.ShaderStage.Pixel).debugInfo.debuggable: + raise rdtest.TestFailureException("Test {} shader can not be debugged".format(test_name)) + + for view in range(2): + if view == 0: + x, y = 100, 140 + else: + x, y = 300, 140 + + # Debug the pixel shader + inputs = rd.DebugPixelInputs() + inputs.view = view + trace: rd.ShaderDebugTrace = self.controller.DebugPixel(x, y, inputs) + if trace.debugger is None: + self.controller.FreeTrace(trace) + raise rdtest.TestFailureException("Test {} view {} did not debug at all".format(test_name, view)) + + cycles, variables = self.process_trace(trace) + output: rd.SourceVariableMapping = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) + debugged = self.evaluate_source_var(output, variables) + slice = view + 1 + sub = rd.Subresource(0, slice, 0) + self.check_pixel_value(pipe.GetOutputTargets()[0].resource, x, y, debugged.value.f32v[0:4], sub=sub) + self.controller.FreeTrace(trace) + + inst = 0 + postvs = self.get_postvs(action, rd.MeshDataStage.VSOut, instance=inst, view=view) + for vtx in range(action.numIndices): + idx = vtx + self.check_debug(vtx, idx, inst, view, postvs) + rdtest.log.print(f"View {view} Slice {slice} passed") + rdtest.log.success("All tests matched") @@ -74,7 +118,12 @@ class VK_Multi_View(rdtest.TestCase): "Output {} at vert {} (idx {}) instance {} view {} has different size ({} values) to expectation ({} values)" .format(name, vtx, idx, inst, view, value.columns, len(expect))) - debugged = value.value.f32v[0:value.columns] + if value.type == rd.VarType.SInt: + debugged = value.value.s32v[0:value.columns] + elif value.type == rd.VarType.UInt: + debugged = value.value.u32v[0:value.columns] + else: + debugged = value.value.f32v[0:value.columns] if not rdtest.value_compare(expect, debugged): raise rdtest.TestFailureException(