fp64 Julia set

fp64 Julia set

The Julia-set face of the double-float technique.

// ═══ typeshade 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,
Break,
Var,
Let,
u32,
uniformStruct,
} from '../src/index.js';
import { VsOut, vs } from './_fullscreen.js';
import type { ShaderExample } from './_shared.js';
// 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.8;
const 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.5255044073468653;
const CENTER_Y = -0.07591217756271362;
const 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)),
);
// |z|² of the last z the loop reached, CARRIED beside z: set from z₀ before the loop,
// refreshed after every step, read by the escape test, and after the loop already the
// |z|² the smooth colouring wants. The test is one compare. It used to recompute |z|² every
// trip, escaped or not, and on the right half in df64: two squares, a df64_add, a df64_le
// and three compares, a trip.
//
// The loop LEAVES at the first escaped z — `If(m2 > 16) Break()` at the top of the trip —
// instead of running to ITER and skipping its body. The natural spelling is the loop
// condition, `j < ITER && m2 <= 16`, and this file could write it; the twin cannot. The
// source language's `for` is counted (surface §17, Rule 7.5 of docs/language-design.md),
// its condition is ONE comparison of the counter against a constant, and the conjunction
// is TS8006. A `break` is the same program in the counted form, so both files spell that.
// It makes everything after the loop non-uniform in WGSL's analysis, and nothing after it
// needs uniform control flow (no sample, derivative or barrier), so Tint takes it.
//
// What it saves is bounded by the wave: a wave runs until its LAST lane leaves, so only a
// tile whose every lane has escaped drops its remaining trips. At 640×480, 831 of the right
// half's 2400 8×8 pixel tiles have every lane escape before trip 128 at the default zoom
// (1272 at a 1e-10 span), and each drops trips that cost a u32 increment, two compares and
// a branch. Measured on the GPU-like evaluator (the fp64-lowered module at f32 precision)
// over the full 640×480 frame, the exit gives the same colour bit for bit on both halves.
const it = Var(f32(0));
const m2 = Var(f32(0));
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.
//
// The squares are carried as well, beside m2. The step needs zx² and zy², and the m2
// refresh at the end of the trip before squared that same z. Squaring it again in the
// step puts the two on opposite sides of the loop's back edge, where neither dominates
// the other and no CSE can share them: 12 f32 operations a trip against 10, counted on
// the emitted WGSL. The shape before m2 was carried squared z twice in one trip as
// well, but there the test dominates the step and a dominator-based CSE shares the
// squares. `fp64-julia.test.ts` holds every loop here to one square of an operand a
// trip.
const zx = Var(toF32(U.field.center.x).add(dx));
const zy = Var(toF32(U.field.center.y).add(dy));
const x2 = Var(zx.mul(zx));
const y2 = Var(zy.mul(zy));
m2.assign(x2.add(y2));
Loop(
u32(0),
(j) => j.lt(u32(ITER)),
() => {
If(m2.gt(16.0), () => {
Break();
});
const nzx = Let(x2.sub(y2).add(C_RE));
zy.assign(zx.mul(zy).mul(2.0).add(C_IM));
zx.assign(nzx);
it.assign(it.add(1.0));
x2.assign(zx.mul(zx));
y2.assign(zy.mul(zy));
m2.assign(x2.add(y2));
},
);
}).else(() => {
// f64 — the same loop, z₀ keeps its extended-precision position, and m2 is taken
// differently. The escape test asks only which side of 16 |z|² lies on, and 48 bits
// change that answer only within an f32 rounding of the threshold, so m2 is squared in
// f32 from the narrowed words instead of in df64: two df64_narrow, two multiplies and an
// add a trip, in place of two df64 squares, a df64_add, a df64_le and three compares.
// `toF32(zx)` rounds hi + lo, which is the high word itself up to a half-ulp tie; the
// high word alone is not an author's to name (the split is compiler-internal, Rule 2.2
// of docs/language-design.md).
//
// So this half has no squares to carry: the step squares z in df64 and the refresh
// squares its narrowed words in f32, two different values, neither computed twice a
// trip. Carrying the df64 squares in variables, with m2 narrowed from them, costs more
// than it saves: a df64 value read from a variable is renormalized (a df64_add with 0,
// `renormForCancel` in fp64-lower.ts) before it feeds the step's cancelling subtraction,
// and the two df64_add that adds a trip outweigh the two f32 multiplies it removes, 62
// f32 operations a trip more on the emitted WGSL.
//
// Near |z|² = 16 a pixel can escape one step earlier or later than the df64 test had
// it, and the smooth colouring absorbs the step: `sn` subtracts log₂ log₂ |z|², which
// rises by about one as |z|² squares past the threshold and cancels the extra count.
// Measured on the GPU-like evaluator over 256×256 samples a half at spans of 1e-4,
// 1e-7, 1e-10 and 1e-13, no pixel's count moved, `sn` moved by 7.6e-6 at most on an
// escaped pixel, and the colour by 1.2e-4 of an 8-bit step; the closest any test came
// to 16 was 6.3e-6 relative, about 50 f32 ulps. Bisecting 560 count boundaries at 1e-4
// down to adjacent f32 uv values finds the case: 3 of 10,080 samples there escape one
// step later, with `sn` moved by 0.027 and the colour by 0.11 of an 8-bit step. The
// double does not side with either test there: it counts with this one at the first
// (`fp64-twins.test.ts` samples it) and with the df64 test at the other two.
const zx = Var(U.field.center.x.add(toF64(dx)));
const zy = Var(U.field.center.y.add(toF64(dy)));
const hx0 = Let(toF32(zx));
const hy0 = Let(toF32(zy));
m2.assign(hx0.mul(hx0).add(hy0.mul(hy0)));
Loop(
u32(0),
(j) => j.lt(u32(ITER)),
() => {
If(m2.gt(16.0), () => {
Break();
});
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));
const hx = Let(toF32(zx));
const hy = Let(toF32(zy));
m2.assign(hx.mul(hx).add(hy.mul(hy)));
},
);
});
// 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 {
@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>;
@vertex
fn 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)));
}
@fragment
fn fs_julia(vo: VsOut) -> @location(0) vec4<f32> {
let _fp64_g = textureLoad(_fp64, vec2<i32>(0, 0), 0).x;
let _licm0 = vec2<f32>(-0.800000011920929, 1.1920929132713809e-8);
let _licm1 = vec2<f32>(0.15600000321865082, -3.218650901359865e-9);
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));
var _v5: f32 = 0.0;
var _v6: f32 = 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))) {
var _v7: f32 = (df64_narrow(_cse1) + _v3);
var _v8: f32 = (df64_narrow(_cse2) + _v4);
var _v9: f32 = (_v7 * _v7);
var _v10: f32 = (_v8 * _v8);
_v6 = (_v9 + _v10);
for (var _v11: u32 = 0u; (_v11 < 128u); _v11 = (_v11 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
let _v12 = ((_v9 - _v10) + -0.8);
_v8 = (((_v7 * _v8) * 2.0) + 0.156);
_v7 = _v12;
_v5 = (_v5 + 1.0);
_v9 = (_v7 * _v7);
_v10 = (_v8 * _v8);
_v6 = (_v9 + _v10);
}
} else {
var _v13: vec2<f32> = df64_add(_cse1, vec2<f32>(_v3, 0.0), _fp64_g);
var _v14: vec2<f32> = df64_add(_cse2, vec2<f32>(_v4, 0.0), _fp64_g);
let _v15 = df64_narrow(_v13);
let _v16 = df64_narrow(_v14);
_v6 = ((_v15 * _v15) + (_v16 * _v16));
for (var _v17: u32 = 0u; (_v17 < 128u); _v17 = (_v17 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
let _v18 = df64_add(df64_sub(df64_sqr(_v13, _fp64_g), df64_sqr(_v14, _fp64_g), _fp64_g), _licm0, _fp64_g);
_v14 = df64_add((df64_mul(_v13, _v14, _fp64_g) * 2.0), _licm1, _fp64_g);
_v13 = _v18;
_v5 = (_v5 + 1.0);
let _v19 = df64_narrow(_v13);
let _v20 = df64_narrow(_v14);
_v6 = ((_v19 * _v19) + (_v20 * _v20));
}
}
let _v21 = ((_v5 - log2(max(log2(max(_v6, 1.0001)), 0.0001))) + 1.0);
let _v22 = step(127.5, _v5);
let _v23 = (_v21 * 0.0078125);
return vec4<f32>((((vec3<f32>(0.5) + (cos(((vec3<f32>(0.0, 0.25, 0.6) + (_v23 * 5.5)) + 2.2)) * 0.5)) * mix(0.35, 1.0, _v23)) * (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_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 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 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;
} 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_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_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_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_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_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);
}
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);
}
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;
vec2 _licm0 = vec2(-0.800000011920929, 1.1920929132713809e-8);
vec2 _licm1 = vec2(0.15600000321865082, -3.218650901359865e-9);
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));
float _v5 = 0.0;
float _v6 = 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);
float _v9 = (_v7 * _v7);
float _v10 = (_v8 * _v8);
_v6 = (_v9 + _v10);
for (uint _v11 = 0u; (_v11 < 128u); _v11 = (_v11 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
float _v12 = ((_v9 - _v10) + -0.8);
_v8 = (((_v7 * _v8) * 2.0) + 0.156);
_v7 = _v12;
_v5 = (_v5 + 1.0);
_v9 = (_v7 * _v7);
_v10 = (_v8 * _v8);
_v6 = (_v9 + _v10);
}
} else {
vec2 _v13 = df64_add(_cse1, vec2(_v3, 0.0), _fp64_g);
vec2 _v14 = df64_add(_cse2, vec2(_v4, 0.0), _fp64_g);
float _v15 = df64_narrow(_v13);
float _v16 = df64_narrow(_v14);
_v6 = ((_v15 * _v15) + (_v16 * _v16));
for (uint _v17 = 0u; (_v17 < 128u); _v17 = (_v17 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
vec2 _v18 = df64_add(df64_sub(df64_sqr(_v13, _fp64_g), df64_sqr(_v14, _fp64_g), _fp64_g), _licm0, _fp64_g);
_v14 = df64_add((df64_mul(_v13, _v14, _fp64_g) * 2.0), _licm1, _fp64_g);
_v13 = _v18;
_v5 = (_v5 + 1.0);
float _v19 = df64_narrow(_v13);
float _v20 = df64_narrow(_v14);
_v6 = ((_v19 * _v19) + (_v20 * _v20));
}
}
float _v21 = ((_v5 - log2(max(log2(max(_v6, 1.0001)), 0.0001))) + 1.0);
float _v22 = step(127.5, _v5);
float _v23 = (_v21 * 0.0078125);
_ret = vec4((((vec3(0.5) + (cos(((vec3(0.0, 0.25, 0.6) + (_v23 * 5.5)) + 2.2)) * 0.5)) * mix(0.35, 1.0, _v23)) * (1.0 - _v22)), 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