Vulkan Environment for SPIR-V

Shaders for Vulkan are defined by the Khronos SPIR-V Specification as well as the Khronos SPIR-V Extended Instructions for GLSL Specification. This appendix defines additional SPIR-V requirements applying to Vulkan shaders.

Versions and Formats

A Vulkan {api-version} implementation must support the {spirv-versions} of SPIR-V and the 1.0 version of the SPIR-V Extended Instructions for GLSL. If the VK_KHR_spirv_1_4 extension is enabled, the implementation must additionally support the 1.4 version of SPIR-V.

A SPIR-V module passed into vkCreateShaderModule is interpreted as a series of 32-bit words in host endianness, with literal strings packed as described in section 2.2 of the SPIR-V Specification. The first few words of the SPIR-V module must be a magic number and a SPIR-V version number, as described in section 2.3 of the SPIR-V Specification.

Capabilities

The table below lists the set of SPIR-V capabilities that may be supported in Vulkan implementations. The application must not use any of these capabilities in SPIR-V passed to vkCreateShaderModule unless one of the following conditions is met for the VkDevice specified in the device parameter of vkCreateShaderModule:

  • The corresponding field in the table is blank.
  • Any corresponding Vulkan feature is enabled.
  • Any corresponding Vulkan extension is enabled.
  • Any corresponding Vulkan property is supported.
  • The corresponding core version is supported (as returned by VkPhysicalDeviceProperties::apiVersion).
Table 96. List of SPIR-V Capabilities and corresponding Vulkan features, extensions, or core version
SPIR-V OpCapability {captableindent} Vulkan feature, extension, or core version {generated}/spirvcap/captable.adoc

The application must not pass a SPIR-V module containing any of the following to vkCreateShaderModule:

  • any OpCapability not listed above,
  • an unsupported capability, or
  • a capability which corresponds to a Vulkan feature or extension which has not been enabled.

SPIR-V Extensions

The following table lists SPIR-V extensions that implementations may support. The application must not pass a SPIR-V module to vkCreateShaderModule that uses the following SPIR-V extensions unless one of the following conditions is met for the VkDevice specified in the device parameter of vkCreateShaderModule:

  • Any corresponding Vulkan extension is enabled.
  • The corresponding core version is supported (as returned by VkPhysicalDeviceProperties::apiVersion).
Table 97. List of SPIR-V Extensions and corresponding Vulkan extensions or core version
SPIR-V OpExtension {captableindent} Vulkan extension or core version {generated}/spirvcap/exttable.adoc

Validation Rules Within a Module

A SPIR-V module passed to vkCreateShaderModule must conform to the following rules:

Standalone SPIR-V Validation

StandaloneSpirvStandalone SPIR-V Validation

Runtime SPIR-V Validation

RuntimeSpirvRuntime SPIR-V Validation

Precision and Operation of SPIR-V Instructions

