Motion GPU supports post-processing through a render graph that sequences passes over ping-pong buffers and optional named render targets. This page covers the render graph model, slot semantics, the three built-in render pass types, and two compute pass types.
For named render targets, see Render Targets. For detailed compute shader documentation, see Compute Shaders.
Pass constructors are framework-agnostic and can be imported from root/core entrypoints.
Render graph model
When you add passes to FragCanvas, the renderer builds an execution plan:
- The base shader renders your material’s
fn frag(...)into thesourceslot. - Each pass reads from an input slot, processes the image, and writes to an output slot.
- After all passes execute, the final output is presented to the canvas (direct if output is
canvas, otherwise via blit fromsource,target, or named target).
<FragCanvas {material} passes={[passA, passB, passC]} />Without any passes, the base shader renders directly to the canvas.
Slot semantics
canvas is output-only (cannot be used as a pass input).
Default ping-pong flow
By default, passes read from source, write to target, and then swap the two:
- Base shader →
source - Pass A: reads
source, writestarget→ swap → A’s output is now insource - Pass B: reads
source, writestarget→ swap → B’s output is now insource - Final:
sourceis blitted tocanvas
Non-swapping passes
If needsSwap: false, the pass writes to its output slot without swapping. This is useful for the final pass that writes directly to canvas:
You can also route through named targets:
Validation rules
planRenderGraph(...) validates the pass sequence before execution:
Built-in render passes
BlitPass
Fullscreen texture sample pass. Copies input to output using a fragment shader with configurable filter mode.
CopyPass
Optimized texture copy with a fullscreen-blit fallback. Attempts copyTextureToTexture when possible — a GPU-side copy that avoids shader execution entirely.
Direct copy conditions (all must be true):
clear === falsepreserve === true- Source and target are different textures
- Neither is the canvas texture
- Same width, height, and format
When any condition fails, CopyPass falls back to an internal BlitPass.
Options are identical to BlitPass.
ShaderPass
Programmable post-process pass with custom WGSL fragment shader:
Fragment contract
The fragment must declare:
Where inputColor is the sampled result from the previous pass (or the base shader).
Hot-swapping shaders
You can change the shader at runtime:
This invalidates the pipeline cache and recompiles the shader module on next render.
Options
Same as BlitPass, plus:
Pass lifecycle
The renderer tracks each pass by object identity:
Multi-pass example
<FragCanvas {material} passes={[bloomPrefilter, gamma]} />RenderPassContext
When implementing custom passes, the render(context) method receives:
Compute passes
Compute passes run GPU compute workloads within the same render graph. They do not participate in slot routing and operate on storage buffers and storage textures instead.
ComputePass
Single-dispatch compute pass:
PingPongComputePass
Iterative compute pass for multi-step simulations:
Compute in the render graph
Compute and render passes coexist in the passes array:
<FragCanvas {material} passes={[simulate, bloomPrefilter, gamma]} />Compute passes dispatch their workgroups before the scene render, so storage textures and buffers are up-to-date when the fragment shader samples them. Render passes execute after the scene as post-processing steps. All passes share the same command encoder.
For full compute shader documentation, see Compute Shaders and Storage Buffers.