Embedding Raw SPIR-V in GLSL with spirv_instruction

The spirv_instruction attribute enables developers to embed arbitrary, raw SPIR-V instructions directly within GLSL shaders through function prototypes. Enabled by the GL_EXT_spirv_intrinsics extension, this feature bridges the gap between the high-level shading language and lower-level SPIR-V intermediate representation, allowing the immediate use of new GPU features, hardware-specific capabilities, or custom SPIR-V opcodes without waiting for official GLSL language revisions or compiler updates.

Enabling the Extension

To use the spirv_instruction attribute, shaders must target the SPIR-V execution environment and enable the extension:

#version 460
#extension GL_EXT_spirv_intrinsics : require

Syntax and Mapping Mechanism

The attribute is applied to a function declaration that has no implementation body. Instead of generating a standard GLSL function call, the compiler treats calls to this function as direct emissions of the specified SPIR-V opcode.

The syntax requires providing the numeric SPIR-V opcode identifier via the id argument:

[[spirv::instruction(id = <opcode_number>)]]
ReturnType functionName(ParameterType1 arg1, ParameterType2 arg2);

When invoked, the compiler converts the declaration into a SPIR-V instruction according to the following rules:

  • Result Type: The GLSL return type defines the SPIR-V instruction's Result Type. If the function returns void, the generated instruction has no Result Type.
  • Result <id>: If the instruction generates a value, the compiler automatically allocates a new SPIR-V <id> to hold the result.
  • Operands: Function parameters are passed sequentially as operands to the SPIR-V instruction in the order they are declared.

Extended Instruction Sets

For instructions belonging to extended instruction sets (such as GLSL.std.450 or vendor-specific sets like OpenCL or Non-Semantic debug instructions), the attribute accepts an extended set identifier:

[[spirv::instruction(set = "GLSL.std.450", id = 12)]]
float inlineFastSqrt(float val);

The set parameter can take a string literal naming the import or an integer referencing a previously defined set extension. The compiler automatically handles the SPIR-V OpExtInstImport and emits an OpExtInst instruction referencing that set.

Practical Implementation Example

The following example demonstrates declaring and calling a raw integer addition instruction (OpIAdd, opcode 128) instead of relying on the standard GLSL + operator:

#version 460
#extension GL_EXT_spirv_intrinsics : require

// Define OpIAdd (SPIR-V Opcode 128)
[[spirv::instruction(id = 128)]]
int rawIntegerAdd(int a, int b);

layout(location = 0) out int result;

void main() {
    int x = 10;
    int y = 20;
    // Emits OpIAdd into the output SPIR-V stream
    result = rawIntegerAdd(x, y);
}

Parameter Qualification and Capabilities

Parameters can include attributes to configure how operands are emitted into the instruction stream:

  • Literal Operands: Operands can be marked to emit as constant immediate literals rather than evaluated ID operands, accommodating instructions that demand fixed literal integer arguments.
  • Required Capabilities: Shaders can pair function definitions with the [[spirv::capability(...)]] attribute, ensuring the compiler inserts required OpCapability declarations into the SPIR-V header to match the injected instruction.