Mapping GLSL Flat Qualifier to SPIR-V OpDecorate Flat

This article explains how shader compilers translate the GLSL flat interpolation qualifier into SPIR-V binary code. It covers the underlying purpose of non-interpolated attributes, how the GLSL compiler associates the qualifier with intermediate representation targets, and the exact emission of OpDecorate and OpMemberDecorate instructions in the resulting SPIR-V module.

The Role of the flat Qualifier in GLSL

In GLSL, vertex output attributes and fragment input attributes are smoothly interpolated across a primitive by default. The flat qualifier explicitly disables interpolation:

// Output from vertex shader
flat out int v_primitiveID;

// Input to fragment shader
flat in int v_primitiveID;

When flat is applied, the fragment shader receives the attribute value directly from the provoking vertex of the primitive without any hardware blending. This qualifier is syntactically mandatory in GLSL for integer types (int, uint, and their vector variants) and double-precision types passing through the rasterizer.

The SPIR-V Decoration Model

SPIR-V separates the structural definition of types and variables from the metadata that alters their behavior. Metadata such as interpolation modes, layout bindings, and memory qualifiers are applied using the decoration mechanism rather than being baked into the data type itself.

To modify how an attribute is consumed across the rasterization interface, SPIR-V defines the Flat decoration:

  • Instruction: OpDecorate
  • Target: The <id> of an OpVariable
  • Decoration: Flat (value 32 in the SPIR-V core specification)

Step-by-Step Translation Process

When a compiler like glslang parses a GLSL shader containing the flat qualifier, it executes the following steps:

  1. AST Node Creation: The compiler parses the variable declaration, identifying both its storage class (in or out) and its auxiliary interpolation qualifier (flat).
  2. Intermediate Variable Allocation: An OpVariable instruction is generated in the SPIR-V module with a storage class of either Input or Output.
  3. Decoration Generation: The compiler emits an OpDecorate instruction targeting the variable’s ID, specifying the Flat decoration before the module's declaration section closes.

Standalone Variable Example

Given the GLSL declaration:

flat out int v_entityID;

The resulting SPIR-V disassembly takes the following form:

; Module Decorations
OpDecorate %v_entityID Flat
OpDecorate %v_entityID Location 0

; Type Declarations
%int = OpTypeInt 32 1
%ptr_Output_int = OpTypePointer Output %int

; Variable Definition
%v_entityID = OpVariable %ptr_Output_int Output

The OpDecorate %v_entityID Flat instruction informs the Vulkan or OpenGL driver's compiler that the pipeline must bypass the barycentric interpolation logic for this specific I/O location.

Handling Interface Blocks with OpMemberDecorate

When the flat qualifier is applied to individual members inside a GLSL interface block rather than an individual variable, SPIR-V shifts from OpDecorate to OpMemberDecorate.

Consider the following GLSL interface block:

out VertexData {
    vec3 normal;
    flat int materialID;
} outData;

In this scenario, flat applies only to the second field of the structure. The SPIR-V representation targets the composite type rather than the global variable:

; Member Decorations
OpMemberDecorate %VertexData 1 Flat
OpMemberDecorate %VertexData 0 Location 0
OpMemberDecorate %VertexData 1 Location 1

; Type and Variable Declarations
%VertexData = OpTypeStruct %v3float %int
%ptr_Output_VertexData = OpTypePointer Output %VertexData
%outData = OpVariable %ptr_Output_VertexData Output

OpMemberDecorate takes the struct type ID, the zero-based index of the target member (in this case, index 1 for materialID), and the Flat decoration.

SPIR-V Validation Requirements for Integers

The SPIR-V specification enforces that any pipeline input or output variable of an integer, 64-bit float, or boolean type must be decorated with Flat. Because of this rule, if a compiler encounters an interface variable with an integer data type, it will implicitly generate the OpDecorate %id Flat instruction to ensure the generated SPIR-V passes validation, conforming directly to the underlying hardware execution model.