KhronosGroup / SPIRV-Cross

SPIRV-Cross is a practical tool and library for performing reflection on SPIR-V and disassembling SPIR-V back to high level languages.
Apache License 2.0
2.09k stars 569 forks source link

SPIR-V -> HLSL : cross compiling bindings overlap #2064

Open Try opened 2 years ago

Try commented 2 years ago

Simple reproducer:

#version 460

layout (location = 0) out vec4 fragColor;

layout(binding = 0, std430) readonly buffer A { vec4 a[]; };
layout(binding = 0, std430) readonly buffer B { uint b[]; };

void main()
{
    fragColor = vec4(0.4, 0.4, 0.8, 1.0);
}

Been cross-compiled to HLSL as:

// error: resource _22 at register 0 overlaps with resource _17 at register 0, space 0
ByteAddressBuffer _17 : register(t0);
ByteAddressBuffer _22 : register(t0);
...

Seemingly there is 'simple' solution for SSBO - to use only a single binding. However, I don't know, if there is a solution for textures. Any opinions on should this be implemented in any way or not? Thanks!

HansKristian-Work commented 2 years ago

Would need similar hacks as MSL where multiple OpVariables alias the same binding. This breaks down if different qualifiers like globallycoherent are applied though.

Textures won't work here. Need to stamp out different space variables that application root signature must resolve. Descriptor aliasing really isn't feasible with HLSL.

Try commented 1 year ago

Would need similar hacks as MSL where multiple OpVariables alias the same binding

I've checked MSL codegen and it doesn't look quite right: https://shader-playground.timjones.io/b4739d6b673576df6b38fb0ad49bc48c

In generated shader there is an issue: buffer now consumes multiple bind-points, what is a problem for engine (and also defeat the purpose of feature). In my end, engine exposes maxBuffers/maxTextures so consuming extra bind-point will break abstraction.

Proposed change (buffers only):

HLSL merge qualifiers:

readonly + writeonly = RWByteAddressBuffer if any of overlapped buffers is coherent, then globallycoherent will be used on merged binding.

Overlap of multiple uniform buffers, can be compiled as:

cbuffer M : register(b0)
{
    uint _data_16_26[MAX_OF_SIZES/4];
};

static const A _16 = (A)_data_16_26; // need to be careful about structure declaration, assuming tight packing here
static const B _26 = (B)_data_16_26;

MSL use reinterpret_cast:

fragment main0_out main0(device A& _14 [[buffer (0)]])
{
    const    device B& _15 = reinterpret_cast<const device B&>(_14);
    volatile device C& _16 = reinterpret_cast<volatile device C&>(_14);
             device D& _17 = reinterpret_cast<device D&>(_14);

It' possible to use const_cast as well, if needed. Uniform/Storage overlap is more tricky as shader is not allowed to cast from device to constant and vise-versa. Probably it's OK to promote constant to device on shader side, effectively turning uniform buffer into readonly storage buffer.