A minimalist WebGPU framework for writing Shadertoy-style fullscreen shaders in pure WGSL.
@motion-core/motion-gpu ships a framework-agnostic core plus Svelte 5, React, and Vue adapters for building fullscreen shader pipelines using WebGPU and WGSL.
It provides a minimal runtime loop, scheduler, and render graph designed specifically for fragment-driven GPU programs.
Unlike general-purpose 3D engines, Motion GPU focuses on a very narrow problem: running fullscreen fragment shaders and multi-pass GPU pipelines.
Motion GPU is designed for applications where the entire scene is driven by fullscreen shaders.
Typical use cases include:
- Shadertoy-style GPU experiments
- Generative art
- Procedural textures
- Multi-pass post-processing pipelines
- GPU simulations
- Shader editors and live-coding tools
- Interactive visual experiments
If your application is primarily a fullscreen fragment shader pipeline, using a full 3D engine can add unnecessary complexity and bundle size.
Three.js is a powerful general-purpose 3D engine. Motion GPU focuses on a much narrower problem: running fullscreen WGSL shader pipelines.
| Feature | Three.js | Motion GPU |
|---|---|---|
| Scope | Full 3D engine | Fullscreen shader framework |
| Shader language | TSL / generated WGSL | Native WGSL |
| Bundle size | large | tiny (3.5-5x smaller) |
| Rendering model | Scene graph | GPU pipeline |
| Shader pipeline | materials | explicit passes |
| Multi-pass | possible but indirect | first-class |
| Shader debugging | generated shaders | direct WGSL |
Motion GPU is not a replacement for Three.js.
Instead, it is designed for cases where a full 3D engine would be unnecessary overhead.
Motion GPU follows a simple three-step flow:
- Define an immutable material with
defineMaterial(...). - Render it with
<FragCanvas />. - Drive runtime updates with
useFrame(...),useMotionGPU(), anduseTexture(...).
-
Fullscreen WebGPU renderer for WGSL fragment shaders
-
Strict material contract and validation (
fn frag(uv: vec2f) -> vec4f) -
Runtime uniform and texture updates without rebuilding the pipeline
-
Frame scheduler with task ordering, stages, invalidation modes, diagnostics and profiling
-
Render graph with built-in post-process passes:
ShaderPassBlitPassCopyPass
-
GPU compute passes:
ComputePass— single-dispatch GPU compute workloadsPingPongComputePass— iterative multi-step simulations with texture A/B alternation
-
Named render targets for multi-pass pipelines
-
Structured error normalization with built-in overlay UI and custom renderer support
-
Advanced runtime API for namespaced shared user context and scheduler presets
@motion-core/motion-gpu/svelte exposes the runtime API for Svelte:
FragCanvasdefineMaterialuseMotionGPUuseFrameusePointeruseTextureShaderPassBlitPassCopyPassComputePassPingPongComputePass
Also exports runtime/core types:
- uniforms
- textures
- render passes
- scheduler
- loader types
@motion-core/motion-gpu/svelte/advanced re-exports everything above, plus:
useMotionGPUUserContextsetMotionGPUUserContextapplySchedulerPresetcaptureSchedulerDebugSnapshot
@motion-core/motion-gpu/react exposes the runtime API for React:
FragCanvasdefineMaterialuseMotionGPUuseFrameusePointeruseTextureShaderPassBlitPassCopyPassComputePassPingPongComputePass
Also exports runtime/core types:
- uniforms
- textures
- render passes
- scheduler
- loader types
@motion-core/motion-gpu/react/advanced re-exports everything above, plus:
useMotionGPUUserContextuseSetMotionGPUUserContextsetMotionGPUUserContextapplySchedulerPresetcaptureSchedulerDebugSnapshot
@motion-core/motion-gpu/vue exposes the runtime API for Vue:
FragCanvasdefineMaterialuseMotionGPUuseFrameusePointeruseTextureShaderPassBlitPassCopyPassComputePassPingPongComputePass
Also exports runtime/core types:
- uniforms
- textures
- render passes
- scheduler
- loader types
@motion-core/motion-gpu/vue/advanced re-exports everything above, plus:
useMotionGPUUserContextsetMotionGPUUserContextapplySchedulerPresetcaptureSchedulerDebugSnapshot
@motion-core/motion-gpu (and explicit alias @motion-core/motion-gpu/core) exposes adapter-building primitives:
defineMaterialresolveMaterialcreateCurrentWritablecreateFrameRegistrycreateMotionGPURuntimeLooploadTexturesFromUrlstoMotionGPUErrorReportShaderPassBlitPassCopyPassComputePassPingPongComputePass
@motion-core/motion-gpu/advanced (and explicit alias @motion-core/motion-gpu/core/advanced) re-exports core plus:
applySchedulerPresetcaptureSchedulerDebugSnapshot
- Svelte 5 is required only for the Svelte adapter entrypoints (
/svelte,/svelte/advanced) - React 18+ is required only for the React adapter entrypoints (
/react,/react/advanced) - Vue 3.5+ is required only for the Vue adapter entrypoints (
/vue,/vue/advanced) - A browser/runtime with WebGPU support
- Secure context (
https://orlocalhost)
npm i @motion-core/motion-gpuMotionGPU documentation is also available for AI tools via Context7.
<!-- App.svelte -->
<script lang="ts">
import { FragCanvas, defineMaterial } from '@motion-core/motion-gpu/svelte';
const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
return vec4f(uv.x, uv.y, 0.25, 1.0);
}
`
});
</script>
<div style="width: 100vw; height: 100vh;">
<FragCanvas {material} />
</div>import { FragCanvas, defineMaterial } from '@motion-core/motion-gpu/react';
const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
return vec4f(uv.x, uv.y, 0.25, 1.0);
}
`
});
export function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<FragCanvas material={material} />
</div>
);
}<!-- App.svelte -->
<script lang="ts">
import { FragCanvas, defineMaterial } from '@motion-core/motion-gpu/svelte';
import Runtime from './Runtime.svelte';
const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
let wave = 0.5 + 0.5 * sin(motiongpuUniforms.uTime + uv.x * 8.0);
return vec4f(vec3f(wave), 1.0);
}
`,
uniforms: {
uTime: 0
}
});
</script>
<FragCanvas {material}>
<Runtime />
</FragCanvas><!-- Runtime.svelte -->
<script lang="ts">
import { useFrame } from '@motion-core/motion-gpu/svelte';
useFrame((state) => {
state.setUniform('uTime', state.time);
});
</script>import { useFrame } from '@motion-core/motion-gpu/react';
export function Runtime() {
useFrame((state) => {
state.setUniform('uTime', state.time);
});
return null;
}<!-- App.svelte -->
<script lang="ts">
import { FragCanvas, defineMaterial, ComputePass } from '@motion-core/motion-gpu/svelte';
import Runtime from './Runtime.svelte';
const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
let idx = u32(uv.x * 255.0);
let particle = particles[idx];
return vec4f(particle.rgb, 1.0);
}
`,
storageBuffers: {
particles: { size: 4096, type: 'array<vec4f>', access: 'read-write' }
}
});
const simulate = new ComputePass({
compute: `
@compute @workgroup_size(64)
fn compute(@builtin(global_invocation_id) id: vec3u) {
let i = id.x;
let t = motiongpuFrame.time;
particles[i] = vec4f(sin(t + f32(i)), cos(t + f32(i)), 0.0, 1.0);
}
`,
dispatch: [16]
});
</script>
<FragCanvas {material} passes={[simulate]}>
<Runtime />
</FragCanvas>import { FragCanvas, defineMaterial, ComputePass } from '@motion-core/motion-gpu/react';
const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
let idx = u32(uv.x * 255.0);
let particle = particles[idx];
return vec4f(particle.rgb, 1.0);
}
`,
storageBuffers: {
particles: { size: 4096, type: 'array<vec4f>', access: 'read-write' }
}
});
const simulate = new ComputePass({
compute: `
@compute @workgroup_size(64)
fn compute(@builtin(global_invocation_id) id: vec3u) {
let i = id.x;
let t = motiongpuFrame.time;
particles[i] = vec4f(sin(t + f32(i)), cos(t + f32(i)), 0.0, 1.0);
}
`,
dispatch: [16]
});
export function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<FragCanvas material={material} passes={[simulate]} />
</div>
);
}defineMaterial(...) validates and freezes:
- WGSL fragment source
- Uniform declarations
- Texture declarations
- Compile-time
defines - Shader
includes - Storage buffer declarations
A deterministic material signature is generated from resolved shader/layout metadata.
Inside useFrame(...) callbacks you update per-frame values:
state.setUniform(name, value)state.setTexture(name, value)state.writeStorageBuffer(name, data, { offset? })state.readStorageBuffer(name)— returnsPromise<ArrayBuffer>state.invalidate(token?)state.advance()
FragCanvas resolves material state, schedules tasks, and decides whether to render based on:
renderMode(always,on-demand,manual)- invalidation / advance state
autoRender
These are enforced by runtime validation.
- Material entrypoint must be:
fn frag(uv: vec2f) -> vec4f
ShaderPassfragment entrypoint must be:
fn shade(inputColor: vec4f, uv: vec2f) -> vec4f
-
useFrame()anduseMotionGPU()must be called inside<FragCanvas>subtree. -
You can only set uniforms/textures that were declared in
defineMaterial(...). -
Uniform/texture/include/define names must match WGSL-safe identifiers:
[A-Za-z_][A-Za-z0-9_]*
-
needsSwap: trueis valid only forinput: 'source'andoutput: 'target'. -
Render passes cannot read from
input: 'canvas'. -
maxDeltaand profiling window must be finite and greater than0. -
ComputePassshader must contain@compute @workgroup_size(...)and afn compute(...)entrypoint with a@builtin(global_invocation_id)parameter. -
PingPongComputePassiterationsmust be>= 1. Thetargetmust reference a texture declared withstorage: trueand explicitwidth/height. -
Compute passes do not participate in render pass slot routing (no
input/output/needsSwap). -
Storage buffer
sizemust be> 0and a multiple of 4. All storage buffers must be declared indefineMaterial({ storageBuffers }).
- Material signature changes (shader/layout/bindings)
outputColorSpacechanges
- Runtime uniform value changes
- Runtime texture source changes
- Clear color changes
- Canvas resize (resources are resized/reallocated as needed)
Run from packages/motion-gpu:
bun run build
bun run check
bun run test
bun run test:e2e
bun run lint
bun run formatbun run perf:core
bun run perf:core:check
bun run perf:core:baseline
bun run perf:runtime
bun run perf:runtime:check
bun run perf:runtime:baselineThis project is licensed under the MIT License.
See the LICENSE file for details.
