# What each dither's hash does if the fragment shader runs at mediump (IEEE half precision,
# every operation rounded to float16; the lowest precision GLSL ES allows mediump to be).
# three.js asks for highp, and every WebGL2 device has it, so this is the fallback case only:
# a GPU without highp in fragment shaders. Prints a table; numpy only.
import numpy as np
h16 = np.float16

def frag(W, H):
    y, x = np.mgrid[0:H, 0:W]
    return (x + .5).astype(h16), (y + .5).astype(h16)

def fract(v):
    return (v - np.floor(v)).astype(h16)

def sin_hash(x, y):
    with np.errstate(all='ignore'):
        d = (x * h16(12.9898)).astype(h16) + (y * h16(78.233)).astype(h16)
        s = np.sin(d.astype(np.float32)).astype(h16)   # sin itself evaluated exactly, then rounded
        return fract((s * h16(43758.5453)).astype(h16))

def ign(x, y):
    with np.errstate(all='ignore'):
        d = ((x * h16(0.06711056)).astype(h16) + (y * h16(0.00583715)).astype(h16)).astype(h16)
        return fract((h16(52.9829189) * fract(d)).astype(h16))

def tile(x, y, n=64):  # texture2D(uNoise, gl_FragCoord.xy / 64.) with repeat: which texel
    return (np.floor((x / h16(n)).astype(h16) * n).astype(int) % n, np.floor((y / h16(n)).astype(h16) * n).astype(int) % n)

def report(W=1290, H=2796):
    x, y = frag(W, H)
    rows = []
    for name, f in (('sin hash', sin_hash), ('IGN', ign)):
        v = f(x, y).astype(np.float64)
        bad = ~np.isfinite(v)
        uniq = [len(np.unique(v[y0:y0 + 64, 600:664][np.isfinite(v[y0:y0 + 64, 600:664])])) for y0 in (0, 700, 1400, 2700)]
        rows.append(f'{name:9s} non-finite {bad.mean():6.1%}   distinct values per 64x64 tile at y=0,700,1400,2700: {uniq}')
    tx, ty = tile(x, y)
    true_tx, true_ty = np.mgrid[0:H, 0:W][1] % 64, np.mgrid[0:H, 0:W][0] % 64
    rows.append(f'blue tile wrong texel {((tx != true_tx) | (ty != true_ty)).mean():6.1%}   (rows past y=2048 can no longer be told apart at half precision)')
    rows.append(f'gl_FragCoord.y exact up to y={int(np.argmax(np.abs(y[:, 0].astype(float) - (np.arange(H) + .5)) > 0))}')
    return '\n'.join(rows)

if __name__ == '__main__':
    print(report())
