Skip to content

@typegpu/kitmeshPool

Mesh pool

Shapes and glTF models in shared buffers, so one pipeline draws any of them.

kit.meshPool

module

meshPool.create(root, { capacity, … }) → MeshPool

meshPool.roomFor(items) → Sized

rootborrows
the root
MeshPoolreturns
a pool you add meshes and models to and destroy
itemstakes
what the pool will hold
Sizedreturns
the capacity, vertices and indices they take, and uvs1: true when a mesh needs it
GPU functions
positionAtvertexAtuv1AtinfluencesAtboundsOf
schemas
ReadingTextureUvsBoxInfluences
helpers
textureLayout
types
SamplerDataTextureDataTexturesDataTextureTransformDataShapeFactorsMeshModelMaterialDataExtraTypeExtrasExtraDataExtraReadersMeshDataModelDataMeshPoolRoomSized
Optionscreate · 7 fields
capacity

number

Limits the number of meshes to at most 256.

extra?

E= none

Declares custom vertex attributes, for example { tint: d.vec4f }.

vertices?

number= 65 536

Limits the total vertex count across all meshes.

indices?

number= 262 144

Limits the total index count across all meshes.

uvs1?

boolean= false

Keeps each mesh's second uv set, uvs1, for textures with uvSet: 1, in 8 more bytes per vertex.

decode?

Decode= createImageBitmap

Decodes encoded images when createImageBitmap is unavailable.

anisotropy?

number= 1

Sets the texture sample count at grazing angles, up to 16.

submits

while setting up

A shape added without an encoder bakes in a compute pass of its own, submitted at once, and a model's images generate their mips as they decode.

owns

5 to 7 buffers ·a uniform per way of reading textures ·a pipeline per shape ·textures ·samplers ·bind groups ·what `images` makes

Positions, normals and uvs, indices and boxes, second uvs with uvs1, joints and weights once a skinned model comes, and the extras when extra names any; each image decoded once per format it is read in, and a group per set of a mesh's textures.

lifecycle

destroy()[Symbol.dispose]

injects
  • definition

    The vertices vertexAt, positionAt and uv1At read, the joints and weights influencesAt reads, and the boxes boundsOf reads.

  • per-instance

    The extras pool.extra reads.

binds
  • 1 storage

    positionAt, the positions.

  • 2 storage

    vertexAt, the positions and the normals and uvs.

  • 1 storage

    uv1At, the second uvs, in a pool made with uvs1.

  • 1 storage

    influencesAt, the joints and weights.

  • 1 storage

    boundsOf, every mesh's box.

  • 1 storage

    pool.extra, every vertex's extras.

  • 1 uniform

    pool.texturesOf(index) for materials, beside its textures and sampler.

Add shapes until add throws. The pool has room for 2048 vertices and 8192 indices.

Separate mesh buffers require rebinding between draws. A mesh pool stores vertices in shared storage buffers and indices in one index buffer. One pipeline can then draw any mesh by its range. Instances use a pool, and custom shaders can too.

import {
import gltf
gltf
} from '@typegpu/kit';
import {
import meshPool
meshPool
} from '@typegpu/kit';
import {
import meshes
meshes
} from '@typegpu/kit/geometry';
const
const pool: meshPool.MeshPool<{}>
pool
=
import meshPool
meshPool
.
function create<{}>(root: TgpuRoot, options: meshPool.Options<{}>): meshPool.MeshPool<{}>

Creates shared vertex storage and an index buffer, with bounds and textures per mesh. One pipeline can draw any mesh. Each vertex uses 12 bytes for position and 8 for normal and UV coordinates, and 8 for a second uv set with uvs1. Skinned models add 8 bytes for joints and weights. Extras add the size of their struct per vertex.

@injects ― definition the vertices vertexAt, positionAt and uv1At read, the joints and weights influencesAt reads, and the boxes boundsOf reads

@injects ― per-instance the extras pool.extra reads

@binds ― 1 storage: positionAt, the positions

@binds ― 2 storage: vertexAt, the positions and the normals and uvs

@binds ― 1 storage: uv1At, the second uvs, in a pool made with uvs1

@binds ― 1 storage: influencesAt, the joints and weights

@binds ― 1 storage: boundsOf, every mesh's box

@binds ― 1 storage: pool.extra, every vertex's extras

@binds ― 1 uniform: pool.texturesOf(index) for materials, beside its textures and sampler

@submits ― setup a shape added without an encoder bakes in a compute pass of its own, submitted at once, and a model's images generate their mips as they decode

