@typegpu/kit/geometrymeshes
Meshes
Boxes, planes, spheres and other shapes, computed in the vertex shader or baked into buffers.
kit.meshes
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
- 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
vertexAtorindexAt; drawing throughinject()uses vertex and index buffers without storage bindings.
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");}
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.
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);Shapes
Section titled “Shapes”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 })orsphere({ segments: 12, rings: 8 }). The icosphere has evenly spaced triangles without crowded poles. Itsinterpolatefunction places points within each triangle. - Triangles wind counter-clockwise when viewed from outside, matching WebGPU’s default
frontFace. UsecullMode: '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.m4x4fexport translation4
Creates a 4-by-4 matrix which translates by the given vector v.
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.m4x4fexport translation4
Creates a 4-by-4 matrix which translates by the given vector v.
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 ad.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 toschema.meshes.attach(shape, struct, fn)adds the fields returned byfn. Both preserve whether the shape is indexed.
Three ways to draw one
Section titled “Three ways to draw one”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");}
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.
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. UsebakeAsyncorbakeIndicesAsyncto compile fill pipelines under a loading screen. - A fully baked mesh exposes its
verticesbuffer and thelayoutset byinject()for vertex attributes. CallupdateVertices(options)to refill vertices when values read byvertexAtchange. 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. |
Many of them
Section titled “Many of them”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.
define('Block', { host?: { shape: d.U32; position: d.Vec3f;} | undefined
host: { shape: d.U32
shape: import d
d.const u32: d.U32export 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.
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].
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.
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.
Sprites
Section titled “Sprites”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.
Errors
Section titled “Errors”- Passing both
encoderandpassto a bake throwsSupply 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 …. concatthrows if schemas or topologies differ.attachthrows if a field already exists on the vertex.
© Software Mansion 2026. All trademarks and copyrights belong to their respective owners. Read about our Privacy Policy.