From 4804753d4a2ec389cc6ecd759f7ea712848fddf0 Mon Sep 17 00:00:00 2001 From: jsmall-nvidia Date: Mon, 24 Aug 2020 15:23:40 -0400 Subject: RWByteAddressBuffer::InterlockedCompareExchangeU64 (#1513) * First pass at incorporating nvapi into test harness. * D3d12 Atomic Float Add via NVAPI working * Dx12 atomic float appears to work. * Atomic float add on Dx12. * Added atomic64 feature addition to vk. Fix correct output for atomic-float-byte-address.slang * Disable atomic float failing tests. * Upgraded VK headers. * Detect atomic float availability on VK. * Try to get test working for in64 atomic. * Made HLSL prelude controlled via the render-test requirements. * Added -enable-nvapi to premake. * Fix D3D12Renderer when NVAPI is not available. * Small improvements to VKRenderer. * Improve atomic documentation in target-compatibility.md. * Fixed NVAPI working on D3D12. * Test for specific NVAPI features. * Remove requiredFeatures from Renderer::Desc as was ignored. Tried to document more around nvapiExtnSlot. * Readded requiredFeatures to Renderer::Desc * Improve comments in the tests. * Rename Fp32 -> F32 Added cas-int64-byte-address-buffer.slang test Co-authored-by: Tim Foley --- docs/target-compatibility.md | 6 +- source/slang/hlsl.meta.slang | 77 ++++++++++++++++------ .../atomic-float-byte-address-buffer-cross.slang | 4 +- ...omic-float-byte-address-buffer-cross.slang.glsl | 8 +-- .../atomic-float-byte-address-buffer.slang | 6 +- .../cas-int64-byte-address-buffer.slang | 45 +++++++++++++ ...as-int64-byte-address-buffer.slang.expected.txt | 8 +++ 7 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 tests/slang-extension/cas-int64-byte-address-buffer.slang create mode 100644 tests/slang-extension/cas-int64-byte-address-buffer.slang.expected.txt diff --git a/docs/target-compatibility.md b/docs/target-compatibility.md index f5b402af3..b6954aabe 100644 --- a/docs/target-compatibility.md +++ b/docs/target-compatibility.md @@ -185,11 +185,13 @@ For CPU targets there is the IFeedbackTexture interface that requires an impleme Currently feature allows atomic float additions on RWByteAddressBuffer. A future update will broader types supported. There are methods on RWByteAddressBuffer... ``` -void RWByteAddressBuffer::InterlockedAddFp32(uint byteAddress, float valueToAdd, out float originalValue); -void RWByteAddressBuffer::InterlockedAddFp32(uint byteAddress, float valueToAdd); +void RWByteAddressBuffer::InterlockedAddF32(uint byteAddress, float valueToAdd, out float originalValue); +void RWByteAddressBuffer::InterlockedAddF32(uint byteAddress, float valueToAdd); void RWByteAddressBuffer::InterlockedAddI64(uint byteAddress, int64_t valueToAdd, out int64_t originalValue); void RWByteAddressBuffer::InterlockedAddI64(uint byteAddress, int64_t valueToAdd); + +void RWByteAddressBuffer::InterlockedCompareExchangeU64(uint byteAddress, uint64_t compareValue, uint64_t value, out uint64_t outOriginalValue); ``` 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 8ec5c2c67..36fe9f216 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -48,6 +48,7 @@ struct ByteAddressBuffer } }; +// AtomicAdd // Make the GLSL atomicAdd available. // We have separate int/float implementations, as the float version requires some specific extensions @@ -58,10 +59,11 @@ __glsl_version(430) __glsl_extension(GL_EXT_shader_atomic_float) float __atomicAdd(__ref float value, float amount); -// Helper for hlsl, using nvAPI +// Helper for hlsl, using NVAPI __target_intrinsic(hlsl, "NvInterlockedAddUint64($0, $1, $2)") uint2 __atomicAdd(RWByteAddressBuffer buf, uint offset, uint2); + // Int versions require glsl 4.30 // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/atomicAdd.xhtml @@ -78,6 +80,32 @@ __glsl_version(430) __glsl_extension(GL_EXT_shader_atomic_int64) int64_t __atomicAdd(__ref int64_t value, int64_t amount); +// CAS - Compare and swap + +// Helper for HLSL, using NVAPI + +__target_intrinsic(hlsl, "NvInterlockedCompareExchangeUint64($0, $1, $2, $3)") +uint2 __cas(RWByteAddressBuffer buf, uint offset, uint2 compareValue, uint2 value); + +__target_intrinsic(glsl, "atomicCompSwap($0, $1, $2)") +__glsl_version(430) +__glsl_extension(GL_EXT_shader_atomic_int64) +uint64_t __cas(__ref uint64_t ioValue, uint64_t compareValue, uint64_t newValue); + +// Conversion between uint64_t and uint2 + +uint2 __asuint2(uint64_t i) +{ + return uint2(uint(i), uint(uint64_t(i) >> 32)); +} + +uint64_t __asuint64(uint2 i) +{ + return (uint64_t(i.y) << 32) | i.x; +} + +// + __intrinsic_op($(kIROp_ByteAddressBufferLoad)) T __byteAddressBufferLoad(ByteAddressBuffer buffer, int offset); @@ -202,16 +230,15 @@ ${{{{ // 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 + // Fp32 Add __target_intrinsic(hlsl, "($3 = NvInterlockedAddFp32($0, $1, $2))") __cuda_sm_version(2.0) __target_intrinsic(cuda, "(*$3 = atomicAdd((float*)$0._getPtrAt($1), $2))") - void InterlockedAddFp32(uint byteAddress, float valueToAdd, out float originalValue); + void InterlockedAddF32(uint byteAddress, float valueToAdd, out float originalValue); - __specialized_for_target(glsl) - void InterlockedAddFp32(uint byteAddress, float valueToAdd, out float originalValue) + void InterlockedAddF32(uint byteAddress, float valueToAdd, out float originalValue) { RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); originalValue = __atomicAdd(buf[byteAddress / 4], valueToAdd); @@ -222,29 +249,24 @@ ${{{{ __target_intrinsic(hlsl, "(NvInterlockedAddFp32($0, $1, $2))") __cuda_sm_version(2.0) __target_intrinsic(cuda, "atomicAdd((float*)$0._getPtrAt($1), $2)") - void InterlockedAddFp32(uint byteAddress, float valueToAdd); + void InterlockedAddF32(uint byteAddress, float valueToAdd); __specialized_for_target(glsl) - void InterlockedAddFp32(uint byteAddress, float valueToAdd) + void InterlockedAddF32(uint byteAddress, float valueToAdd) { RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); __atomicAdd(buf[byteAddress / 4], valueToAdd); } - // Int64 + // Int64 Add __cuda_sm_version(6.0) __target_intrinsic(cuda, "(*$3 = atomicAdd((uint64_t*)$0._getPtrAt($1), $2))") void InterlockedAddI64(uint byteAddress, int64_t valueToAdd, out int64_t originalValue); __specialized_for_target(hlsl) - void InterlockedAddI64(uint byteAddress, int64_t inValueToAdd, out int64_t outOriginalValue) + void InterlockedAddI64(uint byteAddress, int64_t valueToAdd, out int64_t outOriginalValue) { - uint2 valueToAdd; - valueToAdd.x = uint(inValueToAdd); - valueToAdd.y = uint(uint64_t(inValueToAdd) >> 32); - - const uint2 originalValue = __atomicAdd(this, byteAddress, valueToAdd); - outOriginalValue = (int64_t(originalValue.y) << 32) | originalValue.x; + outOriginalValue = __asuint64(__atomicAdd(this, byteAddress, __asuint2(valueToAdd))); } __specialized_for_target(glsl) @@ -260,12 +282,9 @@ ${{{{ void InterlockedAddI64(uint byteAddress, int64_t valueToAdd); __specialized_for_target(hlsl) - void InterlockedAddI64(uint byteAddress, int64_t inValueToAdd) + void InterlockedAddI64(uint byteAddress, int64_t valueToAdd) { - uint2 valueToAdd; - valueToAdd.x = uint(inValueToAdd); - valueToAdd.y = uint(uint64_t(inValueToAdd) >> 32); - __atomicAdd(this, byteAddress, valueToAdd); + __atomicAdd(this, byteAddress, __asuint2(valueToAdd)); } __specialized_for_target(glsl) @@ -275,6 +294,24 @@ ${{{{ __atomicAdd(buf[byteAddress / 8], valueToAdd); } + // CAS UInt64 + + __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); + + __specialized_for_target(hlsl) + void InterlockedCompareExchangeU64(uint byteAddress, uint64_t compareValue, uint64_t value, out uint64_t outOriginalValue) + { + outOriginalValue = __asuint64(__cas(this, byteAddress, __asuint2(compareValue), __asuint2(value))); + } + + __specialized_for_target(glsl) + void InterlockedCompareExchangeU64(uint byteAddress, uint64_t compareValue, uint64_t value, out uint64_t outOriginalValue) + { + RWStructuredBuffer buf = __getEquivalentStructuredBuffer(this); + outOriginalValue = __cas(buf[byteAddress / 8], compareValue, value); + } + ${{{{ } }}}} diff --git a/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang b/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang index e99494d5f..584dcada1 100644 --- a/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang +++ b/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang @@ -19,9 +19,9 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) const float delta = anotherBuffer[idx & 3]; float previousValue = 0; - outputBuffer.InterlockedAddFp32((idx << 2), 1.0f, previousValue); + outputBuffer.InterlockedAddF32((idx << 2), 1.0f, previousValue); // The sum of values in anotherBuffer should also be added int anotherIdx = tid >> 2; - outputBuffer.InterlockedAddFp32(anotherIdx << 2, delta); + outputBuffer.InterlockedAddF32(anotherIdx << 2, delta); } \ No newline at end of file diff --git a/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang.glsl b/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang.glsl index 9fd2f18f9..6e21156aa 100644 --- a/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang.glsl +++ b/tests/slang-extension/atomic-float-byte-address-buffer-cross.slang.glsl @@ -14,7 +14,7 @@ layout(std430, binding = 0) buffer _S2 { } _S3; #line 18 "tests/slang-extension/atomic-float-byte-address-buffer-cross.slang" -void RWByteAddressBuffer_InterlockedAddFp32_0(uint _S4, float _S5, out float _S6) +void RWByteAddressBuffer_InterlockedAddF32_0(uint _S4, float _S5, out float _S6) { uint _S7 = _S4 / uint(4); float _S8 = (atomicAdd((((_S3)._data[(_S7)])), (_S5))); @@ -22,7 +22,7 @@ void RWByteAddressBuffer_InterlockedAddFp32_0(uint _S4, float _S5, out float _S6 return; } -void RWByteAddressBuffer_InterlockedAddFp32_1(uint _S9, float _S10) +void RWByteAddressBuffer_InterlockedAddF32_1(uint _S9, float _S10) { uint _S11 = _S9 / uint(4); float _S12 = (atomicAdd((((_S3)._data[(_S11)])), (_S10))); @@ -43,8 +43,8 @@ layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in;void main() #line 21 float _S14; - RWByteAddressBuffer_InterlockedAddFp32_0(_S13, 1.00000000000000000000, _S14); - RWByteAddressBuffer_InterlockedAddFp32_1(uint(int(tid_0 >> 2) << 2), delta_0); + RWByteAddressBuffer_InterlockedAddF32_0(_S13, 1.00000000000000000000, _S14); + RWByteAddressBuffer_InterlockedAddF32_1(uint(int(tid_0 >> 2) << 2), delta_0); #line 13 return; diff --git a/tests/slang-extension/atomic-float-byte-address-buffer.slang b/tests/slang-extension/atomic-float-byte-address-buffer.slang index 603b92d65..910fefdfe 100644 --- a/tests/slang-extension/atomic-float-byte-address-buffer.slang +++ b/tests/slang-extension/atomic-float-byte-address-buffer.slang @@ -30,12 +30,12 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) //const float delta = anotherBuffer[idx & 3]; float previousValue = 0; - workBuffer.InterlockedAddFp32((idx << 2), 1.0f, previousValue); - //workBuffer.InterlockedAddFp32((idx ^ 2) << 2, 2.0f + delta); + workBuffer.InterlockedAddF32((idx << 2), 1.0f, previousValue); + //workBuffer.InterlockedAddF32((idx ^ 2) << 2, 2.0f + delta); // The sum of values in anotherBuffer should also be added //int anotherIdx = tid >> 2; - //workBuffer.InterlockedAddFp32(anotherIdx << 2, delta); + //workBuffer.InterlockedAddF32(anotherIdx << 2, delta); GroupMemoryBarrierWithGroupSync(); diff --git a/tests/slang-extension/cas-int64-byte-address-buffer.slang b/tests/slang-extension/cas-int64-byte-address-buffer.slang new file mode 100644 index 000000000..b75a9fa04 --- /dev/null +++ b/tests/slang-extension/cas-int64-byte-address-buffer.slang @@ -0,0 +1,45 @@ +// 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 +//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=[0 1 2 3 4 5 6 7]):out,name=outputBuffer +RWByteAddressBuffer outputBuffer; + +[numthreads(16, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + int idx = (tid & 3) ^ (tid >> 2); + + // Try directly reading + uint2 currentValue2 = outputBuffer.Load2(idx << 8); + uint64_t currentValue = uint64_t(currentValue2.y) | currentValue2.x; + + while (true) + { + // This is probably not a great way to do this - InterlockedAddI64 would be better + // but we are doing this to test CAS. + + uint64_t readValue; + outputBuffer.InterlockedCompareExchangeU64(idx << 3, currentValue, currentValue + 1, readValue); + + if (readValue == currentValue) + { + break; + } + + currentValue = readValue; + } +} \ No newline at end of file diff --git a/tests/slang-extension/cas-int64-byte-address-buffer.slang.expected.txt b/tests/slang-extension/cas-int64-byte-address-buffer.slang.expected.txt new file mode 100644 index 000000000..78d24c356 --- /dev/null +++ b/tests/slang-extension/cas-int64-byte-address-buffer.slang.expected.txt @@ -0,0 +1,8 @@ +4 +1 +6 +3 +8 +5 +A +7 -- cgit v1.2.3