Point Cloud Rendering with gl_PrimitivePointIndicesEXT

This article explains how the gl_PrimitivePointIndicesEXT built-in variable functions within GLSL mesh shaders to accelerate and optimize point cloud rendering. By shifting the traditional input-assembler topology management into compute-driven mesh shader stages, developers can dynamically cull, generate, and index massive datasets—such as LiDAR scans or photogrammetry data—directly on the GPU with minimal memory overhead.

The Role of gl_PrimitivePointIndicesEXT

In the GL_EXT_mesh_shader extension, mesh shaders replace traditional vertex and geometry pipelines, providing a compute-like programming model for primitive generation. When configured for point rendering using the layout(points) out; execution mode, the mesh shader must define how output vertices form individual point primitives.

The built-in output array gl_PrimitivePointIndicesEXT[] serves this purpose. For every emitted point primitive, the shader writes a single vertex index to this array:

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

out gl_MeshPerVertexEXT {
    vec4 gl_Position;
} gl_MeshVerticesEXT[];

void main() {
    uint gId = gl_GlobalInvocationID.x;
    uint lId = gl_LocalInvocationIndex;

    // Set total vertices and primitives produced by this workgroup
    SetMeshOutputsEXT(64, 64);

    // Compute or fetch point coordinates
    gl_MeshVerticesEXT[lId].gl_Position = fetchPointPosition(gId);

    // Map the primitive directly to the vertex
    gl_PrimitivePointIndicesEXT[lId] = lId;
}

Each element in gl_PrimitivePointIndicesEXT corresponds to a point primitive that the hardware rasterizer will draw, referencing an attribute entry defined in gl_MeshVerticesEXT.

Dynamic Topology and Index Compaction

Traditional point rendering pipelines require predefined index buffers or depend entirely on drawing unindexed arrays. If individual points must be discarded (due to frustum culling, distance-based level-of-detail (LOD), or classification filtering), traditional pipelines often execute vertex shaders for culled points anyway or rely on compute shader pre-passes to compact index buffers.

gl_PrimitivePointIndicesEXT eliminates the need for external compaction passes:

  1. Per-Point Filtering: Threads within a mesh shader workgroup evaluate point visibility (e.g., against camera frustum planes or depth thresholds).
  2. Subgroup Ballot Compaction: Visible points are compacted using subgroup operations (e.g., subgroupBallot and subgroupExclusiveAdd).
  3. Sparse Index Assignment: Only passing points are assigned an index inside gl_PrimitivePointIndicesEXT, while non-visible points consume neither primitive slots nor rasterization bandwidth.
  4. Dynamic Output Sizing: The final count of visible points is passed directly to SetMeshOutputsEXT(vertexCount, primitiveCount), ensuring zero rasterizer overhead for culled data.

GPU Memory Bandwidth Reduction

Point clouds typically consist of millions to billions of samples, making memory bandwidth the primary rendering bottleneck. By using mesh shaders with gl_PrimitivePointIndicesEXT, point cloud data structures can be decoded on the fly:

  • Compressed Storage: Point positions can be stored as 8-bit or 16-bit quantized integers, octree nodes, or spherical coordinates.
  • On-Chip Expansion: Threads unpack and transform raw memory into floating-point coordinates within workgroup local storage or registers before writing to gl_MeshVerticesEXT.
  • Zero Index Buffer Allocation: Because point indices are generated algebraically in shader code (gl_PrimitivePointIndicesEXT[i] = i), applications avoid storing and streaming multi-gigabyte index buffers across the PCIe bus and VRAM.

Integration with Clustered Point Hierarchies

Modern spatial partitioning algorithms divide dense point clouds into hierarchical bounding volumes or clusters (such as "point-lets"). Task shaders (object shaders) act as a coarse filter, determining whether an entire point cluster resides within the view frustum or requires a specific LOD.

Once a cluster passes the task stage, it dispatches the corresponding mesh shader. The mesh shader workgroup then uses gl_PrimitivePointIndicesEXT to generate the precise stream of visible primitives. This hierarchical approach matches modern GPU SIMD architectures and allows massive point clouds to be rendered at native hardware limits without CPU intervention.