dont-track-me-google
Firefox and Chrome extensions to prevent Google from making links ugly.
Minimalist framework for modern WebGPU visuals.
Motion GPU is a minimalist WebGPU framework for writing Shadertoy-style fullscreen shaders in pure WGSL. It provides a framework-agnostic core with Svelte 5, React, and Vue adapters for building fragment-driven GPU programs and multi-pass rendering pipelines. The framework includes a minimal runtime loop, scheduler, and render graph tailored specifically for fullscreen shader execution, focusing on a narrow GPU workflow rather than general-purpose 3D rendering.
Motion GPU is designed for applications where the entire scene is driven by fullscreen shaders.
Typical use cases include:
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 | 186 kB (gzip) | 25.4 kB (gzip) |
| 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.
Note: Bundle size figures are based on measurements from https://bundlejs.com/
Motion GPU follows a simple three-step flow:
defineMaterial(...).<FragCanvas />.useFrame(...), useMotionGPU(), and useTexture(...).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:
ShaderPassBlitPassCopyPassFragment feedback passes:
PingPongShaderPass — iterative fullscreen fragment simulations with render-texture A/B alternationGPU compute passes:
ComputePass — single-dispatch GPU compute workloadsPingPongComputePass — iterative multi-step simulations with texture A/B alternationNamed 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
/svelte, /svelte/advanced)/react, /react/advanced)/vue, /vue/advanced)https:// or localhost)npm i @motion-core/motion-gpu
MotionGPU 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>
);
}
useFrame<!-- 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>
);
}
Use PingPongShaderPass when the simulation is naturally expressed as a fullscreen fragment shader that reads the previous texture state and writes the next one.
<!-- App.svelte -->
<script lang="ts">
import { FragCanvas, PingPongShaderPass, defineMaterial } from '@motion-core/motion-gpu/svelte';
const feedbackShader = `
fn frag(uv: vec2f) -> vec4f {
let previous = textureSampleLevel(motiongpuPrevious, motiongpuPreviousSampler, uv, 0.0);
let pulse = smoothstep(0.04, 0.0, distance(uv, vec2f(0.5)));
return max(previous * 0.96, vec4f(pulse, pulse * 0.4, 0.0, 1.0));
}
`;
const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
return textureSample(uTrail, uTrailSampler, uv);
}
`,
textures: {
uTrail: { format: 'rgba16float', filter: 'linear' }
}
});
const trail = new PingPongShaderPass({
fragment: feedbackShader,
target: 'uTrail',
width: 512,
height: 512,
format: 'rgba16float',
iterations: 4
});
</script>
<FragCanvas {material} passes={[trail]} />
import { FragCanvas, PingPongShaderPass, defineMaterial } from '@motion-core/motion-gpu/react';
const feedbackShader = `
fn frag(uv: vec2f) -> vec4f {
let previous = textureSampleLevel(motiongpuPrevious, motiongpuPreviousSampler, uv, 0.0);
let pulse = smoothstep(0.04, 0.0, distance(uv, vec2f(0.5)));
return max(previous * 0.96, vec4f(pulse, pulse * 0.4, 0.0, 1.0));
}
`;
const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
return textureSample(uTrail, uTrailSampler, uv);
}
`,
textures: {
uTrail: { format: 'rgba16float', filter: 'linear' }
}
});
const trail = new PingPongShaderPass({
fragment: feedbackShader,
target: 'uTrail',
width: 512,
height: 512,
format: 'rgba16float',
iterations: 4
});
export function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<FragCanvas material={material} passes={[trail]} />
</div>
);
}
defineMaterial(...) validates and freezes:
definesincludesA 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) — returns Promise<ArrayBuffer>state.invalidate(token?)state.advance()FragCanvas resolves material state, schedules tasks, and decides whether to render based on:
renderMode (always, on-demand, manual)autoRenderThese are enforced by runtime validation.
fn frag(uv: vec2f) -> vec4f
Fragment and ShaderPass colors are authored as straight alpha. Motion GPU premultiplies only at final canvas presentation so internal render targets and pass inputs remain straight-alpha.
ShaderPass fragment entrypoint must be:
fn shade(inputColor: vec4f, uv: vec2f) -> vec4f
PingPongShaderPass fragment entrypoint must be:fn frag(uv: vec2f) -> vec4f
PingPongShaderPass iterations must be >= 1. Its target must reference a fragment-visible texture declared in defineMaterial({ textures }) and must not be declared as a compute storage target.
useFrame() and useMotionGPU() 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: true is valid only for input: 'source' and output: 'target'.
Render passes cannot read from input: 'canvas'.
maxDelta and profiling window must be finite and greater than 0.
ComputePass shader must contain @compute @workgroup_size(...) and a fn compute(...) entrypoint with a @builtin(global_invocation_id) parameter.
PingPongComputePass iterations must be >= 1. The target must reference a texture declared with storage: true and explicit width/height.
Compute and fragment feedback passes do not participate in render pass slot routing (no input/output/needsSwap).
Storage buffer size must be > 0 and a multiple of 4. All storage buffers must be declared in defineMaterial({ storageBuffers }).
FragCanvas color pipeline option changesPingPongShaderPass.setFragment(...) changes (only that pass pipeline is rebuilt on next render)Run from packages/motion-gpu:
pnpm run build pnpm run check pnpm run test pnpm run test:e2e pnpm run lint pnpm run format
pnpm run perf:core pnpm run perf:core:check pnpm run perf:core:baseline pnpm run perf:runtime pnpm run perf:runtime:check pnpm run perf:runtime:baseline
This project is licensed under the MIT License.
See the LICENSE file for details.
more like this
Firefox and Chrome extensions to prevent Google from making links ugly.
Pathfinding Algorithm Visualizer with ability to Pause/Rewind during run
search projects, people, and tags