SPIR-V
Standard Portable Intermediate Representation (SPIR) is an intermediate language for shader or kernel code. It enables cross-compilation of shaders to byte-code. SPIR-V is the newest version of SPIR (introduced in 2015).
With SPIRV-Cross, one can translate compiled SPIR-V bytecode to high level formats like HLSL and MSL, to allow for more portability.
Reasoning
Every GPU manufacturer has a unique GPU architecture and ISA and multiple generations of their architecture are supported at the same time. NVIDIA has Lovelace, Ampere, Turing, and more. AMD has RDNA3, RDNA2, and more.
When bytecode is submitted to the driver, it has to compile that bytecode specifically for the graphics hardware on your machine. This means that a compiled shader is only executable on a specific device (and often only with a specific driver version).
SPIR-V attempts to be a standard intermediate representation for this bytecode, but there are competing format for every graphics API:
- Vulkan: SPIR-V
- D3D: DXIL/DXBC
- Metal: AIR
Workflow (using SPIRV-Cross)
D3D11:
- Write shader using HLSL.
- At runtime or build time, you call D3DCompile to emit DXBC (DirectX Bytecode).
- At runtime, pass bytecode to
ID3D11Device_CreateVertexShader
to obtain a shader object.
Vulkan:
- Write shader using HLSL (with SPIR-V binding annotations)
- At build time you use a tool like
glslang
to emit SPIR-V bytecode. - At runtime, call
vkCreateShaderModule
to obtain a shader object.
Metal:
- Write shader using HLSL.
- At build time, emit SPIR-V bytecode.
- Use SPIRV-Cross to translate SPIR-V to MSL.
- At runtime, call Metal's
newLibraryWithSource
to obtain shader object.
Problems when providing a single interface to accommodate all backend-specifics:
- A shader object needs to be part of a pipeline object.
- Pipeline needs to be made aware of the vertex input structure and the data resources (textures, samplers, buffers) which are used by the shader.
- There is no universal method for extracting this information from shader code:
- Provide it by hand, or…
- reflect on code (language-specific, expensive at runtime, maybe unavailable)
- Backend-specific deviations:
- Workgroup size is usually provided in shader bytecode.
- On Metal however, client provides it at dispatch time.