fp64 deep zoom

fp64 deep zoom

에뮬레이션 배정밀도(f32 두 개로 만든 df64).

TypeScript로 쓰고 TypeShade가 컴파일한 fp64 deep zoom 셰이더. 빌드할 때 렌더링함.
// ═══ 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>;
@vertex
fn 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)));
}
@fragment
fn 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 es
precision 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 es
precision 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의 편집기가 받지 않습니다.

GitHub의 파일

이 페이지 편집 문제 보고