Skip to content

@typegpu/kit/geometrymeshes

Meshes

Boxes, planes, spheres and other shapes, computed in the vertex shader or baked into buffers.

kit.meshes

module

meshes.unindexed(g) → Geometry

meshes.bake(root, g, options?) → BakedIndexed

meshes.bakeAsync(root, g, options?) → Promise<BakedIndexed>

meshes.bakeIndices(root, g, options?) → BakedIndices

and meshes.bakeIndicesAsync, meshes.sphere, meshes.plane, meshes.box, meshes.cylinder, meshes.torus, meshes.icosphere, meshes.capsule, meshes.roundedBox

rootborrows
the root
GPU functions
triangleBarycentricstriangleIndexAt
schemas
Vertex
helpers
attachconcatmaptransformisIndexedparametricpatchessurfacestriangleVertexCounttriangleIndexCounttriangleIndicestessellate
types
AttachedPlacedGeometryIndexedGeometryTopologyParametricParametricOptionsUpdateOptionsVertexBufferIndexBufferBakedBakedIndexedBakedIndicesPatchSurface
BakeOptionsbake, bakeAsync · 6 fields
vertices?

VertexBuffer<V>

indices?

IndexBuffer

with?

WithBinding

bindGroups?

readonly TgpuBindGroup[]

encoder?

TgpuCommandEncoder | GPUCommandEncoder

pass?

TgpuComputePass | GPUComputePassEncoder

BakeIndicesOptionsbakeIndices, bakeIndicesAsync · 5 fields
indices?

IndexBuffer

with?

WithBinding

bindGroups?

readonly TgpuBindGroup[]

encoder?

TgpuCommandEncoder | GPUCommandEncoder

pass?

TgpuComputePass | GPUComputePassEncoder

SphereOptionssphere · 3 fields
radius?

number

segments?

number

rings?

number

PlaneOptionsplane · 4 fields
width?

number

depth?

number

widthSegments?

number

depthSegments?

number

BoxOptionsbox · 6 fields
width?

number

height?

number

depth?

number

widthSegments?

number

heightSegments?

number

depthSegments?

number

CylinderOptionscylinder · 5 fields
radius?

number

height?

number

radialSegments?

number

heightSegments?

number

caps?

boolean

TorusOptionstorus · 4 fields
radius?

number

tube?

number

ringSegments?

number

tubeSegments?

number

IcosphereOptionsicosphere · 2 fields
radius?

number

interpolate?

(a: d.v3f, b: d.v3f, c: d.v3f, weights: d.v3f) => d.v3f

CapsuleOptionscapsule · 2 fields
radius?

number

height?

number

Length of the cylindrical section

RoundedBoxOptionsroundedBox · 4 fields
width?

number

height?

number

depth?

number

radius?

number

submits

while setting up

A bake without an encoder or a pass submits a compute pass of its own.

owns

buffers ·a pipeline per baked shape

A baked shape's vertices and indices, and the pass that fills them; a computed shape owns nothing.

lifecycle

destroy()[Symbol.dispose](none)

injects
  • per-instance

    A baked shape's vertices and indices, for the pipeline that draws it.

binds
  • 1 storage

    A baked shape's vertexAt or indexAt; drawing through inject() uses vertex and index buffers without storage bindings.

Step through vertices to build triangles, with back faces dashed. The sphere, cylinder and torus use fewer segments than their defaults.

A mesh from meshes, imported from @typegpu/kit/geometry, has a vertexCount and a vertexAt(i) function marked 'use gpu'. The function computes each vertex from its index. A vertex shader can therefore draw a box without a vertex buffer.

