GL_NV_mesh_shader vs GL_EXT_mesh_shader in GLSL
This article compares NVIDIA’s vendor-specific
GL_NV_mesh_shader extension with the cross-vendor
GL_EXT_mesh_shader extension in GLSL. While both extensions
replace the traditional vertex, geometry, and tessellation pipeline with
a cooperative, compute-like programming model using task (amplification)
and mesh stages, the EXT version introduces standardized syntax, more
explicit memory management, dynamic primitive sizing, and
multidimensional workgroup spawning.
Task Dispatch and Workgroup Spawning
In GL_NV_mesh_shader, the task shader triggers
subsequent mesh shader workgroups using the built-in variable
gl_TaskCountNV. This restricts invocation generation to a
one-dimensional dispatch count:
// GL_NV_mesh_shader
gl_TaskCountNV = 16;In GL_EXT_mesh_shader, workgroup generation is handled
functionally via the built-in function EmitMeshTasksEXT(),
which supports three-dimensional workgroup dispatch grids:
// GL_EXT_mesh_shader
EmitMeshTasksEXT(taskCountX, taskCountY, taskCountZ);Once EmitMeshTasksEXT() executes, control flow
terminates or ignores subsequent emissions, making task dispatch
explicit and aligned with compute dispatch conventions.
Task Payload Communication
Both specifications pass data from the task shader to the mesh shader through a shared memory payload, but the declarations and storage qualifiers differ:
NV Extension: The payload is defined using the
taskNVstorage qualifier applied to an output block in the task shader, and as an input block in the mesh shader:// In task shader (NV) taskNV out TaskData { uint offset; } taskOut;EXT Extension: The payload uses the
taskPayloadSharedEXTstorage qualifier, which functions similarly to shared workgroup memory. Both the task and mesh shaders declare the same variable layout:// In task and mesh shaders (EXT) struct TaskData { uint offset; }; taskPayloadSharedEXT TaskData taskPayload;
Dynamic Primitive and Vertex Output Allocation
Under GL_NV_mesh_shader, the number of vertices and
primitives written by a mesh workgroup is implicitly determined by the
maximum bounds declared in the layout qualifiers
(max_vertices and max_primitives), or manually
capped by what indices are populated:
// GL_NV_mesh_shader
layout(local_size_x = 32) in;
layout(max_vertices = 64, max_primitives = 126) out;
layout(triangles) out;GL_EXT_mesh_shader retains maximum bounds in the layout
declaration for hardware allocation purposes, but mandates an explicit
runtime call to SetMeshOutputCountsEXT() before writing
per-primitive or per-vertex data:
// GL_EXT_mesh_shader
layout(local_size_x = 32) in;
layout(max_vertices = 64, max_primitives = 126) out;
layout(triangles) out;
void main() {
SetMeshOutputCountsEXT(actualNumVertices, actualNumPrimitives);
// Writes to vertices and primitives follow
}Omitting SetMeshOutputCountsEXT() or writing outputs
beyond the specified counts produces undefined behavior.
Primitive Index Representation
The two extensions structure index output buffers differently:
NV Extension: Uses a flat output array,
gl_PrimitiveIndicesNV[]. For a triangle topology, every three consecutive indices correspond to a single primitive:gl_PrimitiveIndicesNV[gl_LocalInvocationIndex * 3 + 0] = idx0; gl_PrimitiveIndicesNV[gl_LocalInvocationIndex * 3 + 1] = idx1; gl_PrimitiveIndicesNV[gl_LocalInvocationIndex * 3 + 2] = idx2;EXT Extension: Uses strongly-typed vector arrays matching the output topology. For triangles, indices are assigned as a
uvec3togl_PrimitiveTriangleIndicesEXT[](oruvec2togl_PrimitiveLineIndicesEXT[]for lines, anduinttogl_PrimitivePointIndicesEXT[]for points):gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(idx0, idx1, idx2);
Built-in Output Structures and Per-Primitive Qualifiers
GL_NV_mesh_shader provides a built-in per-vertex array
gl_MeshVerticesNV[] containing traditional vertex outputs
like gl_Position and gl_PointSize. Custom
per-primitive attributes are denoted using the
perprimitiveNV qualifier on output blocks or variables.
GL_EXT_mesh_shader standardizes this using
gl_MeshVerticesEXT[] for standard vertex attributes and
introduces gl_MeshPrimitivesEXT[] for per-primitive
built-ins (such as gl_PrimitiveID, gl_Layer,
and gl_CullPrimitiveEXT). Custom outputs are divided using
the explicit qualifiers perprimitiveEXT or vertex-scoped
array blocks:
// GL_EXT_mesh_shader custom outputs
layout(location = 0) out vec4 outColor[]; // Per-vertex attribute
layout(location = 1) perprimitiveEXT out uint outMaterialID[]; // Per-primitive attributeSummary of Key Syntax Differences
| Feature | GL_NV_mesh_shader |
GL_EXT_mesh_shader |
|---|---|---|
| Workgroup Generation | gl_TaskCountNV = N; |
EmitMeshTasksEXT(x, y, z); |
| Payload Storage | taskNV out / in |
taskPayloadSharedEXT |
| Output Count Definition | Implicit / maximum bound | Explicit via
SetMeshOutputCountsEXT(v, p) |
| Triangle Index Output | Flat
uint gl_PrimitiveIndicesNV[] |
Vector
uvec3 gl_PrimitiveTriangleIndicesEXT[] |
| Per-Primitive Qualifier | perprimitiveNV |
perprimitiveEXT |
| Built-in Arrays | gl_MeshVerticesNV[] |
gl_MeshVerticesEXT[],
gl_MeshPrimitivesEXT[] |