diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt
index 57c8dfd31..25427a8e6 100644
--- a/util/test/demos/CMakeLists.txt
+++ b/util/test/demos/CMakeLists.txt
@@ -132,6 +132,7 @@ set(VULKAN_SRC
vk/vk_load_store_none.cpp
vk/vk_mem_bench.cpp
vk/vk_mesh_zoo.cpp
+ vk/vk_mesh_shader.cpp
vk/vk_misaligned_dirty.cpp
vk/vk_multi_entry.cpp
vk/vk_multi_present.cpp
diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj
index 91ca55a7f..d4f024d66 100644
--- a/util/test/demos/demos.vcxproj
+++ b/util/test/demos/demos.vcxproj
@@ -323,6 +323,7 @@
+
diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters
index 9d7afe106..241b29aae 100644
--- a/util/test/demos/demos.vcxproj.filters
+++ b/util/test/demos/demos.vcxproj.filters
@@ -703,6 +703,9 @@
Vulkan\demos
+
+ Vulkan\demos
+
D3D12\demos
diff --git a/util/test/demos/test_common.cpp b/util/test/demos/test_common.cpp
index 87b577ac8..b6ff73b9a 100644
--- a/util/test/demos/test_common.cpp
+++ b/util/test/demos/test_common.cpp
@@ -320,6 +320,8 @@ std::vector CompileShaderToSpv(const std::string &source_text, SPIRVTa
case ShaderStage::tesseval: shader_kind = shaderc_tess_evaluation_shader; break;
case ShaderStage::geom: shader_kind = shaderc_geometry_shader; break;
case ShaderStage::comp: shader_kind = shaderc_compute_shader; break;
+ case ShaderStage::mesh: shader_kind = shaderc_mesh_shader; break;
+ case ShaderStage::task: shader_kind = shaderc_task_shader; break;
}
if(target == SPIRVTarget::opengl)
@@ -408,6 +410,8 @@ std::vector CompileShaderToSpv(const std::string &source_text, SPIRVTa
case ShaderStage::tesseval: command_line += " -fshader-stage=tesseval"; break;
case ShaderStage::geom: command_line += " -fshader-stage=geom"; break;
case ShaderStage::comp: command_line += " -fshader-stage=comp"; break;
+ case ShaderStage::mesh: command_line += " -fshader-stage=mesh"; break;
+ case ShaderStage::task: command_line += " -fshader-stage=task"; break;
}
}
else
@@ -453,6 +457,8 @@ std::vector CompileShaderToSpv(const std::string &source_text, SPIRVTa
case ShaderStage::tesseval: command_line += " -S tesseval"; break;
case ShaderStage::geom: command_line += " -S geom"; break;
case ShaderStage::comp: command_line += " -S comp"; break;
+ case ShaderStage::mesh: command_line += " -S mesh"; break;
+ case ShaderStage::task: command_line += " -S task"; break;
}
if(target == SPIRVTarget::opengl)
diff --git a/util/test/demos/test_common.h b/util/test/demos/test_common.h
index ed640649a..3ff413945 100644
--- a/util/test/demos/test_common.h
+++ b/util/test/demos/test_common.h
@@ -78,7 +78,9 @@ enum class ShaderStage
tesseval,
geom,
frag,
- comp
+ comp,
+ mesh,
+ task,
};
bool InternalSpvCompiler();
diff --git a/util/test/demos/vk/vk_mesh_shader.cpp b/util/test/demos/vk/vk_mesh_shader.cpp
new file mode 100644
index 000000000..59fee6801
--- /dev/null
+++ b/util/test/demos/vk/vk_mesh_shader.cpp
@@ -0,0 +1,282 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 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_Mesh_Shader, VulkanGraphicsTest)
+{
+ static constexpr const char *Description = "Draws geometry using mesh shader pipeline.";
+
+ std::string task = R"EOSHADER(
+
+#version 460
+#extension GL_EXT_mesh_shader : require
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+struct PayLoad
+{
+ uint tri[4];
+};
+
+taskPayloadSharedEXT PayLoad payLoad;
+
+void main()
+{
+ payLoad.tri[0] = 0;
+ payLoad.tri[1] = 1;
+ payLoad.tri[2] = 2;
+ payLoad.tri[3] = 3;
+ EmitMeshTasksEXT(4, 1, 1);
+}
+
+)EOSHADER";
+
+ std::string task_mesh = R"EOSHADER(
+
+#version 460
+#extension GL_EXT_mesh_shader : require
+
+struct PayLoad
+{
+ uint tri[4];
+};
+
+taskPayloadSharedEXT PayLoad payLoad;
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+layout(triangles, max_vertices = 3, max_primitives = 1) out;
+layout(location = 0) out vec4 outColor[];
+
+void main()
+{
+ uint triangleCount = 1;
+ uint vertexCount = 3 * triangleCount;
+ SetMeshOutputsEXT(vertexCount, triangleCount);
+
+ uint dtid = gl_GlobalInvocationID.x;
+ uint tri = payLoad.tri[dtid];
+ uint vertIdx = 0;
+ vec4 org = vec4(-0.65, 0.0, 0.0, 0.0) + vec4(0.42, 0.0, 0.0, 0.0) * tri;
+
+ uint vert0 = 0 + vertIdx;
+ uint vert1 = 1 + vertIdx;
+ uint vert2 = 2 + vertIdx;
+
+ gl_MeshVerticesEXT[vert0].gl_Position = vec4(-0.2, -0.2, 0.0, 1.0) + org;
+ gl_MeshVerticesEXT[vert1].gl_Position = vec4(0.0, 0.2, 0.0, 1.0) + org;
+ gl_MeshVerticesEXT[vert2].gl_Position = vec4(0.2, -0.2, 0.0, 1.0) + org;
+
+ outColor[vert0] = vec4(0.0, 0.0, 1.0, 1.0);
+ outColor[vert1] = vec4(0.0, 0.0, 1.0, 1.0);
+ outColor[vert2] = vec4(0.0, 0.0, 1.0, 1.0);
+
+ gl_PrimitiveTriangleIndicesEXT[0] = uvec3(vert0, vert1, vert2);
+}
+
+)EOSHADER";
+
+ std::string simple_mesh = R"EOSHADER(
+
+#version 460
+#extension GL_EXT_mesh_shader : require
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+layout(triangles, max_vertices = 6, max_primitives = 2) out;
+layout(location = 0) out vec4 outColor[];
+
+void main()
+{
+ uint triangleCount = 2;
+ uint vertexCount = 3 * triangleCount;
+
+ SetMeshOutputsEXT(vertexCount, triangleCount);
+
+ for (uint i = 0; i < 2; ++i)
+ {
+ uint vertIdx = i * 3;
+ uint tri = i + 2 * gl_WorkGroupID.x;
+ vec4 org = vec4(-0.65, +0.65, 0.0, 0.0) + vec4(0.42, 0.0, 0.0, 0.0) * tri;
+
+ uint vert0 = 0 + vertIdx;
+ uint vert1 = 1 + vertIdx;
+ uint vert2 = 2 + vertIdx;
+
+ gl_MeshVerticesEXT[vert0].gl_Position = vec4(-0.2, -0.2, 0.0, 1.0) + org;
+ gl_MeshVerticesEXT[vert1].gl_Position = vec4(0.0, 0.2, 0.0, 1.0) + org;
+ gl_MeshVerticesEXT[vert2].gl_Position = vec4(0.2, -0.2, 0.0, 1.0) + org;
+
+ outColor[vert0] = vec4(1.0, 0.0, 0.0, 1.0);
+ outColor[vert1] = vec4(1.0, 0.0, 0.0, 1.0);
+ outColor[vert2] = vec4(1.0, 0.0, 0.0, 1.0);
+
+ gl_PrimitiveTriangleIndicesEXT[i] = uvec3(vert0, vert1, vert2);
+ }
+}
+
+)EOSHADER";
+
+ std::string pixel = R"EOSHADER(
+
+#version 460
+
+layout(location = 0) in vec4 inColor;
+layout(location = 0) out vec4 outColor;
+
+void main()
+{
+ outColor = inColor;
+}
+
+)EOSHADER";
+
+ void Prepare(int argc, char **argv)
+ {
+ devExts.push_back(VK_EXT_MESH_SHADER_EXTENSION_NAME);
+
+ VulkanGraphicsTest::Prepare(argc, argv);
+
+ if(!Avail.empty())
+ return;
+
+ if(devVersion < VK_API_VERSION_1_1)
+ {
+ Avail = "Vulkan device version isn't 1.1";
+ return;
+ }
+
+ static VkPhysicalDeviceMeshShaderFeaturesEXT meshShaderFeatures = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT};
+
+ getPhysFeatures2(&meshShaderFeatures);
+
+ if(!meshShaderFeatures.meshShader)
+ {
+ Avail = "Mesh Shader feature 'meshShader' not available\n";
+ return;
+ }
+
+ if(!meshShaderFeatures.taskShader)
+ {
+ Avail = "Mesh Shader feature 'taskShader' not available";
+ return;
+ }
+
+ meshShaderFeatures.multiviewMeshShader = VK_FALSE;
+ meshShaderFeatures.primitiveFragmentShadingRateMeshShader = VK_FALSE;
+ meshShaderFeatures.meshShaderQueries = VK_FALSE;
+
+ devInfoNext = &meshShaderFeatures;
+ }
+
+ int main()
+ {
+ // initialise, create window, create context, etc
+ if(!Init())
+ return 3;
+
+ VkPipelineLayout layout = createPipelineLayout(
+ vkh::PipelineLayoutCreateInfo({}, {vkh::PushConstantRange(VK_SHADER_STAGE_ALL, 0, 8)}));
+
+ vkh::GraphicsPipelineCreateInfo pipeCreateInfo;
+ VkGraphicsPipelineCreateInfo *vkPipeCreateInfo = NULL;
+
+ pipeCreateInfo.layout = layout;
+ pipeCreateInfo.renderPass = mainWindow->rp;
+
+ VkPipeline pipelines[2];
+ int countTasks[2];
+
+ pipeCreateInfo.stages = {
+ CompileShaderModule(simple_mesh, ShaderLang::glsl, ShaderStage::mesh, "main", {},
+ SPIRVTarget::vulkan12),
+ CompileShaderModule(pixel, ShaderLang::glsl, ShaderStage::frag, "main"),
+ };
+
+ vkPipeCreateInfo = pipeCreateInfo;
+ vkPipeCreateInfo->pVertexInputState = NULL;
+ vkPipeCreateInfo->pInputAssemblyState = NULL;
+
+ pipelines[0] = createGraphicsPipeline(vkPipeCreateInfo);
+ countTasks[0] = 2;
+
+ pipeCreateInfo.stages = {
+ CompileShaderModule(task, ShaderLang::glsl, ShaderStage::task, "main", {},
+ SPIRVTarget::vulkan12),
+ CompileShaderModule(task_mesh, ShaderLang::glsl, ShaderStage::mesh, "main", {},
+ SPIRVTarget::vulkan12),
+ CompileShaderModule(pixel, ShaderLang::glsl, ShaderStage::frag, "main"),
+ };
+
+ vkPipeCreateInfo = pipeCreateInfo;
+ vkPipeCreateInfo->pVertexInputState = NULL;
+ vkPipeCreateInfo->pInputAssemblyState = NULL;
+
+ pipelines[1] = createGraphicsPipeline(vkPipeCreateInfo);
+ countTasks[1] = 1;
+
+ 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());
+
+ vkCmdBeginRenderPass(
+ cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor),
+ VK_SUBPASS_CONTENTS_INLINE);
+
+ setMarker(cmd, "Mesh Shaders");
+ for(size_t i = 0; i < ARRAY_COUNT(pipelines); ++i)
+ {
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[i]);
+ vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport);
+ vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor);
+
+ vkCmdDrawMeshTasksEXT(cmd, countTasks[i], 1, 1);
+ }
+
+ vkCmdEndRenderPass(cmd);
+
+ 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/demos/vk/vk_test.cpp b/util/test/demos/vk/vk_test.cpp
index 1e2a4babd..24f6dbebe 100644
--- a/util/test/demos/vk/vk_test.cpp
+++ b/util/test/demos/vk/vk_test.cpp
@@ -996,6 +996,8 @@ VkPipelineShaderStageCreateInfo VulkanGraphicsTest::CompileShaderModule(
VK_SHADER_STAGE_GEOMETRY_BIT,
VK_SHADER_STAGE_FRAGMENT_BIT,
VK_SHADER_STAGE_COMPUTE_BIT,
+ VK_SHADER_STAGE_MESH_BIT_EXT,
+ VK_SHADER_STAGE_TASK_BIT_EXT,
};
return vkh::PipelineShaderStageCreateInfo(ret, vkstage[(int)stage], entry_point);
diff --git a/util/test/rdtest/analyse.py b/util/test/rdtest/analyse.py
index 74bafed53..ca98bd7a4 100644
--- a/util/test/rdtest/analyse.py
+++ b/util/test/rdtest/analyse.py
@@ -172,6 +172,10 @@ def get_postvs_attrs(controller: rd.ReplayController, mesh: rd.MeshFormat, data_
if sig.stream != 0:
continue
+ # Ignore meshlet output indecies (they are not in postvs)
+ if sig.systemValue == rd.ShaderBuiltin.OutputIndices:
+ continue
+
# Construct a resource format for this element
attr.mesh.format = rd.ResourceFormat()
attr.mesh.format.compByteWidth = rd.VarTypeByteSize(sig.varType)
diff --git a/util/test/rdtest/testcase.py b/util/test/rdtest/testcase.py
index 4805bd2a2..5036ae0fe 100644
--- a/util/test/rdtest/testcase.py
+++ b/util/test/rdtest/testcase.py
@@ -822,3 +822,73 @@ class TestCase:
raise TestFailureException("Recompressed capture file doesn't match re-imported capture file", conv_path, recomp_path, conv_zipxml_path)
log.success("Recompressed and re-imported capture files are identical")
+
+ def check_debug_pixel(self, x: int, y: int):
+ pipe: rd.PipeState = self.controller.GetPipelineState()
+ if not pipe.GetShaderReflection(rd.ShaderStage.Pixel).debugInfo.debuggable:
+ log.print("Skipping undebuggable shader.")
+ return
+
+ # Debug the shader
+ trace = self.controller.DebugPixel(x, y, rd.DebugPixelInputs())
+ if trace.debugger is None:
+ self.controller.FreeTrace(trace)
+ raise TestFailureException(f"Pixel shader could not be debugged.")
+
+ _, variables = self.process_trace(trace)
+ output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0)
+ debugged = self.evaluate_source_var(output, variables)
+ self.controller.FreeTrace(trace)
+
+ try:
+ self.check_pixel_value(pipe.GetOutputTargets()[0].resource, x, y, debugged.value.f32v[0:4])
+ except TestFailureException as ex:
+ raise TestFailureException(f"Pixel shader did not debug correctly. {ex}")
+
+ log.success(f"Pixel shader debugging at {x},{y} was successful")
+
+ def decode_task_data(self, controller: rd.ReplayController, mesh: rd.MeshFormat, payload: rd.ConstantBlock, task: int = 0):
+
+ begin = mesh.vertexByteOffset + mesh.vertexByteStride * task
+ end = min(begin + mesh.vertexByteSize, 0xffffffffffffffff)
+ buffer_data = controller.GetBufferData(mesh.vertexResourceId, begin, end -begin)
+
+ ret = []
+ offset = 0
+ for var in payload.variables:
+ var_data = {}
+ var_data[var.name] = []
+ # This is not complete to decode all possible payload layouts
+ for i in range(var.type.elements):
+ format = rd.ResourceFormat()
+ format.compByteWidth = rd.VarTypeByteSize(var.type.baseType)
+ format.compCount = var.type.columns
+ format.compType = rd.VarTypeCompType(var.type.baseType)
+ format.type = rd.ResourceFormatType.Regular
+
+ data = analyse.unpack_data(format, buffer_data, offset)
+ var_data[var.name] += data
+ offset += format.compByteWidth * format.compCount
+ ret.append(var_data)
+
+ return ret
+
+ def get_task_data(self, action: rd.ActionDescription):
+ mesh: rd.MeshFormat = self.controller.GetPostVSData(0, 0, rd.MeshDataStage.TaskOut)
+ if mesh.numIndices == 0:
+ raise TestFailureException("Task data is empty")
+
+ if len(mesh.taskSizes) == 0:
+ raise TestFailureException("Task data is empty")
+
+ pipe: rd.PipeState = self.controller.GetPipelineState()
+ shader = pipe.GetShaderReflection(rd.ShaderStage.Task)
+ taskIdx = 0
+ task = action.dispatchDimension
+ data = []
+ for x in range(task[0]):
+ for y in range(task[1]):
+ for z in range(task[2]):
+ data += self.decode_task_data(self.controller, mesh, shader.taskPayload, taskIdx)
+ taskIdx += 1
+ return data
diff --git a/util/test/tests/D3D12/D3D12_Mesh_Shader.py b/util/test/tests/D3D12/D3D12_Mesh_Shader.py
index b4eb13c14..fe7a37762 100644
--- a/util/test/tests/D3D12/D3D12_Mesh_Shader.py
+++ b/util/test/tests/D3D12/D3D12_Mesh_Shader.py
@@ -6,76 +6,6 @@ class D3D12_Mesh_Shader(rdtest.TestCase):
demos_test_name = 'D3D12_Mesh_Shader'
demos_frame_cap = 5
- def check_pixel(self, x: int, y: int):
- pipe: rd.PipeState = self.controller.GetPipelineState()
- if not pipe.GetShaderReflection(rd.ShaderStage.Pixel).debugInfo.debuggable:
- rdtest.log.print("Skipping undebuggable shader.")
- return
-
- # Debug the shader
- trace = self.controller.DebugPixel(x, y, rd.DebugPixelInputs())
- if trace.debugger is None:
- self.controller.FreeTrace(trace)
- raise rdtest.TestFailureException(f"Pixel shader could not be debugged.")
-
- _, variables = self.process_trace(trace)
- output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0)
- debugged = self.evaluate_source_var(output, variables)
- self.controller.FreeTrace(trace)
-
- try:
- self.check_pixel_value(pipe.GetOutputTargets()[0].resource, x, y, debugged.value.f32v[0:4])
- except rdtest.TestFailureException as ex:
- raise rdtest.TestFailureException(f"Pixel shader did not debug correctly. {ex}")
-
- rdtest.log.success(f"Pixel shader debugging at {x},{y} was successful")
-
- def decode_task_data(self, controller: rd.ReplayController, mesh: rd.MeshFormat, payload: rd.ConstantBlock, task: int = 0):
-
- begin = mesh.vertexByteOffset + mesh.vertexByteStride * task
- end = min(begin + mesh.vertexByteSize, 0xffffffffffffffff)
- buffer_data = controller.GetBufferData(mesh.vertexResourceId, begin, end -begin)
-
- ret = []
- offset = 0
- for var in payload.variables:
- var_data = {}
- var_data[var.name] = []
- # This is not complete to decode all possible payload layouts
- for i in range(var.type.elements):
- format = rd.ResourceFormat()
- format.compByteWidth = rd.VarTypeByteSize(var.type.baseType)
- format.compCount = var.type.columns
- format.compType = rd.VarTypeCompType(var.type.baseType)
- format.type = rd.ResourceFormatType.Regular
-
- data = analyse.unpack_data(format, buffer_data, offset)
- var_data[var.name] += data
- offset += format.compByteWidth * format.compCount
- ret.append(var_data)
-
- return ret
-
- def get_task_data(self, action: rd.ActionDescription):
- mesh: rd.MeshFormat = self.controller.GetPostVSData(0, 0, rd.MeshDataStage.TaskOut)
- if mesh.numIndices == 0:
- raise self.TestFailureException("Task data is empty")
-
- if len(mesh.taskSizes) == 0:
- raise self.TestFailureException("Task data is empty")
-
- pipe: rd.PipeState = self.controller.GetPipelineState()
- shader = pipe.GetShaderReflection(rd.ShaderStage.Task)
- taskIdx = 0
- task = action.dispatchDimension
- data = []
- for x in range(task[0]):
- for y in range(task[1]):
- for z in range(task[2]):
- data += self.decode_task_data(self.controller, mesh, shader.taskPayload, taskIdx)
- taskIdx += 1
- return data
-
def build_global_taskout_reference(self):
reference = {}
for i in range(2):
@@ -144,7 +74,7 @@ class D3D12_Mesh_Shader(rdtest.TestCase):
postms_ref = self.build_meshout_reference(orgY, color)
postms_data = self.get_postvs(action, rd.MeshDataStage.MeshOut, 0, action.numIndices)
self.check_mesh_data(postms_ref, postms_data)
- self.check_pixel(x, y)
+ self.check_debug_pixel(x, y)
rdtest.log.end_section(name)
y += 100
@@ -162,7 +92,7 @@ class D3D12_Mesh_Shader(rdtest.TestCase):
postms_ref = self.build_meshout_reference(orgY, color)
postms_data = self.get_postvs(action, rd.MeshDataStage.MeshOut, 0, action.numIndices)
self.check_mesh_data(postms_ref, postms_data)
- self.check_pixel(x, y)
+ self.check_debug_pixel(x, y)
rdtest.log.end_section(name)
y += 100
@@ -180,5 +110,5 @@ class D3D12_Mesh_Shader(rdtest.TestCase):
postms_ref = self.build_meshout_reference(orgY, color)
postms_data = self.get_postvs(action, rd.MeshDataStage.MeshOut, 0, action.numIndices)
self.check_mesh_data(postms_ref, postms_data)
- self.check_pixel(x, y)
+ self.check_debug_pixel(x, y)
rdtest.log.end_section(name)
diff --git a/util/test/tests/Vulkan/VK_Mesh_Shader.py b/util/test/tests/Vulkan/VK_Mesh_Shader.py
new file mode 100644
index 000000000..d5e7a7806
--- /dev/null
+++ b/util/test/tests/Vulkan/VK_Mesh_Shader.py
@@ -0,0 +1,84 @@
+import renderdoc as rd
+import rdtest
+from rdtest import analyse
+
+class VK_Mesh_Shader(rdtest.TestCase):
+ demos_test_name = 'VK_Mesh_Shader'
+ demos_frame_cap = 5
+
+ def build_local_taskout_reference(self):
+ reference = {}
+ reference[0] = { 'tri': [0, 1, 2, 3] }
+ return reference
+
+ def build_meshout_reference(self, orgY, color):
+ countTris = 4
+ triSize = 0.2
+ deltX = 0.42
+ orgX = -0.65
+ i = 0
+ reference = {}
+ for tri in range(countTris):
+ for vert in range(3):
+ posX = orgX + tri * deltX
+ posY = orgY
+
+ if vert == 0:
+ posX += -triSize
+ posY += -triSize
+ elif vert == 1:
+ posX += 0.0
+ posY += triSize
+ elif vert == 2:
+ posX += triSize
+ posY += -triSize
+
+ reference[i] = {
+ 'vtx': i,
+ 'idx': i,
+ 'gl_Position': [posX, posY, 0.0, 1.0],
+ 'outColor': color,
+ }
+ i += 1
+ return reference
+
+ def check_capture(self):
+ last_action: rd.ActionDescription = self.get_last_action()
+
+ self.controller.SetFrameEvent(last_action.eventId, True)
+
+ action = self.find_action("Mesh Shaders")
+
+ action = action.next
+ name = f"Pure Mesh Shader Test EID:{action.eventId}"
+ rdtest.log.begin_section(name)
+ self.controller.SetFrameEvent(action.eventId, False)
+
+ x = 70
+ y = 240
+
+ orgY = 0.65
+ color = [1.0, 0.0, 0.0, 1.0]
+ postms_ref = self.build_meshout_reference(orgY, color)
+ postms_data = self.get_postvs(action, rd.MeshDataStage.MeshOut, 0, action.numIndices)
+ self.check_mesh_data(postms_ref, postms_data)
+ self.check_debug_pixel(x, y)
+ rdtest.log.end_section(name)
+
+ y -= 100
+ action = action.next
+ name = f"Amplification Shader with Local Payload EID:{action.eventId}"
+ rdtest.log.begin_section(name)
+ self.controller.SetFrameEvent(action.eventId, False)
+
+ postts_ref = self.build_local_taskout_reference()
+ postts_data = self.get_task_data(action)
+ self.check_task_data(postts_ref, postts_data)
+
+ orgY = 0.0
+ color = [0.0, 0.0, 1.0, 1.0]
+ postms_ref = self.build_meshout_reference(orgY, color)
+ postms_data = self.get_postvs(action, rd.MeshDataStage.MeshOut, 0, action.numIndices)
+ self.check_mesh_data(postms_ref, postms_data)
+ self.check_debug_pixel(x, y)
+ rdtest.log.end_section(name)