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.
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
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.
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.
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.
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
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
constpool:meshPool.MeshPool<{
color:d.Vec4f;
}>
pool=
import meshPool
meshPool.
functioncreate<{
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(
constroot: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.
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.
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
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
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.
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
consttinted:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
}
tinted=
import shading
shading.
constMaterial: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);
consttinted:{
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.
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
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(
constpainted: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.
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:
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.
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.
constcurrent: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.
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.
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.