Skip to content

Clarification on mixed interpolation in nested structures containing integers #2770

Description

@s-perron

Description

According to
VUID-StandaloneSpirv-Flat-04744:

"Any interface variable of integer or double-precision type, or a structure
type containing an integer or double-precision type, that is in the Input
storage class of a fragment shader, must be decorated with Flat."

It does not say that structures containing integers must be decorated with the
flat decoration (e.g., struct T { struct S { int i; float f; } s; }). This
seems like an inconsistency. Even though I assume i should be flat because all
integers have to be flat while the float f is interpolated. Making i flat is not explict as in other cases.

Note that we cannot apply the flat decoration to i itself because the SPIR-V Specification
(Section 2.16.2 "Validation Rules for Shader Capabilities") forbids decorating nested structure type
members:

"If applied to structure-type members, the decorations Noperspective, Flat,
Patch, Centroid, and Sample must be applied only to the top-level members of
the structure type. (Nested objects' types must not be structures whose
members are decorated with these decorations.)"

I believe there should be a clarification in the spec that says that i is
implicitly flat in this case, or make this case invalid because the member s in
T must also be declared as flat.

Note that GLSL is much less general, and that this example cannot be generated
from GLSL (https://godbolt.org/z/9qc36dena).

This SPIR-V cannot be generated from HLSL either.
HLSL allows applying nointerpolation to individual struct
members, and in fact implicitly infers it for all integer interface members
if omitted:

struct S {
  int i; // nointerpolation is implicit here in HLSL
  float f;
};

struct T { S s; };

However, when compiling this HLSL to
SPIR-V using DXC (with or without explicit nointerpolation), the compiler
avoids the nested struct limitation by completely flattening the interface
structure into individual variables at the SPIR-V level (e.g., generating two
separate Input variables: a flat int at Location 0, and a float at
Location 1). See https://godbolt.org/z/916rvE9M7.

Steps to reproduce

Run the following amber script using amber:

amber nested_struct_without_flat.amber 
#!amber

SHADER vertex vert_shader SPIRV-ASM
; SPIR-V
; Version: 1.0
; Bound: 40
; Schema: 0
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %main "main" %position %out_var %gl_Position
               OpName %main "main"
               OpName %position "position"
               OpName %S "S"
               OpMemberName %S 0 "i"
               OpMemberName %S 1 "f"
               OpName %T "T"
               OpMemberName %T 0 "s"
               OpName %out_var "out_var"
               OpName %gl_PerVertex "gl_PerVertex"
               OpMemberName %gl_PerVertex 0 "gl_Position"
               OpName %gl_Position "gl_Position"
               OpDecorate %position Location 0
               OpDecorate %out_var Location 0
               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
               OpDecorate %gl_PerVertex Block
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v4float = OpTypeVector %float 4
%_ptr_Input_v4float = OpTypePointer Input %v4float
   %position = OpVariable %_ptr_Input_v4float Input
        %int = OpTypeInt 32 1
          %S = OpTypeStruct %int %float
          %T = OpTypeStruct %S
%_ptr_Output_T = OpTypePointer Output %T
    %out_var = OpVariable %_ptr_Output_T Output
%gl_PerVertex = OpTypeStruct %v4float
%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
%gl_Position = OpVariable %_ptr_Output_gl_PerVertex Output
      %int_0 = OpConstant %int 0
      %int_1 = OpConstant %int 1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_ptr_Output_int = OpTypePointer Output %int
%_ptr_Output_float = OpTypePointer Output %float
       %main = OpFunction %void None %3
          %5 = OpLabel
         %15 = OpLoad %v4float %position
         %17 = OpAccessChain %_ptr_Output_v4float %gl_Position %int_0
               OpStore %17 %15
         %18 = OpCompositeExtract %float %15 0 ; Extract position.x
         %19 = OpConvertFToS %int %18            ; Convert to int
         %20 = OpAccessChain %_ptr_Output_int %out_var %int_0 %int_0
               OpStore %20 %19
         %22 = OpAccessChain %_ptr_Output_float %out_var %int_0 %int_1
               OpStore %22 %18
               OpReturn
               OpFunctionEnd
END

SHADER fragment frag_shader SPIRV-ASM
; SPIR-V
; Version: 1.0
; Bound: 25
; Schema: 0
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %main "main" %in_var %out_var_int %out_var_float
               OpExecutionMode %main OriginUpperLeft
               OpName %main "main"
               OpName %S "S"
               OpMemberName %S 0 "i"
               OpMemberName %S 1 "f"
               OpName %T "T"
               OpMemberName %T 0 "s"
               OpName %in_var "in_var"
               OpName %out_var_int "out_var_int"
               OpName %out_var_float "out_var_float"
               OpDecorate %in_var Location 0
               ; OpDecorate %in_var Flat  <-- Removed
               OpDecorate %out_var_int Location 0
               OpDecorate %out_var_float Location 1
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
        %int = OpTypeInt 32 1
      %float = OpTypeFloat 32
          %S = OpTypeStruct %int %float
          %T = OpTypeStruct %S
%_ptr_Input_T = OpTypePointer Input %T
     %in_var = OpVariable %_ptr_Input_T Input
%_ptr_Output_int = OpTypePointer Output %int
%out_var_int = OpVariable %_ptr_Output_int Output
%_ptr_Output_float = OpTypePointer Output %float
%out_var_float = OpVariable %_ptr_Output_float Output
      %int_0 = OpConstant %int 0
      %int_1 = OpConstant %int 1
%_ptr_Input_int = OpTypePointer Input %int
%_ptr_Input_float = OpTypePointer Input %float
       %main = OpFunction %void None %3
          %5 = OpLabel
         %15 = OpAccessChain %_ptr_Input_int %in_var %int_0 %int_0
         %16 = OpLoad %int %15
               OpStore %out_var_int %16
         %17 = OpAccessChain %_ptr_Input_float %in_var %int_0 %int_1
         %18 = OpLoad %float %17
               OpStore %out_var_float %18
               OpReturn
               OpFunctionEnd
END

BUFFER position_buf DATA_TYPE vec4<float> DATA
-1.0 -1.0 0.0 1.0
 1.0 -1.0 0.0 1.0
-1.0  1.0 0.0 1.0
 1.0  1.0 0.0 1.0
END

BUFFER out_int_buf FORMAT R32_SINT
BUFFER out_float_buf FORMAT R32_SFLOAT

PIPELINE graphics pipeline
  ATTACH vert_shader
  ATTACH frag_shader
  VERTEX_DATA position_buf LOCATION 0
  BIND BUFFER out_int_buf AS color LOCATION 0
  BIND BUFFER out_float_buf AS color LOCATION 1
  FRAMEBUFFER_SIZE 3 3
END

CLEAR pipeline
RUN pipeline DRAW_ARRAY AS TRIANGLE_STRIP START_IDX 0 COUNT 4
# Without flat, the center pixel should be 0 (for both, but int might be 0 due to failure or interpolation).
EXPECT out_int_buf IDX 4 EQ -1
EXPECT out_float_buf IDX 4 TOLERANCE 0.001 EQ 0.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions