Skip to content

Modules

The contract every TypeGPU Kit module follows, and how to write one.

Every module has a factory, create(root, options), that allocates resources and returns them with shader functions and recording methods. How the kit works introduces the rules modules follow. This page covers the details you rely on when using or writing one.

Follow resources from creation to teardown. The first use creates GPU buffers and bind groups; later frames allocate nothing, destroy() frees four buffers, pipelines and groups release when unreferenced, and the borrowed surface remains.
  • initAsync() compiles every pipeline. It is optional; without it the first recording compiles, which stalls that frame and is slow on WebKit.
  • A few setup calls submit, marked submits setup on their card: meshPool.add(shape) and meshes.bake given no encoder or pass. Call them while loading, or pass the frame’s encoder where supported.
  • destroy() frees the resources listed under owns on the card. It leaves borrowed resources alive. Calling it twice is safe. [Symbol.dispose] is the same function, so using destroys a module at the end of its block on the web. The card’s lifecycle shows whether a module has destroy(). A drawing built by a description has its own drawn.destroy().
  • After destroy(), a method that would put a destroyed resource into GPU work throws an error that names the module, such as “The live lists were destroyed.” That covers inject(), recording methods (compact, sort, pick), writes (a camera’s write) and getters that hand out GPU objects (a target set’s views, a surface’s view). A table throws on every use.
  • Methods that only read CPU state or perform optional work return without an error. frames.run returns false, capture records nothing, take and latest return undefined, and a camera’s update writes nothing. A loop that ticks once more after its scene is torn down ends without an error.

The garbage collector frees what a dropped scene made. On a device that outlives the scene, such as one a gallery shares, destroy frees the large things sooner: targets that follow the screen, large buffers, models and the modules the scene made. Leave small uniforms, samplers, bind groups and pass descriptions to the collector. owner() collects each thing as you make it, so destroy needn’t list them again:

import {
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
} from '@typegpu/kit';
const
const own: Owner
own
=
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
();
const
const view: cameras.OrbitCamera
view
=
import cameras
cameras
.
function orbit(root: TgpuRoot, options: cameras.OrbitOptions): cameras.OrbitCamera

Creates a perspective camera that orbits a target and eases towards a goal set by input.

@returns ― a camera you update every frame and destroy

orbit
(
const root: TgpuRoot
root
, {
OrbitOptions.size: SizeSource

Supplies the size used for the aspect ratio, such as the canvas surface.

size
:
const surface: web.Surface
surface
,
OrbitOptions.target: readonly [number, number, number]

Sets the initial point that the camera orbits and looks at. Change it through goal.target.

target
: [0, 0.4, 0],
OrbitOptions.distance: number

Sets the initial distance from the target to the eye, in world units. Change it through goal.distance.

distance
: 5 });
const
const canvas: passes.Passes<{
readonly color: typeof passes.color;
}>
canvas
=
import passes
passes
.
function create(root: TgpuRoot, screen: passes.Screen, options?: passes.Options): passes.Passes<{
readonly color: typeof passes.color;
}> (+1 overload)

Describes the targets and outputs for scene passes. With a surface such as the canvas, writes only each renderer's colour. With a target set, writes colour into the target marked color, depth into the target marked depth, and other outputs into their named targets. Multisampled targets resolve according to resolve. Targets with layers draw one pass per layer, and every target the description draws into needs the same layers. add builds a renderer's drawing. begin starts a pass. pipe supplies fills such as a camera.

@param ― screen the surface, or the targets, which give the formats; each one the description names is drawn into

@param ― writes what fills each target, by its key in screen

@returns ― a description you destroy, which holds the pipelines it builds

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
).
Passes<{ readonly color: typeof color; }>.pipe(fill: Inject): passes.Passes<{
readonly color: typeof passes.color;
}>

Adds fill after existing fills and returns a description of the same passes. Piped fills take precedence over a drawing's own. For example, passes.create(…).pipe(view.inject()) uses the camera from view.

