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 IDs 0 through 31.
  • gl_SubgroupEqMask.y: Covers invocation IDs 32 through 63.
  • gl_SubgroupEqMask.z: Covers invocation IDs 64 through 95.
  • gl_SubgroupEqMask.w: Covers invocation IDs 96 through 127.

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.x equals 1 << 5 (0x00000020).
  • gl_SubgroupEqMask.y, .z, and .w equal 0.

Relationship with Subgroup Ballot Masks

The GLSL subgroup extension provides several relational mask variables that follow the same uvec4 format:

  • gl_SubgroupEqMask: Bit set where index == gl_SubgroupInvocationID.
  • gl_SubgroupLtMask: Bits set where index < gl_SubgroupInvocationID.
  • gl_SubgroupLeMask: Bits set where index <= gl_SubgroupInvocationID.
  • gl_SubgroupGtMask: Bits set where index > gl_SubgroupInvocationID.
  • gl_SubgroupGeMask: Bits set where index >= 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.