The following rules apply to half, single, and double-precision floating-point instructions:

  • Positive and negative infinities and positive and negative zeros are generated as dictated by IEEE 754, but subject to the precisions allowed in the table below.
  • Signaling NaNs are not required to be generated and exceptions are never raised. Signaling NaN may be converted to quiet NaNs values by any floating-point instruction.
  • The set of operations OpPhi, OpSelect, OpFunctionCall, OpReturnValue, OpVectorExtractDynamic, OpVectorInsertDynamic, OpVectorShuffle, OpCompositeConstruct, OpCompositeExtract, OpCompositeInsert, OpTranspose, OpCopyObject, OpCopyLogical, OpCopyMemory, OpSubgroupReadInvocationKHR, OpSubgroupFirstInvocationKHR, OpGroupNonUniformRotateKHR, OpCooperativeMatrixLoadKHR, OpCooperativeMatrixStoreKHR, OpCooperativeMatrixLoadNV, OpCooperativeMatrixStoreNV, OpAtomicLoad, OpAtomicStore, OpAtomicExchange, OpStore, and OpLoad are referred to as bit-preserving operations.
  • The floating-point environment used for an instruction can be determined as follows:
    • If the SPIR-V specifies it explicitly using the FPFastMath decoration or FPFastMathDefault Execution Mode then that is used.
    • If the environment is not specified in the SPIR-V then it is determined as follows:
      • If the operation is not decorated NoContraction then the flags AllowContract, AllowReassoc, AllowRecip, and AllowTransform are assumed.
      • If any of the following conditions are true then the flags NSZ, NotInf, and NotNaN are assumed:
        • The entry point does not use the Execution Mode
          SignedZeroInfNanPreserve with a bit-width corresponding to one of the operands or to the result type.
        • The operation is not a bit-preserving operation and is not one of OpFConvert, OpFNegate, OpFAdd, OpFSub, OpFMul, OpIsNan, or OpIsInf.
        • The operation is an OpLoad from the Input Storage Class in the fragment shader stage.
  • All bit-preserving operations and the following instructions must not flush denormalized values: OpConstant, OpConstantComposite, OpSpecConstant, OpSpecConstantComposite, and OpBitcast.
  • Denormalized values are supported.
    • By default, any half, single, or double-precision denormalized value input into a shader or potentially generated by any instruction (except those listed above) or any extended instructions for GLSL in a shader may be flushed to zero.
    • If the entry point is declared with the DenormFlushToZero
      Execution Mode then for the affected instructions the denormalized result must be flushed to zero and the denormalized operands may be flushed to zero. Denormalized values obtained via unpacking an integer into a vector of values with smaller bit width and interpreting those values as floating-point numbers must be flushed to zero.
    • The following core SPIR-V instructions must respect the DenormFlushToZero Execution Mode: OpSpecConstantOp (with opcode OpFConvert), OpFConvert, OpFNegate, OpFAdd, OpFSub, OpFMul, OpFDiv, OpFRem, OpFMod, OpVectorTimesScalar, OpMatrixTimesScalar, OpVectorTimesMatrix, OpMatrixTimesVector, OpMatrixTimesMatrix, OpOuterProduct, OpDot; and the following extended instructions for GLSL: Round, RoundEven, Trunc, FAbs, Floor, Ceil, Fract, Radians, Degrees, Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Tanh, Asinh, Acosh, Atanh, Atan2, Pow, Exp, Log, Exp2, Log2, Sqrt, InverseSqrt, Determinant, MatrixInverse, Modf, ModfStruct, FMin, FMax, FClamp, FMix, Step, SmoothStep, Fma, UnpackHalf2x16, Length, Distance, Cross, Normalize, FaceForward, Reflect, Refract, NMin, NMax, and NClamp.
    • The following core SPIR-V instructions must respect the DenormPreserve Execution Mode: OpSpecConstantOp, OpFConvert, OpFNegate, OpFAdd, OpFSub, OpFMul, OpVectorTimesScalar, OpMatrixTimesScalar, OpVectorTimesMatrix, OpMatrixTimesVector, OpMatrixTimesMatrix, OpOuterProduct, OpDot, OpFOrdEqual, OpFUnordEqual, OpFOrdNotEqual, OpFUnordNotEqual, OpFOrdLessThan, OpFUnordLessThan, OpFOrdGreaterThan, OpFUnordGreaterThan, OpFOrdLessThanEqual, OpFUnordLessThanEqual, OpFOrdGreaterThanEqual, OpFUnordGreaterThanEqual; and the following extended instructions for GLSL: FAbs, FSign, Radians, Degrees, FMin, FMax, FClamp, FMix, Fma, PackHalf2x16, PackDouble2x32, UnpackHalf2x16, UnpackDouble2x32, NMin, NMax, and NClamp.

The precision of double-precision instructions is at least that of single precision.

The precision of individual operations is defined in Precision of Individual Operations. Subject to the constraints below, however, implementations may reorder or combine operations, resulting in expressions exhibiting different precisions than might be expected from the constituent operations.

Evaluation of Expressions

Implementations may rearrange floating-point operations using any of the mathematical properties governing the expressions in precise arithmetic, even where the floating- point operations do not share these properties. This includes, but is not limited to, associativity and distributivity, and may involve a different number of rounding steps than would occur if the operations were not rearranged. In shaders that use the SignedZeroInfNanPreserve Execution Mode the values must be preserved if they are generated after any rearrangement but the Execution Mode does not change which rearrangements are valid. This rearrangement can be prevented for particular operations by using the NoContraction decoration.

For example, in the absence of the NoContraction decoration implementations are allowed to implement a + b - a and a×ba{a \times b}\over{a} as b. The SignedZeroInfNanPreserve does not prevent these transformations, even though they may overflow to infinity or NaN when evaluated in floating-point.

If the NoContraction decoration is applied then operations may not be rearranged, so, for example, a + a - a must account for possible overflow to infinity. If infinities are not preserved then the expression may be replaced with a, since the replacement is exact when overflow does not occur and infinities may be replaced with undefined: values. If both NoContraction and SignedZeroInfNanPreserve are used then the result must be infinity for sufficiently large a.

Precision of Individual Operations

The precision of individual operations is defined either in terms of rounding (correctly rounded), as an error bound in ULP, or as inherited from a formula as follows:

Correctly Rounded

