---
title: "The HLSL precise keyword on Vulkan, Metal and WebGPU"
url: https://recursiveai.org/oneshader/cookbook/hlsl-precise-keyword/
description: "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…"
version: "1.2.0"
---

# The HLSL precise keyword on Vulkan, Metal and WebGPU

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

```sh
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

```sh
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

```sh
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](https://recursiveai.org/oneshader/docs/spirv-backend/), [MSL](https://recursiveai.org/oneshader/docs/msl-backend/) and [WGSL](https://recursiveai.org/oneshader/docs/wgsl-backend/).

What `precise` does and doesn’t guarantee on each target: [HLSL precise keyword and floating-point determinism](https://recursiveai.org/oneshader/articles/hlsl-precise-keyword-determinism/).