pipe
(
const view: cameras.OrbitCamera
view
.
CameraBase.inject(): Inject

Fills cameras.current for the pipelines created from the configuration it is piped into.

inject
());
const
const time: TgpuUniform<d.F32>
time
=
const root: TgpuRoot
root
.
TgpuRoot.createUniform<d.F32>(typeSchema: d.F32, initial?: number | ((buffer: TgpuBuffer<NoInfer<d.F32>>) => void) | undefined): TgpuUniform<d.F32> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for small data. For a general-purpose buffer, use

TgpuRoot.createBuffer

.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createUniform
(
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
);
const
const sparks: particles.Particles<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>
sparks
=
const own: Owner
<particles.Particles<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>>(item: particles.Particles<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>) => particles.Particles<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import particles
particles
.
function create<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>(root: TgpuRoot, camera: cameras.CameraBase, options: particles.Options<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>): particles.Particles<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>

Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed steps move particles, and live lists list only live particles for drawing.

@injects ― per-instance emit and the pool, for kernels piped with it

@binds ― 2 storage: emit, the pool and its ring counter

@binds ― 1 storage: alive or particle, the pool

@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead

@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three uniforms; the live lists list the live particles. Each description drawing is added to builds its pipeline.

@records ― spawn once a submission before compact, step once per fixed step, compact once after the steps, then the draw a description added drawing as; clear whenever you like.

@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too

@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())

@returns ― a system you destroy

create
(
const root: TgpuRoot
root
,
const view: cameras.OrbitCamera
view
, {
Options<S extends Schema = WgslStruct<{ position: Vec3f; age: F32; velocity: Vec3f; life: F32; color: Vec4f; size: F32; seed: F32; }>>.capacity: number

Limits the number of particles alive at once. Each emitted particle takes the next ring slot.

capacity
: 2048,
Options<S extends Schema = WgslStruct<{ position: Vec3f; age: F32; velocity: Vec3f; life: F32; color: Vec4f; size: F32; seed: F32; }>>.step: number

Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step advances by it.

step
:
const sim: clock.FixedStep
sim
.
FixedStep.step: number

Sets the step duration in seconds. A new value applies from the next advance: unstepped time carries over, capped at maxSteps new steps, and time keeps the seconds already stepped. Particles and machines take its value when created and keep it. Throws unless the value is finite and above 0.

step
,
Options<S extends Schema = WgslStruct<{ position: Vec3f; age: F32; velocity: Vec3f; life: F32; color: Vec4f; size: F32; seed: F32; }>>.blend?: "add" | "over" | "translucent" | GPUBlendState | undefined

Selects add for sparks and glow, over for dust, translucent for the passes' translucency targets, or a custom blend state. Default add.

blend
: 'add' }));
const
const drawn: passes.Drawn[]
drawn
= [
const canvas: passes.Passes<{
readonly color: typeof passes.color;
}>
canvas
.
Passes<{ readonly color: typeof color; }>.add<d.WgslStruct<{
position: d.Vec4f;
world: d.Vec3f;
local: d.Vec2f;
slot: d.U32;
}>, {
world: d.v3f;
color: d.v4f;
}>(this: passes.Passes<passes.Fed<{
world: d.v3f;
color: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
local: d.v2f;
slot: number;
}>>, drawing: passes.Drawing<d.WgslStruct<{
position: d.Vec4f;
world: d.Vec3f;
local: d.Vec2f;
slot: d.U32;
}>, {
world: d.v3f;
color: d.v4f;
}>): passes.Drawn (+1 overload)

Builds a pipeline for drawing that writes every described output and returns its draw methods. If the drawing cannot supply the fields an output needs, type checking fails with that output's name. Use without to leave it out or instead to replace it.

add
(
const sparks: particles.Particles<d.WgslStruct<{
position: d.Vec3f;
age: d.F32;
velocity: d.Vec3f;
life: d.F32;
color: d.Vec4f;
size: d.F32;
seed: d.F32;
}>>
sparks
.
Particles<WgslStruct<{ position: Vec3f; age: F32; velocity: Vec3f; life: F32; color: Vec4f; size: F32; seed: F32; }>>.drawing: passes.Drawing<d.WgslStruct<{
position: d.Vec4f;
world: d.Vec3f;
local: d.Vec2f;
slot: d.U32;
}>, {
world: d.v3f;
color: d.v4f;
}>

Provides the particle drawing for pass descriptions, as in scene.add(system.drawing). It supplies colour, world position and corner data to outputs, tests depth without writing it and draws the list from the last compact. The description initAsync also compiles system kernels.

drawing
)];
const
const scene: {
view: cameras.OrbitCamera;
frame(frame: Frame): void;
destroy: () => void;
}
scene
= {
view: cameras.OrbitCamera
view
,
function frame(frame: Frame): void
frame
(
frame: Frame
frame
:
(alias) interface Frame
import Frame

Represents one frame being recorded. Each run creates a separate object, so modules can compare frames by identity.

Frame
) {
const time: TgpuUniform<d.F32>
time
.
TgpuBufferBindingBase<F32>.write(data: number, options?: BufferWriteOptions): void
write
(
frame: Frame
frame
.
Frame.time: number

Reports seconds since the first submitted frame.

time
);
const view: cameras.OrbitCamera
view
.
OrbitCamera.update(dt: number): void

Eases towards the goal by dt seconds and writes the uniform if the view changed. Call once per frame.

update
(
frame: Frame
frame
.
Frame.dt: number

Reports seconds since the previous submitted frame. The first frame has a dt of zero.

dt
);
const canvas: passes.Passes<{
readonly color: typeof passes.color;
}>
canvas
.
Passes<{ readonly color: typeof color; }>.record(frame: Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly color: typeof passes.color;
}> | undefined): void

Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the list to avoid allocating each frame. Call begin yourself when you need to choose drawings per frame. Records nothing while enabled is false.

record
(
frame: Frame
frame
,
const drawn: passes.Drawn[]
drawn
);
},
destroy: () => void
destroy
:
const own: Owner
own
.
Owner.destroy(): void

