This guide walks you through installing Motion GPU and building your first fullscreen shader, step by step. By the end, you will understand the core defineMaterial → FragCanvas → runtime hooks (useFrame, usePointer) workflow.
Prerequisites
- Svelte 5 project (SvelteKit or standalone), React 18/19 project, or Vue 3 project
- A browser with WebGPU support (Chrome 113+, Edge 113+, Safari 18+, Firefox Nightly behind flag)
Install
npm install @motion-core/motion-gpu Adapter peer dependencies are optional and only required for the adapter you use:
svelte ^5for@motion-core/motion-gpu/sveltereact ^18 || ^19+react-dom ^18 || ^19for@motion-core/motion-gpu/reactvue ^3.5for@motion-core/motion-gpu/vue
Step 1: Draw a static gradient
The simplest possible setup — a fullscreen UV gradient with no animation:
<!-- 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.2, 1.0);
}
`
});
</script>
<div style="width: 100vw; height: 100vh;">
<FragCanvas {material} />
</div>What is happening here:
defineMaterialvalidates your fragment string and freezes the result into an immutableFragMaterial.- The fragment must declare
fn frag(uv: vec2f) -> vec4f— this is the only hard contract.uvranges from(0, 0)at bottom-left to(1, 1)at top-right. FragCanvasinitializes WebGPU, compiles the shader, and renders every frame.- The container
<div>determines the canvas size.FragCanvasfills its parent.
Step 2: Add a time-based animation
To animate your shader, you need a uniform and a useFrame callback. Because useFrame must run inside the FragCanvas context, you put it in a child component:
<!-- 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: { type: 'f32', value: 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>What is happening here:
uniforms: { uTime: { type: 'f32', value: 0 } }declares a singlef32uniform with initial value0.- In the fragment shader,
uTimeis available asmotiongpuUniforms.uTime— the library generates the binding automatically. useFrameregisters a task that runs every frame before rendering.state.timeis therequestAnimationFrametimestamp in seconds (monotonic clock).state.setUniform('uTime', state.time)updates the uniform value in the runtime override map. The renderer writes only dirty ranges to the GPU buffer.
Step 3: Run this baseline in your app
At this point you have the minimal production baseline:
- strict
frag(...)contract, - validated immutable material,
- runtime updates via
useFramein canvas subtree.
Recommended first-production checklist
- Keep shader contracts strict (
fragfor material,shadefor passes). - Predeclare every runtime-updated uniform/texture in
defineMaterial. - Select render mode intentionally (
always,on-demand,manual) for your workload. - Wire
onErrorand decide overlay strategy (showErrorOverlay/custom renderer).