diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index ed0818b0e..8df03e981 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -135,6 +135,7 @@ set(VULKAN_SRC vk/vk_multi_entry.cpp vk/vk_multi_present.cpp vk/vk_multi_thread_windows.cpp + vk/vk_multi_view.cpp vk/vk_overlay_test.cpp vk/vk_parameter_zoo.cpp vk/vk_pixel_history.cpp diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index e157c3731..300371308 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -319,6 +319,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 3ef4e7d1f..552682bbc 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -685,6 +685,9 @@ D3D12\demos + + Vulkan\demos + diff --git a/util/test/demos/vk/vk_multi_view.cpp b/util/test/demos/vk/vk_multi_view.cpp new file mode 100644 index 000000000..1c89e205f --- /dev/null +++ b/util/test/demos/vk/vk_multi_view.cpp @@ -0,0 +1,304 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2024 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "vk_test.h" + +RD_TEST(VK_Multi_View, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Basic multi-view test like VK_Simple_Triangle but for multi-view rendering"; + + const std::string common = R"EOSHADER( + +#version 460 core + +#extension GL_EXT_multiview : require + +#define v2f v2f_block \ +{ \ + vec4 pos; \ + vec4 col; \ + vec4 uv; \ +} + +)EOSHADER"; + + const std::string multiviewVertex = common + R"EOSHADER( + +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); + + 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( + +layout(triangles) in; +layout(triangle_strip, max_vertices = 3) out; + +layout(location = 0) in v2f_block +{ + vec4 pos; + vec4 col; + vec4 uv; +} gin[3]; + +layout(location = 0) out g2f_block +{ + vec4 pos; + vec4 col; + vec4 uv; +} gout; + +void main() +{ + for(int i = 0; i < 3; i++) + { + gl_Position = gl_in[i].gl_Position; + + gout.pos = gin[i].pos; + gout.col = gin[i].col; + gout.uv = gin[i].uv; + + if (gl_ViewIndex == 0) + gout.col = vec4(1, 0, 0, 1); + if (gl_ViewIndex == 1) + gout.col = vec4(0, 1, 0, 1); + EmitVertex(); + } + EndPrimitive(); +} + +)EOSHADER"; + const std::string multiViewPixel = common + R"EOSHADER( + +layout(location = 0) in v2f vertIn; + +layout(location = 0, index = 0) out vec4 Color; + +void main() +{ + Color = vertIn.col; + if (gl_ViewIndex == 0) + Color = vec4(1, 0, 0, 1); + if (gl_ViewIndex == 1) + Color = vec4(0, 1, 0, 1); +} + +)EOSHADER"; + void Prepare(int argc, char **argv) + { + features.geometryShader = VK_TRUE; + devExts.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); + + VulkanGraphicsTest::Prepare(argc, argv); + + static VkPhysicalDeviceMultiviewFeaturesKHR multiview = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR, + }; + + getPhysFeatures2(&multiview); + if(!multiview.multiview) + Avail = "Multiview feature 'multiview' not available"; + + devInfoNext = &multiview; + } + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + vkh::RenderPassCreator renderPassCreateInfo; + + renderPassCreateInfo.attachments.push_back(vkh::AttachmentDescription( + mainWindow->format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE, VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE)); + + renderPassCreateInfo.addSubpass( + {VkAttachmentReference({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL})}); + + uint32_t countViews = 2; + uint32_t viewMask = (1 << countViews) - 1; + uint32_t correlationMask = viewMask; + uint32_t countLayers = countViews + 2; + + VkRenderPassMultiviewCreateInfo rpMultiviewCreateInfo = { + VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO}; + rpMultiviewCreateInfo.subpassCount = 1; + rpMultiviewCreateInfo.pViewMasks = &viewMask; + rpMultiviewCreateInfo.correlationMaskCount = 1; + rpMultiviewCreateInfo.pCorrelationMasks = &correlationMask; + + renderPassCreateInfo.next((void *)&rpMultiviewCreateInfo); + + VkRenderPass renderPass = createRenderPass(renderPassCreateInfo); + + AllocatedImage fbColourImage( + this, + vkh::ImageCreateInfo(mainWindow->scissor.extent.width, mainWindow->scissor.extent.height, 0, + mainWindow->format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 1, countLayers), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + VkImageViewCreateInfo colourViewInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + colourViewInfo.image = fbColourImage.image; + colourViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + colourViewInfo.format = mainWindow->format; + colourViewInfo.flags = 0; + colourViewInfo.subresourceRange = {}; + colourViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + colourViewInfo.subresourceRange.baseMipLevel = 0; + colourViewInfo.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + colourViewInfo.subresourceRange.baseArrayLayer = 1; + colourViewInfo.subresourceRange.layerCount = countViews; + VkImageView fbColourView = createImageView(&colourViewInfo); + + VkFramebuffer framebuffer = createFramebuffer( + vkh::FramebufferCreateInfo(renderPass, {fbColourView}, mainWindow->scissor.extent)); + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = renderPass; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + pipeCreateInfo.colorBlendState.attachments = {colorBlendAttachment}; + pipeCreateInfo.depthStencilState.depthTestEnable = VK_FALSE; + pipeCreateInfo.depthStencilState.stencilTestEnable = VK_FALSE; + + pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; + pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { + vkh::vertexAttr(0, 0, DefaultA2V, pos), + vkh::vertexAttr(1, 0, DefaultA2V, col), + vkh::vertexAttr(2, 0, DefaultA2V, uv), + }; + + pipeCreateInfo.stages = { + CompileShaderModule(multiviewVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "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"); + + 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"), + }; + testPipes.push_back(createGraphicsPipeline(pipeCreateInfo)); + testNames.push_back("No viewIndex"); + + AllocatedBuffer vb( + this, + vkh::BufferCreateInfo(sizeof(DefaultTri), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(DefaultTri); + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + // Render multiview to its own framebuffer + + vkCmdBeginRenderPass(cmd, + vkh::RenderPassBeginInfo(renderPass, framebuffer, mainWindow->scissor, + {vkh::ClearValue(0.2f, 0.3f, 0.4f, 1.0f)}), + VK_SUBPASS_CONTENTS_INLINE); + + for(size_t i = 0; i < testPipes.size(); ++i) + { + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, testPipes[i]); + vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); + vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + setMarker(cmd, testNames[i]); + vkCmdDraw(cmd, 3, 1, 0, 0); + } + + vkCmdEndRenderPass(cmd); + + // TODO: in the future could copy the multiview renderpass to the framebuffer (left, right) + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/tests/Vulkan/VK_Multi_View.py b/util/test/tests/Vulkan/VK_Multi_View.py new file mode 100644 index 000000000..5a943a3f3 --- /dev/null +++ b/util/test/tests/Vulkan/VK_Multi_View.py @@ -0,0 +1,85 @@ +import renderdoc as rd +import rdtest + +class VK_Multi_View(rdtest.TestCase): + demos_test_name = 'VK_Multi_View' + + def check_capture(self): + if not self.controller.GetAPIProperties().shaderDebugging: + rdtest.log.success("Shader debugging not enabled, skipping test") + return + + x = 200 + y = 150 + + 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 + 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): + # 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].resourceId, 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") + + + def check_debug(self, vtx, idx, inst, view, postvs): + trace: rd.ShaderDebugTrace = self.controller.DebugVertex(vtx, inst, idx, view) + + if trace.debugger is None: + self.controller.FreeTrace(trace) + + raise rdtest.TestFailureException("Couldn't debug vertex {} in instance {} for view {}".format(vtx, inst, view)) + + cycles, variables = self.process_trace(trace) + + for var in trace.sourceVars: + var: rd.SourceVariableMapping + if var.variables[0].type == rd.DebugVariableType.Variable and var.signatureIndex >= 0: + name = var.name + + if name not in postvs[vtx].keys(): + raise rdtest.TestFailureException("Don't have expected output for {}".format(name)) + + expect = postvs[vtx][name] + value = self.evaluate_source_var(var, variables) + + if len(expect) != value.columns: + raise rdtest.TestFailureException( + "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 not rdtest.value_compare(expect, debugged): + raise rdtest.TestFailureException( + "Debugged value {} at vert {} (idx {}) instance {} view {}: {} doesn't exactly match postvs output {}".format( + name, vtx, idx, inst, view, debugged, expect)) + rdtest.log.success('Successfully debugged vertex {} in instance {} for view {}' + .format(vtx, inst, view)) +