Runs after a step ends a particle lifetime, with the particle and its slot.
settings?
Partial<d.InferInput<typeof Settings>>= defaults
Sets initial gravity and drag over defaults.
CollideOptionscollide · 2 fields
bounce?
number= 0.5
Sets the share of a particle's speed into the surface that bounces back out.
friction?
number= 0
Sets the share of a particle's speed along the surface that it loses each step it touches.
records
spawn→compute
step→compute
compact→compute
drawing.draw→render
clear→encoder
spawn once a submission before compact, step once per fixed step, compact once after the steps, then the draw a description added drawing as; clear whenever you like.
owns
6 buffers ·2 pipelines ·`liveLists`
The pool, its ring counter, the burst queue and three uniforms; the live lists list the live particles. Each description drawing is added to builds its pipeline.
Press or drag to burst sparks into 8 192 slots. The head counts births and overwrites the next ring slot even if alive; gravity and drag upload settings only when changed.
Moving thousands of short-lived sparks, dust particles or bubbles on the CPU requires position uploads every frame. On phones, slower Hermes execution can make that loop consume the frame budget. Queue bursts on the CPU and let the GPU spawn, move and draw particles without readback.
Accumulates elapsed time for fixed simulation steps. Accumulates nothing while paused. Throws
unless step is finite and above 0 and maxSteps is an integer above 0.
@returns ― a clock whose advance says how many steps to run each frame
fixedStep({
FixedStepOptions.step: number
Sets the step duration in seconds, such as 1 / 120.
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Describes the targets and outputs for scene passes. With a surface such as the canvas, writes
only each renderer's colour. With a target set, writes colour into the target marked color,
depth into the target marked depth, and other outputs into their named targets. Multisampled
targets resolve according to resolve. Targets with layers draw one pass per layer, and every
target the description draws into needs the same layers. add builds a renderer's drawing.
begin starts a pass. pipe supplies fills such as a camera.
@param ― screen the surface, or the targets, which give the formats; each one the description
names is drawn into
@param ― writes what fills each target, by its key in screen
@returns ― a description you destroy, which holds the pipelines it builds
Adds fill after existing fills and returns a description of the same passes. Piped fills
take precedence over a drawing's own. For example, passes.create(…).pipe(view.inject()) uses
the camera from view.
pipe(
constview:cameras.OrbitCamera
view.
CameraBase.inject(): Inject
Fills cameras.current for the pipelines created from the configuration it is piped into.
Builds a pipeline for drawing that writes every described output and returns its draw
methods. If the drawing cannot supply the fields an output needs, type checking fails with
that output's name. Use without to leave it out or instead to replace it.
Provides the particle drawing for pass descriptions, as in scene.add(system.drawing). It
supplies colour, world position and corner data to outputs, tests depth without writing it
and draws the list from the last compact. The description initAsync also compiles system
kernels.
Queues a burst for the next spawn, trimming it to the remaining capacity. Returns false if
the queue is full, queued bursts fill capacity or the system is destroyed. Throws for a
NaN count.
Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps
back, and stops while the surface is paused. Destroying the animation keeps the loop alive.
Another animation on the same loop continues from its recorded time. A frame error stops the
animation.
@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before
@returns ― an animation you destroy to stop the 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(
constroot:TgpuRoot
root,(
frame:frames.Frame
frame)=>{
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.
Spawns queued bursts before compact. Record it before steps to move those particles this
frame, or after steps to show bursts queued during the steps. Call it once per submission:
the bursts upload through the queue before the submission runs, so a second spawn in it
would replace the first one's bursts. Throws when one pass spawns bursts twice.
spawn(
constcompute:TgpuComputePass
compute);
for(let
let steps:number
steps=
constsim:clock.FixedStep
sim.
FixedStep.advance(dt: number): number
Adds dt seconds and returns the number of steps to run. Each step lasts step seconds, so a
GPU kernel can use a fixed duration. All steps in a frame see the same queued CPU writes. Keep
a step counter on the GPU for values that must change per step.
advance(
frame:frames.Frame
frame.
Frame.dt: number
Reports seconds since the previous submitted frame. The first frame has a dt of zero.
Lists live particles for drawing. Record this once per frame after simulation steps. Drawing
moves each particle ahead seconds along its velocity, such as a fixed-step clock's
sim.ahead, so motion follows the display rate between steps.
compact(
constcompute:TgpuComputePass
compute,
constsim:clock.FixedStep
sim.
FixedStep.ahead: number
Reports the unstepped time in seconds, capped at one step. Equals alpha * step. Use it to
draw objects that far along their velocity between simulation steps.
Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the
list to avoid allocating each frame. Call begin yourself when you need to choose drawings
per frame. Records nothing while enabled is false.
record(
frame:frames.Frame
frame,
constdrawn:passes.Drawn
drawn);
}),
);
canvas.add(sparks.drawing) builds the pipeline into a pass description. canvas.initAsync() compiles drawing pipelines and particle kernels. For a system without a drawing, call sparks.initAsync().
Each frame, call spawn, then step for every fixed step, then compact. Particles spawned before the steps move in that frame. Those spawned afterward still appear, but start moving on the next step.
Spawn once per submission. The bursts upload through the queue before the submission runs, so a second spawn in it would replace the first one’s bursts. To spawn at several moments while loading, such as a prewarm, submit after each spawn.
step moves each live particle by the step option’s seconds: your update, then gravity, drag and motion.
step and compact each run one thread per slot, including dead slots. Size capacity for the maximum simultaneous live count. Drawing visits only live particles. Each fragment reads all 64 bytes of its particle.
The draw is indirect: the live count stays on the GPU unless you read it.
compact takes sim.ahead, the time left after fixed steps, and writes it when it changes. Drawing extrapolates each particle by that time along its velocity, so motion updates on every frame of a 120 Hz display. Pass 0 to draw particles where the last step left them.
settings holds gravity and drag. The settings option overrides particles.defaults, which uses Earth’s gravity and drag that retains one third of speed after a second. Change drag with sparks.settings.patch({ drag: 2 }).
Burst 30 particles or increase lifetime to overwrite live particles as the ring wraps, even if other slots are free. The 48-slot pool also receives small automatic bursts, and particles fade as they age.
A new particle, from a burst or a kernel, takes slot head++ % capacity. It is alive while age < life.
Size capacity for the maximum simultaneous live count. A Blockfall piece can clear four layers of 36 blocks, and each block leaves one patch of dust, so its dust pool holds 144.
When spawning exceeds capacity, the newest particles replace the oldest, even if alive. Bubbles can disappear before reaching the surface if the pool is too small.
Give persistent effects their own system so other bursts cannot overwrite them. The Benthos example fills its water with one burst using life: 1e9. Its snow stays alive without wrapping the ring.
For recurring dust, use one burst with aged: true. Random starting ages keep the particles from fading together.
burst queues particles for the next spawn. Queued bursts can start at most capacity particles per spawn. A burst that would exceed the remaining capacity is trimmed. burst returns false when no capacity remains or the burst queue is full. The queue defaults to 64 bursts. Blockfall uses 144, one per block a piece can clear.
Field
Default
count
Trimmed to what is left of the capacity before a spawn.
For smoke from a chimney or sparks from a torch, emit a number of particles per second. stream(rate, burst) counts them from the steps: each step owes rate times its length, and each spawn queues the whole particles owed and carries the fraction, so the stream keeps its rate at any frame rate.
Emits rate particles a second from burst, which the system keeps and reads at each spawn,
so changes to its fields between frames take effect. The steps owe the particles and the next
spawn queues them, so those a frame's steps owe spawn in the next frame. Throws unless rate
is 0 or more.
@returns ― the stream, whose rate you set
stream(45,
constpuff:{
position:[number,number,number];
area:readonly[0.2,0,0.2];
}
puff);// 45 a second
// Later, while it runs
constchimney:particles.Stream
chimney.
Stream.rate: number
Sets particles a second, 0 or more. Each step owes rate times the step's length, and each
spawn queues the whole particles owed, carrying the fraction. Set it to 0 to pause the
stream.
rate=90;
constpuff:{
position:[number,number,number];
area:readonly[0.2,0,0.2];
}
puff.
position:[number, number, number]
position[0]=2.5;
The system keeps the burst and reads it at each spawn, so a stream follows a moving torch when you change its fields.
Particles the steps of one frame owe spawn at the next frame’s spawn.
Set rate to 0 to pause the stream. The fraction already owed stays.
update(particle, dt, slot) runs on each live particle at the start of a step, before gravity and drag. It is a 'use gpu' function with dt in seconds; slot finds related data, such as the particle’s trail. Copy the argument, modify it and return the particle to keep. Set life to 0 to end it early. The fish tank’s bubbles wobble, grow as they rise and pop at the surface:
const
constrise:(bubble:Particle,dt:number)=>{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
rise=(
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble:
typeParticle={
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
This type describes a particle in shaders.
Particle,
dt: number
dt:number)=>{
'use gpu';
let
let next:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
next=
functionParticle(props?:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}|undefined):{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
This type describes a particle in shaders.
Particle(
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble);
let next:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
next.
velocity:d.v3f
velocity.
v3f.x: number
x=
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble.
velocity:d.v3f
velocity.
v3f.x: number
x+
import std
std.
functionsin(value:number):number(+1overload)
export sin
sin(
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble.
age: number
Holds particle age in seconds.
age*7+
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble.
seed: number
Holds a random value in [0, 1), fixed at birth, for variation in update and shade.
seed*40)*2*
dt: number
dt;
let next:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
next.
size: number
Sets the particle width in world units.
size=
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble.
size: number
Sets the particle width in world units.
size*(1+0.05*
dt: number
dt);
if(
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble.
position:d.v3f
position.
v3f.y: number
y>
constSURFACE:6
SURFACE-
bubble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
bubble.
size: number
Sets the particle width in world units.
size)
let next:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
next.
life: number
Sets lifetime in seconds. A particle is alive while age < life.
life=0;
return
let next:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
next;
};
update first runs on the first simulation step after birth. Use born for initialization at birth. All callbacks can read cameras.current.$. Fill other module dependencies on the callback, for example update: tgpu.fn(rise).pipe(noise.inject()) for cached Perlin noise.
shade(particle, local) returns premultiplied colour for the quad. local ranges from -1 to 1 in each direction. The default particles.softDisc draws a round blob that fades with age. particles.glowingDisc draws a bright core in a soft halo that dims with age and adds light with an alpha of 0, for embers and sparks, as in the ember mirror. The platformer uses separate systems for each blend mode:
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Returns premultiplied colour for a quad fragment at local coordinates in [-1, 1]. It shares
6 storage buffers with place, as place says. Default softDisc.
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Returns premultiplied colour for a quad fragment at local coordinates in [-1, 1]. It shares
6 storage buffers with place, as place says. Default softDisc.
Debris, smoke and dust look like part of a scene when the same sun and lamps light them. Pipe a lighting into shade and call shading.shade with a material and a point. A quad has no surface of its own, so give each particle a normal that faces the eye:
const
constlit:(chip:Particle,local:d.v2f)=>d.v4f
lit=(
chip:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
chip:
typeParticle={
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
This type describes a particle in shaders.
Particle,
local:d.v2f
local:
import d
d.
export v2f
Interface representing its WGSL vector type counterpart: vec2f or vec2.
A vector with 2 elements of type f32
v2f)=>{
'use gpu';
const
constfacing:d.v3f
facing=
import cameras
cameras.
consttowards:(view:{
viewProjection:d.m4x4f;
position:d.v3f;
right:d.v3f;
up:d.v3f;
},point:d.v3f)=>d.v3f
Returns the unit direction towards the viewer from a world point, for shading. Under perspective
projection, it points towards the eye. Under orthographic projection, such as a flat camera or
the sun, it points against the viewing direction.
towards(
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.
This schema describes a surface with the glTF metallic-roughness model.
Material({
color:d.v3f
Sets the diffuse colour of a dielectric or the reflection colour of a metal, in linear
colour space.
color:
chip:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
chip.
color:d.v4f
Holds linear colour with opacity in w.
color.
xyz:d.v3f
xyz,
roughness: number
Sets roughness from 0 for a mirror to 1 for chalk.
roughness:1,
emission:d.v3f
Sets the emitted light in linear colour space. Shading adds it after lighting.
emission:
import d
d.
constvec3f:d.Vec3f
()=>d.v3f(+5 overloads)
vec3f(),
metallic: number
Sets metallic content from 0 for a dielectric to 1 for a metal.
metallic:0,
occlusion: number
Sets the share of ambient light that reaches the surface, from 0 in a crevice to 1 in the
open, such as glTF's occlusion texture. hemisphere and an environment's ambient apply it.
The materials functions set 1.
Computes the final surface colour. It combines ambient light, shadowed sunlight, live point
lights and emission, then applies fog. Every channel stays at or below 65 504, the largest half
float. Create the pipeline from root.pipe(light.inject()) after injecting the kit camera.
shade(
constsurface:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
}
surface,
import shading
shading.
constpointAt:(position:d.v3f,normal:d.v3f)=>{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
}
Creates a point at position with surface normal normal, viewed from the kit camera.
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Returns premultiplied colour for a quad fragment at local coordinates in [-1, 1]. It shares
6 storage buffers with place, as place says. Default softDisc.
Applies a configuration transform to the slots and accessors this function reads,
as root.pipe does to a pipeline's.
pipe(
constlight:lighting.Lighting
light.
Lighting.inject(): Inject
Fills the shading hooks and resources for pipelines created from the configuration you pipe
it into.
inject()),
});
Each particle is lit at its centre, so a whole quad takes one colour of light. Shadows, fog and the lighting’s hooks apply as they do to meshes.
A normal that faces the eye lights the side of a particle you see. Lamps behind a particle leave it dark; for smoke lit from behind, write a reflect hook for the particle alone.
Fill a hook for the particles alone with .with after light.inject(), on the same shade, so it replaces the lighting’s. Soft dust lit at its centre flickers as a whole puff when a moving shadow crosses that point, so turn the sun’s shadow off for it:
const
constunshadowed:shading.SunShadowHook
unshadowed:
import shading
shading.
typeSunShadowHook=(point:shading.Point)=>number
Returns the share of sunlight that reaches a point, as shadows.filtered does. A value of 0
means full shadow and 1 means full light. Values above 1 can model focused light.
SunShadowHook=()=>{
'use gpu';
return
import d
d.
functionf32(v?:number|boolean):number
export f32
A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Returns premultiplied colour for a quad fragment at local coordinates in [-1, 1]. It shares
6 storage buffers with place, as place says. Default softDisc.
particles.collide(solid, { bounce, friction }) makes an update that keeps particles out of a solid. A solid is a signed distance: distance(p) is negative inside and grows away from the surface. A particle nearer than half its size moves out along the distance’s gradient and bounces back the bounce share of its speed into the surface. Each step it touches, it loses the friction share of its speed along the surface. Pass the result as update, or call it from your own, here for pebbles that tumble onto a floor and a boulder:
Makes an update that keeps particles out of solid. A particle closer to its surface than
half its size moves out along the gradient, and its speed into the surface bounces back.
Pass it as update, or call it from your own: return ground(tumble(pebble, dt)).
@param ― solid the signed distance of what particles bounce off, and its gradient if known
@returns ― a 'use gpu' function that takes a particle and returns it collided
collide(
{
Solid.distance: (p:d.v3f)=> number
Returns the signed distance from p to the solid's surface, negative inside.
Sets the share of a particle's speed into the surface that bounces back out. Default 0.5.
bounce:0.4,
CollideOptions.friction?: number |undefined
Sets the share of a particle's speed along the surface that it loses each step it touches.
Default 0.
friction:0.05},
);
const
constupdate:(pebble:Particle,dt:number)=>{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
update=(
pebble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
pebble:
typeParticle={
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
This type describes a particle in shaders.
Particle,
dt: number
dt:number)=>{
'use gpu';
return
constground:<{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}>(particle:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
})=>{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
ground(
consttumble:(pebble:Particle,_dt:number)=>{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
tumble(
pebble:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
pebble,
dt: number
dt));
};
gradient(p), when given, returns the direction in which the distance grows, of any length. Without it, collide estimates one from 4 more calls of distance around the particle.
Distances from @typegpu/sdf, such as sdSphere and sdBox3d, make solids too. std.min of two distances is their union.
collide runs at the start of a step, before gravity and motion, so a particle can sink into a surface by its last step’s travel before it bounces out.
Use particles.define(name, fields) to add fields to the particle struct. Pass the struct as particle. Callbacks, emit and particle(i) then use the extended type. Supply initial values in a burst’s fields; omitted fields start at zero. Firework shells store their kind and star-burst speed, which also determines flash size:
const
constShell:d.WgslStruct<{
position:d.Vec3f;
age:d.F32;
velocity:d.Vec3f;
life:d.F32;
color:d.Vec4f;
size:d.F32;
seed:d.F32;
}&{
kind:d.U32;
reach:d.F32;
}>
Shell=
import particles
particles.
functiondefine<{
kind:d.U32;
reach:d.F32;
}>(name:string,own:{
kind:d.U32;
reach:d.F32;
}&{
readonly position?:"is a field every particle has"|undefined;
readonly age?:"is a field every particle has"|undefined;
readonly velocity?:"is a field every particle has"|undefined;
readonly life?:"is a field every particle has"|undefined;
readonly color?:"is a field every particle has"|undefined;
readonly size?:"is a field every particle has"|undefined;
readonly seed?:"is a field every particle has"|undefined;
}):d.WgslStruct<...>
Defines a particle struct with custom fields after the built-in fields. Supply it as
particle to create; callbacks, emit and particle(i) then use this struct. Define it at
module scope beside shaders. Throws if a custom field duplicates a built-in field.
@param ― name a label for the struct in WGSL
@param ― own the fields of its own, as d.struct takes them
@returns ― the particle's struct, for create and for shaders that build one to emit
define('Shell',{
kind:d.U32
kind:
import d
d.
constu32:d.U32
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,
reach:d.F32
reach:
import d
d.
constf32: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.
@exampleconst value = f32(); // 0
@exampleconst value = f32(1.23); // 1.23
@exampleconst value = f32(true); // 1
f32});
type
typeShell={
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
kind:number;
reach:number;
}
Shell=
import d
d.
typeInferGPU<T>=Textends{
readonly[$gpuRepr]:inferTRepr;
}?TRepr:d.Infer<T>
export InferGPU
Extracts the inferred representation of a resource (as seen by the GPU).
@exampletype A = InferGPU // => number
type B = InferGPU<WgslArray> // => number[]
type C = InferGPU<Atomic> // => atomicU32
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Runs after a step ends a particle lifetime, with the particle and its slot. It can emit into
another system through a fill such as tgpu.fn(burst).pipe(sparks.inject()). It does not
run for live particles overwritten by the ring, particles born dead or particles ended by
clear. A system it emits into binds 2 storage buffers in the step, its pool and ring
counter. Default none.
Emits a particle from a compute shader created from root.pipe(system.inject()). It takes
the next ring slot, overwriting the particle emitted capacity emissions earlier even if
that particle is alive.
emit(
functionParticle(props?:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}|undefined):{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
This type describes a particle in shaders.
Particle({
position:d.v3f
position:
shell:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
kind: number;
reach: number;
}
shell.
position:d.v3f
position,
age: number
Holds particle age in seconds.
age:0,
velocity:d.v3f
velocity:
import d
d.
constvec3f:d.Vec3f
()=>d.v3f(+5 overloads)
vec3f(),
life: number
Sets lifetime in seconds. A particle is alive while age < life.
life:1.6,
color:d.v4f
Holds linear colour with opacity in w.
color:
shell:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
kind: number;
reach: number;
}
shell.
color:d.v4f
Holds linear colour with opacity in w.
color,
size: number
Sets the particle width in world units.
size:
shell:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
kind: number;
reach: number;
}
shell.
reach: number
reach*1.4,
seed: number
Holds a random value in [0, 1), fixed at birth, for variation in update and shade.
seed:
shell:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
kind: number;
reach: number;
}
shell.
seed: number
Holds a random value in [0, 1), fixed at birth, for variation in update and shade.
Queues a burst for the next spawn, trimming it to the remaining capacity. Returns false if
the queue is full, queued bursts fill capacity or the system is destroyed. Throws for a
NaN count.
Sets custom particle fields to the same values across the burst. Default zero.
fields:{
kind?: number |undefined
kind:2,
reach?: number |undefined
reach:18}});
Custom fields use the final 8 bytes of the standard particle. Beyond that, size grows from 64 bytes in multiples of 16. Reusing a built-in name such as age throws.
place(particle, corner) returns a quad corner in world space. corner ranges from -1 to 1 in both directions. The particle position already includes velocity extrapolation for drawing. The default particles.facing produces a camera-facing square of width size. To stretch sparks along velocity, use the camera to orient their width:
/** Twenty milliseconds of travel long, `size` wide. */
const
conststreak:(spark:Particle,corner:d.v2f)=>d.v3f
Twenty milliseconds of travel long, size wide.
streak=(
spark:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
spark:
typeParticle={
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
This type describes a particle in shaders.
Particle,
corner:d.v2f
corner:
import d
d.
export v2f
Interface representing its WGSL vector type counterpart: vec2f or vec2.
A vector with 2 elements of type f32
v2f)=>{
'use gpu';
const
constalong:d.v3f
along=
spark:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
spark.
velocity:d.v3f
velocity*0.01;
const
constacross:d.v3f
across=
import std
std.
cross<d.v3f>(a: d.v3f, b: d.v3f): d.v3f
export cross
cross(
spark:{
position:d.v3f;
age: number;
velocity:d.v3f;
life: number;
color:d.v4f;
size: number;
seed: number;
}
spark.
velocity:d.v3f
velocity,
import cameras
cameras.
constforwardOf:(view:{
viewProjection:d.m4x4f;
position:d.v3f;
right:d.v3f;
up:d.v3f;
})=>d.v3f
Returns the unit viewing direction in world space. Pass cameras.current.$ as view.
forwardOf(
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.
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Returns a quad corner in world space from corner coordinates in [-1, 1]. The particle
position includes the drawing offset along velocity. The drawing binds 2 storage buffers in
both stages, the pool and the live slots, so place and shade share 6. Default facing,
which makes a camera-facing square of width size.
At each pixel, particles.Seen supplies world position and premultiplied colour. At each vertex, particles.Corner supplies clip position, world position, local quad coordinates and particle slot. A pass description can use outputs requiring only these values. Other outputs fail to type-check at add; remove them with without. See Passes for target outputs.
Describes the targets and outputs for scene passes. With a surface such as the canvas, writes
only each renderer's colour. With a target set, writes colour into the target marked color,
depth into the target marked depth, and other outputs into their named targets. Multisampled
targets resolve according to resolve. Targets with layers draw one pass per layer, and every
target the description draws into needs the same layers. add builds a renderer's drawing.
begin starts a pass. pipe supplies fills such as a camera.
@param ― screen the surface, or the targets, which give the formats; each one the description
names is drawn into
@param ― writes what fills each target, by its key in screen
@returns ― a description you destroy, which holds the pipelines it builds
create(
constroot:TgpuRoot
root,
constscreen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
readonly sampled:true;
};
readonly motion:{
readonly format:"rg16float";
readonly sampled:true;
};
readonly id:{
readonly format:"r32uint";
};
readonly depth:{
readonly format:"depth24plus";
readonly sampled:true;
};
}>
screen,
{
hdr:typeofpasses.color
hdr:
import passes
passes.
constcolor:typeofpasses.color
Marks the target that receives each renderer's colour.
color,
motion:passes.Output<unknown,{
position:d.v4f;
}>
motion:
import taa
taa.
constmotion:passes.Output<unknown,{
position:d.v4f;
}>
This output writes surface motion into an rg16float target. It stores preceding UV position
minus current UV position. It computes the preceding position by compiling renderer vertex
code with history.previously, which reads the preceding camera and values of other
temporal fills.
motion,
id:passes.Output<{
id:number;
},unknown>
id,
depth:typeofpasses.depth
depth:
import passes
passes.
constdepth:typeofpasses.depth
Marks the depth target.
depth},
{
Options.jitter?: Position |undefined
Moves clip positions for rasterisation in a 'use gpu' function. Outputs read the original
vertex positions. Use for a sub-pixel offset in temporal antialiasing. Default none.
jitter:
constaa:taa.Taa
aa.
Taa.jitter: (position:d.v4f)=>d.v4f
Offsets vertices by a different fraction of a pixel each frame so frames sample the whole
pixel together. Supply this as jitter in passes.create.
Adds fill after existing fills and returns a description of the same passes. Piped fills
take precedence over a drawing's own. For example, passes.create(…).pipe(view.inject()) uses
the camera from view.
pipe(
constview:cameras.OrbitCamera
view.
CameraBase.inject(): Inject
Fills cameras.current for the pipelines created from the configuration it is piped into.
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Returns a description with keys left out for a renderer that cannot write them. Opaque
drawings write each omitted output's clear. Drawings with a blend preserve the value
underneath. No varyings are carried for omitted outputs.
Builds a pipeline for drawing that writes every described output and returns its draw
methods. If the drawing cannot supply the fields an output needs, type checking fails with
that output's name. Use without to leave it out or instead to replace it.
Provides the particle drawing for pass descriptions, as in scene.add(system.drawing). It
supplies colour, world position and corner data to outputs, tests depth without writing it
and draws the list from the last compact. The description initAsync also compiles system
kernels.
drawing);
They blend and test the depth without writing it, so draw them after the opaque renderers. With blend: 'translucent' they draw into the translucency targets in any order, as bubbles among other glass do.
taa.motion reconstructs the previous corner with the previous camera and ahead, subtracting a step for every step since that frame. Motion is velocity times the time between frames.
For particles born this frame, motion assumes a previous position extrapolated backward along velocity.
A system added to several descriptions, such as a scene’s and the canvas’s, draws the same particles into each.
sparks.emit starts a particle in sparks from any compute shader created from root.pipe(sparks.inject()). The fish tank’s food sheds crumbs from the kernel that counts bites.
Emits a particle from a compute shader created from root.pipe(system.inject()). It takes
the next ring slot, overwriting the particle emitted capacity emissions earlier even if
that particle is alive.
emit(
functionParticle(props?:{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}|undefined):{
position:d.v3f;
age:number;
velocity:d.v3f;
life:number;
color:d.v4f;
size:number;
seed:number;
}
This type describes a particle in shaders.
Particle({
position:d.v3f
position:
import d
d.
constvec3f:d.Vec3f
()=>d.v3f(+5 overloads)
vec3f(),
age: number
Holds particle age in seconds.
age:0,
velocity:d.v3f
velocity:
import d
d.
constvec3f:d.Vec3f
(x:number,y:number,z:number)=>d.v3f(+5 overloads)
vec3f(0,3,0),
life: number
Sets lifetime in seconds. A particle is alive while age < life.
life:2,
color:d.v4f
Holds linear colour with opacity in w.
color:
import d
d.
constvec4f:d.Vec4f
(xyzw:number)=>d.v4f(+9 overloads)
vec4f(1),
size: number
Sets the particle width in world units.
size:0.1,
seed: number
Holds a random value in [0, 1), fixed at birth, for variation in update and shade.
seed:
import d
d.
functionf32(v?:number|boolean):number
export f32
A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)
born(particle, slot) runs when a burst or emit creates a particle. It returns the particle to keep. Use slot to initialize associated data. Firework shells start a trail at their birth position:
Creates trails as rings of recent points, pushed by kernels or written on the CPU, and draws
them as lines. Throws unless capacity is an integer of at least 1 and length is an integer
of at least 2.
@injects ― per-instance push, reset, pointAt and held, for shaders piped with it
@binds ― 2 storage: push or pointAt, the points and the rings
@binds ― 1 storage: held or reset alone, the rings
@binds ― 2 storage, 2 uniform: drawing, the points and the rings, the settings and
passes.pixels
@owns ― 3 buffers, a lines: the points, the rings and the settings, and the lines that draw them
@records ― drawing, added to passes, after the passes that push; clear whenever you like.
@param ― options.width a 'use gpu' function; it and shade carry the fills of what they read,
such as tgpu.fn(swell).pipe(noise.inject()). The ribbons face the camera of the pass
description they are drawn in, which width and shade read too
@returns ― a set of trails you destroy
create(
constroot:TgpuRoot
root,{
Options.capacity: number
Sets the number of trails to an integer of at least 1.
capacity:32,
Options.length: number
Sets the number of points per trail to an integer of at least 2. Pushing beyond this count
drops the oldest point.
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Initializes a particle and its slot when a burst or emit creates it. Return the particle
to keep. Use the slot to initialize related data, such as resetting a trail. The spawn kernel
binds 3 storage buffers, the pool, its ring counter and the burst queue, so born has 5, and
it runs in every kernel that calls emit too. Default none, which keeps the emitted values.
Empties a trail in a shader created from root.pipe(wakes.inject()), as write(trail, [])
does on the CPU. Use it before pushing a respawned object at its new position. It counts as
a push for dispatch concurrency; a later push in the same thread follows it.
Applies a configuration transform to the slots and accessors this function reads,
as root.pipe does to a pipeline's.
pipe(
consttails:trails.Trails
tails.
Trails.inject(): Inject
Fills trail accessors for pipelines created from the configuration you pipe it into.
inject()),
});
died(particle, slot) runs at the end of the step where age reaches life or update sets life to 0. It skips live particles overwritten by the ring, particles born at or beyond their lifetime, and particles ended by clear. Fill the callback with another system to emit there on death:
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Creates a GPU particle pool. CPU bursts and kernel emit calls allocate ring slots. Fixed
steps move particles, and live lists list only live particles for drawing.
@injects ― per-instance emit and the pool, for kernels piped with it
@binds ― 2 storage: emit, the pool and its ring counter
@binds ― 1 storage: alive or particle, the pool
@binds ― 1 storage, 1 uniform: drawnAt, the pool and the last compact's ahead
@owns ― 6 buffers, 2 pipelines, liveLists: the pool, its ring counter, the burst queue and three
uniforms; the live lists list the live particles. Each description drawing is added to builds
its pipeline.
@records ― spawn once a submission before compact, step once per fixed step, compact once
after the steps, then the draw a description added drawing as; clear whenever you like.
@param ― camera the camera the quads face unless place says otherwise; the callbacks read it too
@param ― options.update a 'use gpu' function; it and the other callbacks carry the fills of what
they read beside the camera, such as tgpu.fn(rise).pipe(noise.inject())
Sets the length of a step in seconds, such as a fixed-step clock's sim.step. Each step
advances by it.
step:
constsim:clock.FixedStep
sim.
FixedStep.step: number
Sets the step duration in seconds. A new value applies from the next advance: unstepped time
carries over, capped at maxSteps new steps, and time keeps the seconds already stepped.
Particles and machines take its value when created and keep it. Throws unless the value is
finite and above 0.
Runs after a step ends a particle lifetime, with the particle and its slot. It can emit into
another system through a fill such as tgpu.fn(burst).pipe(sparks.inject()). It does not
run for live particles overwritten by the ring, particles born dead or particles ended by
clear. A system it emits into binds 2 storage buffers in the step, its pool and ring
counter. Default none.
Emits a particle from a compute shader created from root.pipe(system.inject()). It takes
the next ring slot, overwriting the particle emitted capacity emissions earlier even if
that particle is alive.
The draw reads the live count on the GPU without making the CPU wait. To display it, use a readback of args, the indirect draw arguments written by compact. Word 1 holds the live count when compact ran:
Copies a buffer to the CPU without stalling, using a ring of staging buffers. Records one copy
per capture. Throws unless staging is an integer above 0 and hz, when set, is finite and
above 0.
@owns ― 3 buffers: staging buffers mapped in turn, as many as staging
@records ― capture after the pass that writes the buffer; a second capture in a frame does
nothing.
Holds the 5-word indirect draw record written by compact and read by draw. Word 1 is the
live count from the last compact; copy it with a readback to display it.
args,{
Options.hz?: number |undefined
Also captures for latest this many times per second on average, in the frame closest to each
tick. Without a rate, only next() and request cause captures. Default none.
Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps
back, and stops while the surface is paused. Destroying the animation keeps the loop alive.
Another animation on the same loop continues from its recorded time. A frame error stops the
animation.
@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before
@returns ― an animation you destroy to stop the 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(
constroot:TgpuRoot
root,(
frame:frames.Frame
frame)=>{
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.
Spawns queued bursts before compact. Record it before steps to move those particles this
frame, or after steps to show bursts queued during the steps. Call it once per submission:
the bursts upload through the queue before the submission runs, so a second spawn in it
would replace the first one's bursts. Throws when one pass spawns bursts twice.
spawn(
constcompute:TgpuComputePass
compute);
for(let
let steps:number
steps=
constsim:clock.FixedStep
sim.
FixedStep.advance(dt: number): number
Adds dt seconds and returns the number of steps to run. Each step lasts step seconds, so a
GPU kernel can use a fixed duration. All steps in a frame see the same queued CPU writes. Keep
a step counter on the GPU for values that must change per step.
advance(
frame:frames.Frame
frame.
Frame.dt: number
Reports seconds since the previous submitted frame. The first frame has a dt of zero.
Lists live particles for drawing. Record this once per frame after simulation steps. Drawing
moves each particle ahead seconds along its velocity, such as a fixed-step clock's
sim.ahead, so motion follows the display rate between steps.
compact(
constcompute:TgpuComputePass
compute,
constsim:clock.FixedStep
sim.
FixedStep.ahead: number
Reports the unstepped time in seconds, capped at one step. Equals alpha * step. Use it to
draw objects that far along their velocity between simulation steps.
Records a copy when a next() is waiting, a request is pending or the capture rate is due. Maps
the copy after submission. Call after the pass that writes the buffer. A second call in the
same frame does nothing.
capture(
frame:frames.Frame
frame);// after the pass that compacts
const
constlive:number|undefined
live=
constcensus:readback.Readback<number[]>
census.
Readback<number[]>.take(): number[]|undefined
Returns latest once per completed copy, then undefined until the next copy completes. Each
value goes to one caller. Callers sharing a readback split the values between them.
A storage buffer of capacity particles. Bursts and emit fill it; clear(encoder) ends them.
capacity, span
Both equal the configured capacity because any slot can hold a particle. Dispatch pool-wide kernels over span.
settings
Gravity and drag, a uniform. patch changes some fields, write all of them.
args
The indirect draw compact writes. Word 1 is the live count; read it with a readback.
emit
Starts a particle from a kernel created from root.pipe(system.inject()).
alive(i)
Whether slot i holds a live particle, in a kernel created from root.pipe(system.inject()) that visits the pool.
particle(i)
The particle in slot i, below span, in shaders created from root.pipe(system.inject()). It may be dead; alive(i) tells you.
drawnAt(i)
Where the drawing places slot i this frame, its position moved along its velocity by the last compact’s ahead.
live()
The capacity and a filled alive, to spread into a grid, a picker, state machines or live lists over the pool.
drawing
How the live particles draw, for scene.add.
Standard particles use 64 bytes each. A 4096-slot pool takes 256 KiB plus live-list buffers. For custom fields, each queued burst also stores a particle, adding 64 bytes.
burst throws a RangeError on a NaN count: A burst needs a count, got NaN.
spawn throws when one pass spawns bursts twice: particles: a second `spawn` in one submission would replace the first bursts. Submit between them.
define throws on a field a particle has: 'age' is a built-in particle field. Choose another name.
A shader that calls emit, alive or particle from a pipeline not created from root.pipe(system.inject()) throws when it compiles: Nothing filled a particle system's pool…
Particles draw as quads. place can move their four corners, but cannot change the shape topology.
Motion extrapolates backward using velocity at draw time. Gravity and drag between frames make this approximate.
Motion and other outputs cover the whole quad. Transparent corners overwrite motion for the objects behind them.
A shade that calls shading.shade runs the light loop for every pixel of every quad, over all point lights in range. Keep lit particles small or few under many lights.
A stage binds at most 8 storage buffers. A system’s own kernels and stages leave its callbacks this many:
Runs in
Binds of its own
Left for
step
the pool
update and died: 7
spawn
the pool, its ring counter and burst queue
born: 5
drawing
the pool and the live slots
place and shade: 6
Each system died or update emits into takes 2, its pool and ring counter. born runs in every kernel that emits into the system, so what it reads counts there too. Bindings per stage lists what each module binds.
A system steps forward only, so a replay can’t jump back to an earlier moment. For effects you scrub, see Effects you can scrub.