Storing Per-Vertex Attributes in gl_MeshVerticesEXT
This article explains how the gl_MeshVerticesEXT output
array operates in GLSL mesh shaders under the
GL_EXT_mesh_shader extension. It details the structural
definition of built-in per-vertex outputs, how custom user attributes
correlate with this array, and how mesh shader workgroups cooperatively
write and index per-vertex data in GPU memory before primitive
assembly.
Definition and Structure of gl_MeshVerticesEXT
In GLSL mesh shaders (GL_EXT_mesh_shader),
gl_MeshVerticesEXT is a built-in output array of interface
blocks designed specifically to handle standard per-vertex outputs for
the rasterizer. It is defined implicitly in the shader stage as:
out gl_MeshPerVertexEXT {
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
float gl_CullDistance[];
} gl_MeshVerticesEXT[];The array size corresponds to the maximum vertex capacity declared in
the layout qualifier at the top of the shader, such as
layout(max_vertices = N) out;. Each element in the array
represents a single vertex in the emitted meshlet.
Built-in vs. Custom Per-Vertex Attributes
gl_MeshVerticesEXT strictly holds Vulkan's built-in
vertex attributes, most notably gl_Position. Custom user
attributes—such as texture coordinates, normals, or colors—are not
nested inside gl_MeshVerticesEXT. Instead, custom
per-vertex attributes are declared as separate, explicitly sized or
unsized output arrays or interface blocks matching the vertex
capacity:
layout(location = 0) out vec3 outNormal[];
layout(location = 1) out vec2 outTexCoord[];Alternatively, grouped attributes can be written using custom output blocks:
layout(location = 0) out VertexOutput {
vec3 normal;
vec2 uv;
} v_out[];Even though custom outputs are declared independently from
gl_MeshVerticesEXT, they share the exact same indexing
scheme. The vertex located at index i is defined by the
combination of gl_MeshVerticesEXT[i] and all corresponding
custom attributes at index i.
Output Allocation with SetMeshOutputsEXT
Before writing to gl_MeshVerticesEXT or custom vertex
output arrays, the mesh shader must define the actual number of
primitives and vertices being emitted for the current workgroup. This is
accomplished using the built-in function:
SetMeshOutputsEXT(vertexCount, primitiveCount);Once SetMeshOutputsEXT executes, the hardware allocates
backing memory for the specified number of vertices. Attempting to write
to indices greater than or equal to vertexCount results in
undefined behavior.
Indexing and Workgroup Memory Access
Writing to gl_MeshVerticesEXT is fundamentally parallel.
Unlike legacy vertex shaders, which process a single vertex per thread
invocation, a mesh shader workgroup processes an entire meshlet
containing multiple vertices and primitives concurrently.
Attributes in gl_MeshVerticesEXT are stored via direct
array indexing. Threads within the workgroup typically map their local
thread ID (gl_LocalInvocationIndex) to vertex indices:
uint vID = gl_LocalInvocationIndex;
if (vID < vertexCount) {
gl_MeshVerticesEXT[vID].gl_Position = transform * vec4(inPos[vID], 1.0);
outTexCoord[vID] = inUV[vID];
}If the number of vertices exceeds the workgroup size, threads can
iterate using a strided loop until all elements of
gl_MeshVerticesEXT are populated.
Memory Layout Under the Hood
Under the hood, the GPU driver allocates on-chip scratch memory (such
as Shared Memory or dedicated Primitive/Vertex parameter cache) to stage
the gl_MeshVerticesEXT array and custom per-vertex output
arrays.
Because gl_MeshVerticesEXT is structured as an Array of
Structures (AoS) while individual custom arrays are often structured as
Structure of Arrays (SoA), modern compilers optimize access patterns to
ensure coalesced writes across SIMD lanes. After the workgroup finishes
execution and synchronizes, the rasterizer reads the populated storage
slots directly according to the indices defined in
gl_PrimitiveTriangleIndicesEXT or
gl_PrimitiveLineIndicesEXT.