fp64 checker plane
월드 평면 위의 1단위 체커보드.
// ═══ typeshade example — fp64 world-plane checkerboard ═══//// The map-engine failure mode in its purest form: a 1×1-unit checkerboard on// a world plane, viewed 100 million units from the origin (ulp_f32(1e8) = 8 —// EIGHT whole cells wide). The tile grid is recovered with floor/fract ON THE// f64 TYPE (both are in the df64 whitelist): parity = fract((⌊x⌋+⌊y⌋)/2)// stays exact because ⌊x⌋ at 1e8 does not FIT in an f32 — narrowing first is// precisely the bug. The plain-f32 left half only ever sees the coordinate in// 8-cell steps — parity never flips and the checker collapses FLAT; the f64// right half stays a crisp checkerboard with anti-aliased cell borders.//// The `_fp64` guard uniform is auto-injected by the lowering; the render// harnesses bind it to 1.0f by probing the program for the Fp64Guard block.
import { fn, module, vec3, vec4, f32, pow, floor, fract, min, mix, step, smoothstep, toF32, toF64, f32T, vec2fT, vec2f64T, Let, uniformStruct,} from '../src/index.js';import { VsOut, vs } from './_fullscreen.js';import type { ShaderExample } from './_shared.js';
const U = uniformStruct( 'Uniforms', { group: 0, binding: 0, as: 'u' }, { center: vec2f64T, // one DF64Vec2 slot — host packs [hi.x, hi.y, lo.x, lo.y] resolution: vec2fT, zoom_exp: f32T, // view span = 10^-zoom_exp world units (negative = zoom out) fp64: f32T, // toggle: 1 = split-screen f32 | f64 (canonical), 0 = all-f32 },);
const fsChecker = fn( 'fs_checker', { vo: VsOut }, (p) => { const span = Let(pow(f32(10.0), U.field.zoom_exp.neg())); const half = Let(p.vo.uv.x.mul(2.0)); const sx = Let(half.sub(p.vo.uv.x.lt(0.5).select(0.0, 1.0))); const dx = Let(sx.sub(0.5).mul(span)); const dy = Let( p.vo.uv.y.sub(0.5).mul(span).mul(U.field.resolution.y.div(U.field.resolution.x).mul(2.0)), );
const isF32 = Let(p.vo.uv.x.lt(0.5).or(U.field.fp64.lt(0.5))); // f64 path — floor/fract in extended precision; only RESULTS narrow // (cell parity is 0/0.5 exactly; the in-cell fraction is sub-unit). const px = Let(U.field.center.x.add(toF64(dx))); const py = Let(U.field.center.y.add(toF64(dy))); const par64 = Let(toF32(fract(floor(px).add(floor(py)).mul(0.5)))); const fx64 = Let(toF32(fract(px))); const fy64 = Let(toF32(fract(py))); // f32 twin — SAME formulas, world coordinate narrowed first: at 1e8 the // coordinate moves in 8-cell steps, so parity NEVER flips (always even) // and the fraction is identically 0 — the half renders flat. const px32 = Let(toF32(U.field.center.x).add(dx)); const py32 = Let(toF32(U.field.center.y).add(dy)); const par32 = Let(fract(floor(px32).add(floor(py32)).mul(0.5))); const fx32 = Let(fract(px32)); const fy32 = Let(fract(py32));
const par = Let(isF32.select(par32, par64)); const fx = Let(isF32.select(fx32, fx64)); const fy = Let(isF32.select(fy32, fy64));
// Two-tone slate/ivory checker + anti-aliased cell borders. The AA width // comes from the analytic pixel size in WORLD units (span / half-width in // px) — fwidth(fract(x)) would spike across the cell seam itself. const chk = Let(step(0.25, par)); const edge = Let(min(min(fx, f32(1).sub(fx)), min(fy, f32(1).sub(fy)))); const pixw = Let(span.div(U.field.resolution.x.mul(0.5))); const line = Let(smoothstep(f32(0), pixw.mul(1.5).add(1e-9), edge)); const ivory = vec3(0.93, 0.9, 0.82); const slate = vec3(0.23, 0.29, 0.36); const rgb = mix(ivory, slate, chk).mul(mix(f32(0.35), f32(1.0), line)); return vec4(rgb, f32(1)); }, { stage: 'fragment', retAttr: '@location(0)' },);
// `_fp64` guard lands at (group 0, binding 1) automatically.const fp64CheckerPlaneModule = module({ funcs: [vs, fsChecker], uses: [U, VsOut],});
export const fp64CheckerPlane: ShaderExample = { id: 'fp64-checker-plane', title: 'fp64 checker plane', blurb: 'A 1-unit checkerboard on a world plane. Drag the DISTANCE slider (or wheel) to carry the plane out from the origin: near 10⁶ both halves are a crisp checker, but past ~10⁷·² one f32 ulp grows wider than a cell and the plain-f32 left half collapses flat (parity can no longer flip) — while the f64 right half, doing floor/fract on the emulated-double type, keeps cell parity and anti-aliased borders all the way to 10⁹. Watch the exact distance where f32 gives out; flip the fp64 toggle to collapse the right half too.', category: 'cartographic', file: 'fp64-checker-plane.ts', module: fp64CheckerPlaneModule, renderable: true, splitLabels: ['f32', 'f64 (emulated)'], controls: { // Sweep the plane's distance from the origin: base·10^mag + offset (the // fractional offset is the sub-cell detail f32 loses first). At mag = 8 this // is the classic 10⁸ view; drop to 6 and f32 is fine — the point is the // THRESHOLD in between, which the viewer drives. center: { kind: 'logmag2d', magField: 'mag', base: [1, 0.5], offset: [0.3, 0.7] }, resolution: { kind: 'resolution' }, mag: { kind: 'slider', label: 'Distance from origin 10^x', min: 4, max: 9, step: 0.02, value: 6, wheel: true, }, // View span across each half (10^-zoom_exp world units ≈ number of cells). zoom_exp: { kind: 'slider', label: 'Zoom 10^-x', min: -2, max: 5, step: 0.05, value: -0.9, }, fp64: { kind: 'toggle', label: 'fp64 emulation', value: true }, },};struct Uniforms { @align(16) center: DF64Vec2, resolution: vec2<f32>, zoom_exp: f32, fp64: f32,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
struct DF64Vec2 { hi: vec2<f32>, lo: vec2<f32>,}
@group(0) @binding(0) var<uniform> u: Uniforms;@group(0) @binding(1) var _fp64: texture_2d<f32>;
@vertexfn vs(@builtin(vertex_index) vi: u32) -> VsOut { let _cse0 = ((f32((vi & 1u)) * 4.0) - 1.0); let _cse1 = ((f32((vi >> 1u)) * 4.0) - 1.0); return VsOut(vec4<f32>(_cse0, _cse1, 0.0, 1.0), vec2<f32>(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));}
@fragmentfn fs_checker(vo: VsOut) -> @location(0) vec4<f32> { let _fp64_g = textureLoad(_fp64, vec2<i32>(0, 0), 0).x; let _v0 = pow(10.0, (-u.zoom_exp)); let _v1 = (vo.uv.x * 2.0); let _cse0 = (vo.uv.x < 0.5); let _v2 = (_v1 - select(1.0, 0.0, _cse0)); let _v3 = ((_v2 - 0.5) * _v0); let _v4 = (((vo.uv.y - 0.5) * _v0) * ((u.resolution.y / u.resolution.x) * 2.0)); let _v5 = (_cse0 || (u.fp64 < 0.5)); let _cse1 = vec2<f32>(u.center.hi.x, u.center.lo.x); let _v6 = df64_add(_cse1, vec2<f32>(_v3, 0.0), _fp64_g); let _cse2 = vec2<f32>(u.center.hi.y, u.center.lo.y); let _v7 = df64_add(_cse2, vec2<f32>(_v4, 0.0), _fp64_g); let _v8 = df64_narrow(df64_fract((df64_add(df64_floor(_v6, _fp64_g), df64_floor(_v7, _fp64_g), _fp64_g) * 0.5), _fp64_g)); let _cse4 = bitcast<f32>(bitcast<u32>(0.0)); let _cse3 = vec2<f32>(_cse4, _cse4); let _v9 = df64_narrow(df64_fract(df64_add(_v6, _cse3, _fp64_g), _fp64_g)); let _v10 = df64_narrow(df64_fract(df64_add(_v7, _cse3, _fp64_g), _fp64_g)); let _v11 = (df64_narrow(_cse1) + _v3); let _v12 = (df64_narrow(_cse2) + _v4); let _v13 = fract(((floor(_v11) + floor(_v12)) * 0.5)); let _v14 = fract(_v11); let _v15 = fract(_v12); let _v16 = select(_v8, _v13, _v5); let _v17 = select(_v9, _v14, _v5); let _v18 = select(_v10, _v15, _v5); let _v19 = step(0.25, _v16); let _v20 = min(min(_v17, (1.0 - _v17)), min(_v18, (1.0 - _v18))); let _v21 = (_v0 / (u.resolution.x * 0.5)); let _v22 = smoothstep(0.0, ((_v21 * 1.5) + 1e-9), _v20); return vec4<f32>((mix(vec3<f32>(0.93, 0.9, 0.82), vec3<f32>(0.23, 0.29, 0.36), _v19) * mix(0.35, 1.0, _v22)), 1.0);}
fn df64_twoSum(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> { let _v0 = (a + b); let _v1 = (((_v0 * _fp64_g) - a) * _fp64_g); let _v2 = (((a - ((_v0 - _v1) * _fp64_g)) * _fp64_g) + (b - _v1)); return vec2<f32>(_v0, _v2);}
fn df64_quickTwoSum(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> { let _v0 = ((a + b) * _fp64_g); let _v1 = (b - ((_v0 - a) * _fp64_g)); return vec2<f32>(_v0, _v1);}
fn df64_add(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> { var _v0: vec2<f32> = df64_twoSum(a.x, b.x, _fp64_g); let _v1 = df64_twoSum(a.y, b.y, _fp64_g); _v0.y = (_v0.y + _v1.x); _v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g); _v0.y = (_v0.y + _v1.y); _v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g); return _v0;}
fn df64_sub(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> { return df64_add(a, (-b), _fp64_g);}
fn df64_floor(a: vec2<f32>, _fp64_g: f32) -> vec2<f32> { let _v0 = floor(a.x); return select(vec2<f32>(_v0, 0.0), df64_quickTwoSum(_v0, floor(a.y), _fp64_g), (_v0 == a.x));}
fn df64_fract(a: vec2<f32>, _fp64_g: f32) -> vec2<f32> { return df64_sub(a, df64_floor(a, _fp64_g), _fp64_g);}
fn df64_narrow(a: vec2<f32>) -> f32 { return (a.x + a.y);}#version 300 esprecision highp float;precision highp int;
out vec2 uv;
void main() { uint vi = uint(gl_VertexID); float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0); float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0); gl_Position = vec4(_cse0, _cse1, 0.0, 1.0); uv = vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5));}#version 300 esprecision highp float;precision highp int;
struct DF64Vec2 { vec2 hi; vec2 lo;};layout(std140) uniform Uniforms { DF64Vec2 center; vec2 resolution; float zoom_exp; float fp64;} u;
uniform highp sampler2D _fp64;vec2 df64_twoSum(float a, float b, float _fp64_g) { float _v0 = (a + b); float _v1 = (((_v0 * _fp64_g) - a) * _fp64_g); float _v2 = (((a - ((_v0 - _v1) * _fp64_g)) * _fp64_g) + (b - _v1)); return vec2(_v0, _v2);}
vec2 df64_quickTwoSum(float a, float b, float _fp64_g) { float _v0 = ((a + b) * _fp64_g); float _v1 = (b - ((_v0 - a) * _fp64_g)); return vec2(_v0, _v1);}
vec2 df64_add(vec2 a, vec2 b, float _fp64_g) { vec2 _v0 = df64_twoSum(a.x, b.x, _fp64_g); vec2 _v1 = df64_twoSum(a.y, b.y, _fp64_g); _v0.y = (_v0.y + _v1.x); _v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g); _v0.y = (_v0.y + _v1.y); _v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g); return _v0;}
vec2 df64_sub(vec2 a, vec2 b, float _fp64_g) { return df64_add(a, (-b), _fp64_g);}
vec2 df64_floor(vec2 a, float _fp64_g) { float _v0 = floor(a.x); return ((_v0 == a.x) ? df64_quickTwoSum(_v0, floor(a.y), _fp64_g) : vec2(_v0, 0.0));}
vec2 df64_fract(vec2 a, float _fp64_g) { return df64_sub(a, df64_floor(a, _fp64_g), _fp64_g);}
float df64_narrow(vec2 a) { return (a.x + a.y);}in vec2 uv;layout(location = 0) out vec4 _ret;
void main() { float _fp64_g = texelFetch(_fp64, ivec2(0, 0), 0).x; float _v0 = pow(10.0, (-u.zoom_exp)); float _v1 = (uv.x * 2.0); bool _cse0 = (uv.x < 0.5); float _v2 = (_v1 - (_cse0 ? 0.0 : 1.0)); float _v3 = ((_v2 - 0.5) * _v0); float _v4 = (((uv.y - 0.5) * _v0) * ((u.resolution.y / u.resolution.x) * 2.0)); bool _v5 = (_cse0 || (u.fp64 < 0.5)); vec2 _cse1 = vec2(u.center.hi.x, u.center.lo.x); vec2 _v6 = df64_add(_cse1, vec2(_v3, 0.0), _fp64_g); vec2 _cse2 = vec2(u.center.hi.y, u.center.lo.y); vec2 _v7 = df64_add(_cse2, vec2(_v4, 0.0), _fp64_g); float _v8 = df64_narrow(df64_fract((df64_add(df64_floor(_v6, _fp64_g), df64_floor(_v7, _fp64_g), _fp64_g) * 0.5), _fp64_g)); float _cse4 = uintBitsToFloat(floatBitsToUint(0.0)); vec2 _cse3 = vec2(_cse4, _cse4); float _v9 = df64_narrow(df64_fract(df64_add(_v6, _cse3, _fp64_g), _fp64_g)); float _v10 = df64_narrow(df64_fract(df64_add(_v7, _cse3, _fp64_g), _fp64_g)); float _v11 = (df64_narrow(_cse1) + _v3); float _v12 = (df64_narrow(_cse2) + _v4); float _v13 = fract(((floor(_v11) + floor(_v12)) * 0.5)); float _v14 = fract(_v11); float _v15 = fract(_v12); float _v16 = (_v5 ? _v13 : _v8); float _v17 = (_v5 ? _v14 : _v9); float _v18 = (_v5 ? _v15 : _v10); float _v19 = step(0.25, _v16); float _v20 = min(min(_v17, (1.0 - _v17)), min(_v18, (1.0 - _v18))); float _v21 = (_v0 / (u.resolution.x * 0.5)); float _v22 = smoothstep(0.0, ((_v21 * 1.5) + 1e-9), _v20); _ret = vec4((mix(vec3(0.93, 0.9, 0.82), vec3(0.23, 0.29, 0.36), _v19) * mix(0.35, 1.0, _v22)), 1.0);}이 예제를 움직이는 컨트롤에 페이지가 넣을 값이 없어서, 이 페이지에는 그림이 없습니다.
WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).
이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.