@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
89 lines (88 loc) • 5.89 kB
TypeScript
/**
* # WebGPU Debug Marker Implementation — Problem Statement
*
* ## Background
*
* WebGPU exposes `pushDebugGroup` / `popDebugGroup` on two distinct objects:
* - The **render encoder** (`GPUCommandEncoder`), which lives for the lifetime of an entire
* submission (one call to `flushFramebuffer`).
* - The **render pass encoder** (`GPURenderPassEncoder`), which lives only for the duration of
* a single render pass.
*
* WebGPU requires that every push/pop pair is issued on the **same** object, and that all open
* groups are closed before the object ends. Violating either rule is a validation error.
*
* ## The Core Problem
*
* From the Babylon.js side, the user calls `_debugPushGroup` / `_debugPopGroup` at arbitrary
* points in time — completely independently of whether a render pass is currently active or not.
* This creates several mismatched-context scenarios that must be handled transparently.
*
* ## Scenario 1 — Push on render encoder, pop inside a render pass
*
* 1. User calls `_debugPushGroup` while no render pass is active → group is pushed on the
* render encoder and recorded in `_debugMarkersEncoderGroups`.
* 2. A render pass starts (`_currentRenderPass` becomes non-null).
* 3. User calls `_debugPopGroup` while the render pass is active. The pop must logically target
* the render encoder group, but we cannot call `popDebugGroup` on the render encoder while a
* render pass is active (the pass is the live object).
* 4. We detect this mismatch because `_debugMarkersPassGroups` is empty (no group was pushed
* during this render pass), so we increment `_debugMarkersPendingEncoderPops` instead of
* issuing the pop immediately.
* 5. When the render pass ends (`_endCurrentRenderPass`), `_debugPendingPop` drains the
* `_debugMarkersPendingEncoderPops` counter and issues the deferred pops on the render encoder.
*
* **Edge case — deferred pop followed by a render-pass push:** if the user pushes another group
* during the same render pass *after* the deferred pop was recorded, that new group goes into
* `_debugMarkersPassGroups`. When the pass ends, `_debugPopBeforeEndOfEncoder` auto-pops the
* pass-level group, leaving it floating in `_debugMarkersPassGroups`. `_debugPendingPop` then
* pops from `_debugMarkersEncoderGroups` — a separate array that can never contain pass-level
* entries — so the floating pass-level group is preserved intact and will be re-pushed on the
* next render pass by `_debugPushAfterStartOfEncoder`.
*
* ## Scenario 2 — Push inside a render pass, render pass ends before pop
*
* 1. User calls `_debugPushGroup` while a render pass is active → group is pushed on the render
* pass encoder and recorded in `_debugMarkersPassGroups`.
* 2. The render pass ends (e.g. a new render target is bound) before the user calls
* `_debugPopGroup`. All open render-pass groups must be closed before
* `GPURenderPassEncoder.end()` is called.
* 3. `_debugPopBeforeEndOfEncoder` auto-pops every group in `_debugMarkersPassGroups` from the
* render pass encoder. The names remain in `_debugMarkersPassGroups` (the array is not
* cleared) so the logical nesting is preserved and the groups are now "floating".
* 4. After the next render pass is created, `_debugPushAfterStartOfEncoder` re-pushes all
* floating pass groups onto it, so that when the user eventually calls `_debugPopGroup`
* there is always a matching push on the current live object.
*
* ## Scenario 3 — `flushFramebuffer` called mid-frame (multiple times per frame)
*
* `flushFramebuffer` submits all recorded GPU commands and creates a brand-new pair of encoders.
* It can be called more than once during a single Babylon.js frame (e.g. for read-back
* operations, multi-view rendering, or explicit flushes). Each call:
* 1. Ends the current render pass if any (`_endCurrentRenderPass`), which triggers the
* render-pass auto-pop and pending-pop handling described above.
* 2. Calls `_debugPopBeforeEndOfEncoder` to close every still-open group on the render encoder
* before it is finalised and submitted. Only encoder-level groups (`_debugMarkersEncoderGroups`)
* are popped from the encoder; pass-level groups (`_debugMarkersPassGroups`) are already
* floating after step 1 and were never pushed on the encoder, so they are left alone.
* 3. Creates new `_uploadEncoder` / `_renderEncoder` instances.
* 4. Calls `_debugPushAfterStartOfEncoder` to re-open the encoder-level groups on the new render
* encoder, so the user's subsequent pops still find a matching push.
* When `flushFramebuffer` is called from `endFrame` (the last flush of the frame), both arrays
* are cleared (`_debugMarkersEncoderGroups` and `_debugMarkersPassGroups`) because any groups
* still open at end-of-frame are considered abandoned.
*
* ## State variables
*
* - `_debugMarkersEncoderGroups` — names of groups currently open on the render encoder (pushed
* while no render pass was active). These are re-pushed on the new render encoder after each
* mid-frame flush.
* - `_debugMarkersPassGroups` — names of groups pushed while a render pass was active. While a
* pass is live these are open on that pass encoder. When a pass ends they become "floating" —
* auto-popped from the finished pass but not yet on any GPU object — and remain here until the
* next render pass starts and `_debugPushAfterStartOfEncoder` re-opens them on it.
* - `_debugMarkersPendingEncoderPops` — count of user pops that arrived while a render pass was
* active but whose matching push lived on the render encoder. These pops are deferred and
* replayed on the render encoder once the render pass ends.
*/
export {};