subgroupInclusiveAdd vs subgroupExclusiveAdd in GLSL
In GLSL, both subgroupInclusiveAdd and
subgroupExclusiveAdd perform prefix-sum (scan) additions
across active invocations within a subgroup, but they differ
fundamentally in whether the calling invocation's own value is included
in the final sum. While subgroupInclusiveAdd computes the
cumulative sum up to and including the current invocation,
subgroupExclusiveAdd calculates the cumulative sum of all
preceding invocations, omitting the current invocation's input.
Understanding this distinction is essential for optimizing parallel
algorithms such as stream compaction, dynamic resource allocation, and
parallel reduction on modern GPUs.
How Prefix Sums Work in Subgroups
A subgroup represents a collection of invocations (threads) executing
concurrently on a single compute unit (similar to a warp in CUDA or a
wavefront in AMD architectures). Invocations within a subgroup are
ordered by an index known as gl_SubgroupInvocationID.
When performing parallel prefix sums across these invocations, each invocation supplies a value, and the hardware computes a running sum based on invocation order.
subgroupInclusiveAdd
The subgroupInclusiveAdd function computes the sum of
the value provided by the current invocation plus the values of all
preceding active invocations in the subgroup.
Mathematically, for invocation \(i\), the inclusive sum is: \[\text{Result}_i = \sum_{k=0}^{i} \text{Value}_k\]
Example: Assume four active invocations with the following inputs:
- Invocations:
[0, 1, 2, 3] - Values:
[3, 1, 4, 2]
Applying subgroupInclusiveAdd yields:
- Invocation 0:
3(3) - Invocation 1:
4(3 + 1) - Invocation 2:
8(3 + 1 + 4) - Invocation 3:
10(3 + 1 + 4 + 2)
subgroupExclusiveAdd
The subgroupExclusiveAdd function calculates the sum of
all preceding active invocations, strictly excluding the calling
invocation's own value. For the first invocation (index 0), the result
is always the additive identity (0).
Mathematically, for invocation \(i\), the exclusive sum is: \[\text{Result}_i = \sum_{k=0}^{i-1} \text{Value}_k\] (where the sum equals 0 when \(i = 0\))
Example: Using the same four invocations:
- Invocations:
[0, 1, 2, 3] - Values:
[3, 1, 4, 2]
Applying subgroupExclusiveAdd yields:
- Invocation 0:
0(Identity / no preceding elements) - Invocation 1:
3(3) - Invocation 2:
4(3 + 1) - Invocation 3:
8(3 + 1 + 4)
Key Differences Summary
| Feature | subgroupInclusiveAdd |
subgroupExclusiveAdd |
|---|---|---|
| Current Value Included? | Yes | No |
| First Invocation Result | Its own value | 0 (Additive Identity) |
| Last Invocation Result | Total sum of all invocations | Total sum excluding the last value |
| Primary Use Case | Cumulative metrics, bounding checks | Memory offsets, stream compaction |
Common Use Cases
- Stream Compaction and Index Allocation:
subgroupExclusiveAddis the standard choice for calculating array offsets. When filtering elements, each invocation sets a flag of1(keep) or0(discard). An exclusive add provides the exact zero-based destination index where each valid invocation can write its output without memory collisions. - Running Metrics and Thresholds:
subgroupInclusiveAddis commonly used when an invocation needs to know if the cumulative threshold has been met immediately upon considering its own workload, or when computing cumulative distribution functions directly.