Frees everything it took, the last taken first, once; later calls do nothing. When one throws, frees the rest and then rethrows. Also [Symbol.dispose].

destroy
,
};
  • own(thing) returns thing. It takes anything with destroy(), and a fixed resource such as a uniform, which it frees through its buffer. It passes undefined through, for a part you make only with a setting on.
  • own(() => …) runs the function at destroy. Take a texture that a resize replaces this way, as one function that frees the current texture, rather than each texture as it is made.
  • destroy frees the last thing first, so a module goes before the ones it borrows. Calling it twice is safe.
  • Something taken after destroy is freed at once, such as a model that finishes loading after the scene closed.
  • Leave out what the scene borrows: the root, the surface and the host’s resources.

A method takes the smallest piece of the frame it needs, so the call site shows the pass structure.

Method Takes When
step(pass) TgpuComputePass compute dispatches, in a pass you share between modules
draw(pass) TgpuRenderCommands draws into your attachments, or into a render bundle you replay
record(encoder) TgpuCommandEncoder passes of its own, such as a shadow map or a post-processing chain
record(frame) Frame passes of its own that draw into a surface, whose view belongs to the frame
capture(frame) Frame work after the submit too, through frame.afterSubmit
const compute = encoder.beginComputePass();
particles.step(compute);
wind.step(compute);
compute.end();
light.shadow.record(encoder, casters); // opens and ends its own depth pass
const pass = scene.begin(frame);
terrain.draw(pass); // what scene.add(drawing) built
embers.draw(pass);
pass.end();
  • A renderer returns a drawing. A description of passes builds it with scene.add(drawing), which returns a handle with draw(pass).
  • step runs one fixed step and can run several times in a pass. Compute work that runs once per frame has a specific name, such as live lists’ compact or the particles’ spawn.

