fp64 deep zoom
에뮬레이션 배정밀도(f32 두 개로 만든 df64).
// ═══ typeshade example — fp64 deep-zoom precision stripes ═══//// The emulated-double (f64) surface in one screen: a world coordinate near// 1e8 (where f32's ulp is 8 — every sub-integer detail is gone) is swept// across the screen and rendered as fract() stripes. The LEFT half computes in// plain f32 (the origin explicitly narrowed with toF32) and collapses to a// flat field; the RIGHT half runs the SAME formula on the f64 type and shows// clean stripes. Note the authoring surface is identical — `.add/.mul/fract`// — only the declared uniform type differs; fp64Lower rewrites the f64 side// into df64_* calls against the injected emulation library.//// f64 lowering auto-injects the `_fp64` guard uniform (the anti-fast-math// guard, see core/fp64/df64-lib.ts) — nothing to declare; the render// harnesses bind 1.0f into it by probing the program for the Fp64Guard block// (it is injected at LOWERING, so it is absent from the authored reflect()).// The numeric real-GPU gate for fp64 is playground/e2e/_fp64-known-answer.spec.ts.
import { fn, module, vec2, vec4, fract, toF32, toF64, f32T, f64T, u32T, vec2fT, vec4fT, If, ioStruct, builtin, location, uniformStruct,} from '../src/index.js';import type { ShaderExample } from './_shared.js';
const U = uniformStruct( 'Uniforms', { group: 0, binding: 0, as: 'u' }, { origin: f64T, // occupies one vec2<f32> slot — host packs splitF64(ORIGIN) span: f32T, // world units swept across the screen fp64: f32T, // toggle: 1 = split-screen f32 | f64 (canonical), 0 = all-f32 },);
const VsOut = ioStruct('VsOut', { pos: builtin('position', vec4fT), uv: location(0, vec2fT),});
// Oversized fullscreen triangle (same pattern as gradient-pass.ts).const vsFull = fn( 'vs_full', { idx: builtin('vertex_index', u32T) }, (p) => { const pos = vec2(-1, -1); If(p.idx.eq(1), () => { pos.assign(vec2(3, -1)); }).elif(p.idx.eq(2), () => { pos.assign(vec2(-1, 3)); }); return VsOut.construct({ pos: vec4(pos, 0, 1), uv: vec2(pos.x.add(1).mul(0.5), pos.y.add(1).mul(0.5)), }); }, { stage: 'vertex' },);
const fsStripes = fn( 'fs_stripes', { vo: VsOut }, (p) => { const sweep = p.vo.uv.x.mul(U.field.span); // f64 path: the full-precision world coordinate keeps its fraction. const stripes64 = toF32(fract(U.field.origin.add(toF64(sweep)))); // f32 twin — SAME formula, origin narrowed: the fraction is unrepresentable. const stripes32 = fract(toF32(U.field.origin).add(sweep)); // fp64 toggle off → the WHOLE screen takes the f32 path: the right half // collapses flat in place, making the emulation's contribution tangible. const v = p.vo.uv.x.lt(0.5).or(U.field.fp64.lt(0.5)).select(stripes32, stripes64); return vec4(v, v, v, 1.0); }, { stage: 'fragment', retAttr: '@location(0)' },);
// The `_fp64` guard binding lands at (group 0, binding 1) automatically —// first free slot past `u` at binding 0.const fp64DeepZoomModule = module({ funcs: [vsFull, fsStripes], uses: [U, VsOut],});
export const fp64DeepZoom: ShaderExample = { id: 'fp64-deep-zoom', title: 'fp64 deep zoom', blurb: 'Emulated double precision (two-f32 df64): a world coordinate rendered as fract() stripes. Drag the DISTANCE slider out from the origin — near 10⁶ both halves stripe cleanly, but past ~10⁷·² one f32 ulp swallows a whole stripe and the plain-f32 left half collapses flat, while the f64 right half keeps the stripes to 10⁹, identical authoring syntax. Flip the fp64 toggle off to watch the right half collapse too. The auto-injected _fp64 guard uniform must be set to 1.0.', category: 'cartographic', file: 'fp64-deep-zoom.ts', module: fp64DeepZoomModule, renderable: true, splitLabels: ['f32', 'f64 (emulated)'], controls: { // Sweep the coordinate's distance from the origin through the f32 threshold. origin: { kind: 'logmag1d', magField: 'mag', base: 1, offset: 0.3 }, mag: { kind: 'slider', label: 'Distance from origin 10^x', min: 4, max: 9, step: 0.02, value: 6, wheel: true, }, span: { kind: 'slider', label: 'World span', min: 1, max: 8, step: 0.5, value: 4 }, fp64: { kind: 'toggle', label: 'fp64 emulation', value: true }, },};struct Uniforms { origin: vec2<f32>, span: f32, fp64: f32,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
@group(0) @binding(0) var<uniform> u: Uniforms;@group(0) @binding(1) var _fp64: texture_2d<f32>;
@vertexfn vs_full(@builtin(vertex_index) idx: u32) -> VsOut { var _av0: vec2<f32> = vec2<f32>(-1.0, -1.0); if ((idx == 1u)) { _av0 = vec2<f32>(3.0, -1.0); } else if ((idx == 2u)) { _av0 = vec2<f32>(-1.0, 3.0); } return VsOut(vec4<f32>(_av0, 0.0, 1.0), vec2<f32>(((_av0.x + 1.0) * 0.5), ((_av0.y + 1.0) * 0.5)));}
@fragmentfn fs_stripes(vo: VsOut) -> @location(0) vec4<f32> { let _fp64_g = textureLoad(_fp64, vec2<i32>(0, 0), 0).x; let _cse1 = (vo.uv.x * u.span); let _cse0 = select(df64_narrow(df64_fract(df64_add(u.origin, vec2<f32>(_cse1, 0.0), _fp64_g), _fp64_g)), fract((df64_narrow(u.origin) + _cse1)), ((vo.uv.x < 0.5) || (u.fp64 < 0.5))); return vec4<f32>(_cse0, _cse0, _cse0, 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 idx = uint(gl_VertexID); vec2 _av0 = vec2(-1.0, -1.0); if ((idx == 1u)) { _av0 = vec2(3.0, -1.0); } else if ((idx == 2u)) { _av0 = vec2(-1.0, 3.0); } gl_Position = vec4(_av0, 0.0, 1.0); uv = vec2(((_av0.x + 1.0) * 0.5), ((_av0.y + 1.0) * 0.5));}#version 300 esprecision highp float;precision highp int;
layout(std140) uniform Uniforms { vec2 origin; float span; 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 _cse1 = (uv.x * u.span); float _cse0 = (((uv.x < 0.5) || (u.fp64 < 0.5)) ? fract((df64_narrow(u.origin) + _cse1)) : df64_narrow(df64_fract(df64_add(u.origin, vec2(_cse1, 0.0), _fp64_g), _fp64_g))); _ret = vec4(_cse0, _cse0, _cse0, 1.0);}fp64 deep zoom. 빌드할 때 그린 화면입니다.
WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).
이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.