Code Nodes

Render live GLSL shaders, Canvas2D JavaScript, or math plots to an image.

Three nodes let you draw with code, live: GLSL, JavaScript, and Plot. Each has an embedded editor with inline errors; the JavaScript editor also autocompletes the whole scripting API. To use a live render in the rest of the graph, pass it through a Renderer 2D node.

The pattern#

A live render output is a signal, not a finished image. A Renderer 2D node rasterizes that signal into a normal image you can filter, blend, or export.

GLSL / JavaScript / Plot  ->  Renderer 2D  ->  image  ->  filters, Export, ...

Wire the code node's render output into Renderer 2D, then route Renderer 2D's image output downstream. Press Play in the toolbar to start the time clock so animations run. The JavaScript node can also emit finished image outputs that connect straight to image inputs, no Renderer 2D needed (see below).

GLSL#

Shadertoy-style fragment shaders, compiled to a GPU kernel and run every frame. Two entry points work:

//@resolution 480x360

void main() {
    vec3 col = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0.0, 2.0, 4.0));
    writeOutput(vec4(col, 1.0));
}

Or paste a single-pass Shadertoy snippet as-is:

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    fragColor = vec4(uv, 0.5 + 0.5 * sin(iTime), 1.0);
}

Globals: iTime (seconds), iResolution (output size as a vec3), iMouse (vec4), uv (fragment coordinate, 0 to 1), fragCoord. The short form writes its result with writeOutput(color); the Shadertoy form assigns fragColor.

JavaScript#

A general-purpose script node. Declare typed inputs and outputs as directives, write a setup body that wires them, and animate with a per-frame callback. It renders live, produces finished images, computes data, or all three at once:

//@output renderer2d out 480x360

let { noise } = magerie.generators
let { mul } = magerie.ops

let size = { width: out.width, height: out.height }
let tex = mul(noise("voronoi", 7, size), noise("simplex", 3, size))

let spin = 0
onFrame((time, dt) => {
    spin += dt
})

out.draw((ctx, time) => {
    ctx.drawImage(tex, 0, 0, out.width, out.height)
    ctx.save()
    ctx.translate(out.width / 2, out.height / 2)
    ctx.rotate(spin)
    ctx.fillStyle = `hsl(${time * 40}, 70%, 60%)`
    ctx.fillRect(40, -10, 60, 20)
    ctx.restore()
})

Run and Play#

Nothing executes while you type or when a document opens; the editor only checks syntax (errors show inline, with line numbers). Press Run on the node to execute it once, or Play in the toolbar to run every JavaScript node and drive animation. The setup body runs once per Run or Play. Pause and resume continues your state; Stop resets the clock and the next Play starts fresh. Runtime errors (a bad option name, a throw in a callback) appear on the node's error strip.

Outputs#

Each //@output becomes a port and a same-named handle in your code:

Output Use
//@output renderer2d <name> [WxH] A live render. Register a recipe with name.draw((ctx, time, opts) => ...); wire it into Renderer 2D. name.width / name.height carry the declared size (default 480x360, set per output).
//@output image <name> A finished image. Assign name.value = img with an image from the scripting API. Connects straight to image inputs.
//@output float / vec2 / vec3 / vec4 / color <name> Data. Assign name.value = ... to drive parameters downstream.
//@output text <name> A string output.

A draw recipe receives the live clock, so pure-time motion needs nothing else. Use onFrame((time, dt) => ...) for state that accumulates between frames: physics, easing, trails.

The scripting API#

magerie.generators and magerie.ops expose the node library as plain functions: noise, gradients, shapes, scatter, every blend mode, blur, levels, warp, and more, with autocomplete for every signature. Full reference: Scripting API.

Canvas drawing#

Draw recipes get a ctx shaped like a CanvasRenderingContext2D. Most of the Canvas2D API works: state (save / restore), transforms, rectangles, paths, fill / stroke / clip, drawImage (including images from the scripting API), gradients, getImageData, globalAlpha, and globalCompositeOperation (blend modes). Not yet supported (these warn and no-op): text (fillText / strokeText / measureText), shadows, pattern fills, and non-default line caps and joins.

Plot#

A live math grapher. One equation per line as y = f(x); lines like a = ... define reusable values:

//@resolution 512x512
//@xrange -3..3
//@yrange -1.5..1.5

freq = 2
y = sin(freq * x)
y = x^2 / 4

Each y = curve is sampled across the x range and drawn in its own color. //@xrange and //@yrange set the viewport.

Inputs#

Declare ports with //@input directives at the top of the source. Each becomes an input port on the code node; wire an upstream node into it and the value is available by name in your code.

//@input image src
//@input float strength 0..2 = 1
//@input color tint = #ffaa00
//@resolution 1024x1024

void main() {
    vec4 c = src(uv);
    writeOutput(mix(c, tint, strength * 0.5));
}
Directive Becomes
//@input image <name> An image port. In GLSL, sample it as name(uv); in JavaScript read name.value (an image you can pass to ops or drawImage).
//@input float <name> [min..max] [= value] A number port, with an optional slider range and default.
//@input vec2 / vec3 / vec4 <name> [= (...)] A vector port.
//@input color <name> [= #rrggbb] A color port.
//@resolution <w>x<h> The output size for GLSL and Plot; defaults to 480x360. JavaScript sizes each render output inline instead: //@output renderer2d out 800x600.

In JavaScript every input is a handle: read name.value for the live connected value, in the setup body, in onFrame, or in a draw recipe.

Live editing and size#

GLSL and Plot recompile on every edit; JavaScript checks syntax as you type and executes on Run or Play. Compile errors show inline at the offending line. Output renders at 2x backing for sharp edges on retina displays.

Next#