Operations described as correctly rounded will return the infinitely precise result, x, rounded so as to be representable in floating-point. The rounding mode is not specified, unless the entry point is declared with the RoundingModeRTE or the RoundingModeRTZ Execution Mode. These execution modes affect only correctly rounded SPIR-V instructions. These execution modes do not affect OpQuantizeToF16 and PackHalf2x16. If the rounding mode is not specified then this rounding is implementation specific, subject to the following rules. If x is exactly representable then x will be returned. Otherwise, either the floating-point value closest to and no less than x or the value closest to and no greater than x will be returned.

ULP

Where an error bound of n ULP (units in the last place) is given, for an operation with infinitely precise result x the value returned must be in the range [x - n × ulp(x), x + n × ulp(x)]. The function ulp(x) is defined as follows:

  • If there exist non-equal, finite floating-point numbers a and b such that a ≤ x ≤ b then ulp(x) is the minimum possible distance between such numbers, ulp(x)=min_a,bbaulp(x) = \mathrm{min}\_{a,b} | b - a |. If such numbers do not exist then ulp(x) is defined to be the difference between the two non-equal, finite floating-point numbers nearest to x.

Where the range of allowed return values includes any value of magnitude larger than that of the largest representable finite floating-point number, operations may, additionally, return either an infinity of the appropriate sign or the finite number with the largest magnitude of the appropriate sign. If the infinitely precise result of the operation is not mathematically defined then the value returned is undefined:.

Inherited From …​

Where an operation’s precision is described as being inherited from a formula, the result returned must be at least as accurate as the result of computing an approximation to x using a formula equivalent to the given formula applied to the supplied inputs. Specifically, the formula given may be transformed using the mathematical associativity, commutativity and distributivity of the operators involved to yield an equivalent formula. The SPIR-V precision rules, when applied to each such formula and the given input values, define a range of permitted values. If NaN is one of the permitted values then the operation may return any result, otherwise let the largest permitted value in any of the ranges be Fmax and the smallest be Fmin. The operation must return a value in the range [x - E, x + E] where E=max(xF_min,xF_max)E = \mathrm{max} \left( | x - F\_{\mathrm{min}} |, | x - F\_{\mathrm{max}} | \right) . If the entry point is declared with the DenormFlushToZero execution mode, then any intermediate denormal value(s) while evaluating the formula may be flushed to zero. Denormal final results must be flushed to zero. If the entry point is declared with the DenormPreserve Execution Mode, then denormals must be preserved throughout the formula.

For half- (16 bit) and single- (32 bit) precision instructions, precisions are required to be at least as follows:

Table 98. Precision of core SPIR-V Instructions
InstructionSingle precision, unless decorated with RelaxedPrecisionHalf precision

OpFNegate

Correct result.

OpFAdd

Correctly rounded.

OpFSub

Correctly rounded.

OpFMul, OpVectorTimesScalar, OpMatrixTimesScalar

Correctly rounded.

OpMatrixTimesVector

Inherited from .

OpVectorTimesMatrix

Inherited from .

OpMatrixTimesMatrix

Inherited from .

OpOuterProduct

Correctly rounded.

OpDot(x, y)

Inherited from .

OpIsNan, OpIsInf

Correct Result.

OpFOrdEqual, OpFUnordEqual

Correct result.

OpFOrdNotEqual, OpFUnordNotEqual

Correct result.

OpFOrdLessThan, OpFUnordLessThan

Correct result.

OpFOrdGreaterThan, OpFUnordGreaterThan

Correct result.

OpFOrdLessThanEqual, OpFUnordLessThanEqual

Correct result.

OpFOrdGreaterThanEqual, OpFUnordGreaterThanEqual

Correct result.

OpFDiv(x,y)

2.5 ULP for {vert}y{vert} = 0 or {vert}y{vert} in the range [2-126, 2126].

2.5 ULP for {vert}y{vert} = 0 or {vert}y{vert} in the range [2-14, 214].

OpFRem(x,y)

Inherited from x - y {times} trunc(x/y).

OpFMod(x,y)

Inherited from x - y {times} floor(x/y).

OpQuantizeToF16

Correctly rounded.

conversions between types

Correctly rounded.

The OpFRem and OpFMod instructions use cheap approximations of remainder, and the error can be large due to the discontinuity in trunc() and floor(). This can produce mathematically unexpected results in some cases, such as FMod(x,x) computing x rather than 0, and can also cause the result to have a different sign than the infinitely precise result.

Table 99. Precision of GLSL.std.450 Instructions
InstructionSingle precision, unless decorated with RelaxedPrecisionHalf precision

fma()

