Skip to content

@typegpu/kit/gridgrid

Neighbour grid

Items sorted into cells each step, so that a kernel reads only the items near each one.

kit.grid

module

grid.create(root, { capacity, low, size, sight, item, position, itemOf, alive?, … }) → Grid

grid.counting(root, { capacity, keys, line, key }) → Binning

grid.radix(root, { capacity, keys, line, key }) → Binning

rootborrows
the root
sighttakes
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.

binning?

(root: TgpuRoot, options: BinningOptions) => Binning= counting

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
  • sortcompute
  • dispatchcompute
  • dispatchBlockscompute

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
grid
from '@typegpu/kit/grid';
import {
const randf: {
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>;
... 10 more ...;
onUnitSphere: TgpuFn<...>;
}
randf
} from '@typegpu/noise';
const
const SIGHT: 1.5
SIGHT
= 1.5;
/** What a neighbour sees of a boid. */
const
const Seen: 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.

@example const CircleStruct = d.struct({ radius: d.f32, pos: d.vec3f });

@param ― props Record with string keys and TgpuData values, each entry describing one struct member.

struct
({
position: d.Vec3f
position
:
import d
d
.
const vec3f: d.Vec3f
vec3f
,
velocity: d.Vec3f
velocity
:
import d
d
.
const vec3f: d.Vec3f
vec3f
});
const
const rows: Inject
rows
=
const boids: table.Table<table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>>
boids
.
Table<TableDef<"Boid", {}, { position: Vec3f; velocity: Vec3f; }>>.inject(): Inject

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

inject
(); // what position and itemOf read
const
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
=
import grid
grid
.
function create<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>(root: TgpuRoot, options: grid.Options<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>): grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>

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

@returns ― a grid you sort each step and destroy

