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;
}
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;