Understanding subgroupBallotInclusiveBitCount in GLSL
This article provides a direct explanation of the
subgroupBallotInclusiveBitCount function in GLSL, covering
its exact computation, how it handles invocation masks, and its role in
shader programming. In modern graphics and compute pipelines, subgroup
operations allow threads within the same SIMD unit to communicate
efficiently without accessing shared memory. This guide focuses on how
subgroupBallotInclusiveBitCount calculates population
counts up to and including the calling invocation, demonstrating its
behavior and common use cases like prefix sums and parallel stream
compaction.
What
subgroupBallotInclusiveBitCount Computes
In GLSL (specifically via the
GL_KHR_shader_subgroup_ballot or Vulkan subgroup
extensions), subgroupBallotInclusiveBitCount(uvec4 value)
computes the number of bits set to 1 in the provided
value bitmask from bit index 0 up to and
including the current invocation's index
(gl_SubgroupInvocationID).
The ballot bitmask is represented as a 128-bit vector
(uvec4), where each bit corresponds to an invocation in the
subgroup:
- Bit
0corresponds to invocation index0. - Bit
ncorresponds to invocation indexn.
The Computation Process
When an invocation calls
subgroupBallotInclusiveBitCount(value):
- Mask Generation: The hardware constructs an
internal mask where all bit indices from
0togl_SubgroupInvocationIDare set to1, and all bits with indices greater thangl_SubgroupInvocationIDare set to0. - Bitwise AND: This internal mask is logically ANDed
with the input
uvec4 value. - Population Count (popcount): The function returns
the total number of set bits (
1s) remaining after the masking operation.
Because this operation is inclusive, if the current
invocation's own bit in value is 1, it is
counted in the final result.
Inclusive vs. Exclusive Comparison
To understand the inclusive count clearly, consider the difference between the two standard bit count functions:
subgroupBallotInclusiveBitCount(value): Counts bits set to1in the range[0, gl_SubgroupInvocationID].subgroupBallotExclusiveBitCount(value): Counts bits set to1in the range[0, gl_SubgroupInvocationID - 1].
If a ballot mask has bits set at indices 0, 2, and 3:
Invocation ID
(gl_SubgroupInvocationID) |
Bit in Ballot | Inclusive Count | Exclusive Count |
|---|---|---|---|
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 1 | 3 | 2 |
Typical Use Case: Stream Compaction
The primary use case for subgroupBallotInclusiveBitCount
is determining the target index when compacting data across a
subgroup:
// Evaluate a condition across all invocations
bool condition = localData > threshold;
// Get a bitmask of all invocations where condition is true
uvec4 ballot = subgroupBallot(condition);
if (condition) {
// Determine the 1-based output rank within this subgroup
uint rank = subgroupBallotInclusiveBitCount(ballot);
// Convert to a 0-based index to write out compacted data
uint outputIndex = baseOffset + (rank - 1);
outputBuffer[outputIndex] = localData;
}Using the inclusive variant provides the 1-based rank of the current invocation among active threads, which directly informs how many total elements will be written by threads up to that point.