Sets the texture format, as a canvas preferred format would.
pixelRatio?
number= 1
Sets the reported pixelRatio, as for a canvas on a phone.
owns
a texture
An offscreen surface's, replaced when it resizes.
lifecycle
✓destroy()✓[Symbol.dispose]
WGSL in log.shaders
Press frame again to see that a steady frame allocates nothing, with changed counters highlighted. Reset and run a frame before initAsync() to see compilation on first use. steps sets how many fixed steps a frame records.
A GPU module can record a dispatch in the wrong pass, submit unexpectedly or generate invalid WGSL. recordingRoot() lets a test inspect recorded commands in Node. It creates a TypeGPU root on a stand-in device that stores WGSL and logs commands without executing them.
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
shaders: the WGSL of every shader module, in creation order.
events: begin-compute, dispatch, end-compute, begin-render, draw, copy-buffer, submit and more.
state: set-pipeline and set-bind-group for each setPipeline and setBindGroup a pass receives, in order. A draw with the same pipeline as the draw before it adds none.
writes: every queue.writeBuffer, in order, with its buffer, offset and size in bytes.
buffers, textures, views, bindGroups, samplers, querySets and pipelines: how many of each the device created, and counts() all seven at once. submissions: the queue submissions.
alive(): the buffers, textures and query sets created and not yet destroyed, as [Buffer "label"], in creation order.
bytes(buffer): a TypeGPU buffer’s or GPUBuffer’s contents after the queue writes and the copies and clears submitted so far. It returns the bytes themselves, so a test can write into them what a kernel would have, such as a counter.
TypeGPU makes GPU objects the first time something uses them, so the counts grow when a module first records, not when you create it.
recordingRoot({ features: ['timestamp-query'] }) reports device features, for code that branches on them.
Resolution errors, including missing fills, throw as usual. The recording device also throws immediately for errors a GPU reports later. These include missing usage flags, writes or copies out of bounds or not aligned to 4 bytes, a second pending mapAsync, submitting with an open pass and submitting a destroyed buffer.
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Used when you want to check that two objects have the same value.
This matcher recursively checks the equality of all fields, rather than checking for object identity.
Also check log.shaders for unwanted WGSL, such as read_write storage in a vertex shader. Inspect upload sizes in log.writes or contents through bytes. Use log.submissions to check that only your frame submits:
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Allocates GPU buffers and a CPU copy of host rows. Throws unless capacity is an integer in [1,
2^20].
@param ― def the definition its shaders were written against, from table.define
@returns ― a table you destroy
create(
constroot:TgpuRoot
root,
constEnemy:table.TableDef<"Enemy",{
radius:d.F32;
},{}>
Enemy,{
Options<T extends AnyTableDef>.capacity: number
Sets the table capacity for its lifetime. Must be an integer in [1, 2^20], with slots from 0
to capacity - 1. A row id uses enough bits for the slot and the rest for its generation. The
generation has 29 bits at 8 rows and 12 bits at 2^20 rows.
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.
Code that draws to the screen takes a Surface. offscreenSurface(root, { width, height, format }) provides one backed by a texture on a recording or real root. Tests can run the same scene frames without a canvas.
Creates a surface backed by a texture for running scene frames without a canvas. Accepts a
recording root or a real device. view returns one view per texture. destroy destroys the
texture, and later calls to view throw.
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Creates a surface backed by a texture for running scene frames without a canvas. Accepts a
recording root or a real device. view returns one view per texture. destroy destroys the
texture, and later calls to view throw.
@param ― root any root, recording or real
@returns ― a surface you draw into and destroy
offscreenSurface(
constroot:TgpuRoot
root,{
OffscreenSurfaceOptions.width: number
Sets the texture width in pixels.
width:64,
OffscreenSurfaceOptions.height: number
Sets the texture height in pixels.
height:36,
OffscreenSurfaceOptions.format: GPUTextureFormat
Sets the texture format, as a canvas preferred format would.
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)=>
constscene:{
frame(frame:Frame):void;
}
scene.
functionframe(frame:Frame):void
frame(
frame:frames.Frame
frame)).
Frames.run(seconds: number): boolean
Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing
seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns
false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If
any throw, the frame still counts and run rethrows their errors.
run(0);
setSize({ width, height }) replaces the texture, as a canvas does on a new layout; targets that follow the surface rebuild at their next fit(). It takes the same fixed size as a canvas surface’s setSize.
surface.texture can be copied from, so a test on a real GPU can read its pixels.
pixelRatio is 1 unless you give one, such as pixelRatio: 3 for a scene that draws hairlines on a phone.
It pauses and resumes like a canvas surface, so a test can stage a scene on it or host one.
steppedSchedule() lets a test run animation frames requested by a host or animate. step(0, 16) runs one at 0 ms and the next at 16 ms. waiting reports whether a frame is requested.
const
constdisplay:SteppedSchedule
display=
functionsteppedSchedule():SteppedSchedule
Creates a schedule stepped by hand. Each time passed to step runs the last requested animation
frame, as requestAnimationFrame would at that time.
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
Supplies animation frames. Use runtimes.vsync(worker) on a React Native worker runtime, or a
schedule you step by hand in a test. Default requestAnimationFrame.
Node and dawn.node have no createImageBitmap, so a mesh pool can’t decode a glTF model’s images and add throws. Give the scene a decode option that it hands to meshPool.create, and pass a stub from the test, such as a white pixel for every image; the page leaves it out:
Creates a surface backed by a texture for running scene frames without a canvas. Accepts a
recording root or a real device. view returns one view per texture. destroy destroys the
texture, and later calls to view throw.
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Creates a surface backed by a texture for running scene frames without a canvas. Accepts a
recording root or a real device. view returns one view per texture. destroy destroys the
texture, and later calls to view throw.
@param ― root any root, recording or real
@returns ― a surface you draw into and destroy
offscreenSurface(
constroot:TgpuRoot
root,{
OffscreenSurfaceOptions.width: number
Sets the texture width in pixels.
width:64,
OffscreenSurfaceOptions.height: number
Sets the texture height in pixels.
height:36,
OffscreenSurfaceOptions.format: GPUTextureFormat
Sets the texture format, as a canvas preferred format would.
The scene calls meshPool.create(root, { capacity: 32, decode: options.decode }). A model whose images are already { width, height, pixels } needs no decode, so a test can also swap each image for the white pixel before add.
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Creates a surface backed by a texture for running scene frames without a canvas. Accepts a
recording root or a real device. view returns one view per texture. destroy destroys the
texture, and later calls to view throw.
@param ― root any root, recording or real
@returns ― a surface you draw into and destroy
offscreenSurface(
constroot:TgpuRoot
root,{
OffscreenSurfaceOptions.width: number
Sets the texture width in pixels.
width:64,
OffscreenSurfaceOptions.height: number
Sets the texture height in pixels.
height:36,
OffscreenSurfaceOptions.format: GPUTextureFormat
Sets the texture format, as a canvas preferred format would.
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)=>
constscene:{
frame(frame:Frame):void;
}
scene.
functionframe(frame:Frame):void
frame(
frame:frames.Frame
frame));
constloop:frames.Frames
loop.
Frames.run(seconds: number): boolean
Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing
seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns
false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If
any throw, the frame still counts and run rethrows their errors.
run(0);
constloop:frames.Frames
loop.
Frames.run(seconds: number): boolean
Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing
seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns
false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If
any throw, the frame still counts and run rethrows their errors.
run(1/60);
const
constbefore:RecordingCounts
before=
constlog:RecordingLog
log.
RecordingLog.counts(): RecordingCounts
Returns counts of created objects. Steady frames should leave these counts unchanged.
counts();
constloop:frames.Frames
loop.
Frames.run(seconds: number): boolean
Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing
seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns
false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If
any throw, the frame still counts and run rethrows their errors.
run(2/60);
constloop:frames.Frames
loop.
Frames.run(seconds: number): boolean
Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing
seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns
false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If
any throw, the frame still counts and run rethrows their errors.
Used when you want to check that two objects have the same value.
This matcher recursively checks the equality of all fields, rather than checking for object identity.
A frame after a resize creates the new targets and the bind groups that read them, so take the counts once the size has settled.
Counts include only GPU objects. They omit JS allocations such as the handle returned by .with(...). Bind groups at setup, then set the pipeline on the pass during a frame.
A module’s destroy() frees every buffer, texture and query set it made, as its card’s owns lists. Take log.alive() before the module is made, run some frames, destroy it and compare:
Creates a recording WebGPU test double under a TypeGPU root. TypeGPU resolves shaders to WGSL.
The double keeps the WGSL and records commands without compiling or executing shaders. Use it to
check resources, pipelines and recording order without a GPU. Queue writes take effect, and
submitted buffer copies and clears update bytes, so tests can check uploads and readbacks.
Throws for invalid mapping usage, out-of-bounds or unaligned copies and writes, missing resource
usage flags, writes to destroyed buffers, and mapping in the wrong state. Also throws for
non-attachment render targets, bind groups with incompatible resource usages, finishing an
encoder with an open pass, and submitting destroyed resources. Throws at the call, where a
device would report most of these errors later. Reports WebGPU's default limits and records
render bundles, which replay into the pass that executes them. A device member it lacks throws
an error that names it.
@returns ― the root, a log of what it saw, and the bytes it holds
Creates a surface backed by a texture for running scene frames without a canvas. Accepts a
recording root or a real device. view returns one view per texture. destroy destroys the
texture, and later calls to view throw.
@param ― root any root, recording or real
@returns ― a surface you draw into and destroy
offscreenSurface(
constroot:TgpuRoot
root,{
OffscreenSurfaceOptions.width: number
Sets the texture width in pixels.
width:64,
OffscreenSurfaceOptions.height: number
Sets the texture height in pixels.
height:36,
OffscreenSurfaceOptions.format: GPUTextureFormat
Sets the texture format, as a canvas preferred format would.
format:'bgra8unorm'});
const
constbefore:string[]
before=
constlog:RecordingLog
log.
RecordingLog.alive(): string[]
Lists buffers, textures and query sets that have not been destroyed, in creation order, as
[Buffer "label"]. Compare the list before createScene and after scene.destroy() to
detect leaks.
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)=>
constglow:{
record(frame:Frame):void;
destroy():void;
}
glow.
functionrecord(frame:Frame):void
record(
frame:frames.Frame
frame));
for(let
let i:number
i=0;
let i:number
i<10;
let i:number
i++)
constloop:frames.Frames
loop.
Frames.run(seconds: number): boolean
Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing
seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns
false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If
any throw, the frame still counts and run rethrows their errors.
Lists buffers, textures and query sets that have not been destroyed, in creation order, as
[Buffer "label"]. Compare the list before createScene and after scene.destroy() to
detect leaks.
Used when you want to check that two objects have the same value.
This matcher recursively checks the equality of all fields, rather than checking for object identity.
A recording root resolves WGSL without compiling it. Add *.test.gpu.ts tests on dawn.node to catch compiler errors and check shader results. Create their roots with gpuRoot() from @typegpu/kit/testing/dawn:
Creates a root on a dawn.node device. Call this inside a test. At test completion, it fails on
uncaptured WebGPU errors not consumed by takeErrors and destroys the device. Calling
root.destroy() leaves device destruction to this cleanup. The first call creates the Dawn
instance.
@param ― options.features features the device must have, which throw when the adapter lacks one
@returns ― a root whose device the test's end destroys
Creates a surface backed by a texture for running scene frames without a canvas. Accepts a
recording root or a real device. view returns one view per texture. destroy destroys the
texture, and later calls to view throw.
@param ― root any root, recording or real
@returns ― a surface you draw into and destroy
offscreenSurface(
constroot:GpuRoot
root,{
OffscreenSurfaceOptions.width: number
Sets the texture width in pixels.
width:64,
OffscreenSurfaceOptions.height: number
Sets the texture height in pixels.
height:36,
OffscreenSurfaceOptions.format: GPUTextureFormat
Sets the texture format, as a canvas preferred format would.
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:GpuRoot
root,(
frame:frames.Frame
frame)=>
constscene:{
frame(frame:Frame):void;
}
scene.
functionframe(frame:Frame):void
frame(
frame:frames.Frame
frame)).
Frames.run(seconds: number): boolean
Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing
seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns
false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If
any throw, the frame still counts and run rethrows their errors.
run(0);// and validates the frame here
});// fails here on any uncaptured WebGPU error, and destroys the device
gpuRoot({ features }) throws when the adapter lacks one of features, so a test never runs a fallback it didn’t mean to. optionalFeatures are requested where the adapter has them, limits are the device’s required limits, and dawn holds flags for the Dawn instance, such as 'enable-dawn-features=allow_unsafe_apis'.
Call it inside a test: when the test ends, any uncaptured WebGPU error fails it, and the device is destroyed. A test that expects errors takes them first with takeErrors(), which waits for the queue and returns the errors so far:
Waits for the queue and returns pending uncaptured WebGPU errors, then clears them. Use this
for expected errors in a test.
takeErrors()).
Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@param ― separator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
On a machine without a GPU, such as a CI runner, gpuRoot({ dawn: ['backend=null'] }) uses Dawn’s null backend. It compiles WGSL and validates every call at the default limits, and executes nothing, so a test that reads results needs a real adapter.
GPU tests run one file at a time. They catch WGSL compiler errors, binding and format limits, and incorrect shader results. To count pipelines, events and submissions on a GPU, use gpu-debug’s instrument.
Both levels load @typegpu/kit/testing/warnings, which fails any test during which TypeGPU warned, such as about >> on a u32. To let some through, a setup file of your own calls failOnWarnings({ allow: [/deprecated/] }) instead and disposes of what it returns in afterAll, which puts console.warn back.
For @typegpu/kit/testing/dawn, install its optional peers, webgpu for dawn.node and vitest, as dev dependencies. @typegpu/kit/testing/warnings needs only vitest. pnpm 11 stops an install at a build script it hasn’t been told about, and webgpu has one, which only clears macOS’s quarantine flag from a downloaded dawn.node. Allow it, or deny it:
A recording root keeps no texture contents, so a test that reads pixels runs on gpuRoot().
A recording root reports WebGPU’s default limits. Pass recordingRoot({ limits }) to reach a size guard. A device method it lacks throws, naming the method; run that test on gpuRoot().
Maps on a recording root complete in a microtask. To test code that depends on readback taking several frames, use a stand-in that delays mapping.