import {
import cameras
cameras
} from '@typegpu/kit';
import {
import meshes
meshes
} from '@typegpu/kit/geometry';
const
const cube: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
cube
=
import meshes
meshes
.
unindexed<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(g: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>): meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
export unindexed
unindexed
(
import meshes
meshes
.
function box({ width, height, depth, widthSegments, heightSegments, depthSegments, }?: meshes.BoxOptions): meshes.IndexedGeometry
box
());
const
const vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}>
vertex
=
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
.
vertexFn: <{
vertexIndex: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}>(options: {
in: {
vertexIndex: d.BuiltinVertexIndex;
};
out: {
position: d.BuiltinPosition;
normal: d.Vec3f;
};
}) => TgpuVertexFnShell<{
vertexIndex: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}> (+1 overload)
vertexFn
({
in: {
vertexIndex: d.BuiltinVertexIndex;
}
in
: {
vertexIndex: d.BuiltinVertexIndex
vertexIndex
:
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
.
vertexIndex: d.BuiltinVertexIndex
vertexIndex
},
out: {
position: d.BuiltinPosition;
normal: d.Vec3f;
}
out
: {
position: d.BuiltinPosition
position
:
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
.
position: d.BuiltinPosition
position
,
normal: d.Vec3f
normal
:
import d
d
.
const vec3f: d.Vec3f
vec3f
},
})(({
vertexIndex: number
vertexIndex
}) => {
'use gpu';
const
const corner: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
corner
=
const cube: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
cube
.
Geometry<WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.vertexAt(this: void, i: number): {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
vertexAt
(
vertexIndex: number
vertexIndex
);
return {
position: d.v4f
position
:
import cameras
cameras
.
const current: TgpuAccessor<d.WgslStruct<{
viewProjection: d.Mat4x4f;
position: d.Vec3f;
right: d.Vec3f;
up: d.Vec3f;
}>>

Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
viewProjection: d.m4x4f

Transforms world coordinates to clip space.

viewProjection
*
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const corner: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
corner
.
position: d.v3f
position
, 1),
normal: d.v3f
normal
:
const corner: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
corner
.
normal: d.v3f
normal
,
};
});
const
const pipeline: TgpuRenderPipeline<d.Vec4f>
pipeline
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const view: CameraBase
view
.
CameraBase.inject(): Inject

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

inject
()).
WithBinding.createRenderPipeline<{}, {}, {
normal: d.Vec3f;
}, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
attribs?: {} | undefined;
vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
fragment: TgpuFragmentFn<{
normal: d.Vec3f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
targets?: TgpuColorTargetState;
}): TgpuRenderPipeline<...> (+2 overloads)
createRenderPipeline
({
vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
vertex
,
fragment: TgpuFragmentFn<{
normal: d.Vec3f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
normal: d.Vec3f;
}>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
readonly $fragDepth: import("node_modules/typegpu/builtin").BuiltinFragDepth;
readonly $sampleMask: import("node_modules/typegpu/builtin").BuiltinSampleMask;
}>>))
fragment
,
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
: 'bgra8unorm' },
TgpuRenderPipeline<in Targets = never>.DescriptorBase.primitive?: TgpuPrimitiveState

Describes the primitive-related properties of the pipeline.

primitive
: {
cullMode: "back"
cullMode
: 'back' },
});
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.setPipeline(pipeline: TgpuRenderPipeline): void

Sets the current

TgpuRenderPipeline

for subsequent draw calls

setPipeline
(
const pipeline: TgpuRenderPipeline<d.Vec4f>
pipeline
);
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
draw
(
const cube: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
cube
.
Geometry<V extends d.AnyWgslData = WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.vertexCount: number
vertexCount
);

Each shape is centred at the origin. Each vertex is a meshes.Vertex with position, an outward normal and uv coordinates.

Shape Size Division
box() width, height, depth default to 1 widthSegments, heightSegments, depthSegments default to 1
plane() width, depth default to 1, in xz with normal +y widthSegments, depthSegments default to 1
sphere() radius defaults to 0.5 segments around default to 32, rings default to 16
cylinder() radius defaults to 0.5, height defaults to 1, caps defaults to true radialSegments default to 32, heightSegments default to 1
torus() radius defaults to 0.35, tube defaults to 0.15 ringSegments default to 48, tubeSegments default to 24
icosphere() radius defaults to 0.5 segments along each of its 20 triangles’ edges default to 4
capsule() radius defaults to 0.25, height of the middle defaults to 0.5 segments along each patch’s edges default to 4
roundedBox() width, height, depth default to 1, radius defaults to 0.1 segments along each patch’s edges default to 4
  • Use the option names in the table, such as box({ width: 2, height: 0.5 }) or sphere({ segments: 12, rings: 8 }). The icosphere has evenly spaced triangles without crowded poles. Its interpolate function places points within each triangle.
  • Triangles wind counter-clockwise when viewed from outside, matching WebGPU’s default frontFace. Use cullMode: 'back' to draw the outside and 'front' to draw the inside, as of a room around the camera.

