Examples
generic julia.ts
Julia set
Escape-time Julia fractal — z ← z² + c iterated per pixel, coloured by iteration count through a cosine palette. The constant c orbits on autopilot; move the pointer over the canvas to steer it by hand.
Live
Pointer-interactive
0.0s
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 — animated Julia set (escape-time fractal) ═══//// Iterate z ← z² + c per pixel until |z| escapes, then colour by the (smoothed)// iteration count through a cosine palette. `c` orbits slowly so the fractal morphs.// Showcases the DSL's `Loop` + early `Break` + a mutable `var` accumulator, typed// ioStruct fn-params, and scalar×vector broadcast (the palette), all emitted to// both WGSL (WebGPU) and GLSL ES 3.00 (WebGL2).
import { fn, module, u32, f32, vec2, vec3, vec4, sin, cos, dot, mix, Loop, If, Break, Var, Let, f32T, vec4fT,} from '../src/index.ts'import { VsOut, vs, fullscreenUniforms } from './_fullscreen.ts'import type { ShaderExample } from './_shared.ts'const U = fullscreenUniforms({ zoom: f32T, mouse: vec4fT })
// Iridescent cosine palette: 0.5 + 0.5·cos(2π(t + phase)).const palette = fn('palette', { t: f32T }, ({ t }) => { const ph = vec3(0.0, 0.33, 0.67) // scalar t broadcasts over the phase vector; cos() is component-wise. return vec3(0.5).add(cos(t.add(ph).mul(6.283)).mul(0.5))})
const fs = fn( 'fs', { vo: VsOut }, ({ vo }) => { const uv = vo.uv // centre the plane, scale by the zoom uniform const z = Var(vec2(uv.x.mul(2).sub(1), uv.y.mul(2).sub(1)).mul(U.field.zoom)) // the Julia constant: orbits on autopilot; once the pointer has entered // (m.w = 1) it maps to the pointer instead — sweep the cursor to morph the // set by hand. The pointer is normalised to c-space ≈ [−0.8, 0.8]². const m = U.field.mouse const res = U.field.resolution const orbit = vec2( cos(U.field.time.mul(0.31)).mul(0.39).sub(0.4), sin(U.field.time.mul(0.41)).mul(0.39), ) const held = vec2(m.x.div(res.x).mul(2).sub(1).mul(0.8), m.y.div(res.y).mul(2).sub(1).mul(0.8)) const c = Let(mix(orbit, held, m.w)) const it = Var(f32(0)) Loop( u32(0), (i) => i.lt(u32(96)), () => { If(dot(z, z).gt(4), () => { Break() }) // escaped z.assign(vec2(z.x.mul(z.x).sub(z.y.mul(z.y)).add(c.x), z.x.mul(z.y).mul(2).add(c.y))) it.assign(it.add(1)) }, ) // outside points (never escaped) → black core; escaped → palette by iteration const col = palette({ t: it.div(96).add(U.field.time.mul(0.05)) }) return vec4(col.mul(it.div(96)), 1) }, { stage: 'fragment', retAttr: '@location(0)' },)
// `palette` is called via its handle in `fs`, so module() collects it transitively — funcs lists only the entry points.const juliaModule = module({ structs: [U.struct, VsOut.decl], bindings: [U.binding], funcs: [vs, fs],})
export const julia: ShaderExample = { id: 'julia', title: 'Julia set', blurb: 'Escape-time Julia fractal — z ← z² + c iterated per pixel, coloured by iteration count through a cosine palette. The constant c orbits on autopilot; move the pointer over the canvas to steer it by hand.', category: 'generic', file: 'julia.ts', module: juliaModule, renderable: true, controls: { time: { kind: 'time' }, resolution: { kind: 'resolution' }, zoom: { kind: 'slider', label: 'Zoom', min: 0.4, max: 2.0, step: 0.05, value: 1.4 }, mouse: { kind: 'mouse' }, },}struct Uniforms { time: f32, resolution: vec2<f32>, zoom: f32, mouse: vec4<f32>,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
@group(0) @binding(0) var<uniform> U: Uniforms;
fn palette(t: f32) -> vec3<f32> { return (vec3<f32>(0.5) + (cos(((t + vec3<f32>(0.0, 0.33, 0.67)) * 6.283)) * 0.5));}
@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(vo: VsOut) -> @location(0) vec4<f32> { var _v0: vec2<f32> = (vec2<f32>(((vo.uv.x * 2.0) - 1.0), ((vo.uv.y * 2.0) - 1.0)) * U.zoom); let _v1 = mix(vec2<f32>(((cos((U.time * 0.31)) * 0.39) - 0.4), (sin((U.time * 0.41)) * 0.39)), vec2<f32>(((((U.mouse.x / U.resolution.x) * 2.0) - 1.0) * 0.8), ((((U.mouse.y / U.resolution.y) * 2.0) - 1.0) * 0.8)), U.mouse.w); var _v2: f32 = 0.0; for (var _v3: u32 = 0u; (_v3 < 96u); _v3 = (_v3 + 1u)) { if ((dot(_v0, _v0) > 4.0)) { break; } _v0 = vec2<f32>((((_v0.x * _v0.x) - (_v0.y * _v0.y)) + _v1.x), (((_v0.x * _v0.y) * 2.0) + _v1.y)); _v2 = (_v2 + 1.0); } let _lc0 = (_v2 / 96.0); return vec4<f32>((palette((_lc0 + (U.time * 0.05))) * _lc0), 1.0);}#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;};layout(std140) uniform Uniforms { float time; vec2 resolution; float zoom; vec4 mouse;} U;vec3 palette(float t);vec3 palette(float t) { return (vec3(0.5) + (cos(((t + vec3(0.0, 0.33, 0.67)) * 6.283)) * 0.5));}in vec2 uv;layout(location = 0) out vec4 _ret;
vec4 fs_impl(VsOut vo) { vec2 _v0 = (vec2(((vo.uv.x * 2.0) - 1.0), ((vo.uv.y * 2.0) - 1.0)) * U.zoom); vec2 _v1 = mix(vec2(((cos((U.time * 0.31)) * 0.39) - 0.4), (sin((U.time * 0.41)) * 0.39)), vec2(((((U.mouse.x / U.resolution.x) * 2.0) - 1.0) * 0.8), ((((U.mouse.y / U.resolution.y) * 2.0) - 1.0) * 0.8)), U.mouse.w); float _v2 = 0.0; for (uint _v3 = 0u; (_v3 < 96u); _v3 = (_v3 + 1u)) { if ((dot(_v0, _v0) > 4.0)) { break; } _v0 = vec2((((_v0.x * _v0.x) - (_v0.y * _v0.y)) + _v1.x), (((_v0.x * _v0.y) * 2.0) + _v1.y)); _v2 = (_v2 + 1.0); } float _lc0 = (_v2 / 96.0); return vec4((palette((_lc0 + (U.time * 0.05))) * _lc0), 1.0);}
void main() { VsOut vo; vo.pos = gl_FragCoord; vo.uv = uv; _ret = fs_impl(vo);}{ "bindGroups": [ { "group": 0, "entries": [ { "group": 0, "binding": 0, "name": "U", "space": "uniform", "resourceKind": "uniform-buffer", "structName": "Uniforms" } ] } ], "uniforms": [ { "name": "Uniforms", "size": 48, "align": 16, "fields": [ { "name": "time", "type": "f32", "offset": 0, "align": 4, "size": 4 }, { "name": "resolution", "type": "vec2<f32>", "offset": 8, "align": 8, "size": 8 }, { "name": "zoom", "type": "f32", "offset": 16, "align": 4, "size": 4 }, { "name": "mouse", "type": "vec4<f32>", "offset": 32, "align": 16, "size": 16 } ] } ], "storage": [], "entries": [ { "name": "vs", "stage": "vertex", "inputs": [ "u32" ], "output": "struct:VsOut" }, { "name": "fs", "stage": "fragment", "inputs": [ "struct:VsOut" ], "output": "vec4<f32>" } ], "overrides": []}
Run it locally:
npx tsx examples/print.ts julia
— prints the WGSL, GLSL, reflection. Source:
GitHub.
New to the IR? See Concepts; the full authoring + emit surface is in the API reference.