Bruno Croci debugs a Voronoi Shadertoy stutter that only appeared on a Windows RTX 4070: ANGLE/FXC optimizations dropped `fract` for integer-looking inputs, and swapping a literal to `398.1` or using `x-floor(x)` restored smooth motion.
When the fractional part of a float fixes your shader
The other night I wanted to implement a small Voronoi-diagram shader as a music-video background. I got it looking cool, then next morning found that—on one computer only—there was a weird stutter. This is the write-up of a week debugging and disassembling shaders.
The shader
A Worley/Voronoi noise: tile space, pick random centers, nearest of 9 centers colors a cell; UV warping via noise; palette lookup. Noise came from Inigo Quilez-style value noise using fract / hash1.
The problem
Devices tested: Intel iGPU Linux laptops (OK), Pixel 9 Pro (OK), Linux RTX 2070 (OK), Windows RTX 4070 (broken) across Firefox/Chrome/Edge.
An LLM suggested replacing fract(x) with x - floor(x). That fixed the stutter—but follow-up mini-repros failed to show fract returning garbage, invalidating a simple driver-bug story.
The weird fix that taught the real bug
Changing the scalar in vec2 v = o * 398.0 + … to 398.1 also fixed the stutter even with fract still present. Integer-ish literals (398.0, 1.0) stuttered; fractional ones (398.1, 1.001) did not.
ANGLE → HLSL → FXC
Capture via RenderDoc on Chromium showed DirectX 11 (ps_5_0) via ANGLE. Vulkan ANGLE path did not reproduce. Dumping translated HLSL showed near-identical GLSL→HLSL; the bug was in FXC optimization.
With D3DCOMPILE_SKIP_OPTIMIZATION, disassembly kept both floor and frac. With /O3, FXC effectively truncated intermediate values—losing the fractional part used as noise interpolation weights—when operands looked integral. With 398.1, optimized code still retained frac.
Takeaway
What started as a shader night became a week learning ANGLE, DXBC, and FXC quirks. Minimum repro still welcome from graphics folks. Final shader is on Shadertoy; music project: stuffy knows.