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.01k stars 556 forks source link

HLSL: Using half in constant buffer may result in exception for SM 6.2 with native 16bits types #1809

Open jzm-intel opened 2 years ago

jzm-intel commented 2 years ago

When using half in constant buffer in HLSL, SPIRV-Cross may failed to translate the spirv generated by dxc back to HLSL, but throwing an exception read Cannot pack on tighter bounds than 4 bytes in HLSL.

For example, I use the simple HLSL shader:

struct CBData
{
    half Scalar0;  // This half cause the exception
    half4 Color;
};

ConstantBuffer<CBData> CB;

float4 PSMain() : SV_Target0
{
    return pow(CB.Color, 2.2h);
}

under SM 6.2 with native fp16 enabled. I use dxc to generate the SPIRV code like dxc -T ps_6_2 -E PSMain -enable-16bit-types -spirv -fspv-target-env="vulkan1.2" .\test0.hlsl -Fo test0.spv . The generated SPIRV (assembly) is something like

; SPIR-V
; Version: 1.5
; Generator: Google spiregg; 0
; Bound: 24
; Schema: 0
               OpCapability Shader
               OpCapability Float16
               OpCapability UniformAndStorageBuffer16BitAccess
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %PSMain "PSMain" %CB %out_var_SV_Target0
               OpExecutionMode %PSMain OriginUpperLeft
               OpSource HLSL 620
               OpName %type_ConstantBuffer_CBData "type.ConstantBuffer.CBData"
               OpMemberName %type_ConstantBuffer_CBData 0 "Scalar0"
               OpMemberName %type_ConstantBuffer_CBData 1 "Color"
               OpName %CB "CB"
               OpName %out_var_SV_Target0 "out.var.SV_Target0"
               OpName %PSMain "PSMain"
               OpDecorate %out_var_SV_Target0 Location 0
               OpDecorate %CB DescriptorSet 0
               OpDecorate %CB Binding 0
               OpMemberDecorate %type_ConstantBuffer_CBData 0 Offset 0
               OpMemberDecorate %type_ConstantBuffer_CBData 1 Offset 2
               OpDecorate %type_ConstantBuffer_CBData Block
        %int = OpTypeInt 32 1
      %int_1 = OpConstant %int 1
       %half = OpTypeFloat 16
%half_0x1_198p_1 = OpConstant %half 0x1.198p+1
     %v4half = OpTypeVector %half 4
         %11 = OpConstantComposite %v4half %half_0x1_198p_1 %half_0x1_198p_1 %half_0x1_198p_1 %half_0x1_198p_1
%type_ConstantBuffer_CBData = OpTypeStruct %half %v4half
%_ptr_Uniform_type_ConstantBuffer_CBData = OpTypePointer Uniform %type_ConstantBuffer_CBData
      %float = OpTypeFloat 32
    %v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
       %void = OpTypeVoid
         %17 = OpTypeFunction %void
%_ptr_Uniform_v4half = OpTypePointer Uniform %v4half
         %CB = OpVariable %_ptr_Uniform_type_ConstantBuffer_CBData Uniform
%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
     %PSMain = OpFunction %void None %17
         %19 = OpLabel
         %20 = OpAccessChain %_ptr_Uniform_v4half %CB %int_1
         %21 = OpLoad %v4half %20
         %22 = OpExtInst %v4half %1 Pow %21 %11
         %23 = OpFConvert %v4float %22
               OpStore %out_var_SV_Target0 %23
               OpReturn
               OpFunctionEnd

Then I use SPIRV-Cross to translate the generated SIPRV back to HLSL using .\spirv-cross.exe PATH\TO\test0.spv --hlsl --shader-model 62 --hlsl-enable-16bit-types, and I get the exception

SPIRV-Cross threw an exception: Cannot pack on tighter bounds than 4 bytes in HLSL.

On the other hand, when I comment out the Scalar0 in HLSL source, SPIRV-Cross works well, resulting in the following HLSL

cbuffer type_ConstantBuffer_CBData : register(b0, space0)
{
    half4 CB_Color : packoffset(c0);
};

static float4 out_var_SV_Target0;

struct SPIRV_Cross_Output
{
    float4 out_var_SV_Target0 : SV_Target0;
};

void frag_main()
{
    out_var_SV_Target0 = float4(pow(CB_Color, half(2.19921875).xxxx));
}

SPIRV_Cross_Output main()
{
    frag_main();
    SPIRV_Cross_Output stage_output;
    stage_output.out_var_SV_Target0 = out_var_SV_Target0;
    return stage_output;
}

It seems that SPIRV-Cross will use packoffset in the generated HLSL to describe to constant buffer layout, but it can only describe an offset aligned to 4 bytes (like c1.y referred to offset 16+4=20 bytes), while in HLSL SM 6.2 with native 16 bits type such 16 bits data can be packed into a single 16 bytes row in constant buffer, and multiple half (or vector of half) in such a row can align to 2 bytes rather than 4 bytes, which make packoffset invalid, and result in the exception.

HansKristian-Work commented 2 years ago

So how would you express packoffset at 2 bytes? If that is not possible, I don't see what I can do.

jzm-intel commented 2 years ago

Thanks for your kindly reply. I am not sure, but this case only occurs in SM6.2 which required dxc, and it provides a Vulkan-specific attribute, [[vk:offset()]] (Please see [1](https://github.com/Microsoft/DirectXShaderCompiler/blob/master/docs/SPIR-V.rst#memory-layout-rules:~:text=The%20Vulkan-specific%20%5B%5Bvk%3A%3Aoffset()%5D%5D%20attribute%3A%20applies%20to%20all%20resources.), 2), although I think it only effect the SPIRV codegen in dxc. On the other hand, is it possible that some layout attributes can be omitted if identical to default layouts? In this way some cases that doesn't fit in packoffset may just work with default layout. Thanks and sorry for troubling you.