Inherited from OpFMul followed by OpFAdd.

exp(x), exp2(x)

ULP.
ULP.

log(), log2()

3 ULP outside the range . Absolute error < inside the range .
3 ULP outside the range . Absolute error < inside the range .

pow(x, y)

Inherited from exp2(y {times} log2(x)).

sqrt()

Inherited from 1.0 / inversesqrt().

inversesqrt()

2 ULP.

radians(x)

Inherited from , where is a correctly rounded approximation to .

degrees(x)

Inherited from , where is a correctly rounded approximation to .

sin()

Absolute error inside the range .
Absolute error inside the range .

cos()

Absolute error inside the range .
Absolute error inside the range .

tan()

Inherited from .

asin(x)

Inherited from .

acos(x)

Inherited from .

atan(), atan2()

4096 ULP

5 ULP.

sinh(x)

Inherited from .

cosh(x)

Inherited from .

tanh()

Inherited from .

asinh(x)

Inherited from .

acosh(x)

Inherited from .

atanh(x)

Inherited from .

frexp()

Correctly rounded.

ldexp()

Correctly rounded.

length(x)

Inherited from .

distance(x, y)

Inherited from .

cross()

Inherited from OpFSub(OpFMul, OpFMul).

normalize(x)

Inherited from .

faceforward(N, I, NRef)

Inherited from dot(NRef, I) < 0.0 ? N : -N.

reflect(x, y)

Inherited from x - 2.0 {times} dot(y, x) {times} y.

refract(I, N, eta)

Inherited from k < 0.0 ? 0.0 : eta {times} I - (eta {times} dot(N, I) + sqrt(k)) {times} N, where k = 1 - eta {times} eta {times} (1.0 - dot(N, I) {times} dot(N, I)).

round

Correctly rounded.

roundEven

Correctly rounded.

trunc

Correctly rounded.

fabs

Correctly rounded.

fsign

Correctly rounded.

floor

Correctly rounded.

ceil

Correctly rounded.

fract

Correctly rounded.

modf

Correctly rounded.

fmin

Correctly rounded.

fmax

Correctly rounded.

fclamp

Correctly rounded.

fmix(x, y, a)

Inherited from .

step

Correctly rounded.

smoothStep(edge0, edge1, x)

Inherited from , where .

nmin

Correctly rounded.

nmax

Correctly rounded.

nclamp

Correctly rounded.

packHalf2x16

Correctly rounded.

GLSL.std.450 extended instructions specifically defined in terms of the above instructions inherit the above errors. GLSL.std.450 extended instructions not listed above and not defined in terms of the above have undefined: precision.

For the OpSRem and OpSMod instructions, if either operand is negative the result is undefined:.

While the OpSRem and OpSMod instructions are supported by the Vulkan environment, they require non-negative values and thus do not enable additional functionality beyond what OpUMod provides.

OpCooperativeMatrixMulAddNV performs its operations in an implementation-dependent order and internal precision.

OpCooperativeMatrixMulAddKHR performs its operations in an implementation-dependent order and internal precision.

Signedness of SPIR-V Image Accesses

SPIR-V associates a signedness with all integer image accesses. This is required in certain parts of the SPIR-V and the Vulkan image access pipeline to ensure defined results. The signedness is determined from a combination of the access instruction’s Image Operands and the underlying image’s Sampled Type as follows:

  1. If the instruction’s Image Operands contains the SignExtend operand then the access is signed.
  2. If the instruction’s Image Operands contains the ZeroExtend operand then the access is unsigned.
  3. Otherwise, the image accesses signedness matches that of the Sampled Type of the OpTypeImage being accessed.

Image Format and Type Matching

When specifying the Image Format of an OpTypeImage, the converted bit width and type, as shown in the table below, must match the Sampled Type. The signedness must match the signedness of any access to the image.

Formatted accesses are always converted from a shader readable type to the resource’s format or vice versa via Format Conversion for reads and Texel Output Format Conversion for writes. As such, the bit width and format below do not necessarily match 1:1 with what might be expected for some formats.

For a given Image Format, the Sampled Type must be the type described in the Type column of the below table, with its Literal Width set to that in the Bit Width column. Every access that is made to the image must have a signedness equal to that in the Signedness column (where applicable).

Image FormatType-Declaration instructionsBit WidthSignedness

Unknown

Any

Any

Any

Rgba32f

OpTypeFloat

32

N/A

Rg32f

R32f

Rgba16f

Rg16f

R16f

Rgba16

Rg16

R16

Rgba16Snorm

Rg16Snorm

R16Snorm

Rgb10A2

R11fG11fB10f

