Output Point Sprites with gl_PrimitivePointIndicesEXT

Using the GL_EXT_mesh_shader extension in GLSL allows developers to generate and manage point sprite collections directly on the GPU through mesh shaders. This article provides a step-by-step procedure for configuring a mesh shader, allocating geometry outputs, and utilizing gl_PrimitivePointIndicesEXT to define topology for point primitives alongside their vertex positions and attributes.

1. Enable the Extension and Configure Layouts

To output point primitives from a mesh shader, declare the GL_EXT_mesh_shader extension and define the output primitive type as points. Specify the maximum number of vertices and primitives your workgroup will produce, as well as the local execution size.

#version 460
#extension GL_EXT_mesh_shader : require

layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
layout(points, max_vertices = 32, max_primitives = 32) out;

2. Set the Output Allocation

Before populating output arrays, define the actual count of vertices and primitives emitted by the workgroup invocation using the built-in function SetMeshOutputsEXT.

uint totalPoints = 32; // Number of points this threadgroup will output
SetMeshOutputsEXT(totalPoints, totalPoints);

3. Populate Per-Vertex Data

Assign spatial coordinates and sizes for each point sprite using the gl_MeshVerticesEXT array. Each point sprite requires a position in clip space and optionally a point size if custom sizing is enabled in your graphics pipeline.

uint index = gl_LocalInvocationIndex;

if (index < totalPoints) {
    gl_MeshVerticesEXT[index].gl_Position = vec4(computePosition(index), 1.0);
    gl_MeshVerticesEXT[index].gl_PointSize = 16.0; // Size in pixels
}

4. Index Points Using gl_PrimitivePointIndicesEXT

Unlike triangles, which require three indices per primitive in gl_PrimitiveTriangleIndicesEXT, a point primitive consists of a single index. The built-in array gl_PrimitivePointIndicesEXT[] accepts a single uint per primitive pointing to the corresponding index in gl_MeshVerticesEXT.

if (index < totalPoints) {
    // Map primitive index directly to vertex index
    gl_PrimitivePointIndicesEXT[index] = index;
}

5. Assign Per-Primitive Attributes (Optional)

If your pipeline requires per-primitive metadata (such as sprite layer indices, texture array offsets, or cull flags), write these values directly to gl_MeshPrimitivesEXT[].

if (index < totalPoints) {
    gl_MeshPrimitivesEXT[index].gl_Layer = computeTextureLayer(index);
}

Complete Implementation Example

#version 460
#extension GL_EXT_mesh_shader : require

layout(local_size_x = 64) in;
layout(points, max_vertices = 64, max_primitives = 64) out;

// Optional: Output attributes passed to the fragment shader
layout(out) out vec4 outColor[];

void main() {
    uint gID = gl_GlobalInvocationID.x;
    uint lID = gl_LocalInvocationIndex;
    uint count = 64;

    SetMeshOutputsEXT(count, count);

    // Write vertex position and user attributes
    gl_MeshVerticesEXT[lID].gl_Position = vec4(float(lID) * 0.05 - 1.0, 0.0, 0.0, 1.0);
    gl_MeshVerticesEXT[lID].gl_PointSize = 8.0;
    outColor[lID] = vec4(1.0, 0.5, 0.2, 1.0);

    // Establish point topology connection
    gl_PrimitivePointIndicesEXT[lID] = lID;
}