Combine shapes while keeping their vertices computed from indices:

const
const stalk: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
stalk
=
import meshes
meshes
.
function cylinder({ radius, height, radialSegments, heightSegments, caps, }?: meshes.CylinderOptions): meshes.IndexedGeometry
cylinder
({
CylinderOptions.radius?: number | undefined
radius
: 0.08,
CylinderOptions.height?: number | undefined
height
: 0.5,
CylinderOptions.radialSegments?: number | undefined
radialSegments
: 12 });
const
const cap: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
cap
=
import meshes
meshes
.
function sphere({ radius, segments, rings, }?: meshes.SphereOptions): meshes.IndexedGeometry
sphere
({
SphereOptions.radius?: number | undefined
radius
: 0.3,
SphereOptions.segments?: number | undefined
segments
: 16,
SphereOptions.rings?: number | undefined
rings
: 8 });
const
const mushroom: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
mushroom
=
import meshes
meshes
.
concat<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(...parts: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>[]): meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
export concat
concat
(
import meshes
meshes
.
transform<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(g: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>, m: d.m4x4f): meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>> (+1 overload)
export transform
transform
(
const stalk: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
stalk
,
import std
std
.
function translation4(vector: d.v3f): d.m4x4f
export translation4

Creates a 4-by-4 matrix which translates by the given vector v.

@param ― vector - The vector by which to translate.

@returns ― The translation matrix.

translation4
(
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0, 0.25, 0))),
import meshes
meshes
.
transform<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(g: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>, m: d.m4x4f): meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>> (+1 overload)
export transform
transform
(
const cap: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
cap
,
import std
std
.
function translation4(vector: d.v3f): d.m4x4f
export translation4

Creates a 4-by-4 matrix which translates by the given vector v.

@param ― vector - The vector by which to translate.

@returns ― The translation matrix.

translation4
(
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0, 0.5, 0))),
);
  • meshes.transform(shape, matrix) translates, rotates and scales with a d.m4x4f, including normals. Mirroring preserves triangle winding.
  • meshes.concat(a, b, ...) joins shapes with the same schema and topology into an indexed geometry. Its inputs can be unindexed.
  • meshes.map(shape, schema, fn) converts each vertex to schema. meshes.attach(shape, struct, fn) adds the fields returned by fn. Both preserve whether the shape is indexed.

The shapes are indexed. indexAt(i) identifies the vertex used by corner i, so triangles can share vertices. A box has 24 vertices and 36 indices.

Draw Buffers Draw with Each vertex computed
meshes.unindexed(shape) none draw(vertexCount) once per corner, every frame
meshes.bakeIndices(root, shape) index drawIndexed(indexCount) about once, when the GPU reuses it
meshes.bake(root, shape) vertex and index drawIndexed(indexCount) once, when baked

A box vertex needs only a few branches and a matrix multiply, so computing it each frame is cheap. A 32 × 16 sphere has 2 880 corners, each requiring sines and cosines. Bake shapes with more than a few hundred vertices. For many shapes drawn through one pipeline, use a mesh pool.

