fp64 Julia set
The Julia-set face of the double-float technique: the seed c is fixed and the PIXEL becomes z₀, so precision lives entirely in the starting coordinate. The camera parks on a repelling fixed point — a point that is ON the Julia set at every scale — and dives: the plain-f32 left half collapses flat past a ~1e-7 span while the emulated-double right half keeps spiralling to the df64 floor. Drag to pan, wheel to zoom, flip the fp64 toggle to collapse the right half in place.
One source, compiled every way
The DSL source below, compiled to WebGPU (WGSL), WebGL2 (GLSL ES 3.00 — two stages), and a reflection JSON the host binds from. Nothing but the DSL tab is hand-written.
// ═══ @xgis/shader-dsl example — fp64 deep-zoom Julia set ═══//// The Julia twin of fp64-mandelbrot.ts: the SEED is fixed (c = −0.8 + 0.156i)// and the PIXEL becomes z₀, so the precision-critical value is the per-pixel// starting point itself — exactly the extended-precision `center + offset` add// that df64 exists for. The camera parks on a Julia-set point BESIDE the// repelling fixed point z* = (1 + √(1−4c))/2 (repelling fixed points lie ON// the set, and its neighbourhood keeps escape times low and self-similar to// any depth), bisected along the horizontal line y = toF32(Im z*) so that the// center's y survives f32 narrowing EXACTLY: the plain-f32 left half then// collapses to horizontal escape BANDS (the x axis dies first — same// signature as fp64-mandelbrot's thin-line left half) instead of vanishing// into a solid interior fill.//// 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, cos, log2, max, mix, step, toF32, toF64, f32T, vec2fT, vec2f64T, If, Loop, Var, Let, u32, uniformStruct,} from '../src/index.ts'import { VsOut, vs } from './_fullscreen.ts'import type { ShaderExample } from './_shared.ts'
// Seed of the Julia set; both components are exactly f32-representable, so the// f32 half degrades ONLY through the pixel coordinate — the cleanest A/B.const C_RE = -0.8const C_IM = 0.156// On the Julia set beside the repelling fixed point (|2z*| ≈ 3.06 > 1):// y is EXACTLY f32-representable (toF32(Im z*)), x is CPU-bisected onto the// escape boundary along that line — verified to keep 44+ distinct escape// bands per 40² window down to a 1e-12 span, with ~7 surviving row bands on// the narrowed f32 side.const CENTER_X = 1.5255044073468653const CENTER_Y = -0.07591217756271362const ITER = 128
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 complex units fp64: f32T, // toggle: 1 = split-screen f32 | f64 (canonical), 0 = all-f32 },)
const fsJulia = fn( 'fs_julia', { vo: VsOut }, (p) => { const span = Let(pow(f32(10.0), U.field.zoom_exp.neg())) // Each half maps its own 0..1 sub-range onto the SAME complex window // (pan lives on the HOST in full double precision — see fp64-mandelbrot.ts). 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 it = Var(f32(0)) const m2 = Var(f32(0)) // |z|² at escape (frozen once the guard fails) If(p.vo.uv.x.lt(0.5).or(U.field.fp64.lt(0.5)), () => { // f32 twin — z₀ built from the narrowed center: at deep zoom the pixel // coordinate quantizes to f32 ulps and whole columns collapse. const zx = Var(toF32(U.field.center.x).add(dx)) const zy = Var(toF32(U.field.center.y).add(dy)) Loop( u32(0), (j) => j.lt(u32(ITER)), () => { If(zx.mul(zx).add(zy.mul(zy)).le(16.0), () => { const nzx = Let(zx.mul(zx).sub(zy.mul(zy)).add(C_RE)) zy.assign(zx.mul(zy).mul(2.0).add(C_IM)) zx.assign(nzx) it.assign(it.add(1.0)) }) }, ) m2.assign(zx.mul(zx).add(zy.mul(zy))) }).else(() => { // f64 — identical authoring; z₀ keeps its extended-precision position. const zx = Var(U.field.center.x.add(toF64(dx))) const zy = Var(U.field.center.y.add(toF64(dy))) Loop( u32(0), (j) => j.lt(u32(ITER)), () => { If(zx.mul(zx).add(zy.mul(zy)).le(16.0), () => { const nzx = Let(zx.mul(zx).sub(zy.mul(zy)).add(C_RE)) zy.assign(zx.mul(zy).mul(2.0).add(C_IM)) zx.assign(nzx) it.assign(it.add(1.0)) }) }, ) m2.assign(toF32(zx.mul(zx).add(zy.mul(zy)))) })
// Smooth escape time (same log₂ log₂ treatment as fp64-mandelbrot.ts) // through a cool cosine palette; interior stays black. const sn = Let(it.sub(log2(max(log2(max(m2, 1.0001)), 0.0001))).add(1.0)) const inside = Let(step(f32(ITER).sub(0.5), it)) const s = Let(sn.div(ITER)) const ph = vec3(0.0, 0.25, 0.6) const rgb = vec3(0.5) .add(cos(ph.add(s.mul(5.5)).add(2.2)).mul(0.5)) .mul(mix(f32(0.35), f32(1.0), s)) .mul(f32(1).sub(inside)) return vec4(rgb, f32(1)) }, { stage: 'fragment', retAttr: '@location(0)' },)
// `_fp64` guard lands at (group 0, binding 1) automatically.const fp64JuliaModule = module({ funcs: [vs, fsJulia], uses: [U, VsOut],})
export const fp64Julia: ShaderExample = { id: 'fp64-julia', title: 'fp64 Julia set', blurb: 'The Julia-set face of the double-float technique: the seed c is fixed and the PIXEL becomes z₀, so precision lives entirely in the starting coordinate. The camera parks on a repelling fixed point — a point that is ON the Julia set at every scale — and dives: the plain-f32 left half collapses flat past a ~1e-7 span while the emulated-double right half keeps spiralling to the df64 floor. Drag to pan, wheel to zoom, flip the fp64 toggle to collapse the right half in place.', category: 'generic', file: 'fp64-julia.ts', module: fp64JuliaModule, renderable: true, splitLabels: ['f32', 'f64 (emulated)'], controls: { center: { kind: 'pan2d', value: [CENTER_X, CENTER_Y], zoomExpField: 'zoom_exp', unitsPerWidth: 2, }, resolution: { kind: 'resolution' }, zoom_exp: { kind: 'slider', label: 'Zoom 10^-x', min: 0, max: 16, step: 0.05, value: 4, wheel: true, }, fp64: { kind: 'toggle', label: 'fp64 emulation', value: true }, },}struct Uniforms { 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_julia(vo: VsOut) -> @location(0) vec4<f32> { let _licm0 = vec2<f32>(16.0, 0.0); let _licm1 = vec2<f32>(-0.800000011920929, 1.1920929132713809e-8); let _licm2 = vec2<f32>(2.0, 0.0); let _licm3 = vec2<f32>(0.15600000321865082, -3.218650901359865e-9); let _cse0 = (vo.uv.x < 0.5); let _cse1 = vec2<f32>(u.center.hi.x, u.center.lo.x); let _cse2 = vec2<f32>(u.center.hi.y, u.center.lo.y); let _v0 = pow(10.0, (-u.zoom_exp)); let _v1 = (vo.uv.x * 2.0); 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)); var _v5: f32 = 0.0; var _v6: f32 = 0.0; if ((_cse0 || (u.fp64 < 0.5))) { var _v7: f32 = (df64_narrow(_cse1) + _v3); var _v8: f32 = (df64_narrow(_cse2) + _v4); for (var _v9: u32 = 0u; (_v9 < 128u); _v9 = (_v9 + 1u)) { if ((((_v7 * _v7) + (_v8 * _v8)) <= 16.0)) { let _v10 = (((_v7 * _v7) - (_v8 * _v8)) + -0.8); _v8 = (((_v7 * _v8) * 2.0) + 0.156); _v7 = _v10; _v5 = (_v5 + 1.0); } } _v6 = ((_v7 * _v7) + (_v8 * _v8)); } else { var _v11: vec2<f32> = df64_add(_cse1, vec2<f32>(_v3, 0.0)); var _v12: vec2<f32> = df64_add(_cse2, vec2<f32>(_v4, 0.0)); for (var _v13: u32 = 0u; (_v13 < 128u); _v13 = (_v13 + 1u)) { if (df64_le(df64_add(df64_mul(_v11, _v11), df64_mul(_v12, _v12)), _licm0)) { let _v14 = df64_add(df64_sub(df64_mul(_v11, _v11), df64_mul(_v12, _v12)), _licm1); _v12 = df64_add(df64_mul(df64_mul(_v11, _v12), _licm2), _licm3); _v11 = _v14; _v5 = (_v5 + 1.0); } } _v6 = df64_narrow(df64_add(df64_mul(_v11, _v11), df64_mul(_v12, _v12))); } let _v15 = ((_v5 - log2(max(log2(max(_v6, 1.0001)), 0.0001))) + 1.0); let _v16 = step(127.5, _v5); let _v17 = (_v15 / 128.0); return vec4<f32>((((vec3<f32>(0.5) + (cos(((vec3<f32>(0.0, 0.25, 0.6) + (_v17 * 5.5)) + 2.2)) * 0.5)) * mix(0.35, 1.0, _v17)) * (1.0 - _v16)), 1.0);}
fn df64_twoSum(a: f32, b: f32) -> vec2<f32> { let _cse0 = textureLoad(_fp64, vec2<i32>(0, 0), 0).x; let _v0 = (a + b); let _v1 = (((_v0 * _cse0) - a) * _cse0); let _v2 = (((((a - ((_v0 - _v1) * _cse0)) * _cse0) * _cse0) * _cse0) + (b - _v1)); return vec2<f32>(_v0, _v2);}
fn df64_quickTwoSum(a: f32, b: f32) -> vec2<f32> { let _cse0 = textureLoad(_fp64, vec2<i32>(0, 0), 0).x; let _v0 = ((a + b) * _cse0); let _v1 = (b - ((_v0 - a) * _cse0)); return vec2<f32>(_v0, _v1);}
fn df64_split(a: f32) -> vec2<f32> { let _cse0 = textureLoad(_fp64, vec2<i32>(0, 0), 0).x; let _v0 = (a * (_cse0 * 4097.0)); let _v1 = ((_v0 * _cse0) - (_v0 - a)); let _v2 = ((a * _cse0) - _v1); return vec2<f32>(_v1, _v2);}
fn df64_twoProd(a: f32, b: f32) -> vec2<f32> { let _v0 = (a * b); let _v1 = df64_split(a); let _v2 = df64_split(b); let _v3 = (((((_v1.x * _v2.x) - _v0) + (_v1.x * _v2.y)) + (_v1.y * _v2.x)) + (_v1.y * _v2.y)); return vec2<f32>(_v0, _v3);}
fn df64_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { var _v0: vec2<f32> = df64_twoSum(a.x, b.x); let _v1 = df64_twoSum(a.y, b.y); _v0.y = (_v0.y + _v1.x); _v0 = df64_quickTwoSum(_v0.x, _v0.y); _v0.y = (_v0.y + _v1.y); _v0 = df64_quickTwoSum(_v0.x, _v0.y); return _v0;}
fn df64_sub(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { return df64_add(a, (-b));}
fn df64_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { var _v0: vec2<f32> = df64_twoProd(a.x, b.x); _v0.y = (_v0.y + (a.x * b.y)); _v0 = df64_quickTwoSum(_v0.x, _v0.y); _v0.y = (_v0.y + (a.y * b.x)); return df64_quickTwoSum(_v0.x, _v0.y);}
fn df64_le(a: vec2<f32>, b: vec2<f32>) -> bool { return ((a.x < b.x) || ((a.x == b.x) && (a.y <= b.y)));}
fn df64_narrow(a: vec2<f32>) -> f32 { return (a.x + a.y);}#version 300 esprecision highp float;precision highp int;
struct VsOut { vec4 pos; vec2 uv;};out vec2 uv;
VsOut vs_impl(uint vi) { float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0); float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0); return VsOut(vec4(_cse0, _cse1, 0.0, 1.0), vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));}
void main() { VsOut _out = vs_impl(uint(gl_VertexID)); gl_Position = _out.pos; uv = _out.uv;}#version 300 esprecision highp float;precision highp int;
struct VsOut { vec4 pos; vec2 uv;};
struct DF64Vec2 { vec2 hi; vec2 lo;};layout(std140) uniform Uniforms { DF64Vec2 center; vec2 resolution; float zoom_exp; float fp64;} u;
uniform sampler2D _fp64;vec2 df64_twoSum(float a, float b);vec2 df64_quickTwoSum(float a, float b);vec2 df64_split(float a);vec2 df64_twoProd(float a, float b);vec2 df64_add(vec2 a, vec2 b);vec2 df64_sub(vec2 a, vec2 b);vec2 df64_mul(vec2 a, vec2 b);bool df64_le(vec2 a, vec2 b);float df64_narrow(vec2 a);vec2 df64_twoSum(float a, float b) { float _cse0 = texelFetch(_fp64, ivec2(0, 0), 0).x; float _v0 = (a + b); float _v1 = (((_v0 * _cse0) - a) * _cse0); float _v2 = (((((a - ((_v0 - _v1) * _cse0)) * _cse0) * _cse0) * _cse0) + (b - _v1)); return vec2(_v0, _v2);}
vec2 df64_quickTwoSum(float a, float b) { float _cse0 = texelFetch(_fp64, ivec2(0, 0), 0).x; float _v0 = ((a + b) * _cse0); float _v1 = (b - ((_v0 - a) * _cse0)); return vec2(_v0, _v1);}
vec2 df64_split(float a) { float _cse0 = texelFetch(_fp64, ivec2(0, 0), 0).x; float _v0 = (a * (_cse0 * 4097.0)); float _v1 = ((_v0 * _cse0) - (_v0 - a)); float _v2 = ((a * _cse0) - _v1); return vec2(_v1, _v2);}
vec2 df64_twoProd(float a, float b) { float _v0 = (a * b); vec2 _v1 = df64_split(a); vec2 _v2 = df64_split(b); float _v3 = (((((_v1.x * _v2.x) - _v0) + (_v1.x * _v2.y)) + (_v1.y * _v2.x)) + (_v1.y * _v2.y)); return vec2(_v0, _v3);}
vec2 df64_add(vec2 a, vec2 b) { vec2 _v0 = df64_twoSum(a.x, b.x); vec2 _v1 = df64_twoSum(a.y, b.y); _v0.y = (_v0.y + _v1.x); _v0 = df64_quickTwoSum(_v0.x, _v0.y); _v0.y = (_v0.y + _v1.y); _v0 = df64_quickTwoSum(_v0.x, _v0.y); return _v0;}
vec2 df64_sub(vec2 a, vec2 b) { return df64_add(a, (-b));}
vec2 df64_mul(vec2 a, vec2 b) { vec2 _v0 = df64_twoProd(a.x, b.x); _v0.y = (_v0.y + (a.x * b.y)); _v0 = df64_quickTwoSum(_v0.x, _v0.y); _v0.y = (_v0.y + (a.y * b.x)); return df64_quickTwoSum(_v0.x, _v0.y);}
bool df64_le(vec2 a, vec2 b) { return ((a.x < b.x) || ((a.x == b.x) && (a.y <= b.y)));}
float df64_narrow(vec2 a) { return (a.x + a.y);}in vec2 uv;layout(location = 0) out vec4 _ret;
vec4 fs_julia_impl(VsOut vo) { vec2 _licm0 = vec2(16.0, 0.0); vec2 _licm1 = vec2(-0.800000011920929, 1.1920929132713809e-8); vec2 _licm2 = vec2(2.0, 0.0); vec2 _licm3 = vec2(0.15600000321865082, -3.218650901359865e-9); bool _cse0 = (vo.uv.x < 0.5); vec2 _cse1 = vec2(u.center.hi.x, u.center.lo.x); vec2 _cse2 = vec2(u.center.hi.y, u.center.lo.y); float _v0 = pow(10.0, (-u.zoom_exp)); float _v1 = (vo.uv.x * 2.0); float _v2 = (_v1 - (_cse0 ? 0.0 : 1.0)); float _v3 = ((_v2 - 0.5) * _v0); float _v4 = (((vo.uv.y - 0.5) * _v0) * ((u.resolution.y / u.resolution.x) * 2.0)); float _v5 = 0.0; float _v6 = 0.0; if ((_cse0 || (u.fp64 < 0.5))) { float _v7 = (df64_narrow(_cse1) + _v3); float _v8 = (df64_narrow(_cse2) + _v4); for (uint _v9 = 0u; (_v9 < 128u); _v9 = (_v9 + 1u)) { if ((((_v7 * _v7) + (_v8 * _v8)) <= 16.0)) { float _v10 = (((_v7 * _v7) - (_v8 * _v8)) + -0.8); _v8 = (((_v7 * _v8) * 2.0) + 0.156); _v7 = _v10; _v5 = (_v5 + 1.0); } } _v6 = ((_v7 * _v7) + (_v8 * _v8)); } else { vec2 _v11 = df64_add(_cse1, vec2(_v3, 0.0)); vec2 _v12 = df64_add(_cse2, vec2(_v4, 0.0)); for (uint _v13 = 0u; (_v13 < 128u); _v13 = (_v13 + 1u)) { if (df64_le(df64_add(df64_mul(_v11, _v11), df64_mul(_v12, _v12)), _licm0)) { vec2 _v14 = df64_add(df64_sub(df64_mul(_v11, _v11), df64_mul(_v12, _v12)), _licm1); _v12 = df64_add(df64_mul(df64_mul(_v11, _v12), _licm2), _licm3); _v11 = _v14; _v5 = (_v5 + 1.0); } } _v6 = df64_narrow(df64_add(df64_mul(_v11, _v11), df64_mul(_v12, _v12))); } float _v15 = ((_v5 - log2(max(log2(max(_v6, 1.0001)), 0.0001))) + 1.0); float _v16 = step(127.5, _v5); float _v17 = (_v15 / 128.0); return vec4((((vec3(0.5) + (cos(((vec3(0.0, 0.25, 0.6) + (_v17 * 5.5)) + 2.2)) * 0.5)) * mix(0.35, 1.0, _v17)) * (1.0 - _v16)), 1.0);}
void main() { VsOut vo; vo.pos = gl_FragCoord; vo.uv = uv; _ret = fs_julia_impl(vo);}{ "bindGroups": [ { "group": 0, "entries": [ { "group": 0, "binding": 0, "name": "u", "space": "uniform", "resourceKind": "uniform-buffer", "structName": "Uniforms" } ] } ], "uniforms": [ { "name": "Uniforms", "size": 32, "align": 16, "fields": [ { "name": "center", "type": "vec2<f64>", "offset": 0, "align": 16, "size": 16 }, { "name": "resolution", "type": "vec2<f32>", "offset": 16, "align": 8, "size": 8 }, { "name": "zoom_exp", "type": "f32", "offset": 24, "align": 4, "size": 4 }, { "name": "fp64", "type": "f32", "offset": 28, "align": 4, "size": 4 } ] } ], "storage": [], "entries": [ { "name": "vs", "stage": "vertex", "inputs": [ "u32" ], "output": "struct:VsOut" }, { "name": "fs_julia", "stage": "fragment", "inputs": [ "struct:VsOut" ], "output": "vec4<f32>" } ], "overrides": []}
Run it locally:
npx tsx examples/print.ts fp64-julia
— prints the WGSL, GLSL, reflection. Source:
GitHub.
New to the IR? See Concepts; the full authoring + emit surface is in the API reference.