How gl_SubgroupEqMask Works in GLSL
This article provides a technical overview of the
gl_SubgroupEqMask built-in variable in GLSL, explaining how
it represents the current invocation within a subgroup. You will learn
about its underlying data structure, how the bitmask is mapped to the
current thread's index, and how it is applied in subgroup-level GPU
operations without manual bit-shifting.
Definition and Purpose
In modern GLSL (via Vulkan or OpenGL extensions like
GL_KHR_shader_subgroup_basic), threads in a compute or
graphics pipeline execute in lockstep units called subgroups (known as
warps in NVIDIA hardware or wavefronts in AMD hardware). Within a
subgroup, each invocation is assigned a unique index accessible via
gl_SubgroupInvocationID.
The built-in input variable gl_SubgroupEqMask provides a
bitmask where only the bit corresponding to the current invocation's
index (gl_SubgroupInvocationID) is set to 1,
while all other bits are set to 0. The "Eq" in the variable
name stands for "Equal to," indicating that the active bit represents an
index equal to the calling invocation.
Structure and Representation
gl_SubgroupEqMask is defined as a 4-component unsigned
integer vector:
in uvec4 gl_SubgroupEqMask;Because a subgroup can conceptually contain up to 128 invocations,
the bitmask must support up to 128 distinct lane flags. The 128-bit
state is split across the four 32-bit components of the
uvec4:
gl_SubgroupEqMask.x: Covers invocation IDs0through31.gl_SubgroupEqMask.y: Covers invocation IDs32through63.gl_SubgroupEqMask.z: Covers invocation IDs64through95.gl_SubgroupEqMask.w: Covers invocation IDs96through127.
For any invocation \(N\), the bit
set is located at index \(N \pmod{32}\)
within the vector component determined by \(\lfloor N / 32 \rfloor\). All other bits
across all four components remain 0.
For example, if an invocation has
gl_SubgroupInvocationID = 5:
gl_SubgroupEqMask.xequals1 << 5(0x00000020).gl_SubgroupEqMask.y,.z, and.wequal0.
Relationship with Subgroup Ballot Masks
The GLSL subgroup extension provides several relational mask
variables that follow the same uvec4 format:
gl_SubgroupEqMask: Bit set whereindex == gl_SubgroupInvocationID.gl_SubgroupLtMask: Bits set whereindex < gl_SubgroupInvocationID.gl_SubgroupLeMask: Bits set whereindex <= gl_SubgroupInvocationID.gl_SubgroupGtMask: Bits set whereindex > gl_SubgroupInvocationID.gl_SubgroupGeMask: Bits set whereindex >= gl_SubgroupInvocationID.
These masks are precomputed by hardware or the shader compiler,
eliminating the runtime arithmetic and instruction cycles typically
needed to compute dynamic bit shifts like
1u << id.
Practical Application
gl_SubgroupEqMask is commonly used alongside
subgroupBallot(), which produces a uvec4
bitmask showing which invocations evaluated a condition to
true. By performing a bitwise AND between a ballot result
and gl_SubgroupEqMask, a thread can determine its own
membership in a subgroup-wide set:
uvec4 activeLanes = subgroupBallot(condition);
// Check if the current invocation is part of the active set
bool amIActive = any(notEqual(activeLanes & gl_SubgroupEqMask, uvec4(0)));Using gl_SubgroupEqMask allows shader code to interact
with SIMD lanes directly, facilitating operations like prefix sums,
thread leader election, and custom warp-level synchronization.