How subgroupBallotBitCount Counts Bits in GLSL

This article explains how the subgroupBallotBitCount intrinsic function in GLSL evaluates a subgroup ballot to count active invocations. It covers the structure of the uvec4 ballot bitmask, the underlying population count operation executed across its components, and how this differs from prefix-based ballot counting functions.

Understanding the Subgroup Ballot Representation

In GLSL, subgroup ballot operations return a uvec4—a vector of four 32-bit unsigned integers, providing up to 128 bits. Each bit in this bitmask corresponds to a specific invocation within the subgroup, ordered by gl_SubgroupInvocationID.

When an invocation evaluates a condition using subgroupBallot(predicate), the hardware sets the bit corresponding to that invocation's index to 1 if the predicate is true and the invocation is active; otherwise, it is set to 0. If a subgroup has fewer than 128 invocations, the unused higher-order bits are set to 0.

The Bit Counting Mechanism

The subgroupBallotBitCount(uvec4 value) function takes an arbitrary ballot vector and returns the total number of set bits (1s) contained within it. The counting process functions as follows:

  1. Component-Wise Population Count: The hardware executes a population count (popcnt) operation on each 32-bit component of the uvec4 vector (value.x, value.y, value.z, and value.w). The population count determines the number of non-zero bits within each individual 32-bit integer.
  2. Summation: The counts from all four components are summed together into a single scalar uint.

Conceptually, the function evaluates to:

uint totalBits = bitCount(value.x) + bitCount(value.y) + bitCount(value.z) + bitCount(value.w);

Most modern GPUs implement dedicated hardware instructions for population counting, allowing the GPU to determine the number of set bits per 32-bit word in one or very few clock cycles.

Scope and Behavior

  • Global to the Ballot: Unlike subgroupBallotInclusiveBitCount and subgroupBallotExclusiveBitCount, which only sum bits up to the caller's gl_SubgroupInvocationID, subgroupBallotBitCount counts all active bits across the entire 128-bit mask regardless of which invocation calls it.
  • Uniform Result: If every invocation in a subgroup passes the same uvec4 ballot to subgroupBallotBitCount, every invocation receives the exact same return value.
  • Arbitrary Inputs: While commonly used on the result of subgroupBallot(), the function works on any valid uvec4 bitmask, functioning as a 128-bit population count utility.