Bind a pipeline’s groups at setup, then set it on the pass and draw through the pass. Each .with(...) returns another object, and a draw through a different object from the previous draw’s sets the pipeline, groups and buffers again:

pipeline.with(group).with(pass).draw(3); // in the frame: a new object and all state on every draw
const bound = pipeline.with(group); // at setup
pass.setPipeline(bound); // in the frame
pass.draw(3); // a later draw with the same pipeline sets nothing
  • A pass.setBindGroup or setVertexBuffer between draws makes the next draw set everything again.
  • A group that follows a replaced texture comes from memo, and the pipeline bound to it can too, as finish’s is.
  • A module sets only its pipeline on the caller’s pass. It makes no other state changes such as pass.setBindGroup there, because later draws would inherit them. Your frame code can make those changes.

draw takes a render pass or a render bundle encoder. pass.executeBundles([bundle]) replays the recorded draws in one call, which saves per-draw JS work on Hermes:

const bundler = root['~unstable'].createRenderBundleEncoder({
colorFormats: [format],
depthStencilFormat: 'depth24plus',
});
terrain.draw(bundler);
props.draw(bundler);
const bundle = bundler.finish();
// each frame
pass.executeBundles([bundle]);
embers.draw(pass);
  • A bundle replays fixed draw calls while buffer contents can change. Record it again when the draw calls or resources change. Instances need this when a mesh gets its first instance or its textures arrive. Finish needs it when bloom replaces its output.
  • A method that sets a viewport or a scissor can’t go in a bundle. It takes a pass, or the encoder and opens its own passes, as bloom’s record does.

This module damps a buffer of velocities. Its rate is a setting; its step length is fixed at creation:

import {
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
} from '@typegpu/kit';
import {
import d
d
,
import std
std
,
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
, type
(alias) interface TgpuComputePass
import TgpuComputePass

A compute pass recording into a

TgpuCommandEncoder

.

Dispatch either by binding TypeGPU pipelines to it (pipeline.with(pass).dispatchWorkgroups(...)), or proxy-style via pass.setPipeline(pipeline) followed by pass.dispatchWorkgroups(...).

Call end() when done recording.

TgpuComputePass
, type
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
} from 'typegpu';
const
const DampingSettings: d.WgslStruct<{
rate: d.F32;
}>
DampingSettings
=
import d
d
.
struct<{
rate: d.F32;
}>(props: {
rate: d.F32;
}): d.WgslStruct<{
rate: d.F32;
}>
export struct

Creates a struct schema that can be used to construct GPU buffers. Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema). The order of members matches the passed in properties object.

@example const CircleStruct = d.struct({ radius: d.f32, pos: d.vec3f });

@param ― props Record with string keys and TgpuData values, each entry describing one struct member.

struct
({
rate: d.F32
rate
:
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
}); // no root needed
interface
interface DampingOptions
DampingOptions
{
readonly
DampingOptions.count: number
count
: number;
/** Seconds per `step`. */
readonly
DampingOptions.step: number

Seconds per step.

step
: number;
}
export function
function createDamping(root: TgpuRoot, options: DampingOptions): {
settings: TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>;
velocities: TgpuMutable<d.WgslArray<d.Vec2f>>;
initAsync: () => Promise<void>;
step(pass: TgpuComputePass): void;
destroy: () => void;
[Symbol.dispose]: () => void;
}
createDamping
(
root: TgpuRoot
root
:
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
,
options: DampingOptions
options
:
interface DampingOptions
DampingOptions
) {
const {
const count: number
count
,
const step: number

Seconds per step.

step
} =
options: DampingOptions
options
;
const
const own: Owner
own
=
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
('The damping was destroyed.');
const
const settings: TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>
settings
=
const own: Owner
<TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>>(item: TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>) => TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
root: TgpuRoot
root
.
TgpuRoot.createUniform<d.WgslStruct<{
rate: d.F32;
}>>(typeSchema: d.WgslStruct<{
rate: d.F32;
}>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslStruct<{
rate: d.F32;
}>>>) => void) | d.InferInput<NoInfer<d.WgslStruct<{
rate: d.F32;
}>>> | undefined): TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for small data. For a general-purpose buffer, use