const
const ball: meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
ball
=
import meshes
meshes
.
function bakeIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(root: TgpuRoot, g: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>, options?: meshes.BakeIndicesOptions): meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
bakeIndices
(
const root: TgpuRoot
root
,
import meshes
meshes
.
function sphere({ radius, segments, rings, }?: meshes.SphereOptions): meshes.IndexedGeometry
sphere
());
const
const vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}>
vertex
=
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
.
vertexFn: <{
vertexIndex: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}>(options: {
in: {
vertexIndex: d.BuiltinVertexIndex;
};
out: {
position: d.BuiltinPosition;
normal: d.Vec3f;
};
}) => TgpuVertexFnShell<{
vertexIndex: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}> (+1 overload)
vertexFn
({
in: {
vertexIndex: d.BuiltinVertexIndex;
}
in
: {
vertexIndex: d.BuiltinVertexIndex
vertexIndex
:
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
.
vertexIndex: d.BuiltinVertexIndex
vertexIndex
},
out: {
position: d.BuiltinPosition;
normal: d.Vec3f;
}
out
: {
position: d.BuiltinPosition
position
:
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
.
position: d.BuiltinPosition
position
,
normal: d.Vec3f
normal
:
import d
d
.
const vec3f: d.Vec3f
vec3f
},
})(({
vertexIndex: number
vertexIndex
}) => {
'use gpu';
const
const point: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
point
=
const ball: meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
ball
.
Geometry<WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.vertexAt(this: void, i: number): {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
vertexAt
(
vertexIndex: number
vertexIndex
); // vertexIndex contains the value read from the index buffer
return {
position: d.v4f
position
:
import cameras
cameras
.
const current: TgpuAccessor<d.WgslStruct<{
viewProjection: d.Mat4x4f;
position: d.Vec3f;
right: d.Vec3f;
up: d.Vec3f;
}>>

Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
viewProjection: d.m4x4f

Transforms world coordinates to clip space.

viewProjection
*
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const point: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
point
.
position: d.v3f
position
, 1),
normal: d.v3f
normal
:
const point: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
point
.
normal: d.v3f
normal
,
};
});
const
const pipeline: TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer
pipeline
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const view: cameras.CameraBase
view
.
CameraBase.inject(): Inject

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

inject
())
.
WithBinding.createRenderPipeline<{}, {}, {
normal: d.Vec3f;
}, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
attribs?: {} | undefined;
vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
fragment: TgpuFragmentFn<{
normal: d.Vec3f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
targets?: TgpuColorTargetState;
}): TgpuRenderPipeline<...> (+2 overloads)
createRenderPipeline
({
vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
vertex
,
fragment: TgpuFragmentFn<{
normal: d.Vec3f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
normal: d.Vec3f;
}>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
readonly $fragDepth: import("node_modules/typegpu/builtin").BuiltinFragDepth;
readonly $sampleMask: import("node_modules/typegpu/builtin").BuiltinSampleMask;
}>>))
fragment
,
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
: 'bgra8unorm' } })
.
TgpuRenderPipeline<Vec4f>.pipe<TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer>(transform: (pipeline: TgpuRenderPipeline<d.Vec4f>) => TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer): TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer

Applies a transform to this pipeline, letting packages hand out reusable configuration steps, e.g. pipeline.pipe(mesh.inject()).

pipe
(
const ball: meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
ball
.
BakedIndices<WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.inject(): <P extends TgpuRenderPipeline>(pipeline: P) => P & HasIndexBuffer
inject
()); // the index buffer belongs to the pipeline
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.setPipeline(pipeline: TgpuRenderPipeline): void

Sets the current

TgpuRenderPipeline

for subsequent draw calls

setPipeline
(
const pipeline: TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer
pipeline
);
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.drawIndexed(indexCount: number, instanceCount?: number, firstIndex?: number, baseVertex?: number, firstInstance?: number): void
drawIndexed
(
const ball: meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
ball
.
IndexedGeometry<V extends d.AnyWgslData = WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.indexCount: number
indexCount
);
  • Apply a baked mesh’s inject() to the pipeline. The vertex layout and index buffer belong to a pipeline, so do not apply it to the root.
  • Pass { encoder } or { pass } to record the fill into your own commands. Use bakeAsync or bakeIndicesAsync to compile fill pipelines under a loading screen.
  • A fully baked mesh exposes its vertices buffer and the layout set by inject() for vertex attributes. Call updateVertices(options) to refill vertices when values read by vertexAt change.
  • destroy() frees buffers allocated by the bake. It leaves caller-supplied buffers intact.
Bake option Default
vertices a new buffer A vertex and storage buffer matching the shape schema, with capacity for at least vertexCount. For bake only.
indices a new buffer A u32 index and storage buffer with capacity for at least indexCount.
encoder none Records the fill into your encoder instead of submitting one of its own.
pass none Records the fill into your compute pass. Not with encoder.
with the root Creates fill pipelines from this configuration, such as root.pipe(fill) when vertexAt reads accessors.
bindGroups none Sets these bind groups on fill pipelines when vertexAt reads a caller-supplied layout.

