diff options
| -rw-r--r-- | source/slang/type-layout.cpp | 234 | ||||
| -rw-r--r-- | source/slang/type-layout.h | 10 | ||||
| -rw-r--r-- | tests/compute/buffer-layout.slang | 131 | ||||
| -rw-r--r-- | tests/compute/buffer-layout.slang.1.expected.txt | 4 | ||||
| -rw-r--r-- | tests/compute/buffer-layout.slang.2.expected.txt | 4 | ||||
| -rw-r--r-- | tests/compute/buffer-layout.slang.expected.txt | 4 | ||||
| -rw-r--r-- | tests/reflection/arrays.hlsl.expected | 4 | ||||
| -rw-r--r-- | tests/reflection/buffer-layout.slang | 109 | ||||
| -rw-r--r-- | tests/reflection/buffer-layout.slang.1.expected | 192 | ||||
| -rw-r--r-- | tests/reflection/buffer-layout.slang.expected | 192 | ||||
| -rw-r--r-- | tests/reflection/matrix-layout.slang.1.expected | 6 | ||||
| -rw-r--r-- | tests/reflection/matrix-layout.slang.expected | 10 | ||||
| -rw-r--r-- | tests/reflection/structured-buffer.slang.expected | 40 | ||||
| -rw-r--r-- | tools/slang-reflection-test/slang-reflection-test-main.cpp | 148 |
14 files changed, 944 insertions, 144 deletions
diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp index 4281829be..f5cd518b8 100644 --- a/source/slang/type-layout.cpp +++ b/source/slang/type-layout.cpp @@ -78,13 +78,36 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl SimpleArrayLayoutInfo GetArrayLayout( SimpleLayoutInfo elementInfo, LayoutSize elementCount) override { SLANG_RELEASE_ASSERT(elementInfo.size.isFinite()); - auto stride = elementInfo.size.getFiniteValue(); + auto elementSize = elementInfo.size.getFiniteValue(); + auto elementAlignment = elementInfo.alignment; + auto elementStride = RoundToAlignment(elementSize, elementAlignment); + + // An array with no elements will have zero size. + // + LayoutSize arraySize = 0; + // + // Any array with a non-zero number of elements will need + // to have space for N elements of size `elementSize`, with + // the constraints that there must be `elementStride` bytes + // between consecutive elements. + // + if( elementCount > 0 ) + { + // We can think of this as either allocating (N-1) + // chunks of size `elementStride` (for most of the elements) + // and then one final chunk of size `elementSize` for + // the last element, or equivalently as allocating + // N chunks of size `elementStride` and then "giving back" + // the final `elementStride - elementSize` bytes. + // + arraySize = (elementStride * (elementCount-1)) + elementSize; + } SimpleArrayLayoutInfo arrayInfo; arrayInfo.kind = elementInfo.kind; - arrayInfo.size = stride * elementCount; - arrayInfo.alignment = elementInfo.alignment; - arrayInfo.elementStride = stride; + arrayInfo.size = arraySize; + arrayInfo.alignment = elementAlignment; + arrayInfo.elementStride = elementStride; return arrayInfo; } @@ -125,86 +148,179 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl if(fieldInfo.size == 0) return ioStructInfo->size; + // A struct type must be at least as aligned as its most-aligned field. ioStructInfo->alignment = std::max(ioStructInfo->alignment, fieldInfo.alignment); - ioStructInfo->size = RoundToAlignment(ioStructInfo->size, fieldInfo.alignment); - LayoutSize fieldOffset = ioStructInfo->size; - ioStructInfo->size += fieldInfo.size; + + // The new field will be added to the end of the struct. + auto fieldBaseOffset = ioStructInfo->size; + + // We need to ensure that the offset for the field will respect its alignment + auto fieldOffset = RoundToAlignment(fieldBaseOffset, fieldInfo.alignment); + + // The size of the struct must be adjusted to cover the bytes consumed + // by this field. + ioStructInfo->size = fieldOffset + fieldInfo.size; + return fieldOffset; } void EndStructLayout(UniformLayoutInfo* ioStructInfo) override { + SLANG_UNUSED(ioStructInfo); + + // Note: A traditional C layout algorithm would adjust the size + // of a struct type so that it is a multiple of the alignment. + // This is a parsimonious design choice because it means that + // `sizeof(T)` can both be used when copying/allocating a single + // value of type `T` or an array of N values, without having to + // consider more details. + // + // Of course the choice also has down-sides in that wrapping things + // into a `struct` can affect layout in ways that waste space. E.g., + // the following two cases don't lay out the same: + // + // struct S0 { double d; float f; float g; }; + // + // struct X { double d; float f; } + // struct S1 { X x; float g; } + // + // Even though `S0::g` and `S1::g` have the same amount of useful + // data in front of them, they will not land at the same offset, + // and the resulting struct sizes will differ (`sizeof(S0)` will be + // 16 while `sizeof(S1)` will be 24). + // + // Slang doesn't get to be opinionated about this stuff because + // there is already precedent in both HLSL and GLSL for types + // that have a size that is not rounded up to their alignment. + // + // Our default layout rules won't implement the C-like policy, + // and instead it will be injected in the concrete implementations + // that require it. + } +}; + + /// Common behavior for GLSL-family layout. +struct GLSLBaseLayoutRulesImpl : DefaultLayoutRulesImpl +{ + typedef DefaultLayoutRulesImpl Super; + + SimpleLayoutInfo GetVectorLayout(SimpleLayoutInfo elementInfo, size_t elementCount) override + { + // The `std140` and `std430` rules require vectors to be aligned to the next power of + // two up from their size (so a `float2` is 8-byte aligned, and a `float3` is + // 16-byte aligned). + // + // Note that in this case we have a type layout where the size is *not* a multiple + // of the alignment, so it should be possible to pack a scalar after a `float3`. + // + SLANG_RELEASE_ASSERT(elementInfo.kind == LayoutResourceKind::Uniform); + SLANG_RELEASE_ASSERT(elementInfo.size.isFinite()); + + auto size = elementInfo.size.getFiniteValue() * elementCount; + SimpleLayoutInfo vectorInfo( + LayoutResourceKind::Uniform, + size, + RoundUpToPowerOfTwo(size)); + return vectorInfo; + } + + SimpleArrayLayoutInfo GetArrayLayout( SimpleLayoutInfo elementInfo, LayoutSize elementCount) override + { + // The size of an array must be rounded up to be a multiple of its alignment. + // + auto info = Super::GetArrayLayout(elementInfo, elementCount); + info.size = RoundToAlignment(info.size, info.alignment); + return info; + } + + void EndStructLayout(UniformLayoutInfo* ioStructInfo) override + { + // The size of a `struct` must be rounded up to be a multiple of its alignment. + // ioStructInfo->size = RoundToAlignment(ioStructInfo->size, ioStructInfo->alignment); } }; -// Capture common behavior betwen HLSL and GLSL (`std140`) constnat buffer rules -struct DefaultConstantBufferLayoutRulesImpl : DefaultLayoutRulesImpl + /// The GLSL `std430` layout rules. +struct Std430LayoutRulesImpl : GLSLBaseLayoutRulesImpl { - // The `std140` rules require that all array elements - // be a multiple of 16 bytes. - // - // HLSL agrees. + // These rules don't actually need any differences from our + // base/common GLSL layout rules. +}; + + /// The GLSL `std430` layout rules. +struct Std140LayoutRulesImpl : GLSLBaseLayoutRulesImpl +{ + typedef GLSLBaseLayoutRulesImpl Super; + SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, LayoutSize elementCount) override { + // The `std140` rules require that array elements + // be aligned on 16-byte boundaries. + // if(elementInfo.kind == LayoutResourceKind::Uniform) { if (elementInfo.alignment < 16) elementInfo.alignment = 16; - elementInfo.size = RoundToAlignment(elementInfo.size, elementInfo.alignment); } - return DefaultLayoutRulesImpl::GetArrayLayout(elementInfo, elementCount); + return Super::GetArrayLayout(elementInfo, elementCount); } - // The `std140` rules require that a `struct` type be - // aligned to at least 16. - // - // HLSL agrees. UniformLayoutInfo BeginStructLayout() override { + // The `std140` rules require that a `struct` type + // be at least 16-byte aligned. + // return UniformLayoutInfo(0, 16); } }; -struct GLSLConstantBufferLayoutRulesImpl : DefaultConstantBufferLayoutRulesImpl +struct HLSLConstantBufferLayoutRulesImpl : DefaultLayoutRulesImpl { -}; + typedef DefaultLayoutRulesImpl Super; -// The `std140` and `std430` rules require vectors to be aligned to the next power of -// two up from their size (so a `float2` is 8-byte aligned, and a `float3` is -// 16-byte aligned). -// -// Note that in this case we have a type layout where the size is *not* a multiple -// of the alignment, so it should be possible to pack a scalar after a `float3`. -static SimpleLayoutInfo getGLSLVectorLayout( - SimpleLayoutInfo elementInfo, size_t elementCount) -{ - SLANG_RELEASE_ASSERT(elementInfo.kind == LayoutResourceKind::Uniform); - SLANG_RELEASE_ASSERT(elementInfo.size.isFinite()); - - auto size = elementInfo.size.getFiniteValue() * elementCount; - SimpleLayoutInfo vectorInfo( - LayoutResourceKind::Uniform, - size, - RoundUpToPowerOfTwo(size)); - return vectorInfo; -} + // Similar to GLSL `std140` rules, an HLSL constant buffer requires that + // `struct` and array types have 16-byte alignement. + // + // Unlike GLSL `std140`, the overall size of an array or `struct` type + // is *not* rounded up to the alignment, so it is possible for later + // fields to sneak into the "tail space" left behind by a preceding + // structure or array. E.g., in this example: + // + // struct S { float3 a[2]; float b; }; + // + // The stride of the array `a` is 16 bytes per element, but the size + // of `a` will only be 28 bytes (not 32), so that `b` can fit into + // the space after the last array element and the overall structure + // will have a size of 32 bytes. -// The `std140` rules combine the GLSL-specific layout for 3-vectors with the -// alignment padding for structures and arrays that is common to both HLSL -// and GLSL constant buffers. -struct Std140LayoutRulesImpl : GLSLConstantBufferLayoutRulesImpl -{ - SimpleLayoutInfo GetVectorLayout(SimpleLayoutInfo elementInfo, size_t elementCount) override + SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, LayoutSize elementCount) override { - return getGLSLVectorLayout(elementInfo, elementCount); + if(elementInfo.kind == LayoutResourceKind::Uniform) + { + if (elementInfo.alignment < 16) + elementInfo.alignment = 16; + } + return Super::GetArrayLayout(elementInfo, elementCount); } -}; -struct HLSLConstantBufferLayoutRulesImpl : DefaultConstantBufferLayoutRulesImpl -{ - // Can't let a `struct` field straddle a register (16-byte) boundary + UniformLayoutInfo BeginStructLayout() override + { + return UniformLayoutInfo(0, 16); + } + + // HLSL layout rules do *not* impose additional alignment + // constraints on vectors (e.g., all of `float`, `float2`, + // `float3`, and `float4` have 4-byte alignment), but instead + // they impose a rule that any `struct` field must not + // "straddle" a 16-byte boundary. + // + // This has the effect of making it *look* like `float4` + // values have 16-byte alignment in practice, but the + // effects on `float2` and `float3` are more nuanched and + // lead to different result than the GLSL rules. + // LayoutSize AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override { // Skip zero-size fields @@ -234,18 +350,10 @@ struct HLSLConstantBufferLayoutRulesImpl : DefaultConstantBufferLayoutRulesImpl struct HLSLStructuredBufferLayoutRulesImpl : DefaultLayoutRulesImpl { - // TODO: customize these to be correct... -}; - -// The `std430` rules don't include the array/structure alignment padding that -// gets applied to constant buffers, but they do include the padding of 3-vectors -// to be aligned as 4-vectors. -struct Std430LayoutRulesImpl : DefaultLayoutRulesImpl -{ - SimpleLayoutInfo GetVectorLayout(SimpleLayoutInfo elementInfo, size_t elementCount) override - { - return getGLSLVectorLayout(elementInfo, elementCount); - } + // HLSL structured buffers drop the restrictions added for constant buffers, + // but retain the rules around not adjusting the size of an array or + // structure to its alignment. In this way they should match our + // default layout rules. }; struct DefaultVaryingLayoutRulesImpl : DefaultLayoutRulesImpl diff --git a/source/slang/type-layout.h b/source/slang/type-layout.h index 6f6dad055..c326ae989 100644 --- a/source/slang/type-layout.h +++ b/source/slang/type-layout.h @@ -176,6 +176,16 @@ inline LayoutSize maximum(LayoutSize left, LayoutSize right) right.getFiniteValue())); } +inline bool operator>(LayoutSize left, LayoutSize::RawValue right) +{ + return left.isInfinite() || (left.getFiniteValue() > right); +} + +inline bool operator<=(LayoutSize left, LayoutSize::RawValue right) +{ + return left.isFinite() && (left.getFiniteValue() <= right); +} + // Layout appropriate to "just memory" scenarios, // such as laying out the members of a constant buffer. struct UniformLayoutInfo diff --git a/tests/compute/buffer-layout.slang b/tests/compute/buffer-layout.slang new file mode 100644 index 000000000..145da99c8 --- /dev/null +++ b/tests/compute/buffer-layout.slang @@ -0,0 +1,131 @@ +// buffer-layout.slang + +// The goal of this test is to make sure that constant and structured +// buffers obey the rules we expect of the each target API. + +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 +//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute + + +//TEST_INPUT: ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out +RWStructuredBuffer<int> outputBuffer; + +struct A +{ + float x; + float y; +} + +struct S +{ + // The first field in a struct isn't going to be that + // interesting, because it will always get offset zero, + // so we just use this to establish a poorly-aligned + // starting point for the next field. + // + // offset size alignment + // + // 0 4 4 + // + float z; + + // The `std140` and D3D constant buffer ruless both + // ensure a minimum of 16-byte alignment on `struct` + // types, but differ in that D3D does not round up + // the total size of a type to its alignment. + // + // The `std430` and structured buffer rules don't + // perform any over-alignment on `struct` types and + // instead align them using the "natural" rules one + // might expect of, e.g., a C compiler. + // + // offset size alignment + // + // cbuffer 16 8 16 + // std140 16 16 16 + // + // struct 4 8 4 + // std430 4 8 4 + // + A a; + + // Now we insert an ordinary `int` field just as + // a way to probe the offset so far. + // + // offset size alignment + // + // cbuffer 24 4 4 + // std140 32 4 4 + // + // struct 12 4 4 + // std430 12 4 4 + // + int b; + + // As our next stress-test case, we will insert an + // array with elements that aren't a multiple of + // 16 bytes in size. + // + // The contant/uniform buffer rules will set the + // array stride to a multiple of 16 bytes in this case. + // The only difference between D3D rules and `std140` + // here is that D3D does not round up the size to + // the alignment. + // + // The structured/std430 rules don't do anything + // to over-align an array, so it is laid out relatively + // naturally, but note that D3D still follows its rule + // of not letting a vector "straddle" a 16-byte boundary, + // even if it doesn't bump up the alignment of + // vector types. + // + // offset size alignment + // + // cbuffer 32 24 16 + // std140 48 32 32 + // + // struct 16 16 4 + // std430 16 16 8 + // + float2 c[2]; + + // Now we put in one more ordinary `int` field + // just to probe the offset computed so far. + // offset size alignment + // + // cbuffer 56 4 4 + // std140 80 4 4 + // + // struct 32 4 4 + // std430 32 4 4 + // + int d; +} + +//TEST_INPUT:cbuffer(data=[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32]):dxbinding(0),glbinding(0) +ConstantBuffer<S> cb; + +//TEST_INPUT:ubuffer(data=[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32],stride=4):dxbinding(0),glbinding(1) +RWStructuredBuffer<S> sb; + +int test(int val) +{ + val = val+1; + val = val*16 + cb.b; + val = val*256 + cb.d; + val = val*256 + sb[0].b; + val = val*256 + sb[0].d; + return val; +} + +[numthreads(4, 1, 1)] +void computeMain( + uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + + int inVal = tid; + int outVal = test(inVal); + outputBuffer[tid] = outVal; +}
\ No newline at end of file diff --git a/tests/compute/buffer-layout.slang.1.expected.txt b/tests/compute/buffer-layout.slang.1.expected.txt new file mode 100644 index 000000000..5e8ac1dc4 --- /dev/null +++ b/tests/compute/buffer-layout.slang.1.expected.txt @@ -0,0 +1,4 @@ +160E0308 +260E0308 +360E0308 +460E0308 diff --git a/tests/compute/buffer-layout.slang.2.expected.txt b/tests/compute/buffer-layout.slang.2.expected.txt new file mode 100644 index 000000000..efeba7ec3 --- /dev/null +++ b/tests/compute/buffer-layout.slang.2.expected.txt @@ -0,0 +1,4 @@ +18140308 +28140308 +38140308 +48140308 diff --git a/tests/compute/buffer-layout.slang.expected.txt b/tests/compute/buffer-layout.slang.expected.txt new file mode 100644 index 000000000..5e8ac1dc4 --- /dev/null +++ b/tests/compute/buffer-layout.slang.expected.txt @@ -0,0 +1,4 @@ +160E0308 +260E0308 +360E0308 +460E0308 diff --git a/tests/reflection/arrays.hlsl.expected b/tests/reflection/arrays.hlsl.expected index b586e362a..f815ea88c 100644 --- a/tests/reflection/arrays.hlsl.expected +++ b/tests/reflection/arrays.hlsl.expected @@ -31,7 +31,7 @@ standard output = { }, "uniformStride": 16 }, - "binding": {"kind": "uniform", "offset": 16, "size": 160} + "binding": {"kind": "uniform", "offset": 16, "size": 148} }, { "name": "y", @@ -39,7 +39,7 @@ standard output = { "kind": "scalar", "scalarType": "float32" }, - "binding": {"kind": "uniform", "offset": 176, "size": 4} + "binding": {"kind": "uniform", "offset": 164, "size": 4} } ] } diff --git a/tests/reflection/buffer-layout.slang b/tests/reflection/buffer-layout.slang new file mode 100644 index 000000000..51b9680d1 --- /dev/null +++ b/tests/reflection/buffer-layout.slang @@ -0,0 +1,109 @@ +// buffer-layout.slang + +// This test mirrors `tests/compute/buffer-layout.slang`, and it meant +// to confirm that our reflection logic correctly reports the offsets +// that the compute test sees in practice. + +//TEST:REFLECTION:-stage compute -entry main -target hlsl +//TEST:REFLECTION:-stage compute -entry main -target spirv + +struct A +{ + float x; + float y; +} + +struct S +{ + // The first field in a struct isn't going to be that + // interesting, because it will always get offset zero, + // so we just use this to establish a poorly-aligned + // starting point for the next field. + // + // offset size alignment + // + // 0 4 4 + // + float z; + + // The `std140` and D3D constant buffer ruless both + // ensure a minimum of 16-byte alignment on `struct` + // types, but differ in that D3D does not round up + // the total size of a type to its alignment. + // + // The `std430` and structured buffer rules don't + // perform any over-alignment on `struct` types and + // instead align them using the "natural" rules one + // might expect of, e.g., a C compiler. + // + // offset size alignment + // + // cbuffer 16 8 16 + // std140 16 16 16 + // + // struct 4 8 4 + // std430 4 8 4 + // + A a; + + // Now we insert an ordinary `int` field just as + // a way to probe the offset so far. + // + // offset size alignment + // + // cbuffer 24 4 4 + // std140 32 4 4 + // + // struct 12 4 4 + // std430 12 4 4 + // + int b; + + // As our next stress-test case, we will insert an + // array with elements that aren't a multiple of + // 16 bytes in size. + // + // The contant/uniform buffer rules will set the + // array stride to a multiple of 16 bytes in this case. + // The only difference between D3D rules and `std140` + // here is that D3D does not round up the size to + // the alignment. + // + // The structured/std430 rules don't do anything + // to over-align an array, so it is laid out relatively + // naturally, but note that D3D still follows its rule + // of not letting a vector "straddle" a 16-byte boundary, + // even if it doesn't bump up the alignment of + // vector types. + // + // offset size alignment + // + // cbuffer 32 24 16 + // std140 48 32 32 + // + // struct 16 16 4 + // std430 16 16 8 + // + float2 c[2]; + + // Now we put in one more ordinary `int` field + // just to probe the offset computed so far. + // offset size alignment + // + // cbuffer 56 4 4 + // std140 80 4 4 + // + // struct 32 4 4 + // std430 32 4 4 + // + int d; +} + +ConstantBuffer<S> cb; +RWStructuredBuffer<S> sb; + +[numthreads(1, 1, 1)] +void main( + uint3 dispatchThreadID : SV_DispatchThreadID) +{ +}
\ No newline at end of file diff --git a/tests/reflection/buffer-layout.slang.1.expected b/tests/reflection/buffer-layout.slang.1.expected new file mode 100644 index 000000000..890f31481 --- /dev/null +++ b/tests/reflection/buffer-layout.slang.1.expected @@ -0,0 +1,192 @@ +result code = 0 +standard error = { +} +standard output = { +{ + "parameters": [ + { + "name": "cb", + "binding": {"kind": "descriptorTableSlot", "index": 0}, + "type": { + "kind": "constantBuffer", + "elementType": { + "kind": "struct", + "name": "S", + "fields": [ + { + "name": "z", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "a", + "type": { + "kind": "struct", + "name": "A", + "fields": [ + { + "name": "x", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "y", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 4, "size": 4} + } + ] + }, + "binding": {"kind": "uniform", "offset": 16, "size": 16} + }, + { + "name": "b", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 32, "size": 4} + }, + { + "name": "c", + "type": { + "kind": "array", + "elementCount": 2, + "elementType": { + "kind": "vector", + "elementCount": 2, + "elementType": { + "kind": "scalar", + "scalarType": "float32" + } + }, + "uniformStride": 16 + }, + "binding": {"kind": "uniform", "offset": 48, "size": 32} + }, + { + "name": "d", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 80, "size": 4} + } + ] + } + } + }, + { + "name": "sb", + "binding": {"kind": "descriptorTableSlot", "index": 1}, + "type": { + "kind": "resource", + "baseShape": "structuredBuffer", + "access": "readWrite", + "resultType": { + "kind": "struct", + "name": "S", + "fields": [ + { + "name": "z", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "a", + "type": { + "kind": "struct", + "name": "A", + "fields": [ + { + "name": "x", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "y", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 4, "size": 4} + } + ] + }, + "binding": {"kind": "uniform", "offset": 4, "size": 8} + }, + { + "name": "b", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 12, "size": 4} + }, + { + "name": "c", + "type": { + "kind": "array", + "elementCount": 2, + "elementType": { + "kind": "vector", + "elementCount": 2, + "elementType": { + "kind": "scalar", + "scalarType": "float32" + } + }, + "uniformStride": 8 + }, + "binding": {"kind": "uniform", "offset": 16, "size": 16} + }, + { + "name": "d", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 32, "size": 4} + } + ] + } + } + } + ], + "entryPoints": [ + { + "name": "main", + "stage:": "compute", + "parameters": [ + { + "name": "dispatchThreadID", + "semanticName": "SV_DISPATCHTHREADID", + "type": { + "kind": "vector", + "elementCount": 3, + "elementType": { + "kind": "scalar", + "scalarType": "uint32" + } + } + } + ], + "threadGroupSize": [1, 1, 1] + } + ] +} +} diff --git a/tests/reflection/buffer-layout.slang.expected b/tests/reflection/buffer-layout.slang.expected new file mode 100644 index 000000000..25a41bc6d --- /dev/null +++ b/tests/reflection/buffer-layout.slang.expected @@ -0,0 +1,192 @@ +result code = 0 +standard error = { +} +standard output = { +{ + "parameters": [ + { + "name": "cb", + "binding": {"kind": "constantBuffer", "index": 0}, + "type": { + "kind": "constantBuffer", + "elementType": { + "kind": "struct", + "name": "S", + "fields": [ + { + "name": "z", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "a", + "type": { + "kind": "struct", + "name": "A", + "fields": [ + { + "name": "x", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "y", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 4, "size": 4} + } + ] + }, + "binding": {"kind": "uniform", "offset": 16, "size": 8} + }, + { + "name": "b", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 24, "size": 4} + }, + { + "name": "c", + "type": { + "kind": "array", + "elementCount": 2, + "elementType": { + "kind": "vector", + "elementCount": 2, + "elementType": { + "kind": "scalar", + "scalarType": "float32" + } + }, + "uniformStride": 16 + }, + "binding": {"kind": "uniform", "offset": 32, "size": 24} + }, + { + "name": "d", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 56, "size": 4} + } + ] + } + } + }, + { + "name": "sb", + "binding": {"kind": "unorderedAccess", "index": 0}, + "type": { + "kind": "resource", + "baseShape": "structuredBuffer", + "access": "readWrite", + "resultType": { + "kind": "struct", + "name": "S", + "fields": [ + { + "name": "z", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "a", + "type": { + "kind": "struct", + "name": "A", + "fields": [ + { + "name": "x", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 0, "size": 4} + }, + { + "name": "y", + "type": { + "kind": "scalar", + "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 4, "size": 4} + } + ] + }, + "binding": {"kind": "uniform", "offset": 4, "size": 8} + }, + { + "name": "b", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 12, "size": 4} + }, + { + "name": "c", + "type": { + "kind": "array", + "elementCount": 2, + "elementType": { + "kind": "vector", + "elementCount": 2, + "elementType": { + "kind": "scalar", + "scalarType": "float32" + } + }, + "uniformStride": 8 + }, + "binding": {"kind": "uniform", "offset": 16, "size": 16} + }, + { + "name": "d", + "type": { + "kind": "scalar", + "scalarType": "int32" + }, + "binding": {"kind": "uniform", "offset": 32, "size": 4} + } + ] + } + } + } + ], + "entryPoints": [ + { + "name": "main", + "stage:": "compute", + "parameters": [ + { + "name": "dispatchThreadID", + "semanticName": "SV_DISPATCHTHREADID", + "type": { + "kind": "vector", + "elementCount": 3, + "elementType": { + "kind": "scalar", + "scalarType": "uint32" + } + } + } + ], + "threadGroupSize": [1, 1, 1] + } + ] +} +} diff --git a/tests/reflection/matrix-layout.slang.1.expected b/tests/reflection/matrix-layout.slang.1.expected index 221b96a0b..57fd29c6d 100644 --- a/tests/reflection/matrix-layout.slang.1.expected +++ b/tests/reflection/matrix-layout.slang.1.expected @@ -49,7 +49,7 @@ standard output = { "scalarType": "float32" } }, - "binding": {"kind": "uniform", "offset": 96, "size": 64} + "binding": {"kind": "uniform", "offset": 96, "size": 60} } ] } @@ -106,11 +106,11 @@ standard output = { "scalarType": "float32" } }, - "binding": {"kind": "uniform", "offset": 96, "size": 64} + "binding": {"kind": "uniform", "offset": 96, "size": 60} } ] }, - "binding": {"kind": "uniform", "offset": 0, "size": 160} + "binding": {"kind": "uniform", "offset": 0, "size": 156} } ] } diff --git a/tests/reflection/matrix-layout.slang.expected b/tests/reflection/matrix-layout.slang.expected index 04c861ed3..c8aeb2ae1 100644 --- a/tests/reflection/matrix-layout.slang.expected +++ b/tests/reflection/matrix-layout.slang.expected @@ -23,7 +23,7 @@ standard output = { "scalarType": "float32" } }, - "binding": {"kind": "uniform", "offset": 0, "size": 64} + "binding": {"kind": "uniform", "offset": 0, "size": 60} }, { "name": "ab", @@ -49,7 +49,7 @@ standard output = { "scalarType": "float32" } }, - "binding": {"kind": "uniform", "offset": 112, "size": 64} + "binding": {"kind": "uniform", "offset": 112, "size": 60} } ] } @@ -80,7 +80,7 @@ standard output = { "scalarType": "float32" } }, - "binding": {"kind": "uniform", "offset": 0, "size": 64} + "binding": {"kind": "uniform", "offset": 0, "size": 60} }, { "name": "bb", @@ -106,11 +106,11 @@ standard output = { "scalarType": "float32" } }, - "binding": {"kind": "uniform", "offset": 112, "size": 64} + "binding": {"kind": "uniform", "offset": 112, "size": 60} } ] }, - "binding": {"kind": "uniform", "offset": 0, "size": 176} + "binding": {"kind": "uniform", "offset": 0, "size": 172} } ] } diff --git a/tests/reflection/structured-buffer.slang.expected b/tests/reflection/structured-buffer.slang.expected index 70ceb64f2..3e79fd1c5 100644 --- a/tests/reflection/structured-buffer.slang.expected +++ b/tests/reflection/structured-buffer.slang.expected @@ -40,25 +40,35 @@ standard output = { "baseShape": "structuredBuffer", "resultType": { "kind": "struct", + "name": "S", "fields": [ - "name": "a", - "type": { - "kind": "vector", - "elementCount": 2, - "elementType": { + { + "name": "a", + "type": { + "kind": "vector", + "elementCount": 2, + "elementType": { + "kind": "scalar", + "scalarType": "float32" + } + }, + "binding": {"kind": "uniform", "offset": 0, "size": 8} + }, + { + "name": "b", + "type": { "kind": "scalar", "scalarType": "float32" - } - }, - "name": "b", - "type": { - "kind": "scalar", - "scalarType": "float32" + }, + "binding": {"kind": "uniform", "offset": 8, "size": 4} }, - "name": "c", - "type": { - "kind": "scalar", - "scalarType": "uint32" + { + "name": "c", + "type": { + "kind": "scalar", + "scalarType": "uint32" + }, + "binding": {"kind": "uniform", "offset": 12, "size": 4} } ] } diff --git a/tools/slang-reflection-test/slang-reflection-test-main.cpp b/tools/slang-reflection-test/slang-reflection-test-main.cpp index 071a15cd4..3ae614b22 100644 --- a/tools/slang-reflection-test/slang-reflection-test-main.cpp +++ b/tools/slang-reflection-test/slang-reflection-test-main.cpp @@ -308,6 +308,66 @@ static void emitReflectionScalarTypeInfoJSON( write(writer, "\""); } +static void emitReflectionResourceTypeBaseInfoJSON( + PrettyWriter& writer, + slang::TypeReflection* type) +{ + auto shape = type->getResourceShape(); + auto access = type->getResourceAccess(); + write(writer, "\"kind\": \"resource\""); + write(writer, ",\n"); + write(writer, "\"baseShape\": \""); + switch (shape & SLANG_RESOURCE_BASE_SHAPE_MASK) + { + default: + write(writer, "unknown"); + assert(!"unhandled case"); + break; + +#define CASE(SHAPE, NAME) case SLANG_##SHAPE: write(writer, #NAME); break + CASE(TEXTURE_1D, texture1D); + CASE(TEXTURE_2D, texture2D); + CASE(TEXTURE_3D, texture3D); + CASE(TEXTURE_CUBE, textureCube); + CASE(TEXTURE_BUFFER, textureBuffer); + CASE(STRUCTURED_BUFFER, structuredBuffer); + CASE(BYTE_ADDRESS_BUFFER, byteAddressBuffer); +#undef CASE + } + write(writer, "\""); + if (shape & SLANG_TEXTURE_ARRAY_FLAG) + { + write(writer, ",\n"); + write(writer, "\"array\": true"); + } + if (shape & SLANG_TEXTURE_MULTISAMPLE_FLAG) + { + write(writer, ",\n"); + write(writer, "\"multisample\": true"); + } + + if( access != SLANG_RESOURCE_ACCESS_READ ) + { + write(writer, ",\n\"access\": \""); + switch(access) + { + default: + write(writer, "unknown"); + assert(!"unhandled case"); + break; + + case SLANG_RESOURCE_ACCESS_READ: + break; + + case SLANG_RESOURCE_ACCESS_READ_WRITE: write(writer, "readWrite"); break; + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: write(writer, "rasterOrdered"); break; + case SLANG_RESOURCE_ACCESS_APPEND: write(writer, "append"); break; + case SLANG_RESOURCE_ACCESS_CONSUME: write(writer, "consume"); break; + } + write(writer, "\""); + } +} + static void emitReflectionTypeInfoJSON( PrettyWriter& writer, slang::TypeReflection* type) @@ -321,65 +381,13 @@ static void emitReflectionTypeInfoJSON( case slang::TypeReflection::Kind::Resource: { - auto shape = type->getResourceShape(); - auto access = type->getResourceAccess(); - write(writer, "\"kind\": \"resource\""); - write(writer, ",\n"); - write(writer, "\"baseShape\": \""); - switch (shape & SLANG_RESOURCE_BASE_SHAPE_MASK) - { - default: - write(writer, "unknown"); - assert(!"unhandled case"); - break; - -#define CASE(SHAPE, NAME) case SLANG_##SHAPE: write(writer, #NAME); break - CASE(TEXTURE_1D, texture1D); - CASE(TEXTURE_2D, texture2D); - CASE(TEXTURE_3D, texture3D); - CASE(TEXTURE_CUBE, textureCube); - CASE(TEXTURE_BUFFER, textureBuffer); - CASE(STRUCTURED_BUFFER, structuredBuffer); - CASE(BYTE_ADDRESS_BUFFER, byteAddressBuffer); -#undef CASE - } - write(writer, "\""); - if (shape & SLANG_TEXTURE_ARRAY_FLAG) - { - write(writer, ",\n"); - write(writer, "\"array\": true"); - } - if (shape & SLANG_TEXTURE_MULTISAMPLE_FLAG) - { - write(writer, ",\n"); - write(writer, "\"multisample\": true"); - } - - if( access != SLANG_RESOURCE_ACCESS_READ ) - { - write(writer, ",\n\"access\": \""); - switch(access) - { - default: - write(writer, "unknown"); - assert(!"unhandled case"); - break; - - case SLANG_RESOURCE_ACCESS_READ: - break; - - case SLANG_RESOURCE_ACCESS_READ_WRITE: write(writer, "readWrite"); break; - case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: write(writer, "rasterOrdered"); break; - case SLANG_RESOURCE_ACCESS_APPEND: write(writer, "append"); break; - case SLANG_RESOURCE_ACCESS_CONSUME: write(writer, "consume"); break; - } - write(writer, "\""); - } + emitReflectionResourceTypeBaseInfoJSON(writer, type); // TODO: We should really print the result type for all resource // types, but current test output depends on the old behavior, so // we only add result type output for structured buffers at first. // + auto shape = type->getResourceShape(); switch (shape & SLANG_RESOURCE_BASE_SHAPE_MASK) { default: @@ -620,9 +628,37 @@ static void emitReflectionTypeLayoutInfoJSON( write(writer, ",\n"); emitReflectionNameInfoJSON(writer, typeLayout->getName()); break; - } - // TODO: emit size info for types + case slang::TypeReflection::Kind::Resource: + { + // Some resource types (notably structured buffers) + // encode layout information for their result/element + // type, but others don't. We need to check for + // the relevant cases here. + // + auto type = typeLayout->getType(); + auto shape = type->getResourceShape(); + + if( (shape & SLANG_RESOURCE_BASE_SHAPE_MASK) == SLANG_STRUCTURED_BUFFER ) + { + emitReflectionResourceTypeBaseInfoJSON(writer, type); + + if( auto resultTypeLayout = typeLayout->getElementTypeLayout() ) + { + write(writer, ",\n"); + write(writer, "\"resultType\": "); + emitReflectionTypeLayoutJSON( + writer, + resultTypeLayout); + } + } + else + { + emitReflectionTypeInfoJSON(writer, typeLayout->getType()); + } + } + break; + } } static void emitReflectionTypeLayoutJSON( |
