Builtin Library
README.md
gpuSort
A stable least-significant-digit radix sort, run entirely on the GPU, over a caller-owned array of u32 keys.
The three passes here are the body of one radix digit. They read a key array and a permutation and write a new permutation; they never touch whatever the keys describe, so any subsystem with a key per item can drive them. The caller supplies the keys, the identity seed, the per-pass constants and the dispatch sequence.
One pass
| Order | Shader | Dispatch |
|---|---|---|
| 1 | radix_hist | one workgroup of 256 per 256 items |
| 2 | radix_scan | { 1, 1, 1 } |
| 3 | radix_scatter | one workgroup of 256 per 256 items |
Each pass sorts on one 8-bit digit and keeps the relative order of items sharing
that digit, so ceil(bits / 8) passes over the low digits upward compose into a
total order on (key, item index).
Driving it
The caller owns six buffers: the keys, two permutation buffers the passes
ping-pong between, the digit histogram (ceil(count / 256) * 256 entries) and
one constants buffer per pass. A pass reads its constants as
| Word | Meaning |
|---|---|
[0] | item count |
[1] | block count, ceil(count / 256) |
[2] | this pass's digit shift — 0, 8, 16, 24 |
[3] | 1 on the last pass, 0 otherwise |
[4] | the key a dropped item carries |
Every value is fixed for the life of the sorted array, so the constants are written once rather than re-uploaded per frame.
Each pass flips which buffer holds the permutation, so the parity of the pass count decides where the caller seeds the identity for the last scatter to land in the buffer the consumer reads.
An item the caller wants dropped takes a key at or past [4] — one past the
last real key — which parks it in the tail of the permutation; the last pass
resolves it to 0xffffffff, and the consumer reads that as "this slot draws
nothing". A fixed dispatch size with a degenerate tail is what lets a consumer
issue a constant draw with no read-back and no indirect arguments.