TgpuRoot.createBuffer

.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createUniform
(
const DampingSettings: d.WgslStruct<{
rate: d.F32;
}>
DampingSettings
, {
rate: number
rate
: 2 }));
const
const velocities: TgpuMutable<d.WgslArray<d.Vec2f>>
velocities
=
const own: Owner
<TgpuMutable<d.WgslArray<d.Vec2f>>>(item: TgpuMutable<d.WgslArray<d.Vec2f>>) => TgpuMutable<d.WgslArray<d.Vec2f>>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
root: TgpuRoot
root
.
TgpuRoot.createMutable<d.WgslArray<d.Vec2f>>(typeSchema: d.WgslArray<d.Vec2f>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.Vec2f>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.Vec2f>>> | undefined): TgpuMutable<d.WgslArray<d.Vec2f>> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader. Can be mutated in-place on the GPU. For a general-purpose buffer, use

TgpuRoot.createBuffer

.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createMutable
(
import d
d
.
arrayOf<d.Vec2f>(elementType: d.Vec2f, elementCount: number): d.WgslArray<d.Vec2f> (+2 overloads)
export arrayOf

@location ― . Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).

arrayOf
(
import d
d
.
const vec2f: d.Vec2f
vec2f
,
const count: number
count
)));
const
const kernel: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
kernel
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [64],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
})(({
gid: d.v3u
gid
}) => {
'use gpu';
if (
gid: d.v3u
gid
.
v3u.x: number
x
>=
const count: number
count
) return;
const
const decay: number
decay
=
import std
std
.
function exp(value: number): number (+1 overload)
export exp
exp
(-
const settings: TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>
settings
.
TgpuUniform<WgslStruct<{ rate: F32; }>>.$: {
rate: number;
}
$
.
rate: number
rate
*
const step: number

Seconds per step.

step
);
const velocities: TgpuMutable<d.WgslArray<d.Vec2f>>
velocities
.
TgpuMutable<WgslArray<Vec2f>>.$: d.v2f[]
$
[
gid: d.v3u
gid
.
v3u.x: number
x
] *=
const decay: number
decay
;
});
const
const pipeline: TgpuComputePipeline
pipeline
=
root: TgpuRoot
root
.
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const kernel: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
kernel
});
return {
settings: TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>
settings
,
velocities: TgpuMutable<d.WgslArray<d.Vec2f>>
velocities
,
initAsync: () => Promise<void>
initAsync
: () =>
const pipeline: TgpuComputePipeline
pipeline
.
TgpuComputePipeline.initAsync(): Promise<void>

Immediately resolves the pipeline, then awaits device.createComputePipelineAsync(). NOTE: it is not necessary to initialize pipelines manually.

initAsync
(),
function step(pass: TgpuComputePass): void
step
(
pass: TgpuComputePass
pass
:
(alias) interface TgpuComputePass
import TgpuComputePass

A compute pass recording into a

TgpuCommandEncoder

.

Dispatch either by binding TypeGPU pipelines to it (pipeline.with(pass).dispatchWorkgroups(...)), or proxy-style via pass.setPipeline(pipeline) followed by pass.dispatchWorkgroups(...).

Call end() when done recording.

TgpuComputePass
) {
const own: Owner
own
.
Owner.usable(): void

Throws the owner's message if it is destroyed. Call first in methods that use GPU resources, including inject, recording methods, writes and getters that return GPU objects.

usable
();
pass: TgpuComputePass
pass
.
TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

Sets the current

TgpuComputePipeline

for subsequent dispatches

setPipeline
(
const pipeline: TgpuComputePipeline
pipeline
);
pass: TgpuComputePass
pass
.
TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
dispatchWorkgroups
(
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.ceil(x: number): number

Returns the smallest integer greater than or equal to its numeric argument.

@param ― x A numeric expression.

ceil
(
const count: number
count
/ 64));
},
destroy: () => void
destroy
:
const own: Owner
own
.
Owner.destroy(): void

Frees everything it took, the last taken first, once; later calls do nothing. When one throws, frees the rest and then rethrows. Also [Symbol.dispose].

destroy
,
[
var Symbol: SymbolConstructor
Symbol
.
SymbolConstructor.dispose: typeof Symbol.dispose

A method that is used to release resources held by an object. Called by the semantics of the using statement.

dispose
]:
const own: Owner
own
.
Owner.destroy(): void

Frees everything it took, the last taken first, once; later calls do nothing. When one throws, frees the rest and then rethrows. Also [Symbol.dispose].

destroy
,
};
}
export type
type Damping = {
settings: TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>;
velocities: TgpuMutable<d.WgslArray<d.Vec2f>>;
initAsync: () => Promise<void>;
step(pass: TgpuComputePass): void;
destroy: () => void;
[Symbol.dispose]: () => void;
}
Damping
=
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<typeof
function createDamping(root: TgpuRoot, options: DampingOptions): {
settings: TgpuUniform<d.WgslStruct<{
rate: d.F32;
}>>;
velocities: TgpuMutable<d.WgslArray<d.Vec2f>>;
initAsync: () => Promise<void>;
step(pass: TgpuComputePass): void;
destroy: () => void;
[Symbol.dispose]: () => void;
}
createDamping
>;
  • Schemas and shader functions that capture no instance’s resources live at module scope.
  • The kit’s owner takes each resource as it is made and frees them all once at destroy. Its usable() throws its message after destruction, so calling it in step keeps a dispatch from binding destroyed buffers.
  • Validation and allocation happen in the factory; recording methods only record.
  • A test builds it on recordingRoot and checks what it records.
  • d.struct rejects WGSL’s reserved words as field names, such as type and target.
  • unplugin-typegpu doesn’t transform a 'use gpu' object shorthand method, { alive(i) { 'use gpu'; … } }. Write it as a property: alive: (i) => { 'use gpu'; … }.
  • Shift a u32 right with >>>: d.u32(0x32) >>> corner. TypeGPU warns about >> on a u32, and a bare literal on the left of a shift is an i32.
  • Code that runs on React Native follows the platform rules, such as copying an array before sorting it.
Error Cause
Missing value for 'slot:weather' a pipeline reads an accessor nothing filled; create it from root.pipe(weather.inject())
Nothing filled `cameras.current`: … the same, for an accessor whose default is unfilled; it names the inject()
Missing bind groups for layouts a pipeline reads a shared layout, and the pass has no group set for it
readonly buffers cannot be mutated a render pipeline writes a buffer it binds read-only, such as a table’s device rows
bindGroupLayoutCount (5) is larger than the maximum allowed (4) the pipeline uses more bind groups than the device allows; a census counts them
The damping was destroyed. a call after destroy()
Found duplicate TypeGPU version two copies of TypeGPU loaded; see Setting up a project

Write a module builds a module step by step, from its first kernel to its test.

A package exports each module as a namespace. The module’s file calls its factory create and its options Options, and the package entry re-exports the file:

index.ts
// damping.ts: export function create(…), export interface Options, export type Damping
export * as damping from './damping.ts';
export type { Damping } from './damping.ts';

Call damping.create(root, options) and name the result for what it holds, as in const drag = damping.create(...). frames.create and bloom.create follow the same convention. A module in your own program can stay a plain createDamping.