Skip to content

How the kit works

One frame written in plain TypeGPU, then piece by piece with the kit, and the six rules every module follows.

TypeGPU Kit is a set of modules for TypeGPU. Your program keeps the GPU device, the loop and every command. Each module takes over one piece you would otherwise write by hand, such as textures that follow the canvas size, a glow or a pool of particles.

This page writes one frame in plain TypeGPU, then moves it onto the kit one piece at a time. Each step introduces one of the six rules every module follows, yours included.

The frame draws rings that ripple out from the middle of the canvas. A small program on the GPU, a kernel, moves the ripple forward each frame, and a fragment function colours every pixel from it.

The frame this page builds. Turn on Glow to add the bloom from A module borrows.

TypeGPU starts from a root, which holds the GPU device and creates everything else. The program asks it for two pieces of GPU memory and two pipelines, each a GPU function compiled together with the fixed state it needs:

import {
import common
common
,
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
} from 'typegpu';
const
const root: TgpuRoot
root
= await
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
.
init: (options?: InitOptions) => Promise<TgpuRoot>

Requests a new GPU device and creates a root around it. If a specific device should be used instead, use

@see ― initFromDevice. *

@example

When given no options, the function will ask the browser for a suitable GPU device.

const root = await tgpu.init();

@example

If there are specific options that should be used when requesting a device, you can pass those in.

const adapterOptions: GPURequestAdapterOptions = ...;
const deviceDescriptor: GPUDeviceDescriptor = ...;
const root = await tgpu.init({ adapter: adapterOptions, device: deviceDescriptor });

init
();
const
const context: GPUCanvasContext
context
=
const root: TgpuRoot
root
.
TgpuRoot.configureContext(options: ConfigureContextOptions): GPUCanvasContext

Creates and configures context for the provided canvas. Automatically sets the format to navigator.gpu.getPreferredCanvasFormat() if not provided.

@throws ― An error if no context could be obtained

configureContext
({
canvas: HTMLCanvasElement | OffscreenCanvas

The canvas for which a context will be created and configured.

canvas
:
var document: Document

window.document returns a reference to the document contained in the window.

MDN Reference

document
.
ParentNode.querySelector<"canvas">(selectors: "canvas"): HTMLCanvasElement | null (+4 overloads)

Returns the first element that is a descendant of node that matches selectors.

MDN Reference

querySelector
('canvas')! });
const
const tint: TgpuUniform<d.Vec4f>
tint
=
const root: TgpuRoot
root
.
TgpuRoot.createUniform<d.Vec4f>(typeSchema: d.Vec4f, initial?: ((buffer: TgpuBuffer<NoInfer<d.Vec4f>>) => void) | d.InferInput<NoInfer<d.Vec4f>> | undefined): TgpuUniform<d.Vec4f> (+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 vec4f: d.Vec4f
vec4f
,
import d
d
.
const vec4f: d.Vec4f
(x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
vec4f
(0.2, 0.6, 1, 1)); // written by the CPU
const
const phase: TgpuMutable<d.F32>
phase
=
const root: TgpuRoot
root
.
TgpuRoot.createMutable<d.F32>(typeSchema: d.F32, initial?: number | ((buffer: TgpuBuffer<NoInfer<d.F32>>) => void) | undefined): TgpuMutable<d.F32> (+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
.
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
); // written by the kernel
const
const advance: TgpuComputeFn<{}>
advance
=
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: (options: {
workgroupSize: number[];
}) => TgpuComputeFnShell<{}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [1] })(() => {
'use gpu';
const phase: TgpuMutable<d.F32>
phase
.
TgpuMutable<F32>.$: number
$
+= 0.05;
});
const
const rings: TgpuFragmentFn<{
uv: d.Vec2f;
}, d.Vec4f>
rings
=
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
.
fragmentFn: <{
uv: d.Vec2f;
}, d.Vec4f>(options: {
in: {
uv: d.Vec2f;
};
out: d.Vec4f;
}) => TgpuFragmentFnShell<{
uv: d.Vec2f;
}, d.Vec4f> (+1 overload)
fragmentFn
({
in: {
uv: d.Vec2f;
}
in
: {
uv: d.Vec2f
uv
:
import d
d
.
const vec2f: d.Vec2f
vec2f
},
out: d.Vec4f
out
:
import d
d
.
const vec4f: d.Vec4f
vec4f
})(({
uv: d.v2f
uv
}) => {
'use gpu';
const
const wave: number
wave
=
import std
std
.
function sin(value: number): number (+1 overload)
export sin
sin
(
import std
std
.
length<d.v2f>(value: d.v2f): number (+1 overload)
export length
length
(
uv: d.v2f
uv
-
import d
d
.
const vec2f: d.Vec2f
(xy: number) => d.v2f (+3 overloads)
vec2f
(0.5)) * 40 -
const phase: TgpuMutable<d.F32>
phase
.
TgpuMutable<F32>.$: number
$
) * 0.5 + 0.5;
return
const tint: TgpuUniform<d.Vec4f>
tint
.
TgpuUniform<Vec4f>.$: d.v4f
$
*
const wave: number
wave
;
});
const
const advancing: TgpuComputePipeline
advancing
=
const root: TgpuRoot
root
.
WithBinding.createComputePipeline<{}>(descriptor: TgpuComputePipeline.Descriptor<{}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{}>
compute
:
const advance: TgpuComputeFn<{}>
advance
});
const
const drawing: TgpuRenderPipeline<d.Vec4f>
drawing
=
const root: TgpuRoot
root
.
WithBinding.createRenderPipeline<{}, {}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
attribs?: {} | undefined;
vertex: TgpuVertexFn<{}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
fragment: TgpuFragmentFn<{
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
targets?: TgpuColorTargetState;
}): TgpuRenderPipeline<...> (+2 overloads)
createRenderPipeline
({
vertex: TgpuVertexFn<{}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
vertex
:
import common
common
.
const fullScreenTriangle: TgpuVertexFn<{}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}>
export fullScreenTriangle

A vertex function that defines a single full-screen triangle out of three points.

@example

import { common } from 'typegpu';
const pipeline = root.createRenderPipeline({
vertex: common.fullScreenTriangle,
fragment: yourFragmentShader,
});
pipeline.draw(3);

fullScreenTriangle
,
fragment: TgpuFragmentFn<{
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
readonly $fragDepth: import("node_modules/typegpu/builtin").BuiltinFragDepth;
readonly $sampleMask: import("node_modules/typegpu/builtin").BuiltinSampleMask;
}>>))
fragment
:
const rings: TgpuFragmentFn<{
uv: d.Vec2f;
}, d.Vec4f>
rings
, // draws in the canvas's format
});
  • 'use gpu' marks a function TypeGPU compiles for the GPU. Inside one, +, -, * and / work on vectors such as d.vec2f.
  • A uniform holds a few values that are the same for every pixel. The CPU writes it. A mutable buffer is memory that kernels write. A shader reads either through .$.
  • configureContext connects the canvas to the root’s device. Its format and a pipeline’s both default to the browser’s preferred format.
  • common.fullScreenTriangle is TypeGPU’s vertex function for one triangle that covers the canvas, and hands each pixel its uv, from 0 to 1 across the canvas.
  • Compiling a pipeline takes time, so a program creates its pipelines once, before the first frame.