@owns ― 5 to 7 buffers, a uniform per way of reading textures, a pipeline per shape, textures, samplers, bind groups, what images makes: positions, normals and uvs, indices and boxes, second uvs with uvs1, joints and weights once a skinned model comes, and the extras when extra names any; each image decoded once per format it is read in, and a group per set of a mesh's textures

@returns ― a pool you add meshes and models to and destroy

create
(
const root: TgpuRoot
root
, {
Options<E extends Extras = {}>.capacity: number

Limits the number of meshes to at most 256.

capacity
: 16 });
// Bake shapes into the same encoder
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 box: meshPool.Mesh
box
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(shape: meshPool.Shape, encoder?: TgpuCommandEncoder): meshPool.Mesh (+1 overload)

Bakes a shape into the next free ranges in a compute pass. With encoder, it records there; otherwise, it submits at once. Each shape needs its own pipeline, so add shapes while loading. Throws when the pool is full.

add
(
import meshes
meshes
.
function roundedBox({ segments, ...options }?: meshes.patches.RoundedBoxOptions & {
segments?: number;
}): meshes.IndexedGeometry<WgslStruct<{
position: Vec3f;
normal: Vec3f;
uv: Vec2f;
}>>
roundedBox
(),
const encoder: TgpuCommandEncoder
encoder
);
const
const ball: meshPool.Mesh
ball
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(shape: meshPool.Shape, encoder?: TgpuCommandEncoder): meshPool.Mesh (+1 overload)

Bakes a shape into the next free ranges in a compute pass. With encoder, it records there; otherwise, it submits at once. Each shape needs its own pipeline, so add shapes while loading. Throws when the pool is full.

add
(
import meshes
meshes
.
function sphere({ radius, segments, rings, }?: meshes.SphereOptions): meshes.IndexedGeometry
sphere
(),
const encoder: TgpuCommandEncoder
encoder
);
const encoder: TgpuCommandEncoder
encoder
.
TgpuCommandEncoder.submit(): void

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

submit
();
const
const response: Response
response
= await
function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
fetch
('/models/tavern.glb');
const
const tavern: meshPool.Model
tavern
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<never, false> | undefined): gltf.ModelData<never> (+1 overload)

Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies default-scene node transforms and groups meshes by material. Morph targets are omitted.

Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin as meshes do; other parts have no skin bindings.

It checks every index, count and range before reading. Invalid glTF, unsupported file versions and unsupported required features throw with details. Use load for a model whose buffers or images are separate files. For extensions that need conversion, errors name the gltf-transform command.

@param ― source the file's bytes, from anywhere

@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin

@param ― options.rotation a turn of the whole model, for one that doesn't face +z

@returns ― plain arrays and images, for meshPool.add

read
(await
const response: Response
response
.
Body.arrayBuffer(): Promise<ArrayBuffer>
arrayBuffer
()));
await
const tavern: meshPool.Model
tavern
.
Model.ready: Promise<void>

Resolves when all textures have decoded and uploaded, at once for a model whose materials name none. Until then, meshes draw without textures. Rejects if image decoding fails or the pool is destroyed before upload. A destroyed pool uploads nothing.

ready
; // wait for image decoding and uploads

add returns a Mesh or a Model with one mesh per material. A mesh has its pool index and ranges firstIndex, indexCount, baseVertex and vertexCount. Meshes cannot be removed.

  • pool.add(shape) bakes triangle-list shapes from @typegpu/kit/geometry in a compute pass. Supply an encoder to record into it, or omit it to submit immediately. Each shape needs its own bake pipeline, so add shapes while loading.
  • Unindexed shapes get one index per vertex. Baking also computes their bounding boxes.
  • Shapes default to white, roughness 1, metallic 0, single-sided, unmasked and untextured.
  • pool.add(model) copies a gltf.read model into the pool. It validates every mesh before changing the pool. Empty models, insufficient capacity or mismatched normals, UVs, indices, joints or weights throw. Size vertex and index capacity for the sum across models: meshPool.roomFor(items) adds up the meshes, vertices and indices of models such as impostors.CARD, meshes, shapes and counts, to spread into the options, and keeps a second uv set when a mesh carries uvs1.
  • Mesh material stores file factors, with colour alpha in opacity. Textures multiply these factors. Instance fields replace them.
  • An alpha: 'mask' mesh cuts out pixels below alphaCutoff in colour and shadow passes. Alpha is texture alpha times instance opacity. doubleSided draws back faces lit from their side. normalScale controls normal-map strength and occlusionStrength how far the occlusion texture darkens. A mesh whose material names a texture or masks alpha is textured; renderers read textures only for those.
  • Preserve glTF vertex colours from COLOR_0 by declaring an extra named color. A pool without it drops those colours and warns once, unless every colour is white.
  • Skinned meshes retain joints and weights for poses. The pool creates their buffer when the first skinned model is added.