create
(
const root: TgpuRoot
root
, {
...
const boids: table.Table<table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>>
boids
.
Table<TableDef<"Boid", {}, { position: Vec3f; velocity: 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
(), // the table's capacity, alive and span
GridOptions<T extends d.AnyWgslData>.low: d.v3f

Sets the initial lower corner of the grid box. move changes it. Items outside the box belong to the nearest cell.

low
:
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(-20, 0, -20),
GridOptions<T extends d.AnyWgslData>.size: d.v3f

Sets the box extent along x, y and z for the lifetime of the grid. A side within a thousandth of a cell of a whole number of cells takes that number.

size
:
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(40, 10, 40),
GridOptions<T extends d.AnyWgslData>.sight: number

Sets the cell side length. A walk finds every item within this distance.

sight
:
const SIGHT: 1.5
SIGHT
,
item: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>
item
:
const Seen: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>

What a neighbour sees of a boid.

Seen
,
position: table.Getter<d.Vec3f>
position
:
const Boid: table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>
Boid
.
position: table.Getter<d.Vec3f>
position
.
Getter<Vec3f>.pipe(transform: (cfg: Configurable) => Configurable): table.Getter<d.Vec3f>

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
),
itemOf: TgpuGenericFn<(i: number) => {
position: d.v3f;
velocity: d.v3f;
}>
itemOf
:
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
.
fn: <(i: number) => {
position: d.v3f;
velocity: d.v3f;
}>(inner: (i: number) => {
position: d.v3f;
velocity: d.v3f;
}) => TgpuGenericFn<(i: number) => {
position: d.v3f;
velocity: d.v3f;
}> (+2 overloads)
fn
((
i: number
i
: number) => {
'use gpu';
return
const Seen: d.WgslStruct
(props?: {
position: d.v3f;
velocity: d.v3f;
} | undefined) => {
position: d.v3f;
velocity: d.v3f;
}

What a neighbour sees of a boid.

Seen
({
position: d.v3f
position
:
const Boid: table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>
Boid
.
position: table.Getter
(i: number) => d.v3f
position
(
i: number
i
),
velocity: d.v3f
velocity
:
const Boid: table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>
Boid
.
velocity: table.Getter
(i: number) => d.v3f
velocity
(
i: number
i
) });
})
.
TgpuGenericFn<(i: number) => { position: v3f; velocity: v3f; }>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(i: number) => {
position: d.v3f;
velocity: d.v3f;
}>

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
),
});
const
const steer: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
steer
=
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
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [64],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
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
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
})(({
gid: d.v3u
gid
}) => {
'use gpu';
const
const i: number
i
=
gid: d.v3u
gid
.
v3u.x: number
x
;
if (!
const Boid: table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>
Boid
.
alive: TgpuGenericFn
(i: number) => boolean

Tests whether slot i holds a row. Returns false beyond the span.

alive
(
const i: number
i
)) return;
const
const position: d.v3f
position
=
const Boid: table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>
Boid
.
position: table.Getter
(i: number) => d.v3f
position
(
const i: number
i
);
const randf: {
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>;
... 10 more ...;
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.

seed4
(
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const position: d.v3f
position
,
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

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

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(
const i: number
i
))); // the walk turns at random
let
let heading: d.v3f
heading
=
import d
d
.
const vec3f: d.Vec3f
() => d.v3f (+5 overloads)
vec3f
();
let
let seen: number
seen
=
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

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

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(0);
const
const walk: {
home: d.v3i;
turn: d.v3i;
}
walk
=
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.walk: (point: d.v3f) => grid.Walk

Starts a walk at point with random directions from randf. Seed randf first.

walk
(
const position: d.v3f
position
);
for (let
let r: number
r
=
import d
d
.
function u32(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.

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

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

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

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

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

u32
(0);
let r: number
r
<
import grid
grid
.
const RANGES: 9

Counts the ranges in a walk, the home range and its eight neighbours.

RANGES
;
let r: number
r
++) {
const
const range: {
first: number;
count: number;
start: number;
back: number;
}
range
=
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.range: (from: grid.Walk, r: number) => grid.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
(
const walk: {
home: d.v3i;
turn: d.v3i;
}
walk
,
let r: number
r
);
for (let
let j: number
j
=
import d
d
.
function u32(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.

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

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

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

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

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

u32
(0);
let j: number
j
<
const range: {
first: number;
count: number;
start: number;
back: number;
}
range
.
count: number
count
;
let j: number
j
++) {
const
const other: {
position: d.v3f;
velocity: d.v3f;
}
other
=
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.neighbour: (of: grid.Range, j: number) => {
position: d.v3f;
velocity: d.v3f;
}

Returns item j in a range as a value of item. j must be below the range's count.

neighbour
(
const range: {
first: number;
count: number;
start: number;
back: number;
}
range
,
let j: number
j
);
if (
import std
std
.
distance<d.v3f>(a: d.v3f, b: d.v3f): number (+1 overload)
export distance
distance
(
const other: {
position: d.v3f;
velocity: d.v3f;
}
other
.
position: d.v3f
position
,
const position: d.v3f
position
) <
const SIGHT: 1.5
SIGHT
) {
let heading: d.v3f
heading
+=
const other: {
position: d.v3f;
velocity: d.v3f;
}
other
.
velocity: d.v3f
velocity
;
let seen: number
seen
+= 1;
}
}
}
const
const boid: {
position: d.v3f;
velocity: d.v3f;
}
boid
=
const Boid: table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>
Boid
.
device: TgpuAccessor<table.Rows<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>

Provides GPU-written rows with read-write access in compute pipelines and read-only access in render pipelines.

device
.
TgpuAccessor<Rows<{ position: Vec3f; velocity: Vec3f; }>>.$: {
position: d.v3f;
velocity: d.v3f;
}[]
$
[
const i: number
i
]!;
// The walk meets the boid itself, so `seen` is at least 1
const boid: {
position: d.v3f;
velocity: d.v3f;
}
boid
.
velocity: d.v3f
velocity
=
import std
std
.
mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
export mix
mix
(
const boid: {
position: d.v3f;
velocity: d.v3f;
}
boid
.
velocity: d.v3f
velocity
,
let heading: d.v3f
heading
/
let seen: number
seen
, 0.02);
});
const
const steering: TgpuComputePipeline
steering
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const boids: table.Table<table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>>
boids
.
Table<TableDef<"Boid", {}, { position: Vec3f; velocity: Vec3f; }>>.inject(): Inject

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

inject
()).
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.inject(): Inject

Fills the accessors used by the grid's 'use gpu' functions with root.pipe(cells.inject()).

inject
()).
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const steer: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
steer
,
});
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

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
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const boids: table.Table<table.TableDef<"Boid", {}, {
position: d.Vec3f;
velocity: d.Vec3f;
}>>
boids
.
Table<TableDef<"Boid", {}, { position: Vec3f; velocity: Vec3f; }>>.flush(encoder?: TgpuCommandEncoder): void

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
const compute: 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.