The GPU doesn’t run commands as they are called. An encoder records them. Work goes into passes: a compute pass runs kernels, and a render pass draws into textures. A submit sends what the encoder recorded to the GPU, which runs it in order:

function
function frame(): void
frame
() {
const
const encoder: TgpuCommandEncoder
encoder
=
const root: TgpuRoot
root
.
TgpuRoot.createCommandEncoder(descriptor?: GPUCommandEncoderDescriptor): TgpuCommandEncoder

Creates a

TgpuCommandEncoder

for batching multiple render/compute passes (and draws within them) into a single submission.

@example

const encoder = root.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{ view: msaaTexture, resolveTarget: context }],
});
scenePipeline.with(pass).draw(vertexCount);
skyPipeline.with(pass).draw(3);
pass.end();
encoder.submit();

createCommandEncoder
();
const
const compute: TgpuComputePass
compute
=
const encoder: TgpuCommandEncoder
encoder
.
TgpuCommandEncoder.beginComputePass(descriptor?: TgpuComputePassDescriptor): TgpuComputePass

Begins recording a compute pass

beginComputePass
();
const compute: TgpuComputePass
compute
.
TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

Sets the current

TgpuComputePipeline

for subsequent dispatches

setPipeline
(
const advancing: TgpuComputePipeline
advancing
);
const compute: TgpuComputePass
compute
.
TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
dispatchWorkgroups
(1); // runs the kernel once
const compute: TgpuComputePass
compute
.
TgpuComputePass.end(): void

Completes the recording of this compute pass

end
();
const
const pass: TgpuRenderPass
pass
=
const encoder: TgpuCommandEncoder
encoder
.
TgpuCommandEncoder.beginRenderPass(descriptor: TgpuRenderPassDescriptor): TgpuRenderPass

Begins recording a render pass. Attachment views accept TypeGPU textures, texture views and canvas contexts, next to raw

GPUTextureView

s.

beginRenderPass
({
TgpuRenderPassDescriptor.colorAttachments?: ColorAttachment | readonly (ColorAttachment | null)[] | undefined
colorAttachments
: {
ColorAttachment.view: GPUCanvasContext | GPUTextureView | (ColorTextureConstraint & RenderFlag) | TgpuTextureView<d.WgslTexture<WgslTextureProps>> | TgpuTextureRenderView

A

GPUTextureView

describing the texture subresource that will be output to for this color attachment.

view
:
const context: GPUCanvasContext
context
.
GPUCanvasContext.getCurrentTexture(): GPUTexture (+1 overload)

Get the

GPUTexture

that will be composited to the document by the

GPUCanvasContext

next. Note: The same

GPUTexture

object will be returned by every call to

GPUCanvasContext#getCurrentTexture

until "[$Expire the current texture$]" runs, even if that

GPUTexture

is destroyed, failed validation, or failed to allocate.

getCurrentTexture
().
GPUTexture.createView(descriptor?: GPUTextureViewDescriptor): GPUTextureView (+1 overload)

Creates a

GPUTextureView

.

@param ― descriptor - Description of the GPUTextureView to create.

createView
() },
});
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.setPipeline(pipeline: TgpuRenderPipeline): void

Sets the current

TgpuRenderPipeline

for subsequent draw calls

setPipeline
(
const drawing: TgpuRenderPipeline<never>
drawing
);
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
draw
(3); // the triangle's three corners
const pass: TgpuRenderPass
pass
.
TgpuRenderPass.end(): void