Declare custom vertex fields with extra, using the same types as tables. Models supply arrays in extra, and shaders read them through pool.extra:

import {
import gltf
gltf
} from '@typegpu/kit';
import {
import instances
instances
,
import meshPool
meshPool
,
import shading
shading
} from '@typegpu/kit';
const
const pool: meshPool.MeshPool<{
color: d.Vec4f;
}>
pool
=
import meshPool
meshPool
.
function create<{
color: d.Vec4f;
}>(root: TgpuRoot, options: meshPool.Options<{
color: d.Vec4f;
}>): meshPool.MeshPool<{
color: d.Vec4f;
}>

Creates shared vertex storage and an index buffer, with bounds and textures per mesh. One pipeline can draw any mesh. Each vertex uses 12 bytes for position and 8 for normal and UV coordinates, and 8 for a second uv set with uvs1. Skinned models add 8 bytes for joints and weights. Extras add the size of their struct per vertex.

@injects ― definition the vertices vertexAt, positionAt and uv1At read, the joints and weights influencesAt reads, and the boxes boundsOf reads

@injects ― per-instance the extras pool.extra reads

@binds ― 1 storage: positionAt, the positions

@binds ― 2 storage: vertexAt, the positions and the normals and uvs

@binds ― 1 storage: uv1At, the second uvs, in a pool made with uvs1

@binds ― 1 storage: influencesAt, the joints and weights

@binds ― 1 storage: boundsOf, every mesh's box

@binds ― 1 storage: pool.extra, every vertex's extras

@binds ― 1 uniform: pool.texturesOf(index) for materials, beside its textures and sampler

@submits ― setup a shape added without an encoder bakes in a compute pass of its own, submitted at once, and a model's images generate their mips as they decode

@owns ― 5 to 7 buffers, a uniform per way of reading textures, a pipeline per shape, textures, samplers, bind groups, what images makes: positions, normals and uvs, indices and boxes, second uvs with uvs1, joints and weights once a skinned model comes, and the extras when extra names any; each image decoded once per format it is read in, and a group per set of a mesh's textures

@returns ― a pool you add meshes and models to and destroy

create
(
const root: TgpuRoot
root
, {
Options<E extends Extras = {}>.capacity: number

Limits the number of meshes to at most 256.

capacity
: 16,
Options<{ color: Vec4f; }>.extra?: {
color: d.Vec4f;
} | undefined

Declares custom vertex attributes, for example { tint: d.vec4f }. Model meshes supply their data in extra; shaders read it through pool.extra.tint(i). These attributes use a separate buffer with one struct per vertex. Shapes read as zero. Default none.

extra
: {
color: d.Vec4f
color
:
import d
d
.
const vec4f: d.Vec4f
vec4f
} });
const
const painted: meshPool.Model
painted
=
const pool: meshPool.MeshPool<{
color: d.Vec4f;
}>
pool
.
MeshPool<{ color: Vec4f; }>.add(model: meshPool.ModelData<{
color: d.Vec4f;
}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<never, false> | undefined): gltf.ModelData<never> (+1 overload)

Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies default-scene node transforms and groups meshes by material. Morph targets are omitted.

Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin as meshes do; other parts have no skin bindings.

It checks every index, count and range before reading. Invalid glTF, unsupported file versions and unsupported required features throw with details. Use load for a model whose buffers or images are separate files. For extensions that need conversion, errors name the gltf-transform command.

@param ― source the file's bytes, from anywhere

@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin

@param ― options.rotation a turn of the whole model, for one that doesn't face +z

@returns ― plain arrays and images, for meshPool.add

read
(
const bytes: ArrayBuffer
bytes
)); // its COLOR_0, in extra.color
const
const Painted: instances.Kind<"Painted", {}, {}, "host">
Painted
=
import instances
instances
.
function define<"Painted", {}, {}, "host">(name: "Painted", options?: instances.KindOptions<{}, {}, "host">): instances.Kind<"Painted", {}, {}, "host">

