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: subgroupExclusiveAdd is the standard choice for calculating array offsets. When filtering elements, each invocation sets a flag of 1 (keep) or 0 (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: subgroupInclusiveAdd is 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.