Completes the recording of this render pass

end
();
const encoder: TgpuCommandEncoder
encoder
.
TgpuCommandEncoder.submit(): void

Finishes the recording and submits the resulting command buffer to the device queue

submit
(); // one command buffer for the whole frame
function requestAnimationFrame(callback: FrameRequestCallback): number
requestAnimationFrame
(
function frame(): void
frame
);
}
function requestAnimationFrame(callback: FrameRequestCallback): number
requestAnimationFrame
(
function frame(): void
frame
);

A larger program also needs a loop with time, textures that follow the canvas size, effects such as a glow, and thousands of objects. The kit’s modules provide those.

The kit’s frames runs the loop part of frame(). It creates the encoder, calls your function with it, and submits once your function returns. web.surface configures the canvas and follows its size. The passes stay yours:

import {
import frames
frames
} from '@typegpu/kit';
import * as
import web
web
from '@typegpu/kit/web';
const
const surface: web.Surface
surface
=
import web
web
.
function surface(root: TgpuRoot, canvas: HTMLCanvasElement, options?: web.SurfaceOptions): web.Surface

Configures a canvas for the root. Matches its drawing buffer to the layout size or the fixed options.size. Throws if the fixed size is invalid.

@param ― canvas the canvas it configures and sizes

@returns ― a surface you draw into and destroy

surface
(
const root: TgpuRoot
root
,
const canvas: HTMLCanvasElement
canvas
);
const
const loop: frames.Frames
loop
=
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const
const compute: TgpuComputePass
compute
=
frame: frames.Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
.
TgpuCommandEncoder.beginComputePass(descriptor?: TgpuComputePassDescriptor): TgpuComputePass

Begins recording a compute pass

beginComputePass
();
const compute: TgpuComputePass
compute
.
TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

Sets the current

TgpuComputePipeline

for subsequent dispatches

setPipeline
(
const advancing: TgpuComputePipeline
advancing
);
const compute: TgpuComputePass
compute
.
TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
dispatchWorkgroups
(1);
const compute: TgpuComputePass
compute
.
TgpuComputePass.end(): void

Completes the recording of this compute pass

end
();
const
const pass: TgpuRenderPass
pass
=
frame: frames.Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
.
TgpuCommandEncoder.beginRenderPass(descriptor: TgpuRenderPassDescriptor): TgpuRenderPass

Begins recording a render pass. Attachment views accept TypeGPU textures, texture views and canvas contexts, next to raw

GPUTextureView

s.

beginRenderPass
({
TgpuRenderPassDescriptor.colorAttachments?: ColorAttachment | readonly (ColorAttachment | null)[] | undefined
colorAttachments
: {
ColorAttachment.view: GPUTextureView | (ColorTextureConstraint & RenderFlag) | TgpuTextureView<WgslTexture<WgslTextureProps>> | TgpuTextureRenderView | GPUCanvasContext

A

GPUTextureView

describing the texture subresource that will be output to for this color attachment.

view
:
const surface: web.Surface
surface
.
Surface.view(frame: frames.Frame): GPUTextureView

Returns a view of the current canvas texture, created once per texture. Applies pending size changes first, which a pass using context would skip.

view
(
frame: frames.Frame
frame
) } });
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.setPipeline(pipeline: TgpuRenderPipeline): void

Sets the current

TgpuRenderPipeline

for subsequent draw calls

setPipeline
(
const drawing: TgpuRenderPipeline<never>
drawing
);
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
draw
(3);
const pass: TgpuRenderPass
pass
.
TgpuRenderPass.end(): void

Completes the recording of this render pass

end
();
}); // submits frame.encoder after this returns
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
const loop: frames.Frames
loop
); // runs the loop at every animation frame

frame holds the encoder, the time in seconds and dt, the seconds since the last frame.

The first rule: your program owns the device, the loop, the encoder, the passes and the submit. No module submits during a frame. Each records into a pass or an encoder you hand it, so the GPU runs the frame in the order of your calls.

A glow makes the bright rings bleed light. It needs the frame drawn into a texture with more range than the canvas, a high dynamic range (HDR) texture, which a blur then reads. Three modules cover it:

  • targets holds a set of textures that follow the canvas size. fit() replaces them when the size changes.
  • bloom blurs the bright parts of a texture into a glow texture.
  • finish, the final pass, brings HDR colour into the canvas’s range and adds the glow.
import {
import bloom
bloom
,
import finish
finish
,
import targets
targets
} from '@typegpu/kit';
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>

Creates render targets that follow a size. Replaces the whole set when the size changes. Put targets needed only sometimes in their own set and create that set when needed. Throws for transient targets with sampling, storage or mip levels, for multisampled targets with storage or mip levels, and for layered targets that are multisampled, mipped or have more layers than the device allows.

@param ― source the size it follows, such as a surface

@param ― specs a format and options per target, by name

@param ― options.resolution the fraction of each side drawn, which changes without allocating

@returns ― a set of targets you fit every frame and destroy

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true } });
const
const hdr: () => targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
= () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
;
const
const glow: bloom.Bloom
glow
=
import bloom
bloom
.
function create(root: TgpuRoot, options: bloom.Options): bloom.Bloom

Creates bloom from a chain of half-size textures. Each level downsamples with 13 taps, then a tent filter adds it to the next larger level. The result is mip 0 of output, at half the source size. With region, passes draw and sample only that region; changing the region allocates nothing.