Defines an instance kind with custom fields, transform ownership and callbacks for vertex motion or materials. Each kind uses colour and shadow pipelines per renderer. name brands set ids, as in Id<'Fish'>, and labels the table and WGSL. Throws if both material and surface are supplied, or a custom field duplicates a built-in or reserved name.

@param ― name a label for ids and WGSL

@returns ― a kind to give instances.create as kind

define
('Painted', {
KindOptions<{}, {}, "host">.carry?: ((vertexIndex: number, instance: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
mesh: number;
opacity: number;
}) => d.v4f) | undefined

Returns four values to interpolate between vertices and pass to material or surface as carried. For example, read an extra attribute with pool.extra.tint(vertexIndex). vertexIndex indexes the pool, as in meshPool.vertexAt. Default none, which omits four floats per vertex and sets carried to zero.

carry
: (
vertexIndex: number
vertexIndex
) => {
'use gpu';
return
const pool: meshPool.MeshPool<{
color: d.Vec4f;
}>
pool
.
MeshPool<{ color: Vec4f; }>.extra: meshPool.ExtraReaders<{
color: d.Vec4f;
}>

Reads the attributes named in extra for pool vertex i. Create calling shaders from root.pipe(pool.inject()).

extra
.
color: (i: number) => d.v4f
color
(
vertexIndex: number
vertexIndex
);
},
KindOptions<{}, {}, "host">.material?: ((point: shading.Point, instance: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
mesh: number;
opacity: number;
}, base: shading.Material, carried: d.v4f) => shading.Material) | undefined

Returns the material at a point. base combines the instance material fields with the mesh textures, as materials.textured does. carried contains interpolated values from carry. Default base.

material
: (
_point: {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}
_point
,
_instance: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
mesh: number;
opacity: number;
}
_instance
,
base: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
base
,
carried: d.v4f
carried
) => {
'use gpu';
const
const tinted: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
tinted
=
import shading
shading
.
const Material: d.WgslStruct
(props?: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
} | undefined) => {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}

This schema describes a surface with the glTF metallic-roughness model.

Material
(
base: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
base
);
const tinted: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
tinted
.
color: d.v3f

Sets the diffuse colour of a dielectric or the reflection colour of a metal, in linear colour space.

color
=
base: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
base
.
color: d.v3f

Sets the diffuse colour of a dielectric or the reflection colour of a metal, in linear colour space.

color
*
carried: d.v4f
carried
.
rgb: d.v3f
rgb
;
return
const tinted: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
tinted
;
},
});
const
const things: instances.Instances<instances.Kind<"Painted", {}, {}, "host">>
things
=
import instances
instances
.
function create<instances.Kind<"Painted", {}, {}, "host">>(root: TgpuRoot, pool: meshPool.MeshPool, options: instances.Options<instances.Kind<"Painted", {}, {}, "host">>): instances.Instances<instances.Kind<"Painted", {}, {}, "host">> (+1 overload)

Creates a set of mesh instances with transforms, materials and optional custom fields. Each instance uses a row, and renderers draw one list of live rows per mesh.

@param ― pool the mesh pool whose meshes it draws

@param ― options.kind a kind from instances.define

@returns ― a set of instances you flush every frame and destroy

create
(
const root: TgpuRoot
root
,
const pool: meshPool.MeshPool<{
color: d.Vec4f;
}>
pool
, {
Options<Kind<"Painted", {}, {}, "host">>.kind?: instances.Kind<"Painted", {}, {}, "host"> | undefined

Selects a kind from instances.define. Default a plain kind with a transform and material.

kind
:
const Painted: instances.Kind<"Painted", {}, {}, "host">
Painted
,
Options<K extends AnyKind = Plain>.capacity: number

Limits the number of rows. A mesh instance uses one row; a model instance uses one row per mesh.

capacity
: 64 });
const things: instances.Instances<instances.Kind<"Painted", {}, {}, "host">>
things
.
Instances<Kind<"Painted", {}, {}, "host">>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: {
readonly position?: FieldInput<d.Vec3f>;
readonly rotation?: FieldInput<d.Vec4f>;
readonly scale?: number | FieldInput<d.Vec3f>;
readonly color?: FieldInput<d.Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<d.Vec3f>;
readonly opacity?: number;
} | ((mesh: meshPool.Mesh, index: number) => {
readonly position?: FieldInput<d.Vec3f>;
readonly rotation?: FieldInput<d.Vec4f>;
readonly scale?: number | FieldInput<d.Vec3f>;
readonly color?: FieldInput<d.Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<d.Vec3f>;
readonly opacity?: number;
}) | undefined): IdOf<...>

Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row, and all rows move and change together under one id. values may be a function of each mesh, to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the group is empty, a posed kind receives a mesh without joints or the set is full.

