Understanding subgroupMax in GLSL Subgroups

In GLSL, the subgroupMax intrinsic computes the highest numerical value of a specified expression across all currently active invocations in a single execution subgroup. This article explains how subgroupMax functions within the execution model, how it handles active versus inactive invocations, its data type support, and its performance advantages in modern GPU programming.

Core Evaluation Behavior

The subgroupMax function is defined as part of the GL_KHR_shader_subgroup_arithmetic extension and modern Vulkan/OpenGL subgroup operations. When an invocation calls subgroupMax(value), the GPU evaluates the value passed by each invocation within the subgroup, finds the maximum among them, and returns this identical maximum result to all participating invocations.

A subgroup corresponds directly to the underlying hardware execution unit, such as an AMD wavefront or an NVIDIA warp. Because the operation is executed in lockstep at the hardware level, it does not require explicit synchronization barriers or shared memory (shared variables) to share data across invocations.

Active vs. Inactive Invocations

The critical operational detail of subgroupMax is that it evaluates only across active invocations:

  • Active Invocations: An invocation is active if it is currently executing the instruction stream containing the subgroupMax call. If an invocation reaches the function, its local argument is included in the reduction.
  • Inactive Invocations: Invocations that are masked out due to control flow divergence (such as an if branch that only some threads take) or invocations that have already exited do not participate. The values from these inactive invocations are completely ignored and do not affect the calculated maximum.
  • Uniform Results: All active invocations in the subgroup receive the exact same maximum value. Inactive invocations do not receive a result because they are not actively executing the instruction.

If a subgroup contains invocations with values [2, 8, 14, 5] and the invocation holding 14 is inactive due to branching, subgroupMax evaluates only [2, 8, 5] and returns 8 to the three active invocations.

Supported Types and Vector Operations

The subgroupMax function supports basic numerical types:

  • Scalars: Signed integers (int), unsigned integers (uint), and floating-point values (float, double, and 16-bit floats where supported).
  • Vectors: Vector equivalents of supported scalar types (such as vec2, ivec4, uvec3).

When invoked on vector types, subgroupMax operates component-wise. For example, when evaluating a vec2, the maximum x component across all active invocations is determined independently of the maximum y component across those same invocations.

Floating-Point and Edge Cases

For floating-point values, subgroupMax generally adheres to IEEE-754 semantics as implemented by the underlying GPU hardware:

  • If comparing positive and negative zero (+0.0 and -0.0), implementations generally treat them as equal, though returning +0.0 is standard.
  • Handling of NaN (Not a Number) depends on the hardware target and SPIR-V execution modes, but standard graphics rules typically treat NaN inputs such that if one operand is NaN, the non-NaN value is preferred, preventing an invalid result from polluting the active subgroup.