@allocates ― on resize: a new chain and the bind groups of its passes; so does a new source

@owns ― targets, a sampler, 3 pipelines, 2 bind groups and a buffer per level: the chain of glow levels, the settings, a group for each pass, and the region each pass reads

@records ― record after whatever wrote the source; then output holds the glow.

@param ― options.source the HDR picture, read at every record

@param ― options.region the part of the source that holds the picture, read at every record

@returns ― a bloom you record every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr. record reads it on every call, so the texture can be rebuilt.

source
:
const hdr: () => targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
});
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
:
const hdr: () => targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
,
Options.glow?: finish.Rendered | undefined

Adds a bloom, or a glow of your own such as an emission target, before tone mapping. draw reads its output and region on every call. Default none.

glow
});
const
const drawing: TgpuRenderPipeline<d.Vec4f>
drawing
=
const root: TgpuRoot
root
.
WithBinding.createRenderPipeline<{}, {}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
attribs?: {} | undefined;
vertex: TgpuVertexFn<{}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
fragment: TgpuFragmentFn<{
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
targets?: TgpuColorTargetState;
}): TgpuRenderPipeline<...> (+2 overloads)
createRenderPipeline
({
vertex: TgpuVertexFn<{}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
vertex
:
import common
common
.
const fullScreenTriangle: TgpuVertexFn<{}, {
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}>
export fullScreenTriangle

A vertex function that defines a single full-screen triangle out of three points.

@example

import { common } from 'typegpu';
const pipeline = root.createRenderPipeline({
vertex: common.fullScreenTriangle,
fragment: yourFragmentShader,
});
pipeline.draw(3);

fullScreenTriangle
,
fragment: TgpuFragmentFn<{
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
}>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
readonly $fragDepth: import("node_modules/typegpu/builtin").BuiltinFragDepth;
readonly $sampleMask: import("node_modules/typegpu/builtin").BuiltinSampleMask;
}>>))
fragment
:
const rings: TgpuFragmentFn<{
uv: d.Vec2f;
}, d.Vec4f>
rings
,
targets?: TgpuColorTargetState
targets
: {
format?: GPUTextureFormat | undefined

The

GPUTextureFormat

of this color target. The pipeline will only be compatible with

GPURenderPassEncoder

s which use a

GPUTextureView

of this format in the corresponding color attachment.

@default ― navigator.gpu.getPreferredCanvasFormat()

format
: 'rgba16float' }, // into the HDR texture now
});
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.fit(): boolean

Rebuilds all textures if the source size changed, destroying the old textures. Updates regions for the current resolution. Returns true after a rebuild so callers can update bind groups that hold old textures. Call before recording passes that use the targets. Throws after destroy.

fit
();
const
const pass: TgpuRenderPass
pass
=
frame: frames.Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
.
TgpuCommandEncoder.beginRenderPass(descriptor: TgpuRenderPassDescriptor): TgpuRenderPass

Begins recording a render pass. Attachment views accept TypeGPU textures, texture views and canvas contexts, next to raw

GPUTextureView

s.

beginRenderPass
({
TgpuRenderPassDescriptor.colorAttachments?: ColorAttachment | readonly (ColorAttachment | null)[] | undefined
colorAttachments
: {
ColorAttachment.view: TgpuTextureRenderView | (ColorTextureConstraint & RenderFlag) | GPUTextureView | TgpuTextureView<d.WgslTexture<WgslTextureProps>> | GPUCanvasContext

A

GPUTextureView

describing the texture subresource that will be output to for this color attachment.

view
:
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.views: {
readonly hdr: GPUTextureView;
}

Provides attachment views of mip 0, created once per rebuild. Throws after destroy.

views
.
hdr: GPUTextureView
hdr
} });
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.setPipeline(pipeline: TgpuRenderPipeline): void

Sets the current

TgpuRenderPipeline

for subsequent draw calls

setPipeline
(
const drawing: TgpuRenderPipeline<d.Vec4f>
drawing
);
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
draw
(3);
const pass: TgpuRenderPass
pass
.
TgpuRenderPass.end(): void

Completes the recording of this render pass

end
();
const glow: bloom.Bloom
glow
.
Bloom.record(target: frames.Frame | TgpuCommandEncoder, options?: Timed): void

Records bloom passes after the source is written. Optional timer writes measure the passes together.

record
(
frame: frames.Frame
frame
); // opens its own passes
const final: finish.Finish
final
.
Finish.record(target: frames.Frame | TgpuCommandEncoder, view: GPUTextureView, options?: finish.RecordOptions): void

Opens a pass on view, such as surface.view(frame), draws into it and ends it. A viewport draws the picture into that rectangle and clears the rest, transparent with alpha: 'premultiplied' and black otherwise. Optional timer writes measure the pass.

record
(
frame: frames.Frame
frame
,
const surface: web.Surface
surface
.
Surface.view(frame: frames.Frame): GPUTextureView

Returns a view of the current canvas texture, created once per texture. Applies pending size changes first, which a pass using context would skip.

view
(
frame: frames.Frame
frame
));
});

Each module came from a factory, create(root, options). A factory allocates what the module needs and returns a handle:

  • Resources, as TypeGPU objects you read and write. glow.settings is a uniform, so glow.settings.patch({ threshold: 0.8 }) changes the threshold. glow.output is the glow texture, which your own passes can sample.
  • Recording methods, such as glow.record(frame), which record into what you pass them.
  • 'use gpu' functions your shaders can call, on modules that have them, such as particles’ emit.
  • destroy(), which frees what the module made and nothing it borrowed. glow.destroy() frees the glow’s textures, and the screen’s stay.

The second rule: a module borrows. It takes the root and the resources you hand it, keeps no global state, and destroys only what it made, so two glows share nothing. A recording method takes the smallest piece of the frame it needs: a compute or render pass, or the frame or its encoder when it opens passes of its own. A method that eases over time reads dt from the frame.

The figure shows a larger frame: a flow field, particles, a readback and a glow, each recording into the one encoder. Turn a module off to see what it recorded.

    Turn a module off to remove its calls or passes. Point at a resource to follow it from the writing pass to its readers.

    Next to plain WebGPU, TypeGPU and a renderer such as three.js, the kit keeps TypeGPU’s shaders and schemas and leaves you the passes and the submit:

    WebGPU TypeGPU TypeGPU Kit Three.js
    Shaders WGSL strings TypeScript marked 'use gpu' TypeGPU shader functions built-in materials, TSL for your own
    Memory layout byte offsets, by hand schemas such as d.struct TypeGPU schemas and tables attributes and uniforms it uploads
    Objects rows in buffers a scene graph of Object3D
    Passes and order yours yours yours; modules record into them the renderer’s
    The submit yours yours, or one per call without an encoder yours, once a frame the renderer’s
    Reusable pieces functions, slots and accessors modules that borrow your root classes and add-ons

    Creating GPU objects during a frame costs time and memory the frame can’t spare, and a pipeline compiled during a frame stalls it. So the modules allocate when you create them, compile in initAsync, and only record during frames:

    await Promise.all([
    advancing.initAsync(),
    drawing.initAsync(),
    glow.initAsync(),
    final.initAsync(),
    ]);

    After that a steady frame creates no GPU objects at all. The figure runs a scene on a recording root, which counts what the device creates without a GPU. Run a frame twice and the counts stay put.

    WGSL in log.shaders
    Press frame again to see that a steady frame allocates nothing, with changed counters highlighted. Reset and run a frame before initAsync() to see compilation on first use. steps sets how many fixed steps a frame records.

    The third rule: nothing is hidden. Each module’s page opens with a card generated from its source, which states its costs. A test runs every module and checks the card against what it does:

    • allocates names anything a module creates after setup, such as the bind group a video feed makes for each camera frame it copies.
    • owns lists what destroy() frees.
    • binds counts the storage buffers and uniforms its shaders add. WebGPU guarantees 8 storage buffers per shader stage, so the counts tell you what fits in one pipeline.
    • records says where its work goes and in which order.
    • injects names the accessors it fills, which the next section explains.
    • submits setup marks the few calls that submit on their own, such as meshPool.add without an encoder, so you make them while loading.

    recordingRoot() runs your own scene the same way in a test.

    The ripple can become a module too. Then the colouring shader no longer captures phase itself: it reads the ripple through an accessor, a placeholder that a pipeline fills with a real resource when it is created. The module fills its accessor in inject():

    export function
    function createRipple(root: TgpuRoot): {
    heightAt: (uv: d.v2f) => number;
    step: (pass: TgpuComputePass) => void;
    inject: () => Inject;
    }
    createRipple
    (
    root: TgpuRoot
    root
    :
    (alias) interface TgpuRoot
    import TgpuRoot
    TgpuRoot
    ) {
    const
    const phase: TgpuMutable<d.F32>
    phase
    =
    root: TgpuRoot
    root
    .
    TgpuRoot.createMutable<d.F32>(typeSchema: d.F32, initial?: number | ((buffer: TgpuBuffer<NoInfer<d.F32>>) => void) | undefined): TgpuMutable<d.F32> (+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
    .
    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 ripple: TgpuAccessor<d.F32>
    ripple
    =
    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
    .
    accessor: <d.F32>(schema: d.F32, defaultValue?: DataAccessorIn<NoInfer<d.F32>> | undefined) => TgpuAccessor<d.F32> (+1 overload)
    accessor
    (
    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
    ); // a placeholder for some ripple's phase
    const
    const advance: TgpuComputeFn<{}>
    advance
    =
    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: (options: {
    workgroupSize: number[];
    }) => TgpuComputeFnShell<{}> (+1 overload)
    computeFn
    ({
    workgroupSize: number[]
    workgroupSize
    : [1] })(() => {
    'use gpu';
    const phase: TgpuMutable<d.F32>
    phase
    .
    TgpuMutable<F32>.$: number
    $
    += 0.05;
    });
    const
    const advancing: TgpuComputePipeline
    advancing
    =
    root: TgpuRoot
    root
    .
    WithBinding.createComputePipeline<{}>(descriptor: TgpuComputePipeline.Descriptor<{}>): TgpuComputePipeline
    createComputePipeline
    ({
    compute: TgpuComputeFn<{}>
    compute
    :
    const advance: TgpuComputeFn<{}>
    advance
    });
    return {
    heightAt: (uv: d.v2f) => number
    heightAt
    : (
    uv: d.v2f
    uv
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ) => {
    'use gpu';
    return
    import std
    std
    .
    function sin(value: number): number (+1 overload)
    export sin
    sin
    (
    import std
    std
    .
    length<d.v2f>(value: d.v2f): number (+1 overload)
    export length
    length
    (
    uv: d.v2f
    uv
    -
    import d
    d
    .
    const vec2f: d.Vec2f
    (xy: number) => d.v2f (+3 overloads)
    vec2f
    (0.5)) * 40 -
    const ripple: TgpuAccessor<d.F32>
    ripple
    .
    TgpuAccessor<F32>.$: number
    $
    ) * 0.5 + 0.5;
    },
    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
    ) => {
    pass: TgpuComputePass
    pass
    .
    TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

    Sets the current

    TgpuComputePipeline

    for subsequent dispatches

    setPipeline
    (
    const advancing: TgpuComputePipeline
    advancing
    );
    pass: TgpuComputePass
    pass
    .
    TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
    dispatchWorkgroups
    (1);
    },
    inject: () => Inject
    inject
    : ():
    type Inject = (cfg: Configurable) => Configurable

    Fills module accessors for pipelines created from the configuration it is piped into. Modules return it from inject(), as TypeGPU caches do. Use root.pipe(table.inject()).

    Inject
    => (
    cfg: Configurable
    cfg
    ) =>
    cfg: Configurable
    cfg
    .
    Withable<Configurable>.with<d.F32>(accessor: TgpuAccessor<d.F32>, value: DataAccessorIn<NoInfer<d.F32>>): Configurable (+2 overloads)
    with
    (
    const ripple: TgpuAccessor<d.F32>
    ripple
    ,
    const phase: TgpuMutable<d.F32>
    phase
    ), // fills it with this ripple's
    };
    }

    A shader that calls heightAt reads the accessor, so the pipeline that runs it is created from root.pipe(ripple.inject()):

    const
    const rings: TgpuFragmentFn<{
    uv: d.Vec2f;
    }, d.Vec4f>
    rings
    =
    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
    .
    fragmentFn: <{
    uv: d.Vec2f;
    }, d.Vec4f>(options: {
    in: {
    uv: d.Vec2f;
    };
    out: d.Vec4f;
    }) => TgpuFragmentFnShell<{
    uv: d.Vec2f;
    }, d.Vec4f> (+1 overload)
    fragmentFn
    ({
    in: {
    uv: d.Vec2f;
    }
    in
    : {
    uv: d.Vec2f
    uv
    :
    import d
    d
    .
    const vec2f: d.Vec2f
    vec2f
    },
    out: d.Vec4f
    out
    :
    import d
    d
    .
    const vec4f: d.Vec4f
    vec4f
    })(({
    uv: d.v2f
    uv
    }) => {
    'use gpu';
    return
    import d
    d
    .
    const vec4f: d.Vec4f
    (x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
    vec4f
    (0.2, 0.6, 1, 1) *
    const ripple: {
    heightAt(uv: d.v2f): number;
    inject(): Inject;
    }
    ripple
    .
    function heightAt(uv: d.v2f): number
    heightAt
    (
    uv: d.v2f
    uv
    );
    });
    const
    const drawing: TgpuRenderPipeline<d.Vec4f>
    drawing
    =
    const root: TgpuRoot
    root
    .
    WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
    pipe
    (
    const ripple: {
    heightAt(uv: d.v2f): number;
    inject(): Inject;
    }
    ripple
    .
    function inject(): Inject
    inject
    ()).
    WithBinding.createRenderPipeline<{}, {}, {
    uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
    }, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
    attribs?: {} | undefined;
    vertex: TgpuVertexFn<{}, {
    uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
    }> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
    fragment: TgpuFragmentFn<{
    uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
    } & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
    targets?: TgpuColorTargetState;
    }): TgpuRenderPipeline<...> (+2 overloads)
    createRenderPipeline
    ({
    vertex: TgpuVertexFn<{}, {
    uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
    }> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
    vertex
    :
    import common
    common
    .
    const fullScreenTriangle: TgpuVertexFn<{}, {
    uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
    }>
    export fullScreenTriangle

    A vertex function that defines a single full-screen triangle out of three points.

    @example

    import { common } from 'typegpu';
    const pipeline = root.createRenderPipeline({
    vertex: common.fullScreenTriangle,
    fragment: yourFragmentShader,
    });
    pipeline.draw(3);

    fullScreenTriangle
    ,
    fragment: TgpuFragmentFn<{
    uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
    } & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
    uv: import("node_modules/typegpu/data/wgslTypes").Vec2f;
    }>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
    readonly $fragDepth: import("node_modules/typegpu/builtin").BuiltinFragDepth;
    readonly $sampleMask: import("node_modules/typegpu/builtin").BuiltinSampleMask;
    }>>))
    fragment
    :
    const rings: TgpuFragmentFn<{
    uv: d.Vec2f;
    }, d.Vec4f>
    rings
    ,
    targets?: TgpuColorTargetState
    targets
    : {
    format?: GPUTextureFormat | undefined

    The

    GPUTextureFormat

    of this color target. The pipeline will only be compatible with

    GPURenderPassEncoder

    s which use a

    GPUTextureView

    of this format in the corresponding color attachment.

    @default ― navigator.gpu.getPreferredCanvasFormat()

    format
    : 'rgba16float' },
    });

    The fourth rule: modules connect through TypeGPU’s own accessors, slots and pipe, never through a registry. The kit’s modules connect this way everywhere:

    • Every renderer reads the camera through one accessor, cameras.current. A scene picks its camera with .pipe(view.inject()).
    • Any kernel piped with sparks.inject() can start particles in sparks with sparks.emit.

    Connecting modules covers the kinds of accessor, callbacks and resources that are replaced.

    A pipeline is compiled from its code, and a value captured into the code is part of it. Ten ripples with their centres captured into the shader would compile ten pipelines. With the centres in a buffer, one pipeline draws all ten, and an eleventh needs one more row and no new pipeline:

    // One pipeline per ripple: each centre is part of a shader's code
    const
    const ringsAround: ((uv: d.v2f) => number)[]
    ringsAround
    =
    const centres: d.v2f[]
    centres
    .
    Array<v2f>.map<(uv: d.v2f) => number>(callbackfn: (value: d.v2f, index: number, array: d.v2f[]) => (uv: d.v2f) => number, thisArg?: any): ((uv: d.v2f) => number)[]

    Calls a defined callback function on each element of an array, and returns an array that contains the results.

    @param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

    @param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

    map
    ((
    centre: d.v2f
    centre
    ) => (
    uv: d.v2f
    uv
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ) => {
    'use gpu';
    return
    import std
    std
    .
    function sin(value: number): number (+1 overload)
    export sin
    sin
    (
    import std
    std
    .
    length<d.v2f>(value: d.v2f): number (+1 overload)
    export length
    length
    (
    uv: d.v2f
    uv
    -
    centre: d.v2f
    centre
    ) * 40);
    });
    // One pipeline for every ripple: the centres are data
    const
    const centreOf: TgpuReadonly<d.WgslArray<d.Vec2f>>
    centreOf
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createReadonly<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): TgpuReadonly<d.WgslArray<d.Vec2f>> (+1 overload)

    Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for large 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)

    createReadonly
    (
    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
    , 16),
    const centres: d.v2f[]
    centres
    );
    const
    const ringsOf: (i: number, uv: d.v2f) => number
    ringsOf
    = (
    i: number
    i
    : number,
    uv: d.v2f
    uv
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ) => {
    'use gpu';
    return
    import std
    std
    .
    function sin(value: number): number (+1 overload)
    export sin
    sin
    (
    import std
    std
    .
    length<d.v2f>(value: d.v2f): number (+1 overload)
    export length
    length
    (
    uv: d.v2f
    uv
    -
    const centreOf: TgpuReadonly<d.WgslArray<d.Vec2f>>
    centreOf
    .
    TgpuReadonly<WgslArray<Vec2f>>.$: d.v2f[]
    $
    [
    i: number
    i
    ]!) * 40);
    };

    The fifth rule: per-object values live in buffers, and state that evolves stays on the GPU. The kit gives those buffers structure:

    • A table is a buffer of rows with named fields. The CPU writes some fields, kernels write the others, and the table uploads only what changed.
    • Instances draw every mesh’s objects from a table’s rows, and particles live in a pool of rows.

    Data on the GPU covers rows, fixed steps, settings and reading results back.

    A real scene draws more than colour. TAA needs to know where each pixel was last frame, picking needs an id at every pixel, and the depth decides what is in front. Every renderer would have to write all of these.

    Instead, passes.create describes once what each target holds. A renderer hands over a drawing: what each corner carries to its pixels, what a pixel looks like, and how to draw. Here is the ripple as a drawing:

    import {
    import passes
    passes
    } from '@typegpu/kit';
    const
    const Seen: d.WgslStruct<{
    color: d.Vec4f;
    }>
    Seen
    =
    import d
    d
    .
    struct<{
    color: d.Vec4f;
    }>(props: {
    color: d.Vec4f;
    }): d.WgslStruct<{
    color: d.Vec4f;
    }>
    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
    ({
    color: d.Vec4f
    color
    :
    import d
    d
    .
    const vec4f: d.Vec4f
    vec4f
    }); // what a pixel looks like
    const
    const scene: passes.Passes<{
    readonly hdr: typeof passes.color;
    }>
    scene
    =
    import passes
    passes
    .
    function create<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }, {
    readonly hdr: typeof passes.color;
    }>(root: TgpuRoot, screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>, writes: {
    readonly hdr: typeof passes.color;
    }, options?: passes.TargetOptions<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }> | undefined): passes.Passes<{
    readonly hdr: 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 screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>
    screen
    , {
    hdr: typeof passes.color
    hdr
    :
    import passes
    passes
    .
    const color: typeof passes.color

    Marks the target that receives each renderer's colour.

    color
    }).
    Passes<{ readonly hdr: typeof color; }>.pipe(fill: Inject): passes.Passes<{
    readonly hdr: 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 ripple: {
    heightAt(uv: d.v2f): number;
    inject(): Inject;
    }
    ripple
    .
    function inject(): Inject
    inject
    ());
    const
    const water: passes.Drawn
    water
    =
    const scene: passes.Passes<{
    readonly hdr: typeof passes.color;
    }>
    scene
    .
    Passes<{ readonly hdr: typeof color; }>.add<d.WgslStruct<{
    position: d.Vec4f;
    uv: d.Vec2f;
    }>, {
    color: d.v4f;
    }>(this: passes.Passes<passes.Fed<{
    color: d.v4f;
    }, {
    position: d.v4f;
    uv: d.v2f;
    }>>, drawing: passes.Drawing<d.WgslStruct<{
    position: d.Vec4f;
    uv: d.Vec2f;
    }>, {
    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
    ({
    ...
    import passes
    passes
    .
    const fullScreen: {
    Vertex: d.WgslStruct<{
    position: d.Vec4f;
    uv: d.Vec2f;
    }>;
    at: (vertexIndex: number, _instanceIndex: number) => {
    position: d.v4f;
    uv: d.v2f;
    };
    }

    Provides Vertex and at for a renderer that covers the screen with one triangle at depth 0. Draw it with draw(3). Use { ...passes.fullScreen, surfaceOf, color, draw }.

    fullScreen
    , // one triangle over the screen, carrying uv
    Shaders<WgslStruct<{ position: Vec4f; uv: Vec2f; }>, { color: v4f; }>.surfaceOf?: ((vertex: {
    position: d.v4f;
    uv: d.v2f;
    }) => {
    color: d.v4f;
    }) | undefined

    Computes data at a pixel from the interpolated vertex. position is the pixel position. Required for descriptions that write colour and for depthOf.

    surfaceOf
    : (
    corner: {
    position: d.v4f;
    uv: d.v2f;
    }
    corner
    :
    import d
    d
    .
    type Infer<T> = T extends {
    readonly [$repr]: infer TRepr;
    } ? TRepr : T
    export Infer

    Extracts the inferred representation of a resource. For inferring types as seen by the GPU, see

    InferGPU

    @example type A = Infer // => number type B = Infer<WgslArray> // => number[] type C = Infer<Atomic> // => number

    Infer
    <typeof
    import passes
    passes
    .
    const fullScreen: {
    Vertex: d.WgslStruct<{
    position: d.Vec4f;
    uv: d.Vec2f;
    }>;
    at: (vertexIndex: number, _instanceIndex: number) => {
    position: d.v4f;
    uv: d.v2f;
    };
    }

    Provides Vertex and at for a renderer that covers the screen with one triangle at depth 0. Draw it with draw(3). Use { ...passes.fullScreen, surfaceOf, color, draw }.

    fullScreen
    .
    type Vertex: d.WgslStruct<{
    position: d.Vec4f;
    uv: d.Vec2f;
    }>
    Vertex
    >) => {
    'use gpu';
    return
    const Seen: d.WgslStruct
    (props?: {
    color: d.v4f;
    } | undefined) => {
    color: d.v4f;
    }
    Seen
    ({
    color: d.v4f
    color
    :
    import d
    d
    .
    const vec4f: d.Vec4f
    (x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
    vec4f
    (0.2, 0.6, 1, 1) *
    const ripple: {
    heightAt(uv: d.v2f): number;
    inject(): Inject;
    }
    ripple
    .
    function heightAt(uv: d.v2f): number
    heightAt
    (
    corner: {
    position: d.v4f;
    uv: d.v2f;
    }
    corner
    .
    uv: d.v2f
    uv
    ) });
    },
    Shaders<WgslStruct<{ position: Vec4f; uv: Vec2f; }>, { color: v4f; }>.color?: passes.Output<{
    color: d.v4f;
    }, {
    position: d.v4f;
    uv: d.v2f;
    }> | undefined

    Supplies the output for the color target when the description has one.

    color
    :
    import passes
    passes
    .
    const shaded: passes.Output<{
    readonly color: d.v4f;
    }, unknown>

    Writes the surface's color unchanged as a renderer's color output.

    shaded
    , // writes the surface's colour as it is
    Drawing<WgslStruct<{ position: Vec4f; uv: Vec2f; }>, { color: v4f; }>.draw(pass: TgpuRenderCommands): void

    Records a draw into the pass, on which the description has set the pipeline it built. That pipeline binds indices if supplied. drawIndexed throws without an index buffer. A group the drawing sets with pass.setBindGroup stays for later drawings in the pass that read its layout, so give such a layout to one drawing.

    draw
    : (
    pass: TgpuRenderCommands
    pass
    ) =>
    pass: TgpuRenderCommands
    pass
    .
    TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
    draw
    (3),
    });
    import frames
    frames
    .
    function create(root: TgpuRoot, record: Recorder): frames.Frames

    Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

    @submits ― always once a frame after your recorder returns, and again at each frame.submit()

    @param ― record your frame, recorded into each frame's encoder

    @returns ― a loop you run with each frame's time and destroy

    create
    (
    const root: TgpuRoot
    root
    , (
    frame: frames.Frame
    frame
    ) =>
    const scene: passes.Passes<{
    readonly hdr: typeof passes.color;
    }>
    scene
    .
    Passes<{ readonly hdr: typeof color; }>.record(frame: frames.Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
    readonly hdr: 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: frames.Frame
    frame
    , [
    const water: passes.Drawn
    water
    ]));

    scene.add builds the pipeline that writes every target the description lists. Add a motion target to the description and the same drawing writes motion too.

    The sixth rule: scenes are described once, and every renderer, the kit’s included, hands over a drawing. Scenes and passes covers outputs, shadows and other views.

    The kit has no scene graph, ECS, physics, required asset format or engine loop. Parent-child transforms, game state and input stay in your code. A host, such as web.host or React Native’s useScene, runs a scene for you, and the scene still records its own frame.

    Modules return TypeGPU resources, and most fit in one source file. You can edit a module or write your own and record both into the same encoder. The kit also runs on a device it shares with three.js or vgpu: Beside other renderers passes buffers, textures and images between them.

    Modules covers the module contract in detail.