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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions