---
title: Getting started
description: Start with the public `vgpu` package. A program has one `Gpu` context, explicit WGSL bindings, and explicit frames. There are no global uniforms: time comes from the frame clock (`clock(gpu).time`, `.deltaTime`, `.frameCount`) and resolution comes from targets (`target.size`, `target.texelSize`).
---

# Getting started



```ts
import { clock, init, effect, frameLoop, surface } from "vgpu";

const gpu = await init();
const canvas = document.querySelector("canvas")!;
const canvasSurface = surface(gpu, canvas, { dpr: [1, 2] });
const gradient = effect(gpu, `
struct Params { time: f32, texel: vec2f }
@group(0) @binding(0) var<uniform> params: Params;
@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
  return vec4f(uv, sin(params.time) * 0.5 + 0.5, 1.0);
}
`, { set: { params: { time: 0, texel: canvasSurface.texelSize } } });

canvasSurface.onResize(() => {
  gradient.set({ params: { texel: canvasSurface.texelSize } });
});

const time = clock(gpu);
frameLoop(gpu, (frame) => {
  gradient.set({ params: { time: time.time } });
  frame.pass(canvasSurface, gradient);
});
```

Two habits keep this correct as it grows: bindings are set by their WGSL
names — `params` is a struct, so its members nest inside it — and `set()`
writes immediately, so the render loop only writes what actually changes
(`time`); size-class values like `texel` belong in the resize handler.

## Validate with static renders

You do not need a browser — or eyes — to prove a shader right. First, verify
the machine can render at all:

```bash
npx vgpu doctor   # JSON verdict: healthy | unhealthy — each problem with its exact fix
```

`doctor` acquires a real adapter and renders a frame; when something is
missing it prescribes the exact package install or environment variable to
set. Once healthy, render headless and read the pixels back — objective
evidence instead of guesswork:

```ts
import { writeFileSync } from "node:fs";
import { PNG } from "pngjs";
import { init, effect, target } from "vgpu/node";

const SHADER = `
  @fragment fn main() -> @location(0) vec4f {
    return vec4f(0.25, 0.5, 0.75, 1.0);
  }
`;
const width = 160;
const height = 90;
const gpu = await init();
const colorTarget = target(gpu, { size: [width, height] }); // small targets stay fast, even on CPU
effect(gpu, SHADER).draw(colorTarget);
const pixels = await colorTarget.read();                   // RGBA bytes — assert on them
const png = new PNG({ width, height });               // ...and write a PNG you can open
png.data.set(pixels);
writeFileSync("frame.png", PNG.sync.write(png));
gpu.dispose();                                        // stops Dawn's polling so the process exits
```

Keep the loop tight: render → read → adjust → render. Every visual claim you
make should be backed by pixels you actually read — assert on `pixels` when you
know the expected value, and open `frame.png` when you need to judge
composition. PNG encoding is project-owned: `pngjs` is one option, any encoder
works.

If the shader lives in its own `.wgsl` file instead of a template string — even
without a bundler — resolve it first with `resolveShader()`:
[Using vgpu without a bundler](/docs/guides/no-bundler).

The full step-by-step playbook — from `vgpu docs` to browser validation — is
[The default workflow for developing shaders with vgpu](/docs/guides/shader-workflow).
When a shader is multi-pass or a user reports a visual bug, do not iterate by
eye: extract the shader's internal values as pixels and diff them against a CPU
reference, following
[Debugging shaders by extracting internal values](/docs/guides/shader-debugging).

## Default choices

* Use `effect(gpu)` for fullscreen fragment work.
* Use `draw(gpu)` for vertex shaders, meshes, storage-driven vertices, instancing, MRT, and depth.
* Use `effect.draw(target)` for simple single-pass draws; use `frame(gpu, (f) => ...)` to batch multi-pass work and `frameLoop(gpu, ...)` for animation.
* Use `set()` for every binding declared in WGSL; missing bindings fail with `VGPU-R1-BINDING-NEVER-SET`.
* Keep plain JS values plain from their first `set()`; if you need user-owned lifetime, pass a resource from the first `set()`.
* Request optional device capabilities at startup with `init({ requiredFeatures: [...] })` — for example `"timestamp-query"` for `timer(gpu)`; a name the adapter lacks fails init with `VGPU-FEATURE-UNSUPPORTED`.

## Where to go next

Read the concept guides in order — each builds on the previous one:

```bash
vgpu docs cat concepts-context.md         # the Gpu context, surfaces, targets
vgpu docs cat concepts-draws.md           # draw(), meshes, instancing
vgpu docs cat concepts-compilation.md     # compile() and pipeline warmup
vgpu docs cat concepts-effects.md         # fragment effects and set()
vgpu docs cat concepts-passes.md          # frame.pass and multi-pass work
vgpu docs cat concepts-frames.md          # frame batching and animation loops
vgpu docs cat concepts-render-bundles.md  # record draws once, replay cheap
```

Shipping this inside an app? Shaders in their own `.wgsl` files need a bundler
loader plus one ambient TypeScript declaration, and the canvas has to live in a
client component:

```bash
vgpu docs cat nextjs.md   # Next.js (Turbopack or webpack), Vite, .wgsl types, canvas component
```

Not using a bundler at all — plain Node, a script, or a test runner? Resolve
`.wgsl` files and their imports yourself:

```bash
vgpu docs cat no-bundler.md   # resolveShader(), headless Node rendering, ESM + CommonJS usage
```

Rendering an actual 3D scene rather than a fullscreen effect? Depth lives on an
offscreen target, so the scene needs two passes — the unified recipe:

```bash
vgpu docs cat two-pass-rendering.md   # offscreen depth target -> composite to canvas
```

For performance work and testing:

```bash
vgpu docs cat /guides/performance-model.docs.md
vgpu docs cat /guides/performance-patterns.docs.md
vgpu docs cat browser-testing
```


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)