Recursive AI

The HLSL precise keyword on Vulkan, Metal and WebGPU

All oneshader recipes

What HLSL’s precise keyword does when one shader is compiled to SPIR-V, Metal Shading Language and WGSL with OneShader: NoContraction on Vulkan, fast-math pragmas off on Metal, and a warning on WebGPU, which can’t forbid fused multiply-adds.

precise on a variable tells the compiler that the arithmetic feeding it must be computed exactly as written: no fusing a multiply and an add into one FMA, no reordering sums. The classic case is geometry that has to match bit for bit across draws, such as terrain patches sharing an edge. Terrain.hlsl:

HLSL
cbuffer Patch : register(b0) { float4x4 ViewProjection; float3 Origin; float HeightScale; float3 AxisU; float3 AxisV; };
Texture2D<float> HeightMap : register(t1);
SamplerState LinearClamp : register(s2);
 
[shader("vertex")]
float4 VSMain(float2 uv : TEXCOORD0) : SV_Position
{
    float h = HeightMap.SampleLevel(LinearClamp, uv, 0);
    // Patches that share an edge must compute the same position bit for bit,
    // so the compiler may not fuse or reorder the arithmetic that feeds it.
    precise float3 world = Origin + uv.x * AxisU + uv.y * AxisV + float3(0, h * HeightScale, 0);
    return mul(ViewProjection, float4(world, 1));
}

Vulkan: NoContraction

Terminal
oneshader -T vs_6_0 -E VSMain -spirv Terrain.hlsl -Fo terrain.spv
spirv-dis terrain.spv | grep -E "NoContraction|= OpF(Mul|Add)|= OpVectorTimesScalar"
Text
               OpDecorate %41 NoContraction
               OpDecorate %42 NoContraction
               OpDecorate %48 NoContraction
               OpDecorate %49 NoContraction
               OpDecorate %54 NoContraction
               OpDecorate %56 NoContraction
         %41 = OpFMul %v3float %37 %40
         %42 = OpFAdd %v3float %35 %41
         %48 = OpFMul %v3float %44 %47
         %49 = OpFAdd %v3float %42 %48
         %54 = OpFMul %float %30 %53
         %56 = OpFAdd %v3float %49 %55

Every multiply and add that feeds world carries NoContraction, the SPIR-V decoration that forbids the driver from contracting it. DXC marks the same six operations for this shader (it writes the two vector-by-scalar products as OpVectorTimesScalar). The projection after world isn’t marked, because precise covers what feeds the variable, not what reads it. The file passes spirv-val.

Metal: fast-math pragmas off

Terminal
oneshader -T vs_6_0 -E VSMain --target msl Terrain.hlsl -Fo terrain.metal
grep -n "pragma" terrain.metal
Text
50:    #pragma clang fp reassociate(off)
51:    #pragma clang fp contract(off)

Metal compiles with fast math by default. A function with precise arithmetic starts with these two pragmas, which turn off fused multiply-add and reordered sums whatever the host’s math mode. A precise division becomes precise::divide, since no pragma stops a / b becoming a * (1 / b).

WebGPU: a warning

Terminal
oneshader -T vs_6_0 -E VSMain --target wgsl Terrain.hlsl -Fo terrain.wgsl
Text
Terrain.hlsl(11,37): warning OSH5205: 'precise' arithmetic is compiled, but WGSL cannot forbid a multiply-add being fused or a sum being reordered, so the result may differ in the last bits from the other targets. Use 'fma' where the exact rounding matters, or leave WGSL out of the targets for this shader.

WGSL lets an implementation fuse a multiply and an add, and has no way to say otherwise. The shader compiles (naga validates the output), the warning appears once per entry point, and the result may differ from Vulkan and Metal in the last bits. With -WX on the command line, or MSBuildTreatWarningsAsErrors set to true in a build-package project, it stops the build instead. (C#’s TreatWarningsAsErrors doesn’t cover it.)

Limits in 1.2.0

  • Matrix multiplies. If the precise variable is the result of mul with a matrix (precise float4 clip = mul(ViewProjection, p);), OneShader 1.2.0 marks the scalar and vector arithmetic that feeds it but not the matrix-vector multiply itself, which DXC also marks NoContraction. Do the precise part in vector arithmetic, as above, or keep in mind that the driver may contract the matrix product.
  • WGSL can’t express precise, as above. Write fma(a, b, c) where you want a fused result on every target.

The per-target rules are in SPIR-V, MSL and WGSL.

What precise does and doesn’t guarantee on each target: HLSL precise keyword and floating-point determinism.