encoder
.
TgpuCommandEncoder.beginComputePass(descriptor?: TgpuComputePassDescriptor): TgpuComputePass

Begins recording a compute pass

beginComputePass
();
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.sort(pass: TgpuComputePass): void

Sorts live items into cells and copies their itemOf data. Record before a walk.

sort
(
const compute: TgpuComputePass
compute
); // before the kernels that walk
const compute: TgpuComputePass
compute
.
TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

Sets the current

TgpuComputePipeline

for subsequent dispatches

setPipeline
(
const steering: TgpuComputePipeline
steering
);
const compute: TgpuComputePass
compute
.
TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
dispatchWorkgroups
(
var Math: Math

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
(
const boids: 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));
const compute: 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:

const
const crowding: TgpuBuffer<d.WgslArray<d.F32>> & StorageFlag
crowding
=
const root: TgpuRoot
root
.
TgpuRoot.createBuffer<d.WgslArray<d.F32>>(typeSchema: d.WgslArray<d.F32>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.F32>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.F32>>> | undefined): TgpuBuffer<d.WgslArray<d.F32>> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader.

@remarks ― Typed wrapper around a GPUBuffer.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createBuffer
(
import d
d
.
arrayOf<d.F32>(elementType: d.F32, elementCount: number): d.WgslArray<d.F32> (+2 overloads)
export arrayOf

@location ― . Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).

arrayOf
(
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

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

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
, 4096)).
TgpuBuffer<WgslArray<F32>>.$usage<["storage"]>(usages_0: "storage"): TgpuBuffer<d.WgslArray<d.F32>> & StorageFlag
$usage
('storage');
const
const crowds: TgpuMutable<d.WgslArray<d.F32>>
crowds
=
const crowding: TgpuBuffer<d.WgslArray<d.F32>> & StorageFlag
crowding
.
TgpuBuffer<WgslArray<F32>>.as<"mutable">(usage: "mutable"): TgpuMutable<d.WgslArray<d.F32>>
as
('mutable');
const
const crowd: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
crowd
=
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
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [
import grid
grid
.
const WORKGROUP: 64

Sets the threads per workgroup of the kernels dispatch runs, and of the grid's own.

WORKGROUP
],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
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
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
})(({
gid: d.v3u
gid
}) => {
'use gpu';
const
const k: number
k
=
gid: d.v3u
gid
.
v3u.x: number
x
;
if (
const k: number
k
>=
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.count: () => number

Returns the number of items the last sort placed, at sorted indices 0 to count() - 1.

count
()) return;
const
const me: {
position: d.v3f;
velocity: d.v3f;
}
me
=
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.neighbourAt: (index: number) => {
position: d.v3f;
velocity: d.v3f;
}

Returns the item at sorted index index as a value of item.

neighbourAt
(
const k: number
k
);
let
let near: number
near
=
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

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

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(0);
const
const walk: {
home: d.v3i;
turn: d.v3i;
}
walk
=
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.around: (point: d.v3f) => grid.Walk

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
(
const me: {
position: d.v3f;
velocity: d.v3f;
}
me
.
position: d.v3f
position
);
for (let
let r: number
r
=
import d
d
.
function u32(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.

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

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

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

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

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

u32
(0);
let r: number
r
<
import grid
grid
.
const RANGES: 9

Counts the ranges in a walk, the home range and its eight neighbours.

RANGES
;
let r: number
r
++) {
const
const range: {
first: number;
count: number;
start: number;
back: number;
}
range
=
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.range: (from: grid.Walk, r: number) => grid.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
(
const walk: {
home: d.v3i;
turn: d.v3i;
}
walk
,
let r: number
r
);
for (let
let j: number
j
=
import d
d
.
function u32(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.

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

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

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

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

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

u32
(0);
let j: number
j
<
const range: {
first: number;
count: number;
start: number;
back: number;
}
range
.
count: number
count
;
let j: number
j
++) {
let near: number
near
+=
import std
std
.
function max(fst: number, ...rest: number[]): number (+1 overload)
export max
max
(1 -
import std
std
.
distance<d.v3f>(a: d.v3f, b: d.v3f): number (+1 overload)
export distance
distance
(
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.neighbour: (of: grid.Range, j: number) => {
position: d.v3f;
velocity: d.v3f;
}

Returns item j in a range as a value of item. j must be below the range's count.

neighbour
(
const range: {
first: number;
count: number;
start: number;
back: number;
}
range
,
let j: number
j
).
position: d.v3f
position
,
const me: {
position: d.v3f;
velocity: d.v3f;
}
me
.
position: d.v3f
position
) /
const SIGHT: 1.5
SIGHT
, 0);
}
}
const crowds: TgpuMutable<d.WgslArray<d.F32>>
crowds
.
TgpuMutable<WgslArray<F32>>.$: number[]
$
[
const k: number
k
] =
let near: number
near
; // read back as crowds.$[cells.index(range, j)]
});
const
const crowded: TgpuComputePipeline
crowded
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.inject(): Inject

Fills the accessors used by the grid's 'use gpu' functions with root.pipe(cells.inject()).

inject
()).
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const crowd: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
crowd
});
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.sort(pass: TgpuComputePass): void

Sorts live items into cells and copies their itemOf data. Record before a walk.

sort
(
const compute: TgpuComputePass
compute
);
const compute: TgpuComputePass
compute
.
TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

Sets the current

TgpuComputePipeline

for subsequent dispatches

setPipeline
(
const crowded: TgpuComputePipeline
crowded
);
const cells: grid.Grid<d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>>
cells
.
Grid<WgslStruct<{ position: Vec3f; velocity: Vec3f; }>>.dispatch(pass: IndirectDispatcher): void

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
(
const compute: 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.

The box’s low corner is a uniform. move(low) writes it, so a grid that covers the ground around the player follows them across a larger world:

const
const low: d.v3f
low
=
import d
d
.
const vec3f: d.Vec3f
() => d.v3f (+5 overloads)
vec3f
();
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

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
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const low: d.v3f
low
.
v3f.x: number
x
=
const player: d.v3f
player
.
v3f.x: number
x
- 20; // a 40 × 40 box centred on the player
const low: d.v3f
low
.
v3f.z: number
z
=
const player: d.v3f
player
.
v3f.z: number
z
- 20;
const cells: 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
(
const low: d.v3f
low
);
const
const compute: 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.

encoder
.
TgpuCommandEncoder.beginComputePass(descriptor?: TgpuComputePassDescriptor): TgpuComputePass

Begins recording a compute pass

beginComputePass
();
const cells: grid.Grid<d.Vec3f>
cells
.
Grid<Vec3f>.sort(pass: TgpuComputePass): void

Sorts live items into cells and copies their itemOf data. Record before a walk.

sort
(
const compute: TgpuComputePass
compute
); // into the moved box
const compute: TgpuComputePass
compute
.
TgpuComputePass.end(): void

Completes the recording of this compute pass

end
();
});

move uses a queue write. The sort and all walks in the next submit read the updated box. Sort again after a move before the next walk.

@typegpu/kit/grid uses the kit’s required @typegpu/noise peer. Getting started shows the setup.

  • A walk costs three randf draws per item and step.
  • position and itemOf both read an item’s position.