GLSL Mesh Shader Lines: gl_PrimitiveLineIndicesEXT
This article explains how to configure and generate line network
primitives in GLSL using mesh shaders via the
GL_EXT_mesh_shader extension. It covers the required layout
qualifiers, primitive count allocations, and how to populate the
gl_PrimitiveLineIndicesEXT index buffer to define custom
line topologies, segments, and connected wireframes efficiently on the
GPU.
Pipeline Configuration and Layout Qualifiers
To output lines instead of triangles, the mesh shader must declare
the lines output primitive topology alongside the maximum
bounds for vertices and primitives. This requires enabling the
GL_EXT_mesh_shader extension.
#version 460
#extension GL_EXT_mesh_shader : require
layout(local_size_x = 32) in;
layout(lines, max_vertices = 64, max_primitives = 63) out;lines: Sets the primitive assembly mode to discrete line segments.max_vertices: Defines the upper limit of vertices this workgroup can export.max_primitives: Defines the maximum number of line segments generated by the workgroup.
Setting Mesh Outputs
Before writing index data, the mesh shader workgroup must specify the
exact number of active vertices and primitives for the current
execution. This is done collectively using
SetMeshOutputsEXT.
uint numVertices = 64;
uint numLines = 63;
SetMeshOutputsEXT(numVertices, numLines);Every active primitive index written beyond numLines
will be discarded, and invocations must complete this call before the
primitive stage assembles geometry.
Configuring Topology with gl_PrimitiveLineIndicesEXT
In a mesh shader configured for lines, primitive connectivity is
determined by the built-in array
gl_PrimitiveLineIndicesEXT[].
Each element in this array is a two-component unsigned integer vector
(uvec2), representing the start and end vertex indices of a
single line segment:
out uvec2 gl_PrimitiveLineIndicesEXT[];To form a continuous line network, polyline, or arbitrary graph, assign the corresponding vertex indices to each entry.
1. Continuous Line Strips (Polylines)
For a connected sequence of lines where vertex \(i\) connects to vertex \(i + 1\):
uint threadId = gl_LocalInvocationIndex;
if (threadId < numLines) {
gl_PrimitiveLineIndicesEXT[threadId] = uvec2(threadId, threadId + 1);
}2. Disjoint Line Segments
For disconnected pairs of vertices forming isolated segments:
uint threadId = gl_LocalInvocationIndex;
if (threadId < numLines) {
uint baseIndex = threadId * 2;
gl_PrimitiveLineIndicesEXT[threadId] = uvec2(baseIndex, baseIndex + 1);
}3. Arbitrary Line Networks (Grids or Graphs)
Because gl_PrimitiveLineIndicesEXT gives direct indexing
control per primitive, complex graphs can be indexed arbitrarily without
repeating shared vertices:
// Example: Creating a closed loop of 4 vertices
if (threadId == 0) gl_PrimitiveLineIndicesEXT[0] = uvec2(0, 1);
if (threadId == 1) gl_PrimitiveLineIndicesEXT[1] = uvec2(1, 2);
if (threadId == 2) gl_PrimitiveLineIndicesEXT[2] = uvec2(2, 3);
if (threadId == 3) gl_PrimitiveLineIndicesEXT[3] = uvec2(3, 0);Complete Implementation Example
#version 460
#extension GL_EXT_mesh_shader : require
layout(local_size_x = 32) in;
layout(lines, max_vertices = 33, max_primitives = 32) out;
void main() {
uint threadId = gl_LocalInvocationIndex;
uint vertexCount = 33;
uint lineCount = 32;
SetMeshOutputsEXT(vertexCount, lineCount);
// Compute vertex positions
if (threadId < vertexCount) {
float x = float(threadId) / float(vertexCount - 1);
gl_MeshVerticesEXT[threadId].gl_Position = vec4(x * 2.0 - 1.0, sin(x * 6.28), 0.0, 1.0);
}
// Configure line topology
if (threadId < lineCount) {
gl_PrimitiveLineIndicesEXT[threadId] = uvec2(threadId, threadId + 1);
}
}Unlike traditional geometry shaders, mesh shaders allow threads
within the workgroup to write both vertex data
(gl_MeshVerticesEXT) and line topological connections
(gl_PrimitiveLineIndicesEXT) in parallel, eliminating
redundant vertex processing across adjacent lines in the network.