@frapx/fx-passes
fx-* — effect runtime Instantiate it directly. WebGL2 only.
@frapx/fx-passes chains fragment shaders into passes. Each pass renders into its own float buffer, and a pass can be drawn many times per frame and averaged. It runs on the same host as @frapx/fx-canvas, so the manual clock, fixed resolution, captureFrame() and the lifecycle work the same way. @frapx/tool-capture exports it without changes.
Stay with fx-canvas for website backgrounds. Reach for fx-passes when a single pass is not enough (bloom, trails, simulations) or when frames for video need many samples.
pnpm add @frapx/fx-passesimport { createPasses } from "@frapx/fx-passes";
const fx = createPasses({ target: "#stage", resolution: [3840, 2160], clock: "manual", accumulation: { samples: 64, shutter: 0.5, fps: 60 }, passes: [ { name: "scene", fragment: sceneFragment, accumulate: true, format: "rgba32f" }, { name: "bloom", fragment: bloomFragment, inputs: ["scene"], scale: 0.5 }, { name: "final", fragment: finalFragment, inputs: ["scene", "bloom"] } ]});
fx.setTime(2);const pixels = await fx.captureFrame({ format: "rgba16f" });Passes
Section titled “Passes”Passes are drawn in order every frame, and the last one is shown and captured.
| Option | Meaning |
|---|---|
name |
GLSL identifier. Readers sample the output as u_<name>, sized u_<name>Size |
inputs |
Passes to sample. An earlier pass gives this frame’s output. The pass itself or a later one gives the previous frame’s (Shadertoy-style; double-buffered automatically, cleared by resetFeedback()) |
format |
"rgba16f" (default), "rgba32f" or "rgba8" |
scale |
Output size relative to the canvas. u_resolution is the pass’ own size |
filter / wrap |
How readers sample the output |
accumulate |
Draw the pass accumulation.samples times per frame and pass the mean on |
Uniforms and textures are shared: every pass that declares one receives it. Textures take the same forms as @frapx/fx-canvas, including 3D data textures ({ data, size: [nx, ny, nz], format }).
Accumulation
Section titled “Accumulation”An accumulating pass outputs a single sample’s color. The runtime sums the samples in 32-bit float, so the mean never drifts darker the way a running average stored in half floats does. Later passes run once on the mean, so tone mapping and bloom see the converged image.
| Uniform | In accumulating passes |
|---|---|
u_time |
The sample’s moment within the open shutter, [time, time + shutter / fps) |
u_frameTime |
The frame’s time |
u_jitter |
Sub-pixel offset in pixels in [-0.5, 0.5); add it to gl_FragCoord.xy |
u_sampleIndex, u_sampleCount |
The sample’s index and the frame’s sample count |
A shader that depends only on time gets motion blur without changes. Samples follow a deterministic low-discrepancy sequence, and sample 0 is the plain frame.
For a seamless loop, set accumulation.loop to its length in seconds. Every pass then sees the time wrapped into the loop, with each sample’s shutter offset added in double precision, so the frame one loop later is identical to the bit at any sample count.
- Preview: an unchanged frame gains one sample per draw until
samples, and any input change starts over. A running realtime loop shows one sample per frame. A stopped or manual-clock instance keeps refining the still frame on its own. Progress is insamplesDrawnandonSample. - Capture:
captureFrame()draws all samples from scratch. It waits for the GPU between batches so long frames never trip the browser’s GPU watchdog, checkssignalbetween samples, then runs the remaining passes and reads the last one. Uniform, accumulation and feedback changes made while it draws apply to the next frame.
Accumulating passes come first and may only read passes listed after them, which gives those passes’ previous-frame output.
Timelines
Section titled “Timelines”fx.bindUniforms(keyframes({ ... })) evaluates the keyframes at every sample’s
time in accumulating passes, so a keyframed camera is motion blurred like
u_time. See examples/vite-timeline.
Hot reloading
Section titled “Hot reloading”fx.setFragment(passName, source) swaps one pass’ shader in place, keeping its
buffers, the time and uniforms, and restarting the frame’s samples. A compile
error is returned rather than thrown and the previous shader keeps drawing —
see @frapx/tool-dev for an on-page error overlay.
Instance API
Section titled “Instance API”| Member | |
|---|---|
canvas, gl, status, supported, ready, clock |
As in @frapx/fx-canvas |
accumulation, samplesDrawn |
The resolved accumulation settings, and the samples of the current frame drawn so far |
start(), stop(), render(), resize(), destroy() |
Lifecycle. render() at an unchanged time adds the next sample |
setTime(seconds), setClock(clock), setTimeSource(source), setResolution(size) |
Clock and size, as in @frapx/fx-canvas |
setAccumulation(options) |
Changes samples, shutter, fps or loop and restarts the frame’s samples |
captureFrame(options) |
Draws every sample of the current frame and reads the last pass |
resetFeedback() |
Clears the previous-frame outputs |
setFragment(name, source) |
Swaps one pass’ shader (see Hot reloading) |
bindUniforms(source) |
Drives uniforms from a timeline such as keyframes() (see Timelines) |
setUniform(), setUniforms(), setTexture(), setTextures() |
Change user uniforms and textures. Uniforms set during a capture apply to the next frame; a texture applies once it loads |
onResize(state) runs whenever the render size actually changes, as in @frapx/fx-canvas: not on setup, but also after setResolution(), and after the capture when requested during captureFrame(). state carries the instance, gl, the canvas and its new width / height. The pass buffers take the new size on the next draw.
onStatusChange(status) runs with the new value each time status changes (for example "paused" in a hidden tab, or "context-lost"), with the same rules as @frapx/fx-canvas: never for the initial "idle", nor before createPasses() returns, and calling destroy() from it is safe.
respectReducedMotion defaults to true, as in @frapx/fx-canvas: while the OS “reduce motion” setting is on, the loop holds a still frame (still refined up to accumulation.samples). Pass respectReducedMotion: false to keep animating.
Requirements
Section titled “Requirements”WebGL2 with EXT_color_buffer_float, otherwise status is "unsupported". Each pass may use GLSL ES 1.00 or 3.00. With EXT_float_blend, samples are summed by additive blending; without it, by an extra pass.