add
(
const painted: meshPool.Model
painted
);
  • Fields accept 32-bit scalars or vectors, including d.f32, d.u32, d.i32 and d.vec2f through d.vec4i. Supply components in vertex order as Float32Array, Uint32Array or Int32Array matching the field type.
  • Shape extras and omitted fields default to zero. Undeclared extras are dropped with one warning per field, naming the declaration needed to keep it.
  • In a shader created from root.pipe(pool.inject()), pool.extra.color(i) reads the vertex indexed as vertexAt does. Use carry to pass it to fragments.
  • Meshes have colour, metallic-roughness, normal, emission and occlusion textures, all with mipmaps. Colour and emission use sRGB; the others use linear values. Normal maps need no tangents.
  • A texture’s transform scales, rotates and then offsets the coordinates it reads, as glTF’s KHR_texture_transform does, so a mesh can tile one texture and read another from an atlas. Each texture of a mesh keeps its own. The cloister tiles its paving, turns its border and cuts its panels from one atlas this way.
  • A texture with uvSet: 1 reads the mesh’s second uv set, uvs1, as a lightmap or occlusion map baked on its own unwrap does. The pool keeps uvs1 only when created with uvs1: true, which roomFor sets for meshes that carry it, and add throws for a mesh whose texture reads a set it can’t keep. The cloister bakes its occlusion on a second set, and the classroom its daylight and its lamps’ bounce. A mesh’s textures share the colour texture’s sampler, so give a lightmap’s charts margins when the colour texture repeats.
  • The pool decodes each image once per format and shares identical bytes, or the same ImageBitmap, across meshes and models. If one image is used for both colour and normals, it decodes twice.
  • Meshes draw without textures until ready settles. A model whose materials name no texture is ready at once. Decode failures or destroying the pool before completion reject ready. Failed images are not cached, so later models can retry them.

Images decode with createImageBitmap, which some runtimes lack:

  • On React Native, react-native-webgpu installs it on the JS thread only. It ignores colorSpaceConversion: 'none', so an image with a colour profile may decode differently than on the web.
  • Worklets, including hosted scenes, lack createImageBitmap. Decode on the JS thread with images.decodeBitmap, then pass the model with those bitmaps in the scene’s options. A bitmap is a native object, so it reaches the worker as the device does.
  • Alternatively, supply decode returning { width, height, pixels }, with four unpremultiplied bytes per pixel and rows from the top. A Skia decoder can provide this. Encoded images without a decoder cause add to throw.
// On the JS thread, before the scene starts
const model = gltf.read(bytes);
const decoded = { ...model, images: await Promise.all(model.images.map(images.decodeBitmap)) };
  • Node and dawn.node also need a decoder. Pass it through scene options as in Testing.

Build shaders from root.pipe(pool.inject()) to call meshPool.vertexAt(i). It decodes a pool vertex into the @typegpu/kit/geometry vertex type. Bind pool.indices and draw a mesh range:

const
const ring: meshPool.Mesh
ring
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(shape: meshPool.Shape, encoder?: TgpuCommandEncoder): meshPool.Mesh (+1 overload)

Bakes a shape into the next free ranges in a compute pass. With encoder, it records there; otherwise, it submits at once. Each shape needs its own pipeline, so add shapes while loading. Throws when the pool is full.

