(Demo) RGB Triangle with Mesh Shaders in Vulkan

GeeXLab - Mesh Shaders in Vulkan


For a short introduction to mesh shaders, you can read this article that presents mesh shaders in OpenGL. Rendering a triangle with a mesh shader is more or less the same thing in OpenGL and in Vulkan.

In OpenGL we need:
– support of GL_NV_mesh_shader extension
– a GPU program with a mesh and a pixel shaders
– a function to execute the mesh shader: gh_renderer.draw_mesh_tasks()

In Vulkan we need:
– support of VK_NV_mesh_shader extension
– a GPU program with a mesh and a pixel shaders
– a function to execute the mesh shader: gh_vk.draw_mesh_tasks()
– a pipeline

The following code snippet shows how to check the support of VK_NV_mesh_shader for the current GPU (remember, in Vulkan, the demo runs on any Vulkan-capable GPU, defined in the XML file):

local gpu_index = gh_vk.get_current_gpu()
VK_NV_mesh_shader_OK = gh_vk.gpu_is_extension_supported(gpu_index, "VK_NV_mesh_shader")
if (VK_NV_mesh_shader_OK) then

Now the GPU program.

Here is the mesh shader, stored in the ms.mesh file.

#version 450
#extension GL_NV_mesh_shader : require
layout(local_size_x = 1) in;
layout(triangles, max_vertices = 3, max_primitives = 1) out;
// Custom vertex output block
layout (location = 0) out PerVertexData
  vec4 color;
} v_out[];   // [max_vertices]
float scale = 0.95;
const vec3 vertices[3] = {vec3(-1,-1,0), vec3(0,1,0), vec3(1,-1,0)};
const vec3 colors[3] = {vec3(1.0,0.0,0.0), vec3(0.0,1.0,0.0), vec3(0.0,0.0,1.0)};
void main()
  vec4 pos = vec4(vertices[0] * scale, 1.0);
  // GL->VK conventions...
  pos.y = -pos.y; pos.z = (pos.z + pos.w) / 2.0;
  gl_MeshVerticesNV[0].gl_Position = pos; 
  pos = vec4(vertices[1] * scale, 1.0);
  pos.y = -pos.y; pos.z = (pos.z + pos.w) / 2.0;
  gl_MeshVerticesNV[1].gl_Position = pos; 
  pos = vec4(vertices[2] * scale, 1.0);
  pos.y = -pos.y; pos.z = (pos.z + pos.w) / 2.0;
  gl_MeshVerticesNV[2].gl_Position = pos; 
  v_out[0].color = vec4(colors[0], 1.0);
  v_out[1].color = vec4(colors[1], 1.0);
  v_out[2].color = vec4(colors[2], 1.0);
  gl_PrimitiveIndicesNV[0] = 0;
  gl_PrimitiveIndicesNV[1] = 1;
  gl_PrimitiveIndicesNV[2] = 2;
  gl_PrimitiveCountNV = 1;

If we forget the pos.y = -pos.y; pos.z = (pos.z + pos.w) / 2.0; line (OpenGL to Vulkan, clipping space + z depth conversion), the mesh shader is the same in OpenGL and Vulkan.

The pixel shader (ps.frag file):

#version 450
layout (location = 0) in PerVertexData
  vec4 color;
} fragIn;
layout (location = 0) out vec4 FragColor;
void main()
  FragColor = fragIn.color;

Now, we have to generate SPIR-V modules from these GLSL files. This is done with the glslangValidator command line utility. (glslangValidator is available with GeeXLab win64 in the following folder: GeeXLab/spirv/). You can also find this utility in the Vulkan SDK.

The GLSL files are converted to SPIR-V modules with:

glslangValidator ms.mesh -V -o ms.spv
glslangValidator ps.frag -V -o ps.spv

The mesh shader GPU program is created with:

local mesh_shader = demo_dir .. "spirv/ms.spv"
local task_shader = ""
local pixel_shader = demo_dir .. "spirv/ps.spv"
mesh_prog = gh_gpu_program.vk_create_mesh_task_from_spirv_module_file("mesh_prog",
                mesh_shader, "main",   
                task_shader, "",  
                pixel_shader, "main")

A pipeline is a fundamental object of Vulkan. A pipeline stores in a single object all render states (what shaders are used, depth state, blending, etc.). A pipeline is an immutable object: once created, it can not be changed. So if you need to draw an object with solid polygon mode and another object in wireframe, you have to create two pipelines. A complex scene can have hundred or more pipelines…

The pipeline for drawing our RGB triangle is simple. But there is one important difference between this pipeline and pipelines used in other Vulkan demos: there is no vertex stream. Since the vertices are generated in the mesh shader, the Vulkan pipeline needs to know if vertices are sent to the GPU program or not.

Here is the code to create the pipeline:

-- Empty descriptor set.
-- A descriptor set is required to build the pipeline.
ds = gh_vk.descriptorset_create()
mesh_pipeline = gh_vk.pipeline_create("mesh_pipeline", mesh_prog, "")
gh_vk.pipeline_set_attrib_4i(mesh_pipeline, "DEPTH_TEST", 0, 0, 0, 0)
gh_vk.pipeline_set_attrib_4i(mesh_pipeline, "FILL_MODE", POLYGON_MODE_SOLID, 0, 0, 0)
gh_vk.pipeline_set_attrib_4i(mesh_pipeline, "PRIMITIVE_TYPE", PRIMITIVE_TRIANGLE, 0, 0, 0)
gh_vk.pipeline_set_attrib_4i(mesh_pipeline, "CULL_MODE", POLYGON_FACE_NONE, 0, 0, 0)
-- The triangle is generated in the mesh shader. No vertex stream is sent to the shader.
local has_vertices = 0
gh_vk.pipeline_set_attrib_4i(mesh_pipeline, "HAS_VERTEX_BINDING", has_vertices, 0, 0, 0)
gh_vk.pipeline_build(mesh_pipeline, ds)

The GPU program and the pipeline have been created. We are ready to draw our triangle.

FRAME script:

gh_vk.clear_color_depth_buffers(0.2, 0.2, 0.2, 1.0, 1.0)
gh_vk.set_viewport_scissor(0, 0, width, height)
gh_vk.draw_mesh_tasks(0, 1)

gh_vk.frame_command_buffer_begin() and gh_vk.frame_command_buffer_end() are functions of the new Vulkan2 plugin added in GeeXLab 0.32.

The final result:

GeeXLab - Mesh Shaders demo in Vulkan

The demo requires GeeXLab 0.32+ and is available in the geexlab-demopack-mesh-shaders/vk/rgb-triangle/ folder of the mesh shaders demopack.

Leave a Comment

Your email address will not be published. Required fields are marked *