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.05k stars 562 forks source link

HLSL: Register assignment for cbuffer doesn't respect GLSL binding index #1534

Closed sora-jp closed 3 years ago

sora-jp commented 3 years ago

When compiling Vulkan-style GLSL to SPIR-V, and then to HLSL, cbuffer indices get misaligned if a cbuffer gets optimized away in the GLSL -> SPIR-V stage.

Example GLSL (Vertex shader, fragment parts omitted for brevity):

layout(set = 0, binding = 0) uniform _PERFRAME {
    vec4 _Time;
};

layout(set = 0, binding = 1) uniform _PERCAMERA {
    mat4 MATRIX_VP;
    vec4 CAMERA_POSITION;
};

layout(set = 0, binding = 2) uniform _PEROBJ {
    mat4 MATRIX_M;
};

void main() {
        gl_Position = MATRIX_VP * MATRIX_M * vec4(0,0,0,1);
}

Disassembled SPIR-V After compiling to SPIR-V, and then converting back to GLSL:

layout(set = 0, binding = 1, std140) uniform _23_25
{
    mat4 _m0;
    vec4 _m1;
} _25;

layout(set = 0, binding = 2, std140) uniform _31_33
{
    mat4 _m0;
} _33;

HLSL Output:

cbuffer _23_25 : register(b0)
{
    row_major float4x4 _25_m0 : packoffset(c0);
    float4 _25_m1 : packoffset(c4);
};

cbuffer _31_33 : register(b1)
{
    row_major float4x4 _33_m0 : packoffset(c0);
};

Notice how _23_25 is assigned to register b0, even though it is decorated with layout(binding = 1). I'm using Veldrid.SPIRV to interface with SPIRV-Cross from C#. They use a slightly outdated version, but I couldn't find any references to this happening anywhere, so I thought it would be best to create an issue. I can post the full code if needed, but I extracted the relevant bits only for now.

cdavis5e commented 3 years ago

This is because Vulkan and Direct3D have different binding models. Unfortunately, unlike for Metal, there doesn't seem to be an option to make SPIRV-Cross use SPIR-V binding numbers in HLSL. The best you can do is use CompilerHLSL::set_resource_binding_flags() (spvc_compiler_hlsl_set_resource_binding_flags() from C API) to turn off register emission entirely. Perhaps we should add such an option.

sora-jp commented 3 years ago

Is there a way to find out the mapping from GLSL binding to HLSL register via the reflection api? Currently, the workaround I use is to compile the GLSL to SPIR-V with debug information enabled. This seems to force SPIRV-Cross to emit all buffers, even if they are unused.

cdavis5e commented 3 years ago

Is there a way to find out the mapping from GLSL binding to HLSL register via the reflection api?

No. But, and I should've mentioned this earlier, you can control the register-binding mapping by using CompilerHLSL::add_hlsl_resource_binding() (spvc_compiler_hlsl_add_resource_binding() from C); then CompilerHLSL::is_hlsl_resource_binding_used() (spvc_compiler_hlsl_is_resource_used() from C) will tell you whether or not the shader actually uses the resource.

HansKristian-Work commented 3 years ago

I'm not sure what you're doing, since I get the binding mapping. By default, HLSL output will follow SPIR-V decorations.

cbuffer _PERCAMERA : register(b1)
{
    row_major float4x4 _19_MATRIX_VP : packoffset(c0);
    float4 _19_CAMERA_POSITION : packoffset(c4);
};

cbuffer _PEROBJ : register(b2)
{
    row_major float4x4 _25_MATRIX_M : packoffset(c0);
};

cbuffer _PERFRAME : register(b0)
{
    float4 _37_Time : packoffset(c0);
};

static float4 gl_Position;
struct SPIRV_Cross_Output
{
    float4 gl_Position : SV_Position;
};

void vert_main()
{
    gl_Position = mul(float4(0.0f, 0.0f, 0.0f, 1.0f), mul(_25_MATRIX_M, _19_MATRIX_VP));
}

SPIRV_Cross_Output main()
{
    vert_main();
    SPIRV_Cross_Output stage_output;
    stage_output.gl_Position = gl_Position;
    return stage_output;
}
HansKristian-Work commented 3 years ago

Closed as not a bug since I cannot reproduce.