fp64 Mandelbrot

fp64 Mandelbrot

The classic double-float demo.

// ═══ typeshade example — fp64 deep-zoom Mandelbrot ═══
//
// THE classic double-float demo (the df64 technique traces back to the NVIDIA
// CUDA SDK Mandelbrot sample): at a zoom span of ~1e-7 on a filament of the
// needle spike (x ≈ −1.749, where ulp_f32 ≈ 2.4e-7 is WIDER than the whole
// window), f32 cannot distinguish ANY pixel — the LEFT half (plain f32, the
// center explicitly narrowed with toF32) collapses flat, while the RIGHT half
// iterates the SAME formula on the f64 type and shows the filament structure.
// The center is a vec2<f64> uniform (the emulated-double VECTOR type — one
// DF64Vec2 hi/lo-plane slot), and the per-pixel offset is added in extended
// precision.
//
// 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,
vec2,
vec3,
vec4,
f32,
f64,
pow,
cos,
log2,
max,
mix,
step,
toF32,
toF64,
toU32,
f32T,
f64T,
u32T,
vec2fT,
vec4fT,
vec2f64T,
If,
Loop,
Break,
Var,
Let,
u32,
ioStruct,
builtin,
location,
uniformStruct,
} from '../src/index.js';
import type { ShaderExample } from './_shared.js';
// A filament point on the needle spike — a period-3 minibrot neighbourhood
// that stays mid-frame at every zoom (centered on the real axis, y = 0). At
// shallow zoom the escape times sit under ~96 iterations, but zooming toward
// the df64 floor grows them, so the iteration budget is a live uniform
// (`max_iter`, wired to the slider) instead of a baked constant — crank it
// past 96 to keep the deep filament structure resolved.
const CENTER_X = -1.7490368500591793;
const CENTER_Y = 0;
const DEFAULT_ITER = 256;
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
max_iter: f32T, // dynamic escape-time budget; loop bound = toU32(max_iter)
},
);
// ── Escape-time iterate — the f32 and f64 twins ────────────────────────────
// Both run the IDENTICAL z ← z² + c recurrence; the ONLY difference is the
// working precision (double or not — that IS the demo), so the fragment
// shader's split is a clean f32-fn vs f64-fn if/else instead of two inline
// loops. `iters` is the dynamic budget (from `max_iter`); the loop bound is no
// longer baked. Each returns vec2(it, |z|²@escape) so the smooth colouring
// reads both twins through one path.
//
// The DSL types its arithmetic per CONCRETE scalar (`ArithArg<K>` is a
// conditional type that doesn't reduce over an unresolved type param), so the
// shared body can't collapse into one generic fn — the twins are spelled out
// and kept in step. They run the same recurrence and differ in the scalar type,
// the zero literal, and how |z|² is taken for the escape test: the f32 twin
// carries its squares, the f64 twin squares its narrowed words in f32.
const escapeF32 = fn(
'escape_f32',
{ cx: f32T, cy: f32T, iters: u32T },
vec2fT,
({ cx, cy, iters }) => {
// The escape loop is written as fp64-julia.ts's (which records why and what it saves):
// |z|² is carried in m2 beside z, the squares beside it so none is computed twice a
// trip, and the loop leaves at the first escaped z. z₀ = 0, so all three start at 0.
const zx = Var(f32(0));
const zy = Var(f32(0));
const x2 = Var(f32(0));
const y2 = Var(f32(0));
const m2 = Var(f32(0));
const it = Var(f32(0));
Loop(
u32(0),
(j) => j.lt(iters),
() => {
If(m2.gt(16.0), () => {
Break();
});
const nzx = Let(x2.sub(y2).add(cx));
zy.assign(zx.mul(zy).mul(2.0).add(cy));
zx.assign(nzx);
it.assign(it.add(1.0));
x2.assign(zx.mul(zx));
y2.assign(zy.mul(zy));
m2.assign(x2.add(y2));
},
);
return vec2(it, m2);
},
);
const escapeF64 = fn(
'escape_f64',
{ cx: f64T, cy: f64T, iters: u32T },
vec2fT,
({ cx, cy, iters }) => {
// The escape test reads an f32 |z|² squared from the narrowed words, as in
// fp64-julia.ts: 48 bits move |z|² across 16 only from within an f32 rounding of it.
// So this helper carries no squares — the step squares z in df64, the test its
// narrowed words in f32 — and it hands the colouring the f32 |z|² it already has.
const zx = Var(f64(0));
const zy = Var(f64(0));
const m2 = Var(f32(0));
const it = Var(f32(0));
Loop(
u32(0),
(j) => j.lt(iters),
() => {
If(m2.gt(16.0), () => {
Break();
});
const nzx = Let(zx.mul(zx).sub(zy.mul(zy)).add(cx));
zy.assign(zx.mul(zy).mul(2.0).add(cy));
zx.assign(nzx);
it.assign(it.add(1.0));
const hx = Let(toF32(zx));
const hy = Let(toF32(zy));
m2.assign(hx.mul(hx).add(hy.mul(hy)));
},
);
return vec2(it, m2);
},
);
const VsOut = ioStruct('VsOut', {
pos: builtin('position', vec4fT),
uv: location(0, vec2fT),
});
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 fsMandel = fn(
'fs_mandel',
{ 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.
// Panning lives on the HOST (the pan2d control drags `center` itself, in
// full double precision) — the shader only ever sees per-pixel offsets,
// which f32 carries fine at ~span magnitude; the extended-precision add
// against `center` below is where f64 wins.
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)),
);
// The live iteration budget, truncated once to the u32 loop bound both
// twins share (the slider hands us an f32).
const iters = Let(toU32(U.field.max_iter));
const esc = Var(vec2(0, 0)); // (it, |z|²@escape) — filled by whichever twin runs
// fp64 toggle off → BOTH halves take the f32 branch: the right half
// collapses flat in place, making the emulation's contribution tangible.
If(p.vo.uv.x.lt(0.5).or(U.field.fp64.lt(0.5)), () => {
// f32 twin — the center narrowed to f32: at deep zoom cx/cy quantize to
// f32 ulps and whole pixel columns collapse.
const cx = Let(toF32(U.field.center.x).add(dx));
const cy = Let(toF32(U.field.center.y).add(dy));
esc.assign(escapeF32({ cx, cy, iters }));
}).else(() => {
// f64 — the extended-precision add against the vec2<f64> center is where
// the emulation earns its keep.
const cx = Let(U.field.center.x.add(toF64(dx)));
const cy = Let(U.field.center.y.add(toF64(dy)));
esc.assign(escapeF64({ cx, cy, iters }));
});
const it = Let(esc.x);
const m2 = Let(esc.y); // |z|² of the last z the loop reached
// Smooth escape-time colouring (log₂ log₂ |z|² kills the discrete bands —
// same treatment as mandelbrot.ts) so zooming reads as a continuous dive
// instead of strobing colour bands; a LOW-contrast cosine shimmer over the
// smooth count keeps the iso-contour structure readable without the churn.
// Interior (never escaped) stays black.
const sn = Let(it.sub(log2(max(log2(max(m2, 1.0001)), 0.0001))).add(1.0));
const inside = Let(step(U.field.max_iter.sub(0.5), it));
const s = Let(sn.div(U.field.max_iter));
const shade = Let(f32(0.82).add(cos(sn.mul(0.55)).mul(0.18)));
const rgb = mix(vec3(0.03, 0.05, 0.12), vec3(1.0, 0.83, 0.36), s)
.mul(shade)
.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 fp64MandelbrotModule = module({
funcs: [escapeF32, escapeF64, vsFull, fsMandel],
uses: [U, VsOut],
});
// DF64Vec2 std140 buffer order is PLANE-major: [hi.x, hi.y, lo.x, lo.y]
// (hi vec2 at offset 0, lo vec2 at offset 8) — NOT lane-major pairs. The
// pan2d host does this packing (via splitF64) every frame as drags move the
// double-precision camera.
export const fp64Mandelbrot: ShaderExample = {
id: 'fp64-mandelbrot',
title: 'fp64 Mandelbrot',
blurb:
'The classic double-float demo: a Mandelbrot needle-spike filament zoomed to a ~1e-7 span — narrower than one f32 ulp, so the plain-f32 left half collapses flat while the emulated-double f64 right half keeps the structure. Drag to pan and wheel to zoom, map-style — the camera accumulates in full double precision and lands in the vec2<f64> center uniform, so the f64 half stays sharp all the way to the df64 floor (~1e-13) while the f32 half died six orders of magnitude earlier. Raise the iterations slider past the old 96 to keep deep-zoom filaments resolved, or flip the fp64 toggle to collapse the right half in place.',
category: 'generic',
file: 'fp64-mandelbrot.ts',
module: fp64MandelbrotModule,
renderable: true,
splitLabels: ['f32', 'f64 (emulated)'],
controls: {
// Drag pans the center in full double precision; a full-canvas-width drag
// moves 2 × span (each half maps span across half the width).
center: {
kind: 'pan2d',
value: [CENTER_X, CENTER_Y],
zoomExpField: 'zoom_exp',
unitsPerWidth: 2,
},
resolution: { kind: 'resolution' },
// Wheel-zoomable, open past the f32 floor (~7.5) down to where even the
// df64 emulation runs out of bits (~13) — the collapse IS the demo.
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 },
// Escape-time budget — open well past the old baked 96 so deep-zoom
// filaments stay resolved as the escape times grow toward the df64 floor.
max_iter: {
kind: 'slider',
label: 'Iterations',
min: 32,
max: 1024,
step: 16,
value: DEFAULT_ITER,
},
},
};
struct Uniforms {
@align(16) center: DF64Vec2,
resolution: vec2<f32>,
zoom_exp: f32,
fp64: f32,
max_iter: 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>;
fn escape_f32(cx: f32, cy: f32, iters: u32) -> vec2<f32> {
var _v0: f32 = 0.0;
var _v1: f32 = 0.0;
var _v2: f32 = 0.0;
var _v3: f32 = 0.0;
var _v4: f32 = 0.0;
var _v5: f32 = 0.0;
for (var _v6: u32 = 0u; (_v6 < iters); _v6 = (_v6 + 1u)) {
if ((_v4 > 16.0)) {
break;
}
let _v7 = ((_v2 - _v3) + cx);
_v1 = (((_v0 * _v1) * 2.0) + cy);
_v0 = _v7;
_v5 = (_v5 + 1.0);
_v2 = (_v0 * _v0);
_v3 = (_v1 * _v1);
_v4 = (_v2 + _v3);
}
return vec2<f32>(_v5, _v4);
}
fn escape_f64(cx: vec2<f32>, cy: vec2<f32>, iters: u32) -> vec2<f32> {
let _fp64_g = textureLoad(_fp64, vec2<i32>(0, 0), 0).x;
let _cse0 = vec2<f32>(0.0, 0.0);
var _v0: vec2<f32> = _cse0;
var _v1: vec2<f32> = _cse0;
var _v2: f32 = 0.0;
var _v3: f32 = 0.0;
for (var _v4: u32 = 0u; (_v4 < iters); _v4 = (_v4 + 1u)) {
if ((_v2 > 16.0)) {
break;
}
let _v5 = df64_add(df64_sub(df64_sqr(_v0, _fp64_g), df64_sqr(_v1, _fp64_g), _fp64_g), cx, _fp64_g);
_v1 = df64_add((df64_mul(_v0, _v1, _fp64_g) * 2.0), cy, _fp64_g);
_v0 = _v5;
_v3 = (_v3 + 1.0);
let _v6 = df64_narrow(_v0);
let _v7 = df64_narrow(_v1);
_v2 = ((_v6 * _v6) + (_v7 * _v7));
}
return vec2<f32>(_v3, _v2);
}
@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_mandel(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 = u32(u.max_iter);
var _v6: vec2<f32> = vec2<f32>(0.0, 0.0);
let _cse1 = vec2<f32>(u.center.hi.x, u.center.lo.x);
let _cse2 = vec2<f32>(u.center.hi.y, u.center.lo.y);
if ((_cse0 || (u.fp64 < 0.5))) {
let _v7 = (df64_narrow(_cse1) + _v3);
let _v8 = (df64_narrow(_cse2) + _v4);
_v6 = escape_f32(_v7, _v8, _v5);
} else {
let _v9 = df64_add(_cse1, vec2<f32>(_v3, 0.0), _fp64_g);
let _v10 = df64_add(_cse2, vec2<f32>(_v4, 0.0), _fp64_g);
_v6 = escape_f64(_v9, _v10, _v5);
}
let _v11 = _v6.x;
let _v12 = _v6.y;
let _v13 = ((_v11 - log2(max(log2(max(_v12, 1.0001)), 0.0001))) + 1.0);
let _v14 = step((u.max_iter - 0.5), _v11);
let _v15 = (_v13 / u.max_iter);
let _v16 = (0.82 + (cos((_v13 * 0.55)) * 0.18));
return vec4<f32>(((mix(vec3<f32>(0.03, 0.05, 0.12), vec3<f32>(1.0, 0.83, 0.36), _v15) * _v16) * (1.0 - _v14)), 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_split(a: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * (_fp64_g * 4097.0));
let _v1 = ((_v0 * _fp64_g) - (_v0 - a));
let _v2 = ((a * _fp64_g) - _v1);
return vec2<f32>(_v1, _v2);
}
fn df64_twoProd(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * b);
let _v1 = df64_split(a, _fp64_g);
let _v2 = df64_split(b, _fp64_g);
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_twoSqr(a: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * a);
let _v1 = df64_split(a, _fp64_g);
let _v2 = (((((_v1.x * _v1.x) - _v0) * _fp64_g) + (((_v1.x * _v1.y) * 2.0) * _fp64_g)) + ((_v1.y * _v1.y) * _fp64_g));
return vec2<f32>(_v0, _v2);
}
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_mul(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoProd(a.x, b.x, _fp64_g);
_v0.y = (_v0.y + (a.x * b.y));
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + (a.y * b.x));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
fn df64_sqr(a: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoSqr(a.x, _fp64_g);
_v0.y = (_v0.y + ((a.x * a.y) * 2.0));
return df64_quickTwoSum(_v0.x, _v0.y, _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;
struct DF64Vec2 {
vec2 hi;
vec2 lo;
};
layout(std140) uniform Uniforms {
DF64Vec2 center;
vec2 resolution;
float zoom_exp;
float fp64;
float max_iter;
} u;
uniform highp sampler2D _fp64;
uint _f2u(float x) {
return uint(mix(clamp(x, 0.0, 4294967040.0), 0.0, isnan(x)));
}
vec2 escape_f32(float cx, float cy, uint iters) {
float _v0 = 0.0;
float _v1 = 0.0;
float _v2 = 0.0;
float _v3 = 0.0;
float _v4 = 0.0;
float _v5 = 0.0;
for (uint _v6 = 0u; (_v6 < iters); _v6 = (_v6 + 1u)) {
if ((_v4 > 16.0)) {
break;
}
float _v7 = ((_v2 - _v3) + cx);
_v1 = (((_v0 * _v1) * 2.0) + cy);
_v0 = _v7;
_v5 = (_v5 + 1.0);
_v2 = (_v0 * _v0);
_v3 = (_v1 * _v1);
_v4 = (_v2 + _v3);
}
return vec2(_v5, _v4);
}
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_split(float a, float _fp64_g) {
float _v0 = (a * (_fp64_g * 4097.0));
float _v1 = ((_v0 * _fp64_g) - (_v0 - a));
float _v2 = ((a * _fp64_g) - _v1);
return vec2(_v1, _v2);
}
vec2 df64_twoSqr(float a, float _fp64_g) {
float _v0 = (a * a);
vec2 _v1 = df64_split(a, _fp64_g);
float _v2 = (((((_v1.x * _v1.x) - _v0) * _fp64_g) + (((_v1.x * _v1.y) * 2.0) * _fp64_g)) + ((_v1.y * _v1.y) * _fp64_g));
return vec2(_v0, _v2);
}
vec2 df64_sqr(vec2 a, float _fp64_g) {
vec2 _v0 = df64_twoSqr(a.x, _fp64_g);
_v0.y = (_v0.y + ((a.x * a.y) * 2.0));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
vec2 df64_twoProd(float a, float b, float _fp64_g) {
float _v0 = (a * b);
vec2 _v1 = df64_split(a, _fp64_g);
vec2 _v2 = df64_split(b, _fp64_g);
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_mul(vec2 a, vec2 b, float _fp64_g) {
vec2 _v0 = df64_twoProd(a.x, b.x, _fp64_g);
_v0.y = (_v0.y + (a.x * b.y));
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + (a.y * b.x));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
float df64_narrow(vec2 a) {
return (a.x + a.y);
}
vec2 escape_f64(vec2 cx, vec2 cy, uint iters) {
float _fp64_g = texelFetch(_fp64, ivec2(0, 0), 0).x;
vec2 _cse0 = vec2(0.0, 0.0);
vec2 _v0 = _cse0;
vec2 _v1 = _cse0;
float _v2 = 0.0;
float _v3 = 0.0;
for (uint _v4 = 0u; (_v4 < iters); _v4 = (_v4 + 1u)) {
if ((_v2 > 16.0)) {
break;
}
vec2 _v5 = df64_add(df64_sub(df64_sqr(_v0, _fp64_g), df64_sqr(_v1, _fp64_g), _fp64_g), cx, _fp64_g);
_v1 = df64_add((df64_mul(_v0, _v1, _fp64_g) * 2.0), cy, _fp64_g);
_v0 = _v5;
_v3 = (_v3 + 1.0);
float _v6 = df64_narrow(_v0);
float _v7 = df64_narrow(_v1);
_v2 = ((_v6 * _v6) + (_v7 * _v7));
}
return vec2(_v3, _v2);
}
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));
uint _v5 = _f2u(u.max_iter);
vec2 _v6 = vec2(0.0, 0.0);
vec2 _cse1 = vec2(u.center.hi.x, u.center.lo.x);
vec2 _cse2 = vec2(u.center.hi.y, u.center.lo.y);
if ((_cse0 || (u.fp64 < 0.5))) {
float _v7 = (df64_narrow(_cse1) + _v3);
float _v8 = (df64_narrow(_cse2) + _v4);
_v6 = escape_f32(_v7, _v8, _v5);
} else {
vec2 _v9 = df64_add(_cse1, vec2(_v3, 0.0), _fp64_g);
vec2 _v10 = df64_add(_cse2, vec2(_v4, 0.0), _fp64_g);
_v6 = escape_f64(_v9, _v10, _v5);
}
float _v11 = _v6.x;
float _v12 = _v6.y;
float _v13 = ((_v11 - log2(max(log2(max(_v12, 1.0001)), 0.0001))) + 1.0);
float _v14 = step((u.max_iter - 0.5), _v11);
float _v15 = (_v13 / u.max_iter);
float _v16 = (0.82 + (cos((_v13 * 0.55)) * 0.18));
_ret = vec4(((mix(vec3(0.03, 0.05, 0.12), vec3(1.0, 0.83, 0.36), _v15) * _v16) * (1.0 - _v14)), 1.0);
}

This example is steered by a control the page has no value for, so the page shows no picture.

The WGSL and GLSL tabs are the compiler's own output at commit 26de7be8, read from the goldens its emit suite bakes (emit-goldens.test.ts).

This example is written against the fn() builder API, which the editor in the Playground does not take.

File on GitHub

Edit this page Report a problem