How spirv_literal Works in GLSL SPIR-V Intrinsics

This article provides an overview of the spirv_literal qualifier introduced by the GL_EXT_spirv_intrinsics GLSL extension. It explains the distinction between SPIR-V <id> operands and literal operands, demonstrates how spirv_literal forces compile-time constant values to be emitted directly into the SPIR-V instruction binary stream, and outlines the syntactical requirements for using it properly in shader code.

The Problem: SPIR-V IDs vs. Literals

In the SPIR-V binary format, instruction operands are strictly categorized into two primary forms:

  1. ID References (<id>): Pointers to types, variables, intermediate values, or constants declared elsewhere in the module via instructions such as OpConstant.
  2. Literal Numbers: Raw, immediate values encoded directly within the binary words of the instruction itself. These are typically used for bitmasks, enumeration values, memory access flags, and execution modes.

Standard GLSL function arguments are evaluated to values or variables, which compilers like glslang map to SPIR-V <id> operands. If a SPIR-V opcode expects an immediate literal operand rather than an IdRef, passing a standard GLSL expression or even an OpConstant reference will cause validation errors or compilation failure because an <id> is supplied where raw binary data is required.

The Role of spirv_literal

The GL_EXT_spirv_intrinsics extension resolves this issue by providing the spirv_literal parameter qualifier. When applied to an argument in a SPIR-V intrinsic function declaration, spirv_literal informs the compiler that the value passed to this parameter must not be converted into an <id>. Instead, the compiler takes the evaluated constant integer or floating-point value and embeds it as an immediate numeric literal directly into the SPIR-V instruction's word stream.

Syntax and Implementation

To use spirv_literal, the shader must enable the GL_EXT_spirv_intrinsics extension. The qualifier is then declared in front of parameters that correspond directly to literal slots in the target SPIR-V instruction.

#version 460
#extension GL_EXT_spirv_intrinsics : require

// OpMemoryBarrier takes two literal operands:
// 1. Memory Semantics <id> or Scope <id> depending on specification, 
// but certain opcodes or custom instructions demand literal values.
// Below illustrates an intrinsic where the second operand must be a raw literal:
spirv_instruction(id = 224) // OpMemoryBarrier
void spirv_memory_barrier(
    uint scope,
    spirv_literal uint semantics
);

When calling spirv_memory_barrier:

void main() {
    // Correct: Passing a compile-time constant for spirv_literal
    const uint AcquireReleaseMask = 0x8u;
    spirv_memory_barrier(gl_ScopeWorkgroup, AcquireReleaseMask);
}

Compiler Constraints and Rules

When using spirv_literal, several strict rules apply:

  • Compile-Time Constant Evaluation: The argument passed to a spirv_literal parameter must be a constant expression evaluated at compile time. Passing non-constant variables, function inputs, or runtime expressions results in a compile-time error.
  • Type Compatibility: The constant type in GLSL must match the expected width (such as 32-bit integer or 64-bit integer) of the corresponding SPIR-V literal operand.
  • Binary Alignment: The compiler writes the integer or floating-point bit pattern directly into the instruction word sequence without emitting prerequisite OpConstant definitions in the module header for that specific operand slot.