Atomic Type
Add a new type, atomic<T>, to WGSL. For MVP, T must be either u32 or i32. Atomics may only be placed in variables in the workgroup or storage storage classes. Atomics may only be operated on by atomic builtin functions.
Translation
In both HLSL and SPIR-V, atomic<T> should be translated the same as T. In MSL, atomic<T> should translate as volatile atomic_uint or volatile atomic_int.
Atomic Functions
Add the following builtin functions:
atomicAdd(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
atomicMax(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
atomicMin(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
atomicAnd(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
atomicOr(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
atomicXor(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
atomicExchange(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
In all the above cases, the return value is the original value in the atomic prior to the operation.
Also add compare exchange:
atomicCompareExchangeWeak(atomic_ptr : ptr<SC, atomic<T>>, expected : T, value : T) -> vec2<T>
Compare exchange returns a vec2 where the first element is the original value in the atomic and the second value is whether or not the comparison was successful (1 for success, 0 otherwise). This uses the weak version of compare exchange which means that the comparison can fail spuriously (i.e. even if expected matches the original value).
The memory ordering for all atomic functions is relaxed (see section 29.3 of the C++14 specification). This means that no synchronization or ordering guarantees are made on non-atomic memory accesses, nor on atomic memory accesses operating on different memory addresses (i.e. unless they are mutually ordered atomics).
For all atomic functions, the storage class of atomic_ptr must be workgroup or storage. Atomics on storage storage class are device atomics (e.g. Device memory scope in SPIR-V), while atomics in workgroup storage class are workgroup atomics (e.g. shared variable atomics in HLSL).
Atomic functions must not be used in vertex shaders.
Example:
[[block]] struct A {
atomic<u32> a;
array<u32> rta;
};
var<storage> myVar : A;
// ...
const old : u32 = atomicAdd(myVar.a, 1);
Translation
| WGSL |
SPIR-V |
HLSL |
MSL |
GLES |
atomicAdd |
OpAtomicIAdd |
InterlockedAdd |
atomic_fetch_add_explicit |
atomicAdd |
atomicMax |
OpAtomic[S|U]Max1 |
InterlockedMax |
atomic_fetch_max_explicit |
atomicMax |
atomicMin |
OpAtomic[S|U]Min1 |
InterlockedMin |
atomic_fetch_min_explicit |
atomicMin |
atomicAnd |
OpAtomicAnd |
InterlockedAnd |
atomic_fetch_and_explicit |
atomicAnd |
atomicOr |
OpAtomicOr |
InterlockedOr |
atomic_fetch_or_explicit |
atomicOr |
atomicXor |
OpAtomicXor |
InterlockedXor |
atomic_fetch_xor_explicit |
atomicXor |
atomicExchange |
OpAtomicExchange |
InterlockedExchange |
atomic_exchange_explicit |
atomicExchange |
atomicCompareExchangeWeak |
OpAtomicCompareExchange2 |
InterlockedCompareExchange3 |
atomic_compare_exchange_weak_explicit |
atomicCompSwap |
- Signedness is based on
T.
- SPIR-V only provides the strong compare exchange.
- If used in a loop, the loop must be annotated with the attribute
[allow_uav_condition]
Considerations
The new type favours translations from WGSL to other dialects (as opposed to translation to WGSL). MSL requires C++ style atomic types. If just i32 or u32 were used, then the declaration of the variable would need to be modified by its usage, which should generally be avoided. One implication of this choice is that not all HLSL or SPIR-V programs will be convertible to WGSL as those languages support atomic operations on regular memory addresses (no special types). One example is if the SPIR-V or HLSL atomic operates on 32-bit integer in a vector. Translating to WGSL (from SPIR-V and HLSL) has the problem avoided in avoid translating from WGSL to MSL (the use must modify the declaration).
Survey of Implementations
Metal (MSL)
Provides atomic_int and atomic_uint types, so 32-bit integer atomics only. Atomic functions only support memory_order_relaxed (no synchronization with other memory operations). Atomic functions are defined in <metal_atomic>. These functions are not limited by the shader stage.
Metal 1.0 supports the following functions for volatile threadgroup (workgroup) and device atomic memory objects:
- Atomic_store_explicit
- Atomic_load_explicit
- Atomic_exchange_explicit
- Atomic_compare_exchange_weak_explicit
- Atomic_fetch_key_explicit
- And, or, add, max, min, sub, xor
Signedness based on memory type
Signed versions use two’s complement with silent wrap around (i.e. same as SPIR-V)
Metal 2.0 added support for non-volatile versions of the atomic functions.
The biggest difference from SPIR-V is that atomic types are required. Atomic operations cannot be performed on any memory object. MSL also does not support atomic image operations (i.e. no atomics through OpImageTexelPointer).
DirectX (HLSL)
HLSL supports interlocked (atomic) functions on int and uint data types since Shader Model 5. HLSL also supports atomic exchanges on any scalar data type. There are no restrictions on shader stage for any of the functions. Atomics can be performed on shared (workgroup) or resource (storage and texture) variables. All atomic operations use relaxed memory ordering.
The following functions are supported:
- InterlockedAdd
- InterlockedAnd
- InterlockedCompareExchange
- When used in a loop, the loop must be annotated with [allow_uav_condition] (in compute shaders)
- Unclear if this is a strong or weak compare exchange
- InterlockedCompareStore
- Could be mapped to compare exchange
- InterlockedExchange
- InterlockedMax
- InterlockedMin
- InterlockedOr
- InterlockedXor
Vulkan (SPIR-V)
Vulkan supports 32-bit integer atomics in Vulkan 1.0. There are extensions to support 64-bit integer atomics and float/half/double atomics (load, store, exchange, min, max, add). Vulkan also supports integer atomic operations on texel components (via OpImageTexelPointer). The memory scope for atomics must be Workgroup or Device (QueueFamily). The storage class of pointers must be: Workgroup, StorageBuffer (and PhysicalStorageBuffer), Uniform (BufferBlock) or Image. Vulkan supports atomics in compute shaders unconditionally, but requires feature bits (fragmentStoresAndAtomics and vertexStoresAndAtomics) to work in Fragment and Vertex shaders. WebGPU currently forbids vertexStoresAndAtomics.
Vulkan supports the following operations:
- OpAtomicLoad
- OpAtomicStore
- OpAtomicExchange
- OpAtomicCompareExchange
- OpAtomicIIncrement
- OpAtomicIDecrement
- OpAtomic[I|F]Add
- OpAtomicISub
- OpAtomic[U|S|F]Min
- OpAtomic[U|S|F]Max
- OpAtomicAnd
- OpAtomicOr
- OpAtomicXor
Weak Compare Exchange
The weak version of atomic compare exchange may fail spuriously. That is, the comparison may fail even if the expected value matches the value in the atomic memory location. The weak version should generally be used when the cost of failure is low and be put into a loop until it succeeds. The cost of the strong version is much higher than the weak version on some systems and should be used for single tests.
Atomic Type
Add a new type,
atomic<T>, to WGSL. For MVP,Tmust be eitheru32ori32. Atomics may only be placed in variables in theworkgrouporstoragestorage classes. Atomics may only be operated on by atomic builtin functions.Translation
In both HLSL and SPIR-V,
atomic<T>should be translated the same asT. In MSL,atomic<T>should translate asvolatile atomic_uintorvolatile atomic_int.Atomic Functions
Add the following builtin functions:
atomicAdd(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> TatomicMax(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> TatomicMin(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> TatomicAnd(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> TatomicOr(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> TatomicXor(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> TatomicExchange(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> TIn all the above cases, the return value is the original value in the atomic prior to the operation.
Also add compare exchange:
atomicCompareExchangeWeak(atomic_ptr : ptr<SC, atomic<T>>, expected : T, value : T) -> vec2<T>Compare exchange returns a vec2 where the first element is the original value in the atomic and the second value is whether or not the comparison was successful (1 for success, 0 otherwise). This uses the weak version of compare exchange which means that the comparison can fail spuriously (i.e. even if
expectedmatches the original value).The memory ordering for all atomic functions is relaxed (see section 29.3 of the C++14 specification). This means that no synchronization or ordering guarantees are made on non-atomic memory accesses, nor on atomic memory accesses operating on different memory addresses (i.e. unless they are mutually ordered atomics).
For all atomic functions, the storage class of
atomic_ptrmust beworkgrouporstorage. Atomics onstoragestorage class are device atomics (e.g.Devicememory scope in SPIR-V), while atomics inworkgroupstorage class are workgroup atomics (e.g. shared variable atomics in HLSL).Atomic functions must not be used in
vertexshaders.Example:
Translation
atomicAddOpAtomicIAddInterlockedAddatomic_fetch_add_explicitatomicAddatomicMaxOpAtomic[S|U]Max1InterlockedMaxatomic_fetch_max_explicitatomicMaxatomicMinOpAtomic[S|U]Min1InterlockedMinatomic_fetch_min_explicitatomicMinatomicAndOpAtomicAndInterlockedAndatomic_fetch_and_explicitatomicAndatomicOrOpAtomicOrInterlockedOratomic_fetch_or_explicitatomicOratomicXorOpAtomicXorInterlockedXoratomic_fetch_xor_explicitatomicXoratomicExchangeOpAtomicExchangeInterlockedExchangeatomic_exchange_explicitatomicExchangeatomicCompareExchangeWeakOpAtomicCompareExchange2InterlockedCompareExchange3atomic_compare_exchange_weak_explicitatomicCompSwapT.[allow_uav_condition]Considerations
The new type favours translations from WGSL to other dialects (as opposed to translation to WGSL). MSL requires C++ style atomic types. If just
i32oru32were used, then the declaration of the variable would need to be modified by its usage, which should generally be avoided. One implication of this choice is that not all HLSL or SPIR-V programs will be convertible to WGSL as those languages support atomic operations on regular memory addresses (no special types). One example is if the SPIR-V or HLSL atomic operates on 32-bit integer in a vector. Translating to WGSL (from SPIR-V and HLSL) has the problem avoided in avoid translating from WGSL to MSL (the use must modify the declaration).Survey of Implementations
Metal (MSL)
Provides atomic_int and atomic_uint types, so 32-bit integer atomics only. Atomic functions only support memory_order_relaxed (no synchronization with other memory operations). Atomic functions are defined in <metal_atomic>. These functions are not limited by the shader stage.
Metal 1.0 supports the following functions for volatile threadgroup (workgroup) and device atomic memory objects:
Signedness based on memory type
Signed versions use two’s complement with silent wrap around (i.e. same as SPIR-V)
Metal 2.0 added support for non-volatile versions of the atomic functions.
The biggest difference from SPIR-V is that atomic types are required. Atomic operations cannot be performed on any memory object. MSL also does not support atomic image operations (i.e. no atomics through OpImageTexelPointer).
DirectX (HLSL)
HLSL supports interlocked (atomic) functions on int and uint data types since Shader Model 5. HLSL also supports atomic exchanges on any scalar data type. There are no restrictions on shader stage for any of the functions. Atomics can be performed on shared (workgroup) or resource (storage and texture) variables. All atomic operations use relaxed memory ordering.
The following functions are supported:
Vulkan (SPIR-V)
Vulkan supports 32-bit integer atomics in Vulkan 1.0. There are extensions to support 64-bit integer atomics and float/half/double atomics (load, store, exchange, min, max, add). Vulkan also supports integer atomic operations on texel components (via OpImageTexelPointer). The memory scope for atomics must be Workgroup or Device (QueueFamily). The storage class of pointers must be: Workgroup, StorageBuffer (and PhysicalStorageBuffer), Uniform (BufferBlock) or Image. Vulkan supports atomics in compute shaders unconditionally, but requires feature bits (fragmentStoresAndAtomics and vertexStoresAndAtomics) to work in Fragment and Vertex shaders. WebGPU currently forbids vertexStoresAndAtomics.
Vulkan supports the following operations:
Weak Compare Exchange
The weak version of atomic compare exchange may fail spuriously. That is, the comparison may fail even if the expected value matches the value in the atomic memory location. The weak version should generally be used when the cost of failure is low and be put into a loop until it succeeds. The cost of the strong version is much higher than the weak version on some systems and should be used for single tests.