Rgba8

Rg8

R8

Rgba8Snorm

Rg8Snorm

R8Snorm

Rgba32i

OpTypeInt

32

1

Rg32i

R32i

Rgba16i

Rg16i

R16i

Rgba8i

Rg8i

R8i

Rgba32ui

0

Rg32ui

R32ui

Rgba16ui

Rg16ui

R16ui

Rgb10a2ui

Rgba8ui

Rg8ui

R8ui

R64i

OpTypeInt

64

1

R64ui

0

The SPIR-V Type is defined by an instruction in SPIR-V, declared with the Type-Declaration Instruction, Bit Width, and Signedness from above.

Compatibility Between SPIR-V Image Dimensions and Vulkan ImageView Types

SPIR-V Image Dim values are compatible with VkImageView

viewType values as defined below:

Table 100. SPIR-V and Vulkan ImageView Dimension Compatibility
SPIR-V Image DimCompatible Vulkan ImageView viewTypes

1D

VK_IMAGE_VIEW_TYPE_1D, VK_IMAGE_VIEW_TYPE_1D_ARRAY

2D

VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_VIEW_TYPE_2D_ARRAY

3D

VK_IMAGE_VIEW_TYPE_3D

Cube

VK_IMAGE_VIEW_TYPE_CUBE, VK_IMAGE_VIEW_TYPE_CUBE_ARRAY

Compatibility Between SPIR-V Image Formats and Vulkan Formats

SPIR-V Image Format values are compatible with VkFormat values as defined below:

Table 101. SPIR-V and Vulkan Image Format Compatibility
SPIR-V Image FormatCompatible Vulkan Format

Unknown

Any

R8

VK_FORMAT_R8_UNORM

R8Snorm

VK_FORMAT_R8_SNORM

R8ui

VK_FORMAT_R8_UINT

R8i

VK_FORMAT_R8_SINT

Rg8

VK_FORMAT_R8G8_UNORM

Rg8Snorm

VK_FORMAT_R8G8_SNORM

Rg8ui

VK_FORMAT_R8G8_UINT

Rg8i

VK_FORMAT_R8G8_SINT

Rgba8

VK_FORMAT_R8G8B8A8_UNORM

Rgba8Snorm

VK_FORMAT_R8G8B8A8_SNORM

Rgba8ui

VK_FORMAT_R8G8B8A8_UINT

Rgba8i

VK_FORMAT_R8G8B8A8_SINT

Rgb10A2

VK_FORMAT_A2B10G10R10_UNORM_PACK32

Rgb10a2ui

VK_FORMAT_A2B10G10R10_UINT_PACK32

R16

VK_FORMAT_R16_UNORM

R16Snorm

VK_FORMAT_R16_SNORM

R16ui

VK_FORMAT_R16_UINT

R16i

VK_FORMAT_R16_SINT

R16f

VK_FORMAT_R16_SFLOAT

Rg16

VK_FORMAT_R16G16_UNORM

Rg16Snorm

VK_FORMAT_R16G16_SNORM

Rg16ui

VK_FORMAT_R16G16_UINT

Rg16i

VK_FORMAT_R16G16_SINT

Rg16f

VK_FORMAT_R16G16_SFLOAT

Rgba16

VK_FORMAT_R16G16B16A16_UNORM

Rgba16Snorm

VK_FORMAT_R16G16B16A16_SNORM

Rgba16ui

VK_FORMAT_R16G16B16A16_UINT

Rgba16i

VK_FORMAT_R16G16B16A16_SINT

Rgba16f

VK_FORMAT_R16G16B16A16_SFLOAT

R32ui

VK_FORMAT_R32_UINT

R32i

VK_FORMAT_R32_SINT

R32f

VK_FORMAT_R32_SFLOAT

Rg32ui

VK_FORMAT_R32G32_UINT

Rg32i

VK_FORMAT_R32G32_SINT

Rg32f

VK_FORMAT_R32G32_SFLOAT

Rgba32ui

VK_FORMAT_R32G32B32A32_UINT

Rgba32i

VK_FORMAT_R32G32B32A32_SINT

Rgba32f

VK_FORMAT_R32G32B32A32_SFLOAT

R64ui

VK_FORMAT_R64_UINT

R64i

VK_FORMAT_R64_SINT

R11fG11fB10f

VK_FORMAT_B10G11R11_UFLOAT_PACK32

Ray Query Precision and Operation

The values returned by OpRayQueryGetIntersectionTriangleVertexPositionsKHR are transformed by the geometry transform, which is performed at standard floating-point precision, but without a specifically defined order of floating-point operations to perform the matrix multiplication.