Open Nielsbishere opened 2 months ago
OpMemberDecorate %_struct_5 5 Offset 20
(which is int16_t f;
)
I guess I can't find any spec text (i assume it might be somewhere deep down in it) that says that the struct size is 22
or 24
in this case
I can confirm the logic of SPIRV-Reflect
is it grabs the highest offset value in the struct and then proceeds to calculate that last element.
it should calculate it by taking the largest member (U64)
what if your largest member is a struct, like
struct Nicu {
SomeStruct, a;
int16_t b;
};
I am not sure how it would calculate it there
(basically will bring up in spec WG to confirm what the "right" answer should be here and how it should be calculated, because teh Vulkan Validation Layers are doing the same logic as well currently)
@spencer-lunarg I'm more specifically talking about the HLSL structured buffer packing rules and there it 100% mismatches the reflection that can be obtained from DXIL vs the reflection that can be obtained from SPIRV (24 would be the correct answer). Maraneshi has a good writeup about these rules: https://maraneshi.github.io/HLSL-ConstantBufferLayoutVisualizer/ (see Addendum). Essentially it has similar rules to C/C++ where the largest data type is the alignment the struct should use. With nested structs they are aligned to their largest size as well. So if SomeStruct has a U64 in it it would basically force Nicu to become 16 bytes. If this mismatches with SSBO layouts, I'm not 100% sure, as I've not been using GLSL for a while now.
See the output of
struct PSInput
{
float4 color : COLOR;
};
struct Nest {
uint64_t a;
};
struct Test {
Nest a;
float b;
};
StructuredBuffer<Test> test;
float4 PSMain(PSInput input) : SV_TARGET
{
return input.color * test[0].b.xxxx;
}
On https://shader-playground.timjones.io/ where it produces:
; struct struct.Test
; {
;
; struct struct.Nest
; {
;
; uint64_t a; ; Offset: 0
;
; } a; ; Offset: 0
;
; float b; ; Offset: 8
;
; } $Element; ; Offset: 0 Size: 16
@Nielsbishere ok, I know glsl/hlsl have various memory layouts, (https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/shader_memory_layout.adoc) but I need to make sure I get it right for how SPIR-V defines it
... basically I have had this on my todo list to get better documented in the spec (or somewhere) and a set of SPIR-V to test things with as there are clearly many edge cases to test for
@Nielsbishere I just realize I have also been mixing up stride
with size
:facepalm:
No problem. For me I also don't care too much about size
, I only care about stride. Because the DXIL backend can't accurately figure out the size of a struct for nested structs.
@Nielsbishere can I ask where the stride
of 22
blows up, like I guess want to also better understand who is expecting this to be 24
, because if you had an array of Nicu
SPIR-V would have an ArrayStride
for it
This data is used for reflection purposes and is used for both analysis and to send data to the shader. The two should match because otherwise DXIL and SPIRV have mismatching behavior (I unify both binaries into a single reflection format for ease of use).
spriv-reflect isn't gaslighting you. When this code was written, there was not a clear definition on size vs stride of a struct in a StructuredBuffer. Both were reversed engineered from the behavior of the drivers at the time because there was no documentation published on HLSL buffer packing. There's a good chance the driver that we leaned on the hardest to for the calculations didn't quite get it right either. There may have been something like the stride needs to align to the size of the smallest machine type (4 bytes) but oh...well for StructuredBuffer the driver might ignore that.
The code was originally written to line up with DXBC and not DXIL. DXIL wasn't in widespread usage at the time and wouldn't be for almost another two years.
It's been a while since I've written that code and I haven't had the time to revisit in now that DXIL is more common than DXBC.
The rules for packing ConstantBuffer and StructuredBuffer are a little bit different and also have changed over time with the inclusion of types that are smaller than 4 bytes. We need to revisit these with an updated test to line up with DXIL.
The following HLSL (compiled with DXC's flags for dx buffer layouts) produces incorrect spirv-reflect behavior:
Turns into:
As the U32 at offset 0x0000011c says; the stride of _runtimearr__struct_5 is 24. However, when running this with spirv-reflect it will gaslight me that
binding->block.members[0]->padded_size == 22
:That doesn't match expected behavior and is likely because 22 = 0x14 + sizeof(I16) = 0x16. My best guess is it calculates the struct size rather than reflects it. If it does calculate it, then it should calculate it by taking the largest member (U64) and align the struct size to it. Doing that manually seems very confusing.