How Is gl_LocalInvocationIndex Calculated in GLSL?

In GLSL compute shaders, gl_LocalInvocationIndex is a built-in 1D integer variable that uniquely identifies an invocation within its local workgroup. It is computed by flattening the three-dimensional gl_LocalInvocationID into a single linear scalar value using row-major ordering where the X dimension is the most rapidly changing coordinate, followed by Y, and then Z. Understanding this calculation is essential for managing shared memory layouts, optimizing cache locality, and indexing flat buffers efficiently.

The Mathematical Formula

The relationship between the 3D local invocation identifier (gl_LocalInvocationID), the local workgroup dimensions (gl_WorkGroupSize), and the 1D index is defined formally by the OpenGL and Vulkan shading language specifications as:

\[gl\_LocalInvocationIndex = gl\_LocalInvocationID.z \times gl\_WorkGroupSize.x \times gl\_WorkGroupSize.y + gl\_LocalInvocationID.y \times gl\_WorkGroupSize.x + gl\_LocalInvocationID.x\]

In this equation:

  • gl_WorkGroupSize.x, gl_WorkGroupSize.y, and gl_WorkGroupSize.z represent the dimensions declared in the shader layout qualifier (layout(local_size_x = ..., local_size_y = ..., local_size_z = ...) in;).
  • gl_LocalInvocationID.x ranges from \(0\) to \(gl\_WorkGroupSize.x - 1\).
  • gl_LocalInvocationID.y ranges from \(0\) to \(gl\_WorkGroupSize.y - 1\).
  • gl_LocalInvocationID.z ranges from \(0\) to \(gl\_WorkGroupSize.z - 1\).

Step-by-Step Example

Consider a compute shader defined with a local workgroup size of \(4 \times 4 \times 2\):

layout(local_size_x = 4, local_size_y = 4, local_size_z = 2) in;

For a thread executing at invocation coordinates gl_LocalInvocationID = uvec3(2, 3, 1):

  1. Multiply the Z coordinate by the total number of threads in an XY slice:

\[1 \times (4 \times 4) = 16\]

  1. Multiply the Y coordinate by the width of a single row along the X axis:

\[3 \times 4 = 12\]

  1. Add the X coordinate offset:

\[2\]

  1. Sum all components together:

\[16 + 12 + 2 = 30\]

The resulting gl_LocalInvocationIndex for this thread is 30, covering a valid index range from \(0\) to \(31\) (\(4 \times 4 \times 2 - 1\)).

Common Practical Applications

Computing and using gl_LocalInvocationIndex directly serves several primary purposes in compute shaders:

  • Shared Memory Indexing: Flat shared arrays (declared without multidimensional bounds) can be addressed directly with gl_LocalInvocationIndex without manual index arithmetic.
  • Single-Thread Workgroup Initialization: A common pattern is checking if (gl_LocalInvocationIndex == 0u) to execute single-threaded initialization routines or reset workgroup-level variables before calling barrier().
  • Coalesced Memory Access: Mapping linearized 1D compute threads directly to contiguous array elements in Shader Storage Buffer Objects (SSBOs) ensures aligned, coalesced memory access across hardware warps and wavefronts.