A set of live lists takes a vertex or index count for each group in draws. A geometry supplies that count, so pass the shape:

const
const Block: table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>
Block
=
import table
table
.
function define<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>(name: "Block", parts: {
host?: {
shape: d.U32;
position: d.Vec3f;
} | undefined;
device?: {} | undefined;
}): table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>

Declares CPU-written host fields and GPU-written device fields using the records accepted by d.struct. Define it at module scope beside shaders. Throws if no fields are declared, a field appears in both parts, the CPU writer cannot write a field type such as f16, or a field uses a reserved member name such as alive.

name brands row ids as RowId<'Enemy'> and labels structs, buffers, WGSL functions and errors. It is not a lookup key.

@param ― name a label for ids and WGSL

@param ― parts the host fields the CPU writes and the device fields the GPU writes

@returns ― a definition whose getters shaders read and that table.create allocates

define
('Block', {
host?: {
shape: d.U32;
position: d.Vec3f;
} | undefined
host
: {
shape: d.U32
shape
:
import d
d
.
const u32: d.U32
export u32

A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

@example const value = u32(7); // 7

@example const value = u32(3.14); // 3

@example const value = u32(-1); // 4294967295

@example const value = u32(-3.1); // 0

u32
,
position: d.Vec3f
position
:
import d
d
.
const vec3f: d.Vec3f
vec3f
} });
const
const blocks: table.Table<table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>>
blocks
=
import table
table
.
function create<table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>>(root: TgpuRoot, def: table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>, options: table.Options<table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>>): table.Table<table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>> (+1 overload)

Allocates GPU buffers and a CPU copy of host rows. Throws unless capacity is an integer in [1, 2^20].

@param ― def the definition its shaders were written against, from table.define

@returns ― a table you destroy

create
(
const root: TgpuRoot
root
,
const Block: table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>
Block
, {
Options<T extends AnyTableDef>.capacity: number

Sets the table capacity for its lifetime. Must be an integer in [1, 2^20], with slots from 0 to capacity - 1. A row id uses enough bits for the slot and the rest for its generation. The generation has 29 bits at 8 rows and 12 bits at 2^20 rows.

capacity
: 4096 });
const
const cube: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
cube
=
import meshes
meshes
.
unindexed<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(g: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>): meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
export unindexed
unindexed
(
import meshes
meshes
.
function box({ width, height, depth, widthSegments, heightSegments, depthSegments, }?: meshes.BoxOptions): meshes.IndexedGeometry
box
());
const
const ball: meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
ball
=
import meshes
meshes
.
function bakeIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(root: TgpuRoot, g: meshes.IndexedGeometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>, options?: meshes.BakeIndicesOptions): meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
bakeIndices
(
const root: TgpuRoot
root
,
import meshes
meshes
.
function sphere({ radius, segments, rings, }?: meshes.SphereOptions): meshes.IndexedGeometry
sphere
({
SphereOptions.segments?: number | undefined
segments
: 12,
SphereOptions.rings?: number | undefined
rings
: 8 }));
const
const rows: Inject
rows
=
const blocks: table.Table<table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>>
blocks
.
Table<TableDef<"Block", { shape: U32; position: Vec3f; }, {}>>.inject(): Inject

Fills the definition's accessors with this table's buffers.

inject
();
const
const live: liveLists.LiveLists
live
=
import liveLists
liveLists
.
function create(root: TgpuRoot, options: liveLists.Options): liveLists.LiveLists

Creates a list of live slots and an indirect draw per group. Each group reserves capacity slots. A shader finds rows with live.slot(group, i) using a captured group index. With firstInstance, it can use live.instanceSlot(instanceIndex) instead. Throws if the groups, draws or workgroup size are invalid, the slot buffer exceeds maxStorageBufferBindingSize, or firstInstance lacks its required feature.

@param ― options.alive whether a slot holds a live row, carrying the fills of what it reads, such as a table's from rows.live(), spread into the options with its capacity

@param ― options.draws one draw per group

@returns ― live lists you run every frame and destroy

