From 2dc1f89fb069decb93dbe950fed9665453303550 Mon Sep 17 00:00:00 2001 From: jsmall-nvidia Date: Wed, 26 Aug 2020 14:38:24 -0400 Subject: Added more Atomic support for int64 types on RWByteAddressBuffer (#1515) * Support for more 64 bit atomics on ByteAddressBuffer. * min max 64bit test. * Disable CUDA version of min max 64 bit test - as produces the wrong output. * Update target-compatibility.md with added 64 bit atomics. Co-authored-by: Yong He --- docs/target-compatibility.md | 9 +- source/slang/hlsl.meta.slang | 135 ++++++++++++++++++++- .../atomic-int64-byte-address-buffer.slang | 5 + ...ic-int64-byte-address-buffer.slang.expected.txt | 12 +- .../atomic-min-max-u64-byte-address-buffer.slang | 52 ++++++++ ...-max-u64-byte-address-buffer.slang.expected.txt | 8 ++ 6 files changed, 210 insertions(+), 11 deletions(-) create mode 100644 tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang create mode 100644 tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang.expected.txt diff --git a/docs/target-compatibility.md b/docs/target-compatibility.md index b6954aabe..182aada09 100644 --- a/docs/target-compatibility.md +++ b/docs/target-compatibility.md @@ -182,7 +182,7 @@ For CPU targets there is the IFeedbackTexture interface that requires an impleme ## RWByteAddressBuffer Atomic -Currently feature allows atomic float additions on RWByteAddressBuffer. A future update will broader types supported. There are methods on RWByteAddressBuffer... +The additional supported methods on RWByteAddressBuffer are... ``` void RWByteAddressBuffer::InterlockedAddF32(uint byteAddress, float valueToAdd, out float originalValue); @@ -192,6 +192,13 @@ void RWByteAddressBuffer::InterlockedAddI64(uint byteAddress, int64_t valueToAdd void RWByteAddressBuffer::InterlockedAddI64(uint byteAddress, int64_t valueToAdd); void RWByteAddressBuffer::InterlockedCompareExchangeU64(uint byteAddress, uint64_t compareValue, uint64_t value, out uint64_t outOriginalValue); + +uint64_t RWByteAddressBuffer::InterlockedMaxU64(uint byteAddress, uint64_t value); +uint64_t RWByteAddressBuffer::InterlockedMinU64(uint byteAddress, uint64_t value); + +uint64_t RWByteAddressBuffer::InterlockedAndU64(uint byteAddress, uint64_t value); +uint64_t RWByteAddressBuffer::InterlockedOrU64(uint byteAddress, uint64_t value); +uint64_t RWByteAddressBuffer::InterlockedXorU64(uint byteAddress, uint64_t value); ``` On HLSL based targets this functionality is achieved using [NVAPI](https://developer.nvidia.com/nvapi). For this to work it is necessary to have NVAPI available on your system. The 'prelude' functionality in the Slang API allows for text to be inserted before any Slang code generated code is output. If the input source uses an NVAPI feature - like the methods above - it will output code that *assumes* that `nvHLSLExtns.h` is included. The following code from `render-test-main.cpp` sets up a suitable prelude for HLSL that includes `nvHLSLExtns.h` with an absolute path. diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index 36fe9f216..3a574bc42 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -80,7 +80,7 @@ __glsl_version(430) __glsl_extension(GL_EXT_shader_atomic_int64) int64_t __atomicAdd(__ref int64_t value, int64_t amount); -// CAS - Compare and swap +// Cas - Compare and swap // Helper for HLSL, using NVAPI @@ -92,6 +92,56 @@ __glsl_version(430) __glsl_extension(GL_EXT_shader_atomic_int64) uint64_t __cas(__ref uint64_t ioValue, uint64_t compareValue, uint64_t newValue); +// Max + +__target_intrinsic(hlsl, "NvInterlockedMaxUint64($0, $1, $2)") +uint2 __atomicMax(RWByteAddressBuffer buf, uint offset, uint2 value); + +__target_intrinsic(glsl, "atomicMax($0, $1)") +__glsl_version(430) +__glsl_extension(GL_EXT_shader_atomic_int64) +uint64_t __atomicMax(__ref uint64_t ioValue, uint64_t value); + +// Min + +__target_intrinsic(hlsl, "NvInterlockedMinUint64($0, $1, $2)") +uint2 __atomicMin(RWByteAddressBuffer buf, uint offset, uint2 value); + +__target_intrinsic(glsl, "atomicMin($0, $1)") +__glsl_version(430) +__glsl_extension(GL_EXT_shader_atomic_int64) +uint64_t __atomicMin(__ref uint64_t ioValue, uint64_t value); + +// And + +__target_intrinsic(hlsl, "NvInterlockedAndUint64($0, $1, $2)") +uint2 __atomicAnd(RWByteAddressBuffer buf, uint offset, uint2 value); + +__target_intrinsic(glsl, "atomicAnd($0, $1)") +__glsl_version(430) +__glsl_extension(GL_EXT_shader_atomic_int64) +uint64_t __atomicAnd(__ref uint64_t ioValue, uint64_t value); + +// Or + +__target_intrinsic(hlsl, "NvInterlockedOrUint64($0, $1, $2)") +uint2 __atomicOr(RWByteAddressBuffer buf, uint offset, uint2 value); + +__target_intrinsic(glsl, "atomicOr($0, $1)") +__glsl_version(430) +__glsl_extension(GL_EXT_shader_atomic_int64) +uint64_t __atomicOr(__ref uint64_t ioValue, uint64_t value); + +// Xor + +__target_intrinsic(hlsl, "NvInterlockedXorUint64($0, $1, $2)") +uint2 __atomicXor(RWByteAddressBuffer buf, uint offset, uint2 value); + +__target_intrinsic(glsl, "atomicXor($0, $1)") +__glsl_version(430) +__glsl_extension(GL_EXT_shader_atomic_int64) +uint64_t __atomicXor(__ref uint64_t ioValue, uint64_t value); + // Conversion between uint64_t and uint2 uint2 __asuint2(uint64_t i) @@ -219,7 +269,7 @@ struct $(item.name) } ${{{{ if (item.op == kIROp_HLSLRWByteAddressBufferType) - { + { }}}} // float32 and int64 atomic support. This is a Slang specific extension, it uses @@ -230,7 +280,7 @@ ${{{{ // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_EXT_shader_atomic_float // https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/master/extensions/EXT/SPV_EXT_shader_atomic_float_add.html - // Fp32 Add + // F32 Add __target_intrinsic(hlsl, "($3 = NvInterlockedAddFp32($0, $1, $2))") __cuda_sm_version(2.0) @@ -294,7 +344,7 @@ ${{{{ __atomicAdd(buf[byteAddress / 8], valueToAdd); } - // CAS UInt64 + // Cas uint64_t __target_intrinsic(cuda, "(*$4 = atomicCAS((uint64_t*)$0._getPtrAt($1), $2, $3))") void InterlockedCompareExchangeU64(uint byteAddress, uint64_t compareValue, uint64_t value, out uint64_t outOriginalValue); @@ -312,6 +362,83 @@ ${{{{ outOriginalValue = __cas(buf[byteAddress / 8], compareValue, value); } + // Max + + __cuda_sm_version(3.5) + __target_intrinsic(cuda, "atomicMax((uint64_t*)$0._getPtrAt($1), $2)") + uint64_t InterlockedMaxU64(uint byteAddress, uint64_t value); + + __specialized_for_target(hlsl) + uint64_t InterlockedMaxU64(uint byteAddress, uint64_t value) { return __asuint64(__atomicMax(this, byteAddress, __asuint2(value))); } + + __specialized_for_target(glsl) + uint64_t InterlockedMaxU64(uint byteAddress, uint64_t value) + { + RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); + return __atomicMax(buf[byteAddress / 8], value); + } + + // Min + + __cuda_sm_version(3.5) + __target_intrinsic(cuda, "atomicMin((uint64_t*)$0._getPtrAt($1), $2)") + uint64_t InterlockedMinU64(uint byteAddress, uint64_t value); + + __specialized_for_target(hlsl) + uint64_t InterlockedMinU64(uint byteAddress, uint64_t value) { return __asuint64(__atomicMin(this, byteAddress, __asuint2(value))); } + + __specialized_for_target(glsl) + uint64_t InterlockedMinU64(uint byteAddress, uint64_t value) + { + RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); + return __atomicMin(buf[byteAddress / 8], value); + } + + // And + + __target_intrinsic(cuda, "atomicAnd((uint64_t*)$0._getPtrAt($1), $2)") + uint64_t InterlockedAndU64(uint byteAddress, uint64_t value); + + __specialized_for_target(hlsl) + uint64_t InterlockedAndU64(uint byteAddress, uint64_t value) { return __asuint64(__atomicAnd(this, byteAddress, __asuint2(value))); } + + __specialized_for_target(glsl) + uint64_t InterlockedAndU64(uint byteAddress, uint64_t value) + { + RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); + return __atomicAnd(buf[byteAddress / 8], value); + } + + // Or + + __target_intrinsic(cuda, "atomicOr((uint64_t*)$0._getPtrAt($1), $2)") + uint64_t InterlockedOrU64(uint byteAddress, uint64_t value); + + __specialized_for_target(hlsl) + uint64_t InterlockedOrU64(uint byteAddress, uint64_t value) { return __asuint64(__atomicOr(this, byteAddress, __asuint2(value))); } + + __specialized_for_target(glsl) + uint64_t InterlockedOrU64(uint byteAddress, uint64_t value) + { + RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); + return __atomicOr(buf[byteAddress / 8], value); + } + + // Xor + + __target_intrinsic(cuda, "atomicXor((uint64_t*)$0._getPtrAt($1), $2)") + uint64_t InterlockedXorU64(uint byteAddress, uint64_t value); + + __specialized_for_target(hlsl) + uint64_t InterlockedXorU64(uint byteAddress, uint64_t value) { return __asuint64(__atomicXor(this, byteAddress, __asuint2(value))); } + + __specialized_for_target(glsl) + uint64_t InterlockedXorU64(uint byteAddress, uint64_t value) + { + RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); + return __atomicXor(buf[byteAddress / 8], value); + } + ${{{{ } }}}} diff --git a/tests/slang-extension/atomic-int64-byte-address-buffer.slang b/tests/slang-extension/atomic-int64-byte-address-buffer.slang index 628c675a2..80ce2150c 100644 --- a/tests/slang-extension/atomic-int64-byte-address-buffer.slang +++ b/tests/slang-extension/atomic-int64-byte-address-buffer.slang @@ -28,5 +28,10 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) int anotherIdx = tid >> 2; outputBuffer.InterlockedAddI64(anotherIdx << 3, 3); + + // Bit logical + outputBuffer.InterlockedOrU64((idx << 3), (uint64_t(2) << 32) | (tid << 4)); + outputBuffer.InterlockedXorU64((idx << 3), tid << 8); + outputBuffer.InterlockedAndU64((idx << 3), (uint64_t(tid | 2) << 32) | 0xffffffff); } diff --git a/tests/slang-extension/atomic-int64-byte-address-buffer.slang.expected.txt b/tests/slang-extension/atomic-int64-byte-address-buffer.slang.expected.txt index 811dc1584..67fea32ae 100644 --- a/tests/slang-extension/atomic-int64-byte-address-buffer.slang.expected.txt +++ b/tests/slang-extension/atomic-int64-byte-address-buffer.slang.expected.txt @@ -1,8 +1,8 @@ -10 -1 -12 +F0 3 -14 -5 -16 +F2 +3 +F4 +7 +F6 7 diff --git a/tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang b/tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang new file mode 100644 index 000000000..bfe7fb2a4 --- /dev/null +++ b/tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang @@ -0,0 +1,52 @@ +// No atomic support on CPU +//DISABLE_TEST(compute):COMPARE_COMPUTE_EX:-cpu -compute +// No support for int64_t on DX11 +//DISABLE_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute +// No support for int64_t on fxc - we need SM6.0 and dxil +// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/hlsl-shader-model-6-0-features-for-direct3d-12 +//DISABLE_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 -nvapi-slot u0 +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 -profile cs_6_0 -use-dxil -render-features atomic-int64 -nvapi-slot u0 -compile-arg -O2 +//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute -render-features atomic-int64 +// For some reason this doesn't work correctly on CUDA? That it behaves as if always does Min. Min and Max do appropriate +// things tho, because if I force the condition I do get the right answer +//DISABLE_TEST(compute):COMPARE_COMPUTE_EX:-cuda -compute + +// The test doesn't directly use this, but having this defined makes the 0 slot available if NVAPI is going to be used +// Only strictly necessary on the D3D12 path +//TEST_INPUT:ubuffer(data=[0 0 0 0 ], stride=4):name=nvapiBuffer +RWStructuredBuffer nvapiBuffer; + +//TEST_INPUT:ubuffer(data=[2 2 2 2 2 2 2 2], stride=4):out,name=outputBuffer +RWByteAddressBuffer outputBuffer; + +//TEST_INPUT:ubuffer(data=[0 1 2 3 4 5 6 7], stride=4):name=inputBuffer +RWStructuredBuffer inputBuffer; + +[numthreads(16, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + + // Produces a different result on CUDA? + + uint64_t value; + { + int idx = tid & 3; + // Do 64 bit load that works on CUDA + value = (uint64_t(inputBuffer[idx * 2 + 1]) << 32) | inputBuffer[idx * 2]; + } + + { + int idx = (tid & 3) ^ (tid >> 2); + if (idx & 1) + { + outputBuffer.InterlockedMaxU64((idx << 3), value); + } + else + { + outputBuffer.InterlockedMinU64((idx << 3), value); + } + } +} + + diff --git a/tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang.expected.txt b/tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang.expected.txt new file mode 100644 index 000000000..d33a683ed --- /dev/null +++ b/tests/slang-extension/atomic-min-max-u64-byte-address-buffer.slang.expected.txt @@ -0,0 +1,8 @@ +0 +1 +6 +7 +0 +1 +6 +7 -- cgit v1.2.3