The Standard, Portable Intermediate Representation - V (SPIR-V) is an intermediate language for defining shaders. It is intended to be a compiler target from a number of different languages, which allows users the freedom to write shader languages however they want, while allowing implementations to not have to deal with the parsing of more complex languages like GLSL.
The SPIR-V registry contains the current SPIR-V specification, along with its extensions.
|This article is a stub. You can help the OpenGL Wiki by expanding it.|
SPIR-V's compilation model looks similar to that of GLSL, but it does have some unique characteristics.
As with GLSL, SPIR-V makes use of shader and program objects. Because SPIR-V is a binary format, SPIR-V shaders are loaded into shader objects via the use of the shader binary API:
shaders is an array of count length of previously created shader objects that the SPIR-V data will be loaded into. So this function can load the same SPIR-V source code into multiple shader objects.
SPIR-V has a specific binaryFormat: the enumerator GL_SHADER_BINARY_FORMAT_SPIR_V. The binary is the loaded SPIR-V itself, with a byte length of length. This must include the entire SPIR-V, as defined by the specification, including header information.
The use of this function will replace the shaders specified by previous calls to glShaderSource or glShaderBinary. Loading a non-SPIR-V binary or loading GLSL source strings into the program will make it no longer contain SPIR-V code.
While a shader object has a SPIR-V binary loaded into it, the object becomes slightly different. glGetShaderiv(shader, GL_SPIR_V_BINARY) will return GL_TRUE.
Entry points and specialization
SPIR-V is similar to GLSL, but it has some differences. Two differences are particularly relevant.
- A single SPIR-V file can have function entry-points for multiple shader stages, even of different types.
- SPIR-V has the concept of "specialization constants": parameters which the user can provide before the SPIR-V is compiled into its final form.
Before a SPIR-V shader object can be used, you must specify which entry-point to use and provide values for any specialization constants used by that entry-point. This is done through a single function:
pEntryPoint is the string name of the entry-point that this SPIR-V shader object will use. pConstantIndex and pConstantValue are arrays containing the index of each specialization constant and the corresponding values that will be used. These arrays are numSpecializationConstants in length. Specialization constants not referenced by pConstantIndex use the default values specified in the SPIR-V shader.
Specializing a SPIR-V shader is analogous to compiling a GLSL shader. So if this function completes successfully, the shader object's compile status is GL_TRUE. If specialization fails, then the shader infolog has information explaining why and an OpenGL Error is generated.
pEntryPoint must name a valid entry point. Also, the entry point's "execution model" (SPIR-V speak for "Shader Stage") must match the stage the shader object was created with. Specialization can also fail if pConstantIndex references a specialization constant index that the SPIR-V binary does not use. If specialization fails, the shader's info log is updated appropriately.
Once specialized, SPIR-V shaders cannot be re-specialized. However, you can reload the SPIR-V binary data into them, which will allow them to be specialized again.
SPIR-V shader objects that have been specialized can be used to link programs (separable or otherwise). If you link multiple shader objects in the same program, then either all of them must be SPIR-V shaders or none of them may be SPIR-V shaders. You cannot link SPIR-V to non-SPIR-V shaders in a program.
Also, note that SPIR-V shaders must have an entry-point. So SPIR-V modules for the same stage cannot be linked together. Each SPIR-V shader object must provide all of the code for its module.
You can use separable programs built from SPIR-V shaders in the same pipeline object as non-SPIR-V shaders.
Mapping to GLSL
The OpenGL specification still considers GLSL to be the primary OpenGL shading language. Fortunately, SPIR-V has many concepts that map to GLSL concepts. The mapping in most cases is obvious, so we will discuss the non-obvious cases here.
GLSL provides a comprehensive API for querying resource interfaces that are part of a linked program. These APIs are allowed for parts of a program that comes from SPIR-V, within one restriction.
Because SPIR-V is an intermediate language, things like names are unnecessary. As such, while SPIR-V does permit you to assign a name to a particular construct, it does not require you to do so. Therefore, any OpenGL introspection query that involves the name of a SPIR-V variable or other construct may not produce reasonable results.
If you have the index of a SPIR-V-defined resource, and you query the name, you may get an empty string (NUL-terminated). But you may also get the name assigned to it in the SPIR-V shader. Which you get is implementation-dependent.
What is not implementation-dependent is the ability to query aspects of a resource from its name. Even if the SPIR-V shader assigns the resource a name, any query by name will produce either -1 or GL_INVALID_INDEX, whichever is appropriate for the value being queried.
All other forms of introspection are perfectly valid. For example, you can query the number of default block uniforms, then iterate over each one, reliably fetching all of the usual information (type, location, etc). Except for the name, of course.
Input/output interface matching for user-defined variables in SPIR-V works by matching explicit Locations. As such, all variables that are used for input/output interfaces must have a location assigned. Even members of blocks (via structs) must have locations assigned.
An output variable for a particular Location and Component must have a corresponding input variable with the same Location and Component values.
If the type of an output variable is a built-in scalar or vector type, then the type of the corresponding input does not have to match exactly. The output must have the same basic type as the input, and it must provide at least as many components as the input receives.
If the type of the output is a struct, then the input struct must match in the types, numbers, and declaration order of their members. Output arrays match input arrays if they have the same number of array elements and the same type.
Decorations must match between corresponding variables, except for interpolation decorations.
For built-in interface variables, things are a bit more complex. In SPIR-V, you can only have one built-in input block and one built-in output block. Each such block must have all of its members decorated with BuiltIn, and the block cannot have any members decorated with Location. Lastly, the top-level members of the block must be built-in types.
Interface matching for built-in blocks requires an exact match, except for the fragment shader input block. That one (which contains all of the built-in variables) does not need to match the previous Vertex Processing stage's built-in variables. But the built-in blocks between vertex processing stages do need to match exactly.
Any shader stage entry-point that will output feedback information must explicitly use the Xfb execution mode.
SPIR-V does not support subroutines at all, so you may not use them.
SPIR-V is extensible. And working with all of OpenGL's features requires some SPIR-V extensions.