fp64 Mandelbrot
f32 두 개로 만든 배정밀도의 고전 데모.
// ═══ 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);}
@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_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 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;
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);}이 예제를 움직이는 컨트롤에 페이지가 넣을 값이 없어서, 이 페이지에는 그림이 없습니다.
WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).
이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.