Repository navigation
[WebGPU] Retain, replay and report render-pass command stream batches - #19084
matthargett wants to merge 3 commits into
Conversation
Initialize command-stream state only when a host installs a provider and submit no-provider draws directly. Preserve provider transition flushing and permanent native fallback, with focused draw and bundle coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Hosts that record batched render-pass command streams get an explicit ownership and recovery contract: - The stream retains the JS object of every backend resource a pending command word references (pipeline, buffers, bind groups) until the host has consumed the batch, so a wrapper collected between append and flush can no longer leave a dangling id in the words. - Recording is all-or-nothing: a host that returns false or throws from _recordCommands has recorded nothing and left the encoder usable. - A rejected batch is replayed through direct encoder calls from the words and the retained objects, in encoding order, before the stream disables itself. No draw of the frame is dropped, so render-once targets keep a complete result. - The provider can implement onDisabled(info) to receive the structured reason, the logged message, the host error and the number of draws replayed, for application telemetry. A throwing handler is logged and does not interrupt rendering. - Batches flush after 1024 draws so the word buffer and the retained-object array keep a fixed, reused capacity over long sessions. The wire protocol (opcodes, layouts, version 1) is unchanged, and none of this runs unless a provider is installed.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
deltakosh
left a comment
There was a problem hiding this comment.
Hello @matthargett,
Please invoke onDisabled on its provider in webgpuRenderPassCommandStream.ts:619-623: extracting the method loses this, so class-based handlers cannot report the failure (the catch then swallows it into a log line). Preserve the receiver, as getResourceId and getLastError already do, and add coverage for a handler accessing provider state.
Thanks!
deltakosh
left a comment
There was a problem hiding this comment.
Hello @matthargett,
The provider receiver fix resolves my previous concern, and the class-based regression test covers it. No further blocking issues found.
Thanks!
Follow-up to #19075 for the native command-stream contract questions raised there: resource ownership, failure atomicity and recovery scope. Stacked on #19076; its commit is the first one here and drops out on rebase once it merges.
What changes
Resource ownership. The stream retains the JS object of every backend resource a pending command word references (pipeline, index/vertex/indirect buffers, bind groups) until the host has consumed the batch, one entry per emitted word in encoding order. This is the guarantee
CommandBufferEncoder._pendinggives the bgfx NativeEngine. A wrapper collected between append and flush can no longer leave a dangling id in pending words. From_recordCommandson, the host owns the lifetime for the rest of the pass (the BabylonNative decoder clones the resource handle at decode time).Failure atomicity.
_recordCommandsis now documented as all-or-nothing: afalsereturn or an exception means nothing was recorded and the encoder is still usable. The stream relies on this to replay a rejected batch itself. Hosts that cannot roll back must validate the whole batch before applying it.Recovery. When a batch is rejected, the recorder throws, or the pass lost its recorder, the stream replays the batch through direct encoder calls from the words and the retained objects, in encoding order, before disabling itself. No draw of the frame is dropped, so render-once targets keep a complete result. Disabling stays permanent for the engine's lifetime, since a rejection past the protocol handshake means a broken host decoder.
Field diagnostics. The provider can implement
onDisabled(info), invoked once with a structured reason (protocol-mismatch,recorder-missing,recording-rejected,recording-threw), the logged message, the host's last error and the number of draws replayed, so integrated applications can report the fallback through their own telemetry. The hook is guarded: an exception inside it is logged and does not interrupt rendering. TheLogger.Errorentry is unchanged.Bounded batches. A batch flushes after 1024 draws (
WebGPURenderPassCommandStreamMaxDrawsPerBatch). The word buffer and the retained-object array stop growing past a fixed, reused capacity over long sessions, the bridge crossing is still amortized over up to 1024 draws, and the next batch re-emits state as after any flush.The wire protocol (opcodes, layouts, version 1) is unchanged. Browsers are untouched: none of this code runs unless a provider is installed.
Tests
webgpuRenderPassCommandStream.test.tsadds coverage for retention order and release after flush, replay order for indexed, non-indexed and indirect draws, a throwing recorder, the structuredonDisabledreport (including a throwing handler), protocol-mismatch reporting, and the batch bound. The 21 existing stream tests and the 12 draw-submission tests from #19076 pass unchanged;tsc, lint, format and the tree-shaking barrel checks pass.