Here’s a crazy helpful answer that Nvidia gave:
The use of sin() here is exactly the issue; graphics APIs in general don’t guarantee that sin() produces consistent results across APIs or platforms. The best practice for random number generators is to use integer-based RNG instead of sin(); this is guaranteed to be consistent, often produces better results, and is sometimes faster.
The short answer is that I recommend using one of the “2->1” hashes in Jarzynski and Olano’s “Hash Functions for GPU Rendering” https://jcgt.org/published/0009/03/02/ instead - e.g. Inigo Quilez’ Shader - Shadertoy BETA, or Dave Hoskins’ Hash Without Sine collection at Shader - Shadertoy BETA.
sin() can have an error of: The maximum absolute error is 0.0008 in the interval from -100Pi to +100Pi
Normally, this maximum absolute error of 0.0008 is un-noticeable. Unfortunately, the Rey 1998 “43758” RNG perfectly hits its weak spots: the dot product with vec2(12.9898,78.233) produces values outside the -100Pi to 100Pi range (so even the maximum absolute error guarantee goes away), and then the multiplication by 43758.5453123 followed by fract() makes the result depend on its least significant bits.
Here are the Shadertoy shaders that were consistent across GPUs (up to an MSE = sqrt(sum((img1-img2)^2)/(channels*image size)) of 1/255):
Shader - Shadertoy BETA (based on Quick And Easy GPU Random Numbers In D3D11 – Nathan Reed’s coding blog)
Shader - Shadertoy BETA
Shader - Shadertoy BETA
Shader - Shadertoy BETA
Shader - Shadertoy BETA