Visitar URL original
[WebGPU] Retain, replay and report render-pass command stream batches by matthargett · Pull Request #19084 · BabylonJS/Babylon.js · GitHub
Skip to content

[WebGPU] Retain, replay and report render-pass command stream batches - #19084

Open
matthargett wants to merge 3 commits into
BabylonJS:masterfrom
rebeckerspecialties:webgpu-command-stream-retention-replay
Open

matthargett wants to merge 3 commits into
BabylonJS:masterfrom
rebeckerspecialties:webgpu-command-stream-retention-replay

Conversation

@matthargett

Copy link
Copy Markdown
Contributor

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._pending gives the bgfx NativeEngine. A wrapper collected between append and flush can no longer leave a dangling id in pending words. From _recordCommands on, the host owns the lifetime for the rest of the pass (the BabylonNative decoder clones the resource handle at decode time).

Failure atomicity. _recordCommands is now documented as all-or-nothing: a false return 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. The Logger.Error entry 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.ts adds coverage for retention order and release after flush, replay order for indexed, non-indexed and indirect draws, a throwing recorder, the structured onDisabled report (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.

Popov72 and others added 2 commits October 29, 2026 13:00
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.
Copilot AI balanced review requested due to automatic review settings October 29, 2026 20:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 deltakosh self-assigned this Oct 29, 2026

@deltakosh deltakosh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 deltakosh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @matthargett,

The provider receiver fix resolves my previous concern, and the class-based regression test covers it. No further blocking issues found.

Thanks!

@deltakosh
deltakosh requested a review from Popov72 October 29, 2026 21:19

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants