the cell side length. A walk finds every item within this distance
alive?takes
tests whether a slot holds an item. It, position and itemOf need fills for the resources they read. Spread a table's rows.live() into the options to include its live test, capacity and span
Gridreturns
a grid you sort each step and destroy
keytakes
returns each slot's key
Binningreturns
a binning the grid owns and destroys
schemas
WalkRange
constants
WORKGROUPBLOCKRANGES
types
CandidatesBinningGrid
Optionscreate · 11 fields
capacity
number
Sets the number of slots, including empty slots.
low
d.v3f
Sets the initial lower corner of the grid box.
size
d.v3f
Sets the box extent along x, y and z for the lifetime of the grid.
sight
number
Sets the cell side length.
item
T
Defines the data a walk reads for each item.
position
(i: number) => d.v3f
A 'use gpu' function that returns the position of item i.
itemOf
(i: number) => d.Infer<T>
A 'use gpu' function that returns what a walk reads of item i, as a value of item.
Orders the items by cell each step: counting or radix.
margin?
number= 0
Widens dispatchBlocks to every block within this many blocks of one holding items, for work that reaches past the items, such as a surface around them.
alive?
(i: number) => boolean= every slot
A 'use gpu' function that tests whether slot i holds an item.
span?
() => number= capacity
Returns the highest live slot plus one, read when sort records, such as a table's from live().
BinningOptionsbinning in create, counting, radix · 4 fields
capacity
number
Sets the number of slots to sort, including empty slots.
keys
number
Sets the number of keys, a whole number of lines.
line
number
Sets the keys in a line, one per cell along x.
key
(i: number) => number
A 'use gpu' function that returns the key of slot i, below keys, or keys for an empty slot.
records
sort→compute
dispatch→compute
dispatchBlocks→compute
sort in a compute pass each step, before the kernels that walk.
owns
buffers ·a uniform ·pipelines ·bind groups ·a sorter with `radix`
Each cell's sorted range, the blocks holding items, each item's copy in sorted order, the dispatches, the box, the binning's resources.
lifecycle
✓initAsync()✓destroy()✓[Symbol.dispose]
injects
per-instance
walk, around, range, neighbour, slot, slotAt, neighbourAt, count, holds and block, for shaders piped with it.
binds
1 uniform
walk or around, the box.
2 storage
range and neighbour or neighbourAt, each cell's sorted range and each item's copy.
1 storage
slot or slotAt, the sorted slots.
1 storage
count, the sorted count.
3 storage
holds and block, which blocks hold items, the sort count and their list.
Tap an item or choose Turn again to change its walk through a box one cell deep. The grid visits sorted ranges from the starting cell, keeps neighbours within sight up tostop after, and numbers them in visit order across the three nonempty ranges of its 3×3 cells.
Boids, crowds and collisions need each item’s neighbours. Testing every pair costs the square of the item count. The fish tank’s 3 340 fish would need 11 million distance checks per step. A neighbour grid sorts the items by cell once a step, and a kernel reads only the cells around each item.
import*as
import grid
gridfrom'@typegpu/kit/grid';
import{
constrandf:{
seed:TgpuFn<(seed:d.F32)=>d.Void>;
seed2:TgpuFn<(seed:d.Vec2f)=>d.Void>;
seed3:TgpuFn<(seed:d.Vec3f)=>d.Void>;
seed4:TgpuFn<(seed:d.Vec4f)=>d.Void>;
sample:TgpuFn<()=>d.F32>;
sampleExclusive:TgpuFn<()=>d.F32>;
normal:TgpuFn<(mu:d.F32,sigma:d.F32)=>d.F32>;
...10more...;
onUnitSphere:TgpuFn<...>;
}
randf}from'@typegpu/noise';
const
constSIGHT:1.5
SIGHT=1.5;
/** What a neighbour sees of a boid. */
const
constSeen:d.WgslStruct<{
position:d.Vec3f;
velocity:d.Vec3f;
}>
What a neighbour sees of a boid.
Seen=
import d
d.
struct<{
position:d.Vec3f;
velocity:d.Vec3f;
}>(props:{
position:d.Vec3f;
velocity:d.Vec3f;
}):d.WgslStruct<{
position:d.Vec3f;
velocity:d.Vec3f;
}>
export struct
Creates a struct schema that can be used to construct GPU buffers.
Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema).
The order of members matches the passed in properties object.
Creates a grid that sorts items by cell so kernels can find nearby items. Walks visit the home
cell first and choose directions at random, for boids, crowds or collisions. Throws unless
sight is above 0.
@injects ― per-instance walk, around, range, neighbour, slot, slotAt, neighbourAt,
count, holds and block, for shaders piped with it
@binds ― 1 uniform: walk or around, the box
@binds ― 2 storage: range and neighbour or neighbourAt, each cell's sorted range and each item's copy
@binds ― 1 storage: slot or slotAt, the sorted slots
@binds ― 1 storage: count, the sorted count
@binds ― 3 storage: holds and block, which blocks hold items, the sort count and their list
@owns ― buffers, a uniform, pipelines, bind groups, a sorter with radix: each cell's sorted
range, the blocks holding items, each item's copy in sorted order, the dispatches, the box, the
binning's resources
@records ― sort in a compute pass each step, before the kernels that walk
@param ― options.sight the cell side length. A walk finds every item within this distance
@param ― options.alive tests whether a slot holds an item. It, position and itemOf need fills for
the resources they read. Spread a table's rows.live() into the options to include its
live test, capacity and span
Tests whether slot i holds a row. Returns false beyond the span.
alive(
consti:number
i))return;
const
constposition:d.v3f
position=
constBoid:table.TableDef<"Boid",{},{
position:d.Vec3f;
velocity:d.Vec3f;
}>
Boid.
position:table.Getter
(i:number)=>d.v3f
position(
consti:number
i);
constrandf:{
seed:TgpuFn<(seed:d.F32)=>d.Void>;
seed2:TgpuFn<(seed:d.Vec2f)=>d.Void>;
seed3:TgpuFn<(seed:d.Vec3f)=>d.Void>;
seed4:TgpuFn<(seed:d.Vec4f)=>d.Void>;
sample:TgpuFn<()=>d.F32>;
sampleExclusive:TgpuFn<()=>d.F32>;
normal:TgpuFn<(mu:d.F32,sigma:d.F32)=>d.F32>;
...10more...;
onUnitSphere:TgpuFn<...>;
}
randf.
seed4:(seed:d.v4f)=>void
Threads do not share the generator's State.
As a result, unless you change the seed in each thread,
each thread will produce the same sequence.
randf.randSeed4 sets the private seed of the thread.
@param ― seed seed value to set. For the best results, all elements should be in [-1000, 1000] range.
Returns range r of RANGES. Range 0 is the home range. Other ranges offset y and z by 0, 1
or -1 cells, using the directions chosen by the walk. Ranges outside the grid are empty.
range(
constwalk:{
home:d.v3i;
turn:d.v3i;
}
walk,
let r:number
r);
for(let
let j:number
j=
import d
d.
functionu32(v?:number|boolean):number
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.
Uploads changes since the last flush with queue.writeBuffer. Writes reach the queue before
the next submit. Call once per frame before passes that read the table. A HistoryTable takes
the frame's encoder and throws without it.
flush();
const
constcompute:TgpuComputePass
compute=
frame:frames.Frame
frame.
Frame.encoder: TgpuCommandEncoder
Records commands for submission after the recorder returns. submit() replaces this encoder.
Read it at each call instead of keeping it, and let the frame submit it.
An intrinsic object that provides basic mathematics functionality and constants.
Math.
Math.ceil(x: number): number
Returns the smallest integer greater than or equal to its numeric argument.
@param ― x A numeric expression.
ceil(
constboids:table.Table<table.TableDef<"Boid",{},{
position:d.Vec3f;
velocity:d.Vec3f;
}>>
boids.
Table<TableDef<"Boid",{},{position: Vec3f; velocity: Vec3f; }>>.span: number
Reports the highest live slot plus one.
span/64));
constcompute:TgpuComputePass
compute.
TgpuComputePass.end(): void
Completes the recording of this compute pass
end();
});
A cell is sight wide, so the nine ranges of three cells along x that a walk visits hold every item within sight. Cells along x sort together in order, so each range is one run of sorted items.
A walk visits its own range first, then adjacent ranges. It uses randf to choose a direction along each axis, so seed it per item first. This avoids a directional bias when a walk stops early, such as after 16 neighbours.
sort copies each itemOf result into sorted order using the item schema. A kernel can then move items while reading their neighbours’ pre-step values. Copy only the fields neighbours need. The fish tank copies position, velocity and species.
cells.slot(range, j) returns the original slot i for a neighbour’s itemOf(i) value. Use it to act on that item or skip the walker itself. A walk can start anywhere, such as at a frog’s mouth, and identify nearby items by slot.
slot binds a third storage buffer. A kernel short of bindings can copy the index in itemOf instead, at four bytes or more a neighbour.
cells.around(point) starts a fixed-order walk without random draws, reading each range in sorted order. Use it when a shader reads every item in the ranges, such as for a fluid’s density or point lights. cells.slotAt(index) returns the original slot at a sorted index. A range spans first through first + count - 1. cells.candidates() returns both functions with fills, for lighting’s points.candidates.
sort covers slots below span(), or below capacity without one, alive or not. Without alive, every slot holds an item, and the types refuse a span.
An item outside the box belongs to the nearest cell. sight and box dimensions determine buffer sizes and shader constants. Changing either requires another grid.
Each grid has its own accessors, so one kernel can walk two, say the fish and the pellets, each piped in with its own inject().
Order matters when a walk stops early, such as after 16 neighbours. A walk that starts at the corner visits the −z, −y and −x cells before its own, so its first neighbours lie mostly on those sides, and a school steered by them drifts towards the corner. The grid’s walk starts at its own range, then the ranges beside it along y, then along z, each from its middle cell and in a random direction, so no side comes first on average:
Reduce the population or raise the stopping count to shrink the bias from starting in a corner. Arrows magnify mean neighbour offsets sixfold in this one-cell-deep box; the grid stays near zero, while the corner-first walk points toward the corner.
After sort, the live items sit at sorted indices 0 to cells.count() - 1, neighbours near each other. A kernel over sorted indices reads its own item with cells.neighbourAt(k), and keeps values it computes, such as a fluid’s pressure, at the same index. cells.index(range, j) gives a neighbour’s sorted index, where a later kernel finds that value. cells.dispatch runs a kernel over the sorted items, in workgroups of grid.WORKGROUP threads, enough for the count:
Starts a walk at point with fixed directions and no random samples, reading each range in
sorted order. Use it when a shader visits every item in the nearby ranges, such as for point
lights in a fragment shader or a fluid's density. Neighbouring items then read nearby memory.
around(
constme:{
position:d.v3f;
velocity:d.v3f;
}
me.
position:d.v3f
position);
for(let
let r:number
r=
import d
d.
functionu32(v?:number|boolean):number
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.
@exampleconst value = u32(); // 0
@exampleconst value = u32(7); // 7
@exampleconst value = u32(3.14); // 3
@exampleconst value = u32(-1); // 4294967295
@exampleconst value = u32(-3.1); // 0
u32(0);
let r:number
r<
import grid
grid.
constRANGES:9
Counts the ranges in a walk, the home range and its eight neighbours.
Returns range r of RANGES. Range 0 is the home range. Other ranges offset y and z by 0, 1
or -1 cells, using the directions chosen by the walk. Ranges outside the grid are empty.
range(
constwalk:{
home:d.v3i;
turn:d.v3i;
}
walk,
let r:number
r);
for(let
let j:number
j=
import d
d.
functionu32(v?:number|boolean):number
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.
Dispatches the pipeline set on the compute pass over the sorted items after sort, in
workgroups of WORKGROUP, enough for count() and at least one. Each thread's global index is
a sorted index; threads at or beyond count() must return.
dispatch(
constcompute:TgpuComputePass
compute);
A sort places items in a new order, so values kept in sorted order last until the next sort.
A sort also lists the blocks of 4 × 4 × 4 cells that hold items. cells.dispatchBlocks(pass) runs one workgroup of the pass’s pipeline per listed block, in any order, and workgroup w finds its block with cells.block(w), counted in blocks. cells.holds(block) tests whether a block holds items; blocks outside the grid hold none. With margin: n, the sort also lists every block within n blocks of one holding items, once each, so a fluid’s surface is rebuilt only in and around the water, as in SPH Sculpt.
binning sets how sort orders the items. Both give the same ranges and blocks.
grid.counting, the default, counts the items of each cell with atomics and reserves one range per line of cells along x holding items. Its cost follows the item count and the lines holding items. Lines, and the items of one cell, land in any order, so a step need not repeat exactly.
grid.radix sorts every slot by key with a sorter, eight bits a pass. Its cost follows the capacity and the number of cells, and a sort repeats exactly. Use it when items fill most lines of the box, or when a step must repeat exactly.
A grid holds eight bytes per cell and counting adds eight more.
Records a frame into one encoder and submits once, with a clock. Borrows the root. The
synchronous recorder borrows each frame's encoder.
@submits ― always once a frame after your recorder returns, and again at each frame.submit()
@param ― record your frame, recorded into each frame's encoder
@returns ― a loop you run with each frame's time and destroy
create(
constroot:TgpuRoot
root,(
frame:frames.Frame
frame)=>{
constlow:d.v3f
low.
v3f.x: number
x=
constplayer:d.v3f
player.
v3f.x: number
x-20;// a 40 × 40 box centred on the player
constlow:d.v3f
low.
v3f.z: number
z=
constplayer:d.v3f
player.
v3f.z: number
z-20;
constcells:grid.Grid<d.Vec3f>
cells.
Grid<Vec3f>.move(low: d.v3f): void
Moves the box origin to low without changing size or sight. The next submission sees this
origin throughout, so sort again before the next walk.
move(
constlow:d.v3f
low);
const
constcompute:TgpuComputePass
compute=
frame:frames.Frame
frame.
Frame.encoder: TgpuCommandEncoder
Records commands for submission after the recorder returns. submit() replaces this encoder.
Read it at each call instead of keeping it, and let the frame submit it.