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:
- Per-Point Filtering: Threads within a mesh shader workgroup evaluate point visibility (e.g., against camera frustum planes or depth thresholds).
- Subgroup Ballot Compaction: Visible points are
compacted using subgroup operations (e.g.,
subgroupBallotandsubgroupExclusiveAdd). - Sparse Index Assignment: Only passing points are
assigned an index inside
gl_PrimitivePointIndicesEXT, while non-visible points consume neither primitive slots nor rasterization bandwidth. - 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.