# wgpu-kit API Reference
> Browser GPGPU middle layer. Works in any WebGPU browser (Chrome/Edge 113+, Safari 18+). Zero runtime dependencies.
> Version: v0.9.10 · 简体中文参考:[API.zh-CN.md](API.zh-CN.md)
**Entry points**
| Import | Contents |
| --- | --- |
| [wgpu-kit](#wgpu-kit--kernel-core) | GpuContext · Buffer · elementKernel · PingPong · rawKernel · defineSchema · definePack · errors |
| [wgpu-kit/particles](#wgpu-kitparticles--particles) | particle-life simulation with GPU rendering |
| [wgpu-kit/life](#wgpu-kitlife--artificial-life) | Turing patterns · Physarum · Boids · soft tentacles |
| [wgpu-kit/fields](#wgpu-kitfields--flow) | vector-field advection trails |
| [wgpu-kit/grid](#wgpu-kitgrid--generic-spatial-neighborhood) | generic spatial neighborhood (counting-sort hash) |
| [wgpu-kit/image](#wgpu-kitimage--applyimage) | GPU filter pipeline |
| [wgpu-kit/react](#wgpu-kitreact--particlecanvas) | `` |
| [wgpu-kit/three](#wgpu-kitthree--threepoints) | three.js interop |
| [wgpu-kit/media](#wgpu-kitmedia--canvasrecorder) | canvas recording |
| [wgpu-kit/vite](#wgpu-kitvite--kernel-hot-reload) | kernel hot-reload plugin |
---
# wgpu-kit · kernel core
## GpuContext
Shared GPU device context for the whole library. Lazily acquires the adapter/device
and requests the adapter's maximum storage-buffer limits automatically.
### Static method
##### GpuContext.get ( ) : Promise\
Returns the global singleton, creating it on first call. Failures clear the
cached promise so the environment can be retried after a fix.
### Properties
| Property | Type | Description |
| --- | --- | --- |
| .device | GPUDevice (readonly) | Native GPUDevice (escape hatch for custom resources) |
| .adapterInfo | string (readonly) | Adapter description, e.g. `nvidia / lovelace` |
### Methods
##### .sync ( ) : Promise\
Resolves when all submitted GPU work has completed. Useful before readbacks and tests.
##### .lost : Promise\ (readonly)
Rejects when the device is lost.
---
## Buffer
A typed GPU memory array. Handles byte sizing, usage flags, write validation
and staging readbacks.
### Constructor
##### Buffer.create ( kind : ScalarKind, length : number ) : Promise\
| Parameter | Type | Description |
| --- | --- | --- |
| kind | ScalarKind | Element type, see table below |
| length | number | Element count (positive integer) |
Throws `UsageError` on invalid kind or length.
**ScalarKind table**
| kind | WGSL | bytes/elem | TypedArray |
| --- | --- | --- | --- |
| `f32` | f32 | 4 | Float32Array |
| `i32` | i32 | 4 | Int32Array |
| `u32` | u32 | 4 | Uint32Array |
| `vec2f` | vec2\ | 8 | Float32Array |
| `vec2i` | vec2\ | 8 | Int32Array |
| `vec2u` | vec2\ | 8 | Uint32Array |
| `vec3f` | vec3\ | 12 (align 16) | Float32Array |
| `vec4f` | vec4\ | 16 | Float32Array |
### Properties
| Property | Type | Description |
| --- | --- | --- |
| .kind | ScalarKind (readonly) | Element type |
| .length | number (readonly) | Element count |
| .gpuBuffer | GPUBuffer (readonly) | Native handle — escape hatch for any engine or pipeline |
### Methods
##### .write ( data : TypedArray ) : void
Upload CPU → GPU. Throws `UsageError` when the TypedArray type or component
count does not match the buffer declaration.
##### .read ( ) : Promise\
Download GPU → CPU. The staging buffer and `mapAsync` sequencing are managed
by the library.
##### .destroy ( ) : void
Frees the GPU memory. The buffer must not be used afterwards.
### Code Example
```ts
import { Buffer } from 'wgpu-kit';
const pos = await Buffer.create('vec2f', 100_000);
pos.write(new Float32Array(200_000));
const back = await pos.read();
```
---
## elementKernel
**The core API of this library.** Declarative compute kernels: you only write
the per-element WGSL function (`userFn`); workgroups, dispatch, double-buffer
bindings, uniform packing, count guarding and compile-error line mapping are
generated for you.
### Code Example
```ts
import { elementKernel, Buffer } from 'wgpu-kit';
const pos = await Buffer.create('vec2f', 100_000);
const vel = await Buffer.create('vec2f', 100_000);
const integrate = elementKernel({
name: 'integrate',
state: { pos: 'vec2f' },
inputs: { vel: 'vec2f' },
uniforms: { dt: 'f32', friction: 'f32' },
code: `
fn userFn(idx: u32, dt: f32, friction: f32) {
pos[idx] = (pos[idx] + vel[idx] * dt) * friction;
}
`,
});
await integrate.run({ pos, vel }, { dt: 0.02, friction: 0.914 });
// hot reload: compile-then-swap; on compile failure the old kernel is kept
await integrate.replace(`
fn userFn(idx: u32, dt: f32) {
pos[idx] = pos[idx] * 2.0;
}
`);
```
### Constructor
##### elementKernel ( spec : ElementKernelSpec ) : ElementKernel
| spec property | type | description | default |
| --- | --- | --- | --- |
| name | string | Debug label (appears in errors) | `'kernel'` |
| state | Record\ | Read-**write** buffers | `{}` |
| inputs | Record\ | **Read-only** buffers | `{}` |
| uniforms | Record\ | Uniform scalars (f32/i32/u32 only) | `{}` |
| workgroupSize | number | Workgroup size, range 1..512 | `64` |
| code | string | User WGSL function, **must be named `userFn`** | required |
**The `code` contract:**
1. The function must be named **`userFn`** (the only naming convention);
2. First parameter is always `idx: u32` — out-of-range threads are guarded by the library;
3. Following parameters receive `uniforms` values **in declaration order**;
4. `state`/`inputs` field names are arrays inside the function — index them directly;
5. `count` is a reserved name (injected automatically from the first resource);
6. Field names across state/inputs/uniforms must not repeat.
Throws `UsageError` on invalid descriptors, reserved names, duplicate fields
or out-of-range workgroupSize.
### Properties
| Property | Type | Description |
| --- | --- | --- |
| .name | string (readonly) | Debug name |
| .source | string (readonly) | The generated full WGSL (for debugging) |
| .uniformLayout | UniformLayout (readonly) | Uniform layout (fields / offsets / total size) |
| .workgroupSize | number (readonly) | Effective workgroup size |
### Methods
##### .run ( resources, uniforms? ) : Promise\
Dispatch one step.
| Parameter | Type | Description |
| --- | --- | --- |
| resources | Record\ | Every state/inputs field mapped to a Buffer |
| uniforms | Record\ | Uniform values matching the declaration |
Throws `UsageError` (missing resource / type mismatch / length mismatch) or
`CompileError` (WGSL failure — **line numbers map back into your `code`**).
##### .replace ( code : string ) : Promise\
Hot reload: swap in new `code` and rebuild the pipeline. Compile-then-swap —
on failure the old kernel keeps running untouched; on success the bind-group
cache is cleared and the next `run` uses the new logic.
##### .destroy ( ) : void
Frees the pipeline and uniform buffer (does not touch passed-in Buffers).
---
## PingPong
Double buffering for iterative simulations: read the "current" side, write
the "other" side, `swap()` at end of frame. Essential for particle, fluid and
cellular simulations.
### Constructor
##### PingPong.create ( kinds : Record\, length : number ) : Promise\
```ts
const pp = await PingPong.create({ pos: 'vec2f', vel: 'vec2f' }, 100_000);
```
### Members
| Member | Type | Description |
| --- | --- | --- |
| .current | Record\ (readonly) | Current-frame side (render / readback) |
| .other | Record\ (readonly) | The write target for the kernel |
| .swap ( ) | void | Flip the sides |
| .runWith ( fn ) | Promise\ | Calls fn(write, read) then swaps automatically |
| .destroy ( ) | void | Destroys all buffers |
---
## rawKernel
The full-WGSL escape hatch: you write all the WGSL (including binding
declarations and the entry point), the library only creates the pipeline and
submits.
### Constructor
##### rawKernel ( code : string, entryPoint? : string, label? : string ) : RawKernel
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| code | string | Full WGSL | required |
| entryPoint | string | Entry function name | `'main'` |
| label | string | Debug label | `'rawKernel'` |
##### .run ( entries : GPUBindGroupEntry[], workgroups : number ) : Promise\
`entries` are declared entirely by you; `workgroups` is the X-dimension
workgroup count.
### Code Example
```ts
const double = rawKernel(`
@group(0) @binding(0) var data: array;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3u) {
if (gid.x >= arrayLength(&data)) { return; }
data[gid.x] = data[gid.x] * 2u;
}
`);
await double.run([{ binding: 0, resource: { buffer: myBuffer.gpuBuffer } }], 16);
```
---
# wgpu-kit · defineSchema
Typed schema layer: declare a field table once and get compile-time TS row
types, generated WGSL struct code, and typed per-field GPU buffers from the
same declaration. Eliminates JS/WGSL schema drift and untyped buffer I/O.
Honest boundary: errors inside WGSL function bodies are still WGSL-compiler
errors (with your-line mapping); full WGSL type-checking is out of scope.
## defineSchema ( fields ) : Schema
| Parameter | Type | Description |
| --- | --- | --- |
| fields | `Record` (const object) | Field names and kinds in declaration order |
`ScalarKind`: `'f32' | 'i32' | 'u32' | 'vec2f' | 'vec2i' | 'vec2u' | 'vec3f' | 'vec4f'`.
### Members
##### .fields : F
The original declaration — feeds `elementKernel`'s `state` directly (names and
order are the single source of truth).
##### .wgslStruct ( name : string, addressSpace? : 'storage' | 'uniform' ) : string
Generates `struct { … }`. In `'storage'` semantics, `vec3f` members get
`@size(16)` (WGSL array-element stride rule) so GPU layout can never drift from
the CPU side.
##### .buffers ( count ) : Promise\
Creates one typed buffer per field (matching elementKernel's binding model).
### SchemaBuffers
| Member | Description |
| --- | --- |
| `.` | `TypedBuffer` — `write(rows)` / `read(): Promise` with typed row objects (`{x, y}` for `vec2f`, …); `.raw` is the plain `Buffer` (escape hatch) |
| `.raws()` | `{ [field]: Buffer }` — pass directly to `elementKernel.run()` |
| `.destroy()` | Destroys all field buffers |
### Code Example
```ts
import { defineSchema, elementKernel, type SchemaInfer } from 'wgpu-kit';
const Boid = defineSchema({ pos: 'vec2f', vel: 'vec2f', species: 'u32' });
type BoidRow = SchemaInfer; // { pos: {x,y}, vel: {x,y}, species: number }
const bufs = await Boid.buffers(count);
bufs.pos.write([{ x: 1, y: 2 }]); // typo'd key/component = compile error
const k = elementKernel({ state: Boid.fields, code: 'fn userFn(idx: u32) { … }' });
await k.run(bufs.raws());
const rows = await bufs.pos.read(); // typed rows
```
---
# wgpu-kit · definePack / registerPack
The pack platform: third-party simulations register through the same contract
the built-in packs use — unified lifecycle, stats, a self-verification
`probe()`, and a runtime registry.
## PackSim
| Member | Required | Description |
| --- | --- | --- |
| `attach?(canvas)` | no | Build a renderer (canvas packs) |
| `tick()` | yes | Advance one frame (compute + optional render) |
| `stats?()` | no | Lightweight live stats (`{ fps }` …) |
| `probe?()` | no | Return physics invariants — the verify harness collects and displays them |
| `destroy()` | yes | Release all GPU resources |
## definePack ( pack ) : WgpuKitPack
| Parameter | Type | Description |
| --- | --- | --- |
| pack.name | string | Lowercase identifier; registry namespace |
| pack.description? | string | Shown by `listPacks()` |
| pack.create(config?) | function | Returns `Promise` |
## registerPack ( pack ) / getPack ( name ) / listPacks ()
Registry operations. Duplicate registration of the same name throws
(`UsageError`) — built-ins cannot be shadowed.
### Code Example
```ts
import { definePack, registerPack, listPacks } from 'wgpu-kit';
const orbit = definePack({
name: 'orbit',
create: async (config) => ({
tick() { /* … */ },
async probe() { return { energyDrift: 0.003 }; },
destroy() { /* … */ },
}),
});
registerPack(orbit);
listPacks(); // includes 'particles', 'fields', 'orbit'
```
---
# wgpu-kit/particles · particles
Particle Life: a 4-species force matrix with three neighborhood algorithms,
two color modes, live parameter updates and shareable snapshots. GPU rendering
included.
## Code Example
```ts
import { particles } from 'wgpu-kit/particles';
const sim = await particles({ count: 100_000, forces: 'cells' });
await sim.attach(document.querySelector('canvas'));
function frame() {
sim.tick();
requestAnimationFrame(frame);
}
frame();
```
## Constructor
##### particles ( config? : ParticlesConfig ) : Promise\
### ParticlesConfig
| Property | Type | Description | Default |
| --- | --- | --- | --- |
| count | number | Particle count (1..1,000,000) | `8192` |
| forces | 'cells' \| 'snakes' \| 'orbitals' \| 'viruses' \| 'random' \| ForceMatrix | Force matrix preset or custom 16-number array (row = actor, col = target; positive = attract) | `'cells'` |
| mode | 'grid' \| 'tiled' \| 'n2' | Neighborhood algorithm. grid = counting-sort spatial hash (recommended, ~O(N)); tiled = tiled brute force; n2 = full brute force (≤30k) | `'grid'` |
| color | 'species' \| 'velocity' | Color by species or by speed | `'species'` |
| bounds | 'wrap' \| 'clamp' | Boundary handling | `'wrap'` |
| seed | string \| number | Random seed (spawn distribution + random matrix), reproducible | `'wgpu-kit'` |
| rMax | number | Interaction radius (world units) | `0.12` |
| beta | number | Near/far force split (0..1) | `0.3` |
| forceFactor | number | Force strength multiplier | `10` |
| frictionHalfLife | number | Velocity half-life (seconds) | `0.04` |
| dt | number | Time step (seconds) | `0.02` |
| pointSize | number | Point size (clip-space ratio) | `0.004` |
| maxNeighbors | number | Per-particle candidate cap (safety fuse for extreme clumps) | `32768` |
**ForceMatrix**: a length-16 array where `m[i*4+j]` is the force of species i
on species j (−1..1). Presets: `cells` (classic), `snakes`, `orbitals`,
`viruses` (predation), `random` (generated from the seed).
**World scaling**: the world area grows proportionally with `count` (density
locked to the 16k level) and the camera zooms out — any scale produces the
same per-particle physics.
## Properties
| Property | Type | Description |
| --- | --- | --- |
| .config | ResolvedConfig (readonly) | The fully resolved configuration |
## Methods
##### .attach ( canvas : HTMLCanvasElement ) : Promise\
Binds the render target and creates the render pipeline. Call once.
##### .tick ( dtMultiplier? : number ) : void
Steps one frame (compute + render). `dtMultiplier` supports 0 (pause), <1
(slow motion), >1 (fast forward).
##### .setForces ( forces ) : void
Hot-swaps the force matrix (preset name or custom array) — no rebuild.
##### .setParams ( params ) : void
Hot-updates physics `{ rMax?, beta?, forceFactor?, frictionHalfLife?, dt? }`.
Note: changing `rMax` in grid mode triggers a grid rebuild (~100ms).
##### .snapshot ( ) : string
Serializes the configuration as JSON — interoperates with the playground's
`?p=…&m=…&s=…` URL parameters for sharing.
##### .stats ( ) : { fps : number; gpuErrors : number }
Runtime stats. `gpuErrors > 0` means GPU validation errors occurred (check
this first when diagnosing a black canvas).
##### .buffers ( ) : { pos, vel, species }
Current-frame buffers (`Buffer` instances — read them or hand to three.js).
##### .destroy ( ) : void
Releases everything. The sim is unusable afterwards.
---
# wgpu-kit/life · artificial life
Four standalone emergence simulations with the same shape:
`attach(canvas)` / `tick()` / `stats()` / `destroy()`.
## turing ( config? ) : Promise\
Gray-Scott reaction-diffusion: two chemicals grow coral / cell / wave patterns.
| config | type | description | default |
| --- | --- | --- | --- |
| size | number | Grid edge (square) | `512` |
| preset | 'coral' \| 'mitosis' \| 'spots' \| 'waves' \| 'custom' | Parameter preset | `'coral'` |
| feed / kill | number | Custom f/k (when preset = custom) | per preset |
| steps | number | Iterations per frame | `12` |
| colormap | string | 'duotone' (default) \| 'amber' \| 'ice' \| 'mono' | — |
| seed | string \| number | Seed for initial spots | `'life'` |
| Extra method | description |
| --- | --- |
| .sprinkle ( count? = 6 ) | Sprinkle perturbation at random spots |
| .sampleB ( ) : Promise\ | Read back the B concentration field |
Preset parameters: `coral` f=0.0545 k=0.062 · `mitosis` f=0.0367 k=0.0649 ·
`spots` f=0.03 k=0.062 · `waves` f=0.014 k=0.045.
## physarum ( config? ) : Promise\
Physarum slime mold: three-sensor agents sense, turn, move, deposit; the trail
diffuses and decays — networks emerge on their own.
| config | type | description | default |
| --- | --- | --- | --- |
| agents | number | Agent count | `100_000` |
| mapSize | number | Trail map edge | `1024` |
| sensorAngle / sensorDist / turnAngle / step | number | Sensing & motion | 0.5 / 0.012 / 0.45 / 0.003 |
| decay | number | Per-frame decay | `0.06` |
| colormap | string | default 'amber' | — |
Extra method: `sampleTrail()`.
## boids ( config? ) : Promise\
Boids flocking: separation / alignment / cohesion over a counting-sort grid.
| config | type | description | default |
| --- | --- | --- | --- |
| count | number | Individuals | `3000` |
| perception | number | Perception radius | `0.05` |
| maxSpeed | number | Max speed | `0.012` |
| wSep / wAli / wCoh | number | Separation / alignment / cohesion weights | 1.6 / 1.0 / 0.8 |
| size | number | Triangle size | `0.009` |
Extra method: `buffers(): { pos, vel }`.
## tentacles ( config? ) : Promise\
Soft tentacles: Verlet chains anchored on golden-angle drifting anchors, with
gravity and damping for a jellyfish-like drift.
| config | type | description | default |
| --- | --- | --- | --- |
| chains / segments | number | Chains / nodes per chain | 48 / 64 |
| segLen / gravity / damping | number | Node spacing / gravity / damping | 0.018 / 0.00035 / 0.985 |
| iterations | number | Constraint relaxation passes per frame | `10` |
| thickness / colorCycle | number | Dot size base / hue cycle speed | 0.006 / 0.35 |
---
# wgpu-kit/fields · flow
Vector-field advection trails: particles are advected through an analytic
field and deposit a fading trail — a base layer for wind/flow visualization.
## Constructor
##### flow ( config? : FlowConfig ) : Promise\
| config | type | description | default |
| --- | --- | --- | --- |
| count | number | Advected particles | `131_072` |
| mapSize | number | Trail map edge | `1024` |
| field | 'vortex' \| 'curl' \| 'twin' | Field type: vortex / curl noise / twin vortices | `'curl'` |
| speed | number | Per-frame step | `0.004` |
| decay | number | Per-frame decay | `0.045` |
| deposit | number | Deposit amount | `1.0` |
| colormap | string | 'ice' (default) \| 'amber' \| 'duotone' \| 'mono' | — |
| seed | string \| number | Seed | `'flow'` |
### Methods
Same shape as the other sims: `attach / tick / stats / sampleTrail / destroy`.
---
# wgpu-kit/image · applyImage
GPU filter pipeline: source → per-op ping-pong passes → target canvas.
## Constructor
##### applyImage ( source, target, ops ) : Promise\
| Parameter | type | description |
| --- | --- | --- |
| source | HTMLCanvasElement \| HTMLImageElement \| ImageBitmap | Input image |
| target | HTMLCanvasElement | Output canvas (WebGPU backend) |
| ops | ImageOp[] | Operator pipeline, executed in order |
Returns `{ width, height, passes, readback(): Promise }` —
`readback()` gives GPU-direct pixels (RGBA, rows aligned to 256B), usable in
headless tests.
### ImageOp table
| op | params | description |
| --- | --- | --- |
| { op: 'grayscale' } | — | Grayscale (Rec.709 luma) |
| { op: 'invert' } | — | Invert |
| { op: 'edge', amount? } | amount default 1 | Sobel edge detect |
| { op: 'blur', radius? } | radius 1..4, default 1 | Box blur |
| { op: 'sharpen', amount? } | amount default 1 | 3×3 sharpen |
| { op: 'brightness', value } | −1..1 | Brightness |
| { op: 'contrast', value } | 0..2, 1 = unchanged | Contrast |
### Code Example
```ts
const r = await applyImage(srcCanvas, outCanvas, [
{ op: 'blur', radius: 2 },
{ op: 'edge', amount: 1 },
]);
const px = await r.readback();
```
---
# wgpu-kit/react · ParticleCanvas
React binding for the particles pack. Mounting creates the simulation + rAF
loop; unmounting destroys everything (StrictMode safe).
## Props
`ParticleCanvasProps` extends `ParticlesConfig` (count/forces/mode/seed/…)
with:
| Property | type | description |
| --- | --- | --- |
| className | string | Passed to the canvas |
| style | CSSProperties | Passed to the canvas (defaults to 100% × 100%) |
| onReady | (sim: ParticlesSim) => void | Called when the sim is ready; grab it here for imperative control |
### Code Example
```tsx
console.log(s.stats())} />
```
**Convention**: change config by changing `key` (declarative); imperative
control goes through `onReady`.
---
# wgpu-kit/three · threePoints
Snapshot-based three.js interop: each frame the simulation positions are read
back into a `BufferAttribute`, working with any three renderer (WebGL or
WebGPU).
## Constructor
##### threePoints ( sim : ParticlesSim, THREE : ThreeAPI, opts? ) : ThreePointsHandle
| Parameter | type | description |
| --- | --- | --- |
| sim | ParticlesSim | A created particle simulation |
| THREE | ThreeAPI | Your three module (minimal surface: Points / BufferGeometry / BufferAttribute / PointsMaterial) |
| opts.size | number | Point size (default 0.015) |
| opts.color | number | Material color (default 0x8fb4ff) |
### Members
| member | type | description |
| --- | --- | --- |
| .points | unknown (readonly) | The THREE.Points to add to your scene |
| .update ( ) | Promise\ | Sync one position snapshot (call before rendering) |
| .dispose ( ) | void | Release (also destroys the sim) |
Zero-copy TSL interop is on the roadmap; the snapshot mode costs one
readback per frame (millisecond-scale) — fine for small/medium counts.
---
# wgpu-kit/media · CanvasRecorder
Canvas recording (a MediaRecorder wrapper): container negotiated
automatically (mp4 preferred, webm fallback) with one-call download.
## Constructor
##### new CanvasRecorder ( )
Throws when the environment has no MediaRecorder support.
### Properties & Methods
| member | type | description |
| --- | --- | --- |
| .recording | boolean (readonly) | Whether a recording is in progress |
| .mimeType | string (readonly) | The negotiated container |
| .start ( canvas, videoBitsPerSecond? = 12_000_000 ) | void | Start recording |
| .stop ( ) | Promise\ | Stop and return `{ blob, mimeType, seconds, bytes }`; throws if the product is empty |
### Helpers
##### pickMime ( ) : string \| null
Returns the best available container (`webm vp9` → `webm vp8` → `webm` → `mp4`).
##### downloadBlob ( blob : Blob, filename : string ) : void
Triggers a browser download.
### Code Example
```ts
const rec = new CanvasRecorder();
rec.start(canvas);
setTimeout(async () => {
const r = await rec.stop();
downloadBlob(r.blob, `universe.${r.mimeType.includes('mp4') ? 'mp4' : 'webm'}`);
}, 5000);
```
---
# wgpu-kit/vite · kernel hot reload
Vite plugin + client helper: saving a WGSL file triggers `kernel.replace()`
within milliseconds (compile failures keep the old kernel).
## Constructor
##### wgpuKitHotReload ( ) : VitePluginLike
The Vite plugin. Watches `*.wgsl` files and pushes new code to the page.
##### hotKernel ( kernel : ElementKernel, hot : ImportMetaHot \| undefined, file : string ) : void
Client helper: registers a kernel on the hot-reload channel.
### Code Example
```ts
// vite.config.ts
import { wgpuKitHotReload } from 'wgpu-kit/vite';
export default { plugins: [wgpuKitHotReload()] };
// app code
import simSrc from './sim.wgsl?raw';
const k = elementKernel({ state: { a: 'f32' }, code: simSrc });
hotKernel(k, import.meta.hot, './sim.wgsl');
```
---
# Errors
All errors extend `WgpuKitError`:
| error | thrown when | guidance |
| --- | --- | --- |
| `WebGPUUnavailableError` | No WebGPU / no adapter | Point users at the `detect.html` page |
| `CompileError` | WGSL compilation failed | Message contains **your code's line numbers**; hot reload keeps the old kernel |
| `UsageError` | Argument mismatch (type / length / missing field) | Fix the call per the message |
Runtime validation errors (uncapturederror) are logged to the console and
counted in `sim.stats().gpuErrors` — **when diagnosing a black canvas, check
this counter first**.
---
# Performance & limits
| item | value | environment |
| --- | --- | --- |
| particles end-to-end | 200,000 @ 142fps | RTX 4060 Laptop, playground |
| particle compute (grid) | 131k @ 0.89ms/frame | same, headless bench |
| neighborhood algorithms | grid ~O(N), 8.5× faster than brute force at 66k | same-session A/B |
| bundle size | core gzip 5.5kB; +particles 10.3kB | gzip |
Full data and repro commands: see the repository benchmarks page. **Scaling
tip**: as `count` grows, keep the world density constant (handled
automatically) and consider lowering `rMax` — the radius determines the
neighbor count, which dominates the cost.
---
# wgpu-kit/grid · generic spatial neighborhood
Counting-sort spatial hash extracted from the particles pack. Any simulation
that needs "find my neighbors" (SPH fluids / collision / clustering) can use
it. Measured 8.5× over brute force, ~O(N).
## Constructor
##### createNeighborGrid ( config : NeighborGridConfig ) : Promise\
| config property | type | description | default |
| --- | --- | --- | --- |
| count | number | number of entities | required |
| worldHalf | number | world half-width | required |
| cellSize | number | cell edge length (usually = interaction radius) | required |
| workgroupSize | number | workgroup size | `64` |
### Properties & methods
| member | type | description |
| --- | --- | --- |
| .gridSize | number (readonly) | grid edge length (cells per side) |
| .cells | number (readonly) | total cell count |
| .cellStart | Buffer (readonly) | first ordered slot per cell |
| .cellFill | Buffer (readonly) | end slot per cell (fill cursor) |
| .order | Buffer (readonly) | entity indices in cell order |
| .update ( pos : Buffer ) : void | build the grid from positions (counts → scan → scatter) |
| .destroy ( ) : void | release |
### Code Example
```ts
import { createNeighborGrid } from 'wgpu-kit/grid';
const grid = await createNeighborGrid({ count: 100_000, worldHalf: 1.0, cellSize: 0.12 });
// each frame: build the grid, then read cellStart/cellFill/order in your force kernel
grid.update(posBuffer);
```
---
# wgpu-kit/observe · observability
GPU timing, device diagnostics and canvas helpers — the runtime counterpart
of the published benchmark numbers.
## timeGpu
##### timeGpu ( fn : (ctx) => void | Promise ) : Promise
Measures real GPU milliseconds spent inside `fn` using timestamp queries
(Chrome/Edge). Rejects with `UsageError` when the device lacks the feature.
### Code Example
```ts
import { timeGpu } from 'wgpu-kit/observe';
const ms = await timeGpu(() => sim.tick());
console.log(`GPU: ${ms.toFixed(2)} ms/frame`);
```
---
# wgpu-kit/observe · watchDevice
##### watchDevice ( opts : { onError?, onRebuild? } ) : void
Registers error/loss callbacks on the shared device and **automatically
rebuilds the context** when the GPU device is lost — essential for
long-running pages (installations, dashboards). `onError(message, recoverable)`
fires for validation errors (recoverable) and device loss (not).
---
# wgpu-kit/observe · canvas helpers
##### preferredCanvasFormat ( ) : GPUTextureFormat
The preferred canvas format for this browser.
##### resizeCanvas ( canvas : HTMLCanvasElement, dprCap? = 2 ) : boolean
Sizes the canvas backing store to `clientSize x min(dpr, dprCap)`. Returns
whether the size actually changed (skip rebuilds when `false`).