Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Motion GPU
https://github.com/motion-core/motion-gpu
Admin
Motion GPU is a tiny WebGPU runtime for writing Shadertoy-style fullscreen shaders in pure WGSL with
...
Tokens:
55,641
Snippets:
363
Trust Score:
7.5
Update:
1 month ago
Context
Skills
Chat
Benchmark
94.6
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Motion GPU Motion GPU is a lightweight WebGPU runtime for writing Shadertoy-style fullscreen shaders in pure WGSL. It provides a framework-agnostic core with a Svelte 5 adapter for building fullscreen shader pipelines, offering a minimal runtime loop, scheduler, and render graph specifically designed for fragment-driven GPU programs. Unlike general-purpose 3D engines, Motion GPU focuses on running fullscreen fragment shaders and multi-pass GPU pipelines with a significantly smaller bundle size (3.5-5x smaller than Three.js). The library follows a simple three-step workflow: define an immutable material with `defineMaterial()`, render it with `<FragCanvas />`, and drive runtime updates with hooks like `useFrame()`, `useMotionGPU()`, and `useTexture()`. It supports runtime uniform and texture updates without pipeline rebuilds, a frame scheduler with task ordering and profiling, and a render graph with built-in post-process passes (ShaderPass, BlitPass, CopyPass). Motion GPU is ideal for generative art, procedural textures, GPU simulations, shader editors, and interactive visual experiments. ## Installation ```bash npm i @motion-core/motion-gpu ``` ## defineMaterial Creates an immutable material object with validated shader, uniform, and texture contracts. The material is frozen and safe to share and cache. ```typescript import { defineMaterial } from '@motion-core/motion-gpu/svelte'; const material = defineMaterial({ fragment: ` fn frag(uv: vec2f) -> vec4f { let t = sin(motiongpuUniforms.uTime * 3.0); let color = mix(motiongpuUniforms.uColorA, motiongpuUniforms.uColorB, t * 0.5 + 0.5); return vec4f(color, 1.0); } `, uniforms: { uTime: 0, // f32 (inferred) uColorA: [1.0, 0.2, 0.3], // vec3f (inferred) uColorB: { type: 'vec3f', value: [0.2, 0.5, 1.0] } // vec3f (explicit) }, textures: { uAlbedo: { colorSpace: 'srgb', filter: 'linear', generateMipmaps: true } }, defines: { PI: 3.14159265359, RAY_BOUNCE_MAX: { type: 'i32', value: 10 } } }); ``` ## FragCanvas Component The main Svelte component for rendering materials. Handles WebGPU initialization, canvas sizing, scheduling, and render passes. ```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 { return vec4f(uv.x, uv.y, 0.25, 1.0); } ` }); </script> <FragCanvas {material} clearColor={[0.05, 0.05, 0.1, 1]} outputColorSpace="srgb" renderMode="always" autoRender={true} maxDelta={0.1} dpr={2} passes={[vignettePass]} renderTargets={{ halfRes: { scale: 0.5 } }} adapterOptions={{ powerPreference: 'high-performance' }} showErrorOverlay={true} onError={(report) => console.error(report)} class="shader-canvas" > <Runtime /> </FragCanvas> ``` ## useFrame Hook Registers a callback that runs every frame, providing access to timing data and methods to update uniforms and textures. ```svelte <script lang="ts"> import { useFrame } from '@motion-core/motion-gpu/svelte'; let mouseX = 0.5; let mouseY = 0.5; useFrame((state) => { // Access frame timing state.setUniform('uTime', state.time); state.setUniform('uDelta', state.delta); // Update uniforms state.setUniform('uMouse', [mouseX, mouseY]); // Update textures state.setTexture('uAlbedo', myImageBitmap); // Request re-render in on-demand mode state.invalidate(); // Request single frame in manual mode state.advance(); }, { stage: 'early', // Execution order: 'early' | 'default' | 'late' autoInvalidate: true // Auto-invalidate on uniform/texture changes }); </script> ``` ## useMotionGPU Hook Returns the MotionGPU context for accessing runtime state and controlling rendering from within FragCanvas children. ```svelte <script lang="ts"> import { useMotionGPU } from '@motion-core/motion-gpu/svelte'; const gpu = useMotionGPU(); // Access reactive stores (use $store in templates, .current in useFrame) const width = gpu.size.current.width; const height = gpu.size.current.height; // Control rendering function togglePause() { gpu.autoRender.set(!gpu.autoRender.current); } function switchToOnDemand() { gpu.renderMode.set('on-demand'); } // Manual control gpu.invalidate(); // Request re-render in on-demand mode gpu.advance(); // Request single frame in manual mode // Access scheduler for profiling gpu.scheduler.setProfilingEnabled(true); gpu.scheduler.setProfilingWindow(60); const stats = gpu.scheduler.getProfilingSnapshot(); </script> <p>Canvas: {$gpu.size.width}x{$gpu.size.height} @ {$gpu.dpr}x</p> <button onclick={togglePause}> {$gpu.autoRender ? 'Pause' : 'Resume'} </button> ``` ## useTexture Hook Loads textures from URLs with reactive loading/error state and automatic cleanup. ```svelte <script lang="ts"> import { useFrame, useTexture } from '@motion-core/motion-gpu/svelte'; // Load multiple textures const loaded = useTexture( ['/assets/albedo.png', '/assets/normal.png'], { colorSpace: 'srgb', generateMipmaps: true, decode: { premultiplyAlpha: 'none' } } ); // Use in frame callback useFrame((state) => { if (loaded.loading.current) return; if (loaded.error.current) return; const textures = loaded.textures.current; if (textures) { state.setTexture('uAlbedo', { source: textures[0].source }); state.setTexture('uNormal', { source: textures[1].source }); } }); // Reload on demand function refreshTextures() { loaded.reload(); } </script> {#if $loaded.loading} <p>Loading textures...</p> {:else if $loaded.error} <p>Error: {$loaded.error.message}</p> {:else} <p>Loaded {$loaded.textures?.length ?? 0} textures</p> {/if} ``` ## ShaderPass Programmable post-process pass with custom WGSL fragment shader. The fragment must declare `fn shade(inputColor: vec4f, uv: vec2f) -> vec4f`. ```typescript import { ShaderPass } from '@motion-core/motion-gpu/svelte'; // Vignette effect const vignettePass = new ShaderPass({ fragment: ` fn shade(inputColor: vec4f, uv: vec2f) -> vec4f { let dist = distance(uv, vec2f(0.5, 0.5)); let v = smoothstep(0.9, 0.35, dist); return vec4f(inputColor.rgb * v, inputColor.a); } `, enabled: true, needsSwap: true, // Swap source/target after render input: 'source', // Read from source buffer output: 'target', // Write to target buffer filter: 'linear' }); // Gamma correction to canvas (final pass) const gammaPass = new ShaderPass({ needsSwap: false, input: 'source', output: 'canvas', fragment: ` fn shade(inputColor: vec4f, uv: vec2f) -> vec4f { return vec4f(pow(inputColor.rgb, vec3f(1.0 / 2.2)), inputColor.a); } ` }); // Hot-swap shader at runtime vignettePass.setFragment(` fn shade(inputColor: vec4f, uv: vec2f) -> vec4f { return inputColor; // Passthrough } `); ``` ## BlitPass Fullscreen texture blit pass that copies input to output using configurable filter mode. ```typescript import { BlitPass } from '@motion-core/motion-gpu/svelte'; const blitPass = new BlitPass({ enabled: true, needsSwap: true, input: 'source', output: 'target', filter: 'nearest', // 'linear' or 'nearest' clear: false, clearColor: [0, 0, 0, 1], preserve: true }); ``` ## CopyPass Optimized texture copy with GPU-side `copyTextureToTexture` when possible, falling back to blit when direct copy conditions aren't met. ```typescript import { CopyPass } from '@motion-core/motion-gpu/svelte'; const copyPass = new CopyPass({ enabled: true, needsSwap: true, input: 'source', output: 'target', filter: 'linear' }); // Direct GPU copy when: same size/format, different textures, neither is canvas ``` ## Uniform Types Supported uniform types with shorthand and explicit declaration forms. ```typescript import { defineMaterial } from '@motion-core/motion-gpu/svelte'; const material = defineMaterial({ fragment: ` fn frag(uv: vec2f) -> vec4f { let t = motiongpuUniforms.uTime; let pos = motiongpuUniforms.uPosition; let color = motiongpuUniforms.uColor; let tint = motiongpuUniforms.uTint; let matrix = motiongpuUniforms.uModelMatrix; return vec4f(color * t, 1.0); } `, uniforms: { // Shorthand (type inferred from value shape) uTime: 0, // f32 uPosition: [0.5, 0.5], // vec2f uColor: [1.0, 0.5, 0.0], // vec3f uTint: [1.0, 1.0, 1.0, 0.8], // vec4f // Explicit (required for mat4x4f) uBrightness: { type: 'f32', value: 1.0 }, uModelMatrix: { type: 'mat4x4f', value: [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] } } }); // Built-in frame uniforms (auto-provided, don't declare): // motiongpuFrame.time - f32 timestamp in seconds // motiongpuFrame.delta - f32 frame delta in seconds // motiongpuFrame.resolution - vec2f canvas size in pixels ``` ## Texture Configuration Configure texture sampling, upload behavior, and update strategies. ```typescript import { defineMaterial } from '@motion-core/motion-gpu/svelte'; const material = defineMaterial({ fragment: ` fn frag(uv: vec2f) -> vec4f { let color = textureSample(uAlbedo, uAlbedoSampler, uv); let video = textureSample(uVideo, uVideoSampler, uv); return mix(color, video, 0.5); } `, textures: { uAlbedo: { colorSpace: 'srgb', // 'srgb' or 'linear' flipY: true, // Flip vertically on upload generateMipmaps: true, // Generate mip chain premultipliedAlpha: false, // Alpha premultiplication update: 'once', // 'once' | 'onInvalidate' | 'perFrame' anisotropy: 4, // Anisotropic filtering (1-16) filter: 'linear', // 'linear' or 'nearest' addressModeU: 'repeat', // 'clamp-to-edge' | 'repeat' | 'mirror-repeat' addressModeV: 'repeat' }, uVideo: { update: 'perFrame' // Re-upload every frame for video } } }); ``` ## Video Texture Example Bind video elements as textures with automatic per-frame updates. ```svelte <script lang="ts"> import { FragCanvas, defineMaterial, useFrame } from '@motion-core/motion-gpu/svelte'; let video: HTMLVideoElement; const material = defineMaterial({ fragment: ` fn frag(uv: vec2f) -> vec4f { return textureSample(uVideo, uVideoSampler, uv); } `, textures: { uVideo: { update: 'perFrame' } } }); function Runtime() { useFrame((state) => { if (video && video.readyState >= 2) { state.setTexture('uVideo', video); } }); } </script> <video bind:this={video} src="/assets/loop.mp4" autoplay loop muted playsinline /> <FragCanvas {material}> <Runtime /> </FragCanvas> ``` ## Render Targets Define named off-screen render targets for multi-pass pipelines. ```svelte <script lang="ts"> import { FragCanvas, ShaderPass, defineMaterial } from '@motion-core/motion-gpu/svelte'; const material = defineMaterial({ fragment: `fn frag(uv: vec2f) -> vec4f { return vec4f(uv, 0.5, 1.0); }` }); // Write to named target const prePass = new ShaderPass({ needsSwap: false, output: 'fxMain', fragment: ` fn shade(inputColor: vec4f, uv: vec2f) -> vec4f { return vec4f(inputColor.rgb * vec3f(uv, 1.0), inputColor.a); } ` }); // Read from named target, write to canvas const compositePass = new ShaderPass({ needsSwap: false, input: 'fxMain', output: 'canvas', fragment: ` fn shade(inputColor: vec4f, uv: vec2f) -> vec4f { return vec4f(inputColor.bgr, inputColor.a); } ` }); </script> <FragCanvas {material} passes={[prePass, compositePass]} renderTargets={{ fxMain: { scale: 1.0 }, halfRes: { scale: 0.5 }, custom: { width: 512, height: 512, format: 'rgba16float' } }} /> ``` ## Compile-Time Defines Inject WGSL constants at material definition time for conditional compilation. ```typescript import { defineMaterial } from '@motion-core/motion-gpu/svelte'; const material = defineMaterial({ defines: { PI: 3.14159265359, // f32 (inferred) ENABLE_SHADOWS: true, // bool (inferred) MAX_ITERATIONS: { type: 'i32', value: 100 }, PRECISION: { type: 'f32', value: 0.001 } }, fragment: ` fn frag(uv: vec2f) -> vec4f { var result = vec3f(0.0); for (var i = 0; i < MAX_ITERATIONS; i = i + 1) { result += uv.x * sin(f32(i) * PI * PRECISION); } if (ENABLE_SHADOWS) { result *= 0.8; } return vec4f(result, 1.0); } ` }); ``` ## Render Modes Control frame rendering frequency for different use cases. ```svelte <script lang="ts"> import { FragCanvas, useMotionGPU, useFrame, defineMaterial } from '@motion-core/motion-gpu/svelte'; const material = defineMaterial({ fragment: `fn frag(uv: vec2f) -> vec4f { return vec4f(uv, 0.5, 1.0); }`, uniforms: { uTime: 0 } }); function RuntimeOnDemand() { const gpu = useMotionGPU(); $effect(() => { gpu.renderMode.set('on-demand'); const canvas = gpu.canvas; if (!canvas) return; const handler = () => gpu.invalidate(); canvas.addEventListener('pointermove', handler); return () => canvas.removeEventListener('pointermove', handler); }); useFrame((state) => { state.setUniform('uTime', state.time); }, { autoInvalidate: false }); } </script> <!-- Always: Render every frame (default) --> <FragCanvas {material} renderMode="always" /> <!-- On-demand: Render only when invalidate() is called --> <FragCanvas {material} renderMode="on-demand"> <RuntimeOnDemand /> </FragCanvas> <!-- Manual: Render only when advance() is called --> <FragCanvas {material} renderMode="manual" /> ``` ## Framework-Agnostic Core Build custom adapters using the core primitives without Svelte dependency. ```typescript import { defineMaterial, resolveMaterial, createCurrentWritable, createFrameRegistry, createMotionGPURuntimeLoop, loadTexturesFromUrls, toMotionGPUErrorReport, ShaderPass, BlitPass, CopyPass } from '@motion-core/motion-gpu/core'; // Create a material const material = defineMaterial({ fragment: `fn frag(uv: vec2f) -> vec4f { return vec4f(uv, 0.5, 1.0); }` }); // Resolve to renderer-ready data const resolved = resolveMaterial(material); console.log(resolved.signature); // Cache key console.log(resolved.uniformLayout); // GPU buffer layout console.log(resolved.textureKeys); // Sorted texture names // Load textures const textures = await loadTexturesFromUrls( ['/assets/albedo.png', '/assets/normal.png'], { colorSpace: 'srgb', generateMipmaps: true } ); textures.forEach(t => console.log(`Loaded ${t.url}: ${t.width}x${t.height}`)); // Create reactive stores const dpr = createCurrentWritable(window.devicePixelRatio); dpr.set(2); console.log(dpr.current); // 2 ``` ## Summary Motion GPU is designed for applications where the entire scene is driven by fullscreen shaders, offering a focused alternative to general-purpose 3D engines. Typical use cases include Shadertoy-style GPU experiments, generative art, procedural texture generation, multi-pass post-processing pipelines, GPU simulations, shader editors and live-coding tools, and interactive visual experiments. The library enforces strict contracts (material entrypoint `fn frag(uv: vec2f) -> vec4f`, ShaderPass entrypoint `fn shade(inputColor: vec4f, uv: vec2f) -> vec4f`) to ensure predictable behavior. Integration follows the pattern of defining materials once, rendering them with `<FragCanvas />`, and using hooks (`useFrame`, `useMotionGPU`, `useTexture`) within the component tree for runtime updates. The render graph supports ping-pong buffers and named render targets for complex multi-pass pipelines. Uniform and texture updates don't rebuild the GPU pipeline, only material signature changes (shader/layout/bindings) or outputColorSpace changes trigger rebuilds. For custom framework adapters, the core module provides all necessary primitives without any Svelte dependency.