Press read() to wait for the next capture of a buffer containing the frame index. Set 60 Hz, a slow map and one staging slot to see captures skipped while the buffer maps.
The GPU executes commands after the CPU records them. A result such as the object under a cursor therefore reaches the CPU in a later frame. TypeGPU’s buffer.read() creates a staging buffer, submit and promise per call. A readback reuses a ring of staging buffers, records copies in your frame’s encoder and maps them after submit.
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.
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.
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 it
constscheduler:coroutines.Coroutines
scheduler.
Coroutines.run(frame: frames.Frame): void
Resumes every coroutine whose wait is complete. Call while a frame is recording. Coroutine and
wait errors go to the coroutine's yield* or to onError, rather than escaping run.
run(
frame:frames.Frame
frame);
const
constpolled:number[]|undefined
polled=
constprobe:readback.Readback<number[]>
probe.
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.
take();// polled: each new value once, or undefined
Waits for a promise, such as a readback's next() or pipelines.compile. Resumes at the first
run after it settles, with its value or its error.
@param ― promise the promise to wait for
@returns ― a wait to yield*
until(
constprobe:readback.Readback<number[]>
probe.
Readback<number[]>.next(): Promise<number[]>
Returns a promise for the value from the next capture, and requests that capture. Promises
asked for before one capture share its copy. Rejects if the readback is destroyed or mapping
fails. Waits indefinitely without a capture. In a coroutine, wait with
yield* coroutines.until(probe.next()).
capture(frame) copies the whole buffer at that point in the command order. Place it after the pass that fills a counter, or before the passes when a picker must read the frame already on screen.
It records only while a next() waits, after a request(), or when hz is due.
While every staging buffer is mapping, it is skipped and tried again next frame.
Record the pass that fills the buffer every frame and let hz pick the frames that copy; a pass that counts costs microseconds. Gating that pass with a clock of your own makes two cadences that disagree, so a copy can land in a frame the pass skipped.
A frame captures once; a second call does nothing.
A frame that is never submitted hands its reads to the next capture.
latest holds the newest value. latestFrame identifies the frame that copied it. Reads return the buffer’s value transformed by decode, if supplied. If decode throws, reads waiting on that copy fail with its error.
take() returns latest once per completed copy, then undefined until another copy completes. Two callers split those values. If both need every value, have each read latest and track latestFrame.
next() returns a promise for the next capture’s value. Promises asked for until then share one copy. It stays pending until a capture completes. In a coroutine, wait for it with yield* coroutines.until(probe.next()).
request() makes the next capture copy with no read waiting, for a buffer written on demand rather than every frame. A picker’s pick requests its own copy. A capture skipped while every staging buffer maps leaves the request open.
destroy(), or using, destroys the staging buffers and rejects waiting and later promises. capture then records nothing, and take and latest give undefined.
For counters that only increase, compare each read with the last value to handle late or repeated reads. Events does that for named counters.
Reads decode the whole schema into JavaScript values, one object per struct, so a large array of structs allocates thousands of objects per read. Pass bytes: true to skip decoding. decode then receives the copied bytes, which follow the schema’s WGSL layout with its padding. They are valid only during the call, so read them into arrays of your own. Without decode, reads return a copy as an ArrayBuffer.
const
constCell:d.WgslStruct<{
level:d.F32;
source:d.U32;
}>
Cell=
import d
d.
struct<{
level:d.F32;
source:d.U32;
}>(props:{
level:d.F32;
source:d.U32;
}):d.WgslStruct<{
level:d.F32;
source:d.U32;
}>
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.
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.
@param ― source the buffer it copies
@returns ― a reader you capture into and destroy
create(
constroot:TgpuRoot
root,
constcells:TgpuBuffer<d.WgslArray<d.WgslStruct<{
level:d.F32;
source:d.U32;
}>>>&StorageFlag
cells,{
BytesOptions<T= ArrayBuffer>.bytes: true
Reads the copied bytes without decoding them through the buffer's schema.
Converts the copied bytes to the value returned by next and latest, such as typed arrays of
your own. The bytes are valid only during the call, so copy what you keep. If it throws, reads
for that copy fail with the error. Default a copy of the bytes.
Call reset() after clearing a buffer or reloading a scene to discard earlier copies. It sets latest to undefined and latestFrame to -1, and moves waiting reads to the next capture.
To restart counters, clear the buffer in the frame, before the passes that count, and reset(). Otherwise a copy made before the clear can land after it, and counters that take differences run backwards.
A late map never replaces a newer latest.
A promise that already resolved keeps its value, even if its coroutine resumes after a reset. Compare a held result with latest.
A table’s changes reach the GPU at the first submit after its flush. Compare latestFrame with that frame’s index when a value must be newer than a change.
Device loss rejects every map. Promises waiting on a copy reject with the map’s error, later captures also fail, and latest keeps its last value. Create the modules again on another root to recover.