How Is gl_NumWorkGroups Used in GLSL?
In OpenGL Shading Language (GLSL) compute shaders,
gl_NumWorkGroups is a built-in variable that provides the
total dimensions of the work group grid dispatched by the host
application. This article covers the definition of
gl_NumWorkGroups, its relationship to host dispatch
commands, how it interacts with other built-in compute variables, and
practical implementation patterns for boundary checks and global
indexing.
What is gl_NumWorkGroups?
In GLSL compute shaders, gl_NumWorkGroups is declared
implicitly as a uvec3:
in uvec3 gl_NumWorkGroups;It contains the number of work groups passed to the compute dispatch
call from the CPU application side (such as
glDispatchCompute(num_groups_x, num_groups_y, num_groups_z)
in OpenGL). Each component (x, y, and
z) corresponds directly to the parameters supplied in that
call.
Relationship with Other Compute Built-ins
To understand how gl_NumWorkGroups is utilized, it helps
to understand how it fits within the GLSL compute hierarchy:
gl_WorkGroupSize: The local size of a single work group, defined statically in the shader vialayout(local_size_x = ..., local_size_y = ..., local_size_z = ...) in;.gl_WorkGroupID: The index of the current work group within the overall dispatch grid, ranging from(0, 0, 0)up togl_NumWorkGroups - 1.gl_LocalInvocationID: The index of the current thread within its local work group.gl_GlobalInvocationID: The unique global index of the thread across all work groups, calculated automatically asgl_WorkGroupID * gl_WorkGroupSize + gl_LocalInvocationID.
While gl_GlobalInvocationID gives the current thread's
position, gl_NumWorkGroups represents the upper boundary of
the active group grid.
Primary Use Cases
1. Calculating Total Grid Dimensions
When a compute shader needs to know the total global invocation count
across all dimensions dynamically, it multiplies
gl_NumWorkGroups by gl_WorkGroupSize:
uvec3 totalInvocations = gl_NumWorkGroups * gl_WorkGroupSize;This is useful when the dispatch size varies at runtime and the shader needs to adapt its computations or normalization factors to the full dataset size without hardcoding uniform values.
2. Flattening Multi-Dimensional Indices
When flattening 2D or 3D grids into a 1D linear buffer,
gl_NumWorkGroups helps compute stride lengths and total
capacity:
#version 430 core
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
layout(std430, binding = 0) buffer OutputBuffer {
float data[];
};
void main() {
uvec3 totalSize = gl_NumWorkGroups * gl_WorkGroupSize;
// Compute 1D linear index from 2D coordinates
uint linearIndex = gl_GlobalInvocationID.y * totalSize.x + gl_GlobalInvocationID.x;
data[linearIndex] = float(linearIndex);
}3. Grid-Stride Loops and Work Distribution
In scenarios where the number of data elements exceeds the total number of launched threads, compute shaders use a grid-stride loop. In this pattern, each thread processes multiple elements spaced apart by the total grid size:
#version 430 core
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
layout(std430, binding = 0) buffer DataBuffer {
uint elements[];
};
uniform uint totalElementCount;
void main() {
uint stride = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint index = gl_GlobalInvocationID.x;
while (index < totalElementCount) {
elements[index] *= 2;
index += stride;
}
}Utilizing gl_NumWorkGroups directly inside the shader
allows compute pipelines to remain flexible and decoupled from fixed
buffer dimensions, adapting automatically to whatever dispatch
dimensions the host application submits.