Motion GPU supports post-processing through a post-scene render graph that sequences render passes over ping-pong buffers and optional named render targets. The same passes array also accepts compute passes, but compute passes are extracted into a separate pre-scene compute phase and never participate in slot routing.
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 with two execution groups:
- All enabled compute passes dispatch first, in declaration order. They update storage buffers/textures before the scene shader samples them.
- The base shader renders your material’s
fn frag(...). If post-process render passes or a final color pipeline are enabled, it renders into an internal surface; otherwise it renders directly tocanvas. - Enabled render passes execute after the base shader, in declaration order. Each render pass reads from an input slot, processes the image, and writes to an output slot.
- The renderer presents the resolved final surface to the physical canvas. With
color.toneMapping,color.dynamicRange, orcolor.workingFormat, this final step is a private presentation pass that applies color transforms after all render passes.
Without render passes and without a color presentation pipeline, the base shader renders directly to the canvas. Compute-only pipelines keep that direct path. Enabling Khronos PBR Neutral, HDR presentation, or a custom workingFormat allocates an internal presentation surface so the final color transform can remain last.
Slot semantics
canvas is output-only (cannot be used as a pass input).
When a final color pipeline is active, render passes should still write output: 'canvas' for their final result. The renderer maps that logical slot to an internal rgba16float target when needed, then runs the private presentation pass to the physical swapchain. Custom passes should use context.output or context.beginRenderPass() instead of manually writing to the physical canvas.
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 render-pass slot usage before execution. Compute passes are kept in the plan for diagnostics and pre-scene dispatch ordering, but they do not make slots available and cannot satisfy render-pass read dependencies.
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
RenderPassContext
When implementing custom passes, the render(context) method receives:
Compute passes
Compute passes run GPU compute workloads from the same passes prop, but they execute in the pre-scene phase. 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 frame
Compute and render passes coexist in the passes array:
All enabled compute passes dispatch before the scene render, regardless of where render passes appear in the array. Their relative order against other compute passes is preserved. Enabled render passes then execute after the scene as post-processing steps, preserving their relative order against other render passes. All passes share the same command encoder and one queue submit.
For full compute shader documentation, see Compute Shaders and Storage Buffers.