Dispatch IDs and you
Do you frequently find yourself struggling to remember how the various built-in compute shader semantic values work by glancing at the image below?
You can find this image on the various SV_*
Win32 documentation pages such as this one.
It’s easy to get confused as to when to use which semantic value if you’re not used to it, so here’s how I remember things.
The main thing to remember is that both SV_GroupID
and SV_GroupIndex
are poorly named, or at the very least, are inconsistently named.
After we examine the different compute shader semantics available, you’ll see what I mean (or you might not, this is just how I tend to think of things).
You’re also welcome to skip to the summary table below.
Compute shader semantics
There are three IDs we need to consider:
SV_DispatchThreadID
SV_GroupThreadID
SV_GroupID
Recall that compute dispatches are specified in a two-level hierarchy (ignoring the notion of WaveSize
for now).
First, threads are grouped into, well, groups. The size of these groups is given by the three arguments to the numthreads
attribute attached to your compute shader entry point.
The groups themselves are launched on the host using a call to Dispatch
, which form the next layer of the hierarchy.
The number of threads in a group is independent of the number of groups dispatched.
So, if you had an entry function declaration like so:
[numthreads(5, 5, 1)]
void main();
the group size would be 25 threads in total, spanning a 5x5x1 conceptual volume, where each cell in that volume is a separate thread or invocation.
The address of a thread within a group is given by an SV_GroupThreadID
value, which is a uint3
as you might expect.
Crucially, the SV_GroupThreadID
has nothing to do with how many groups are present in a given dispatch.
In contrast, SV_DispatchThreadID
is also a “thread ID” so we expect the value to identify a thread somehow.
The prefix Dispatch
here gives us a hint. Whereas the SV_GroupThreadID
identifies a thread within a group scope,
the SV_DispatchThreadID
instead identifies a thread across the entire dispatch.
That is, if we invoked Dispatch(3, 2, 4)
with the entry point above, we would be dispatching a total of $3 \times 2\times 4 = 24$ groups total,
for a total of $24 * 25 = 600$ total threads, and the SV_DispatchThreadID
will thus take on 600 unique values while the shader executes.
As with threads within a group, groups within a dispatch are addressed using 3 coordinates, so combining the group address and the address of a thread
within a group, it should be clear why an SV_DispatchThreadID
has a uint3
type, just like the SV_GroupThreadID
does.
This brings us to the SV_GroupID
value. As you might expect, unlike the SV_*ThreadID
values which identify threads within some scope,
the SV_GroupID
identifies a group instead. Here, the scope is omitted because only one scope is really possible.
Namely, the SV_GroupID
value identifies a group within the dispatch. Given the example again of invoking Dispatch(3, 2, 4)
, the SV_GroupID
would
take on uint3
values spanning the 24-cell volume of groups (each containing a 5x5x1 set of threads). The reason I dislike the name
personally is because while the scope of the ID is unambiguous (it has to be a dispatch after all), I would have preferred SV_DispatchGroupID
to
mirror the naming convention used for the other two values we’ve seen so far. If you have trouble remembering which is which, it might be helpful
to think of SV_GroupID
as SV_DispatchGroupID
instead to recover the logical consistency with the other terms.
With those terms out of the way, we arrive at the last value to remember:SV_GroupIndex
. Unlike SV_GroupID
, whose values are mapped to groups,
SV_GroupIndex
values are mapped to threads. Confusing, right? Specifically, SV_GroupIndex
values map to “threads within a group”.
Given the main function above with attribute numthreads(5, 5, 1)
, SV_GroupID
would take on values from 0 to 24 inclusive.
The naming of this value is particularly egregious, because while the index describes “threads”, the term “thread” doesn’t appear in the name itself.
Note that there is no SV_DispatchIndex
value which would hypothetically be a flattened thread index unique across the entire dispatch.
This value is sometimes useful in compute shaders, and you generally compute it yourself by flattening an SV_DispatchThreadID
.
Summary
To summarize, here’s a table with the terms in the leftmost column, a description in the center column, and what I think they should have been named on the rightmost column. The suggested renamings are mainly here as a mnemonic device, and in some cases as discussed, the original names are fine.
Semantic | Description | Preferred spelling |
---|---|---|
SV_DispatchThreadID |
Unique uint3 ID of a thread within the dispatch |
SV_DispatchThreadID |
SV_GroupThreadID |
Unique uint3 ID of a thread within a group |
SV_GroupThreadID |
SV_GroupID |
Unique uint3 ID of a group within a dispatch |
SV_DispatchGroupID |
SV_GroupIndex |
Unique uint index of a thread within a group |
SV_GroupThreadIndex |
Doesn’t exist | Unique uint index of a thread within a dispatch |
SV_DispatchThreadIndex |