create
(
const root: TgpuRoot
root
, {
...
const blocks: table.Table<table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>>
blocks
.
Table<TableDef<"Block", { shape: U32; position: Vec3f; }, {}>>.live(): table.Live

Returns the capacity and live test for live lists, a grid, a picker or state machines. Use liveLists.create(root, { ...coins.live(), draws }).

live
(),
ListOptions.group?: ((i: number) => number) | undefined

Maps a live slot to its group in a shader, such as with a table field's getter. Skips rows whose group is out of range. Default every live row belongs to group 0.

group
:
const Block: table.TableDef<"Block", {
shape: d.U32;
position: d.Vec3f;
}, {}>
Block
.
shape: table.Getter<d.U32>
shape
.
Getter<U32>.pipe(transform: (cfg: Configurable) => Configurable): table.Getter<d.U32>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const rows: Inject
rows
),
ListOptions.draws?: readonly liveLists.IndirectDraw[] | undefined

Provides one draw per group. Requires 1 to 256 draws. Default an empty draw for each of groups, for lists that only dispatch.

draws
: [
const cube: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
cube
,
const ball: meshes.BakedIndices<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
ball
], // a block of shape 0 is a cube, of shape 1 a ball
});

An unindexed shape uses drawIndirect. A baked indexed mesh uses drawIndexedIndirect and needs its inject() applied to the pipeline.

A sprite is a square facing the camera. The shapes have no square in the xy plane. Read a 2 × 2 plane through position.xz, from -1 to 1, to place its 6 vertices facing the camera:

const
const quad: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
quad
=
import meshes
meshes
.
unindexed<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(g: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>): meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
export unindexed
unindexed
(
import meshes
meshes
.
function plane({ width, depth, widthSegments, depthSegments, }?: meshes.PlaneOptions): meshes.IndexedGeometry
plane
({
PlaneOptions.width?: number | undefined
width
: 2,
PlaneOptions.depth?: number | undefined
depth
: 2 }));
/** Corner `i` of a square `size` wide around `center`, facing the screen. */
const
const sprite: (i: number, center: d.v3f, size: number) => {
position: d.v4f;
uv: d.v2f;
}

Corner i of a square size wide around center, facing the screen.

sprite
= (
i: number
i
: number,
center: d.v3f
center
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
,
size: number
size
: number) => {
'use gpu';
const
const corner: d.v2f
corner
=
const quad: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
quad
.
Geometry<WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.vertexAt(this: void, i: number): {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
vertexAt
(
i: number
i
).
position: d.v3f
position
.
xz: d.v2f
xz
;
const
const world: d.v3f
world
=
center: d.v3f
center
+ (
import cameras
cameras
.
const current: TgpuAccessor<d.WgslStruct<{
viewProjection: d.Mat4x4f;
position: d.Vec3f;
right: d.Vec3f;
up: d.Vec3f;
}>>

Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
right: d.v3f

Gives the unit x and y axes of the view in world space, for quads that face the screen. With the viewing direction from forwardOf, they form a right-handed basis.

right
*
const corner: d.v2f
corner
.
v2f.x: number
x
+
import cameras
cameras
.
const current: TgpuAccessor<d.WgslStruct<{
viewProjection: d.Mat4x4f;
position: d.Vec3f;
right: d.Vec3f;
up: d.Vec3f;
}>>

Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
up: d.v3f
up
*
const corner: d.v2f
corner
.
v2f.y: number
y
) * (
size: number
size
/ 2);
return {
position: d.v4f
position
:
import cameras
cameras
.
const current: TgpuAccessor<d.WgslStruct<{
viewProjection: d.Mat4x4f;
position: d.Vec3f;
right: d.Vec3f;
up: d.Vec3f;
}>>

Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
viewProjection: d.m4x4f

Transforms world coordinates to clip space.

viewProjection
*
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const world: d.v3f
world
, 1),
uv: d.v2f
uv
:
const corner: d.v2f
corner
* 0.5 + 0.5 };
};

This use of the plane mirrors it, so its triangles wind clockwise on screen. Use the default cullMode: 'none' for sprites.

  • Passing both encoder and pass to a bake throws Supply either an encoder or a compute pass, not both.
  • A bake throws if a supplied buffer is too small. The message names the vertex or index count, such as The vertex count … exceeds the buffer capacity ….
  • concat throws if schemas or topologies differ. attach throws if a field already exists on the vertex.