You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Updated by Copilot on behalf of @bghgary]
## Context
Replaces the old per-thread encoder model
(`GetEncoderForThread`/`EndEncoders` + `UpdateToken` wrapping
`SafeTimespanGuarantor`) with a **single frame encoder** owned by
`DeviceImpl`, synchronized by a `FrameCompletionScope` RAII gate.
`SafeTimespanGuarantor`, `UpdateToken`, and the `Update` classes are
removed. `DeviceUpdate` remains as a no-op shim — removing it is an API
break and a separate PR (#1653).
## Model
`StartRenderingCurrentFrame` acquires the encoder (`bgfx::begin`) and
opens the gate; `FinishRenderingCurrentFrame` waits for all
`FrameCompletionScope`s to release, closes the gate, ends the encoder,
and submits the frame. All consumers (NativeEngine, Canvas, NativeXr)
read the shared encoder via `GetActiveEncoder()`. `SubmitCommands` and
`ReadTexture` each hold a stack-scoped `FrameCompletionScope`, so the
encoder can't be ended mid-work; when invoked outside a frame (e.g. an
XHR callback) the scope blocks until the next frame opens.
```
Main Thread JS Thread bgfx render thread
StartRenderingCurrentFrame()
m_frameEncoder = bgfx::begin(true)
m_frameBlocked = false; CV notify
tick frameStartDispatcher -----> RAF callbacks
submitCommands()
AcquireFrameCompletionScope (immediate; frame open)
GetEncoder() -> m_frameEncoder; draw...
scope released; pendingScopes-- -> CV notify
FinishRenderingCurrentFrame()
wait scopes == 0; m_frameBlocked = true
tick beforeRenderDispatcher
bgfx::end(m_frameEncoder)
Frame() -> bgfx::frame() ------------------------------> GPU submit & render
tick afterRenderDispatcher
```
## Synchronization rules
1. Single encoder, acquired before the gate opens (mutex gives memory
ordering) and ended only after all scopes release.
2. `SubmitCommands`/`ReadTexture` always hold a scope — the encoder
can't be ended mid-command, regardless of microtask draining or
reentrancy.
3. Apps must bracket any main-thread wait for JS work with
`Start`/`Finish`, or `SubmitCommands` blocks forever waiting for the
gate (HeadlessScreenshotApp, StyleTransferApp, UnitTests do this).
4. `Canvas::Flush` acquires a scope when called outside a frame, then
discards residual encoder state before nanovg rendering (the shared
encoder carries NativeEngine state).
## Notes
- `Tests.ExternalTexture.Msaa.cpp` opens the next frame before waiting
on `startup()` — a workaround for the legacy async `AddToContextAsync`
pattern, obsoleted once #1646's sync `CreateForJavaScript` lands.
- `Tests.ExternalTexture.RestoreAfterDeviceLoss` (added on master after
this branch) uses the same async pattern; the same frame-pump workaround
is applied here. Obsoleted once #1646's sync `CreateForJavaScript`
lands.
- bgfx: added `m_encoderBeginLock` in `bgfx::frame()` to prevent a
deadlock with `bgfx::destroy()`.
- Follow-up: removing the `DeviceUpdate` no-op shim is an API-breaking
change handled in #1653.
---------
Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com>
Co-authored-by: Gary Hsu <bghgary@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0 commit comments