Search code examples
hlsl

HLSL 6.0 Vertex Shader returns wrong type without error


Can anyone help me understand why the following HLSL doesn't produce a warning or error? I'm defining a type and returning it, but it doesn't match the function's return type. Is this allowed for any particular reason, or would this be a bug to report (to compiler team)?

I would assume it was a bug, but this seems like a pretty strange and obvious thing to go unnoticed. If not, is there a reason something like this would be allowed? The types are the same size, and possibly could be compatible, but I still wouldn't expect this to work.

The version of my dxc compiler is 1.7.2207.

struct vinBake
{
    float4      Position        : ATTRIB0;      // local position of the vertex
    float4      Color           : ATTRIB1;      // color channels
    float3      TexCoord        : ATTRIB2;      // UV Texture Coordinates (z value represents texture index, if used)
    float4      Prop            : ATTRIB3;      // enhanced logic properties
    float4      Attr            : ATTRIB4;      // enhanced logic attributes
};
struct lerpBlit
{
    float4      ClipPos         : SV_POSITION;  // projected clip-space screen position of vertex
    float4      Diffuse         : COLOR0;       // diffuse color
    float3      Tex             : TEXCOORD0;    // tex coords (x,y) + texture array index (z)
};
struct lerpLine
{
    float4      ClipPos         : SV_POSITION;  // projected clip-space screen position of vertex
    float4      Diffuse         : COLOR0;       // diffuse color
    float       Factor          : TEXCOORD0;    // factor value of this position (0->1)
    float       Thickness       : TEXCOORD1;    // thickness of line
    float       Feather         : TEXCOORD2;    // falloff of line
};
lerpBlit main(vinBake vin)
{
    lerpLine pin;
    pin.ClipPos = float4(0,0,0,1);
    pin.Diffuse = float4(1,1,1,1);
    pin.Factor = 0;
    pin.Thickness = 0;
    pin.Feather = 0;
    return pin;
}

Solution

  • You observe implicit casting between structure types that is not a bug.

    Two your structs lerpBlit and lerpBlit are structural identical, they have the same byte size and all underlying types can be implicitly casted if we flatten both structs. Floats Factor, Thickness and Feather are combined to float3 Tex that is just a composite type, array of 3 floats. ClipPos and Diffuse are mapped 1-to-1. It means they are eligible for implicit casting as in your case.

    More than that, they can be casted explicitly if right-hand type has more size.

    For example:

    struct lerpBlit
    {
        float4      ClipPos         : SV_POSITION;  // projected clip-space screen position of vertex
        float4      Diffuse         : COLOR0;       // diffuse color
        float3      Tex             : TEXCOORD0;    // tex coords (x,y) + texture array index (z)
    };
    struct lerpLine
    {
        float4      ClipPos         : SV_POSITION;  // projected clip-space screen position of vertex
        float4      Diffuse         : COLOR0;       // diffuse color
        float       Factor          : TEXCOORD0;    // factor value of this position (0->1)
        float       Thickness       : TEXCOORD1;    // thickness of line
        float       Feather         : TEXCOORD2;    // falloff of line
        float       NewVar          : TEXCOORD3;   
    };
    lerpBlit main()
    {
        lerpLine pin;
        pin.ClipPos = float4(0,0,0,1);
        pin.Diffuse = float4(1,1,1,1);
        pin.Factor = 0;
        pin.Thickness = 0;
        pin.Feather = 0;
        pin.Feather = 0;
        pin.NewVar = 42;
        
        // return pin; // Error! No implicit casting more.
        return (lerpBlit)pin; // That's fine, sizeof(lerpLine) >= sizeof(lerpBlit)
    }
    

    Additionally, you can output DXIL to check how your structs are converted and what output is.

    dxbc2dxil.exe <file_with_dxc.exe_output> /disasm-dxbc
    

    Part of the output for your case is below:

    
    ; Output signature:
    ;
    ; Name                 Index             InterpMode DynIdx
    ; -------------------- ----- ---------------------- ------
    ; SV_Position              0          noperspective       
    ; COLOR                    0                 linear       
    ; TEXCOORD                 0                 linear     
    
      call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0, float 0.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 1, float 0.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 2, float 0.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 3, float 1.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 0, float 1.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 1, float 1.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 2, float 1.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 3, float 1.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 2, i32 0, i8 0, float 0.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 2, i32 0, i8 1, float 0.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
      call void @dx.op.storeOutput.f32(i32 5, i32 2, i32 0, i8 2, float 0.000000e+00), !dbg !83 ; line:31 col:12  ; StoreOutput(outputSigId,rowIndex,colIndex,value)
    

    With regards to reasons why it's so. I assume it's a part of support for composite types that allow you to write the code like that:

        struct {float x, y, z;} s;
        struct S {float r, g, b;} s2;
        struct {float r, g, b, a;} s3;
        s2 = s;
        s2 = (S)s3;
        
        float3 v;
        float4 v2;
        v = (float3)v2;