Remove vsync and dirty-flag frame gating, optimize PBR sampling and lighting, add a direct-to-canvas loadout, and preserve camera state on viewer resize. Add regression coverage and update rendering documentation. Amp-Thread-ID: https://ampcode.com/threads/T-01a0b382-1e79-712b-abd1-d29201c9bb5e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
4.8 KiB
Worker message reference
All fields below are top-level beside type and request. Except for init and upload-texture, transfer lists are empty.
type |
Fields | Transfer list | Success result |
Constraints / effects |
|---|---|---|---|---|
init |
canvas: OffscreenCanvas, arenaBytes: number |
[canvas] |
{ buffer: SharedArrayBuffer, rows: Descriptor[], features: string[] } |
Once only; initializes WASM/WebGPU. features lists optional device features that were available (timestamp-query, indirect-first-instance). |
create-rows |
name, rows, stride, format |
— | { rows: descriptor, commit: boolean } |
Nonempty name; rows > 0; stride ≥16 and multiple of 16; format f32/u32/i32. Existing name may only retain format/stride and grow. commit: true means the rows moved: copy the old bytes into the descriptor and send commit-rows. |
create-rows-batch |
rows: Array<{name,rows,stride,format}> |
— | { rows: descriptor[] (input order), commit: string[] } |
Nonempty. Sequential; in-place creations/growths survive a later failure, staged relocations are discarded. commit lists the arrays that moved. Active GPU resources refresh once for in-place growth. |
commit-rows |
names: string[] |
— | undefined |
Publishes staged relocations after the caller copied them. Must name every staged array. Frees the old rows and refreshes bound GPU buffers once. While a relocation is staged the worker queues every other request until this arrives. |
delete-rows |
name |
— | undefined |
Not signals, not active slots, not referenced by active graph. |
allocate-object |
name, count: u32 = 1 |
— | { id, rows: descriptor, commit: boolean } |
Not signals; count > 0; reserves count contiguous rows starting at id; may grow. commit as in create-rows. |
delete-object |
name, id: u32 |
— | undefined |
ID must be the first row of an active span; the whole span is zeroed. |
sync-rows |
name, first: u32 = 0, count: u32 |
— | undefined |
Uploads rows first .. first + count of the array into every active GPU buffer bound to it (any sync). ROW_RANGE when the span exceeds the array. No-op without an active loadout. |
compile-graph |
serialized: string |
— | graph ID string | Parses and stores; same ID replaces previously stored graph. Does not activate it. |
switch-loadout |
id |
— | undefined |
Builds GPU resources and activates stored graph. Clears bundleDirty. |
upload-texture |
name, mipLevel: u32, image: ImageBitmap |
[image] |
undefined |
Uploads immediately if active graph has the texture and caches source for future switches. Source extent must fit destination/mip. |
delete-texture |
name |
— | undefined |
Deletes/closes cached mip sources; does not remove graph texture. |
play |
— | — | undefined |
Enables render-loop ticks; resets last-time baseline. |
pause |
— | — | undefined |
Stops loop updates/renders. |
set-fps |
fps: u32 |
— | undefined |
0 uncapped; maximum 1000. |
set-profiler |
enabled (boolean-coerced) |
— | boolean | Result says timestamp queries are supported. Enables only when requested and supported. |
Rust/WASM numeric conversion applies to u32 fields; callers should send finite nonnegative integers in range rather than rely on coercion.
Profiler events
When enabled and supported, the worker polls every 250 ms and may send an unsolicited message with no request field:
{
type: "profile",
stats: {
frame: 42,
milliseconds: 0.31,
readbackMilliseconds: 4.8,
adapter: "Adapter name · DeviceType · Backend",
canvas: { width: 1280, height: 720 },
passes: [{ name: "triangle", milliseconds: 0.31 }]
}
}
milliseconds is the sum of GPU timestamp durations across passes, not CPU frame time or the interval between frames. It excludes scheduling waits and readback latency; readbackMilliseconds reports the time from starting that frame to receiving its timestamp results.
One pass timing corresponds to a compiled execution; compatible adjacent render passes are merged into one execution labelled first … last (N draws). Samples are throttled and asynchronous, so they are diagnostics, not one event per frame. Disabling clears pending published statistics and the worker polling interval.
Texture lifecycle
Create an ImageBitmap, then relinquish it to the worker:
const image = await createImageBitmap(blob);
await request("upload-texture", { name: "albedo", mipLevel: 0, image }, [image]);
The name is a graph texture ID, not an arbitrary row name. Upload can precede graph activation because sources are cached. Non-transient textures with an unchanged descriptor can be retained across switches; cached levels are reapplied when needed. delete-texture only removes these cached sources.