How SV_GroupIndex Linearizes 3D Coordinates in DirectX
In DirectX High Level Shading Language (HLSL),
SV_GroupIndex is a system-generated value that flattens a
thread's three-dimensional coordinate within a thread group into a
single, one-dimensional scalar index. This article explains the
mathematical formula DirectX uses to linearize these 3D coordinates, how
the layout is determined by the numthreads attribute, and
how to utilize this index for efficient resource access such as
groupshared memory.
The Thread Group Context
When writing a compute shader in HLSL, the dimensions of a thread
group are defined using the [numthreads(X, Y, Z)]
attribute. Within this group, each individual thread has a unique 3D
position represented by the SV_GroupThreadID semantic, with
components ranging as follows:
- \(0 \le x < X\)
- \(0 \le y < Y\)
- \(0 \le z < Z\)
While working with 3D coordinates is intuitive for volumetric
calculations, image processing, or spatial partitioning, hardware
architectures and data buffers operate linearly. DirectX provides
SV_GroupIndex to bridge this gap, mapping the
multi-dimensional coordinates directly to an integer ranging from \(0\) to \((X
\times Y \times Z) - 1\).
The Linearization Formula
DirectX linearizes 3D thread coordinates using row-major ordering (lexicographical order), meaning that the \(X\) dimension increments fastest, followed by \(Y\), and finally \(Z\).
The formula used by the runtime is:
\[\text{SV\_GroupIndex} = z \times (X \times Y) + y \times X + x\]
Where:
- \(x, y, z\) are the components of
the current thread's
SV_GroupThreadID. - \(X, Y\) are the group dimensions
defined in
[numthreads(X, Y, Z)]. - The \(Z\) dimension size determines the upper bound but is not directly used to compute intermediate strides.
Practical Example
Consider a compute shader configured with the following attribute:
[numthreads(4, 4, 2)]
Here, the dimensions are \(X = 4\), \(Y = 4\), and \(Z = 2\), yielding a total of \(32\) threads per group.
If a thread has an SV_GroupThreadID of
(2, 3, 1):
- Calculate the layer offset for the Z dimension: \(1 \times (4 \times 4) = 16\)
- Calculate the row offset for the Y dimension: \(3 \times 4 = 12\)
- Add the column offset for the X dimension: \(2\)
- Sum the offsets: \(16 + 12 + 2 = 30\)
The thread with SV_GroupThreadID(2, 3, 1) receives an
SV_GroupIndex of 30.
Primary Applications of SV_GroupIndex
The linearized index is primarily used for:
- Groupshared Memory Indexing:
groupsharedmemory is allocated locally per thread group. Linear arrays in groupshared memory are declared with a single size (e.g.,groupshared float cache[32]), andSV_GroupIndexprovides a 1:1 mapping for threads to read from or write to these arrays without manual index calculation. - Cooperative Reductions: Algorithms like parallel reduction or prefix sums require power-of-two thread stepping, which is far simpler to compute using a 1D sequence than a 3D coordinate space.
- Execution Masking: When a single thread within a
group needs to initialize data or execute a scalar operation, checking
if (SV_GroupIndex == 0)ensures only one thread runs the block regardless of group geometry.