add
(
import meshes
meshes
.
function torus({ radius, tube, ringSegments, tubeSegments, }?: meshes.TorusOptions): meshes.IndexedGeometry
torus
());
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: <{
i: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}>(options: {
in: {
i: d.BuiltinVertexIndex;
};
out: {
position: d.BuiltinPosition;
normal: d.Vec3f;
};
}) => TgpuVertexFnShell<{
i: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}> (+1 overload)
vertexFn
({
in: {
i: d.BuiltinVertexIndex;
}
in
: {
i: d.BuiltinVertexIndex
i
:
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
},
})(({
i: number
i
}) => {
'use gpu';
const
const corner: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
corner
=
import meshPool
meshPool
.
const vertexAt: (i: number) => {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}

Decodes pool vertex i into an @typegpu/kit/geometry vertex. In a mesh draw, the vertex index already includes baseVertex. Create calling shaders from root.pipe(pool.inject()).

vertexAt
(
i: number
i
); // i already counts from the mesh's baseVertex
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 pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.inject(): Inject

Fills mesh resources for pipelines created from the configuration you pipe it into.

inject
())
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const view: cameras.OrbitCamera
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
: 'rgba16float' } });
const
const drawing: TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer
drawing
=
const pipeline: TgpuRenderPipeline<d.Vec4f>
pipeline
.
TgpuRenderPipeline<Vec4f>.withIndexBuffer(buffer: TgpuBuffer<d.BaseData> & IndexFlag, offsetElements?: number, sizeElements?: number): TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer (+1 overload)
withIndexBuffer
(
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.indices: TgpuBuffer<d.WgslArray<d.U32>> & IndexFlag & StorageFlag

Holds all mesh indices, relative to each mesh baseVertex. Throws after destroy.

indices
); // at setup
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.setPipeline(pipeline: TgpuRenderPipeline): void

Sets the current

TgpuRenderPipeline

for subsequent draw calls

setPipeline
(
const drawing: TgpuRenderPipeline<d.Vec4f> & HasIndexBuffer
drawing
);
const pass: TgpuRenderPass
pass
.
TgpuRenderCommands.drawIndexed(indexCount: number, instanceCount?: number, firstIndex?: number, baseVertex?: number, firstInstance?: number): void
drawIndexed
(
const ring: meshPool.Mesh
ring
.
Mesh.indexCount: number
indexCount
, 1,
const ring: meshPool.Mesh
ring
.
Mesh.firstIndex: number
firstIndex
,
const ring: meshPool.Mesh
ring
.
Mesh.baseVertex: number
baseVertex
);
  • Depth passes can read only meshPool.positionAt(i). influencesAt(i) reads joints and weights. boundsOf(mesh.index) returns the mesh box in local space.
  • Bind pool.texturesOf(mesh.index) before drawing a textured mesh. In the fragment shader, use materials.textured and materials.mappedNormal(normal, world, uv, uv1), with uv1 from meshPool.uv1At(i), which reads zero in a pool without uvs1. For masked meshes, discard where materials.masked(uv, uv1, opacity) requests it. Use cullMode: 'none' for double-sided meshes. meshPool.textureLayout exposes the bind-group layout for custom sampling, and materials.uvOf(meshPool.textureLayout.$.reading.uvs.color, uv, uv1) gives the coordinates the colour texture reads, through uv1 only in a pipeline piped with the inject() of a pool made with uvs1.
What
a pool 20 bytes per vertex and 4 per index of capacity. Defaults use about 2.4 MB.
skinned 8 additional bytes per vertex of capacity after the first skinned model. Defaults add 512 KB.
uvs1 8 additional bytes per vertex of capacity, as full floats. Defaults add 512 KB.
extras Struct size per vertex of capacity. A d.vec4f adds 1 MB at default capacity.
an image 4 bytes per pixel plus one third for mipmaps, per format. A 1024 × 1024 image uses 5.6 MB.
Message Thrown by
The mesh pool is full (64 meshes). add, past the pool’s capacity
The mesh pool has room for 120 vertices and 400 indices; … add, for a shape or a model that doesn’t fit
Mesh 2 of the model has … add, for a model whose arrays don’t agree, naming the count that is off
Mesh 2 of the model supplies extra 'tint' as Int32Array. … add, for an extra in another array than its type reads
Mesh 2 of the model reads uv set 1 in its emission texture… add, for a texture reading uvs1 that the mesh lacks or the pool doesn’t keep
This platform has no createImageBitmap to decode images … add, for a model with encoded images on a runtime without it, given no decode
The mesh pool was destroyed before its images were … ready, rejected
The mesh pool was destroyed. add, inject, texturesOf and indices, after destroy
The mesh pool has no skinned model. … a pipeline that reads joints, such as a posed kind’s renderer, compiled before the pool held a skinned model
Nothing filled the mesh pool's vertices: … a pipeline that calls vertexAt without root.pipe(pool.inject())
  • The pool’s size is fixed at creation.
  • Indices use u32 because WGSL has no 16-bit storage.
  • UVs use half floats. Their precision is 1/2048 below 1 and 1/16 between 64 and 128, so large tiled UV values can drift.
  • All four textures use one sampler, taken from the first texture. Colour is first when present.
  • Normal maps use per-pixel screen-space derivatives without tangents. Maps baked with MikkTSpace tangents can differ near UV seams or stretched coordinates.