Skip to content

osr: Fix external BeginFrame timing, optional tick age on SendExternalBeginFrame (see #4311) - #4312

Open
super-ienien wants to merge 2 commits into
chromiumembedded:masterfrom
super-ienien:ebf-timing-v4
Open

super-ienien wants to merge 2 commits into
chromiumembedded:masterfrom
super-ienien:ebf-timing-v4

Conversation

@super-ienien

@super-ienien super-ienien commented Oct 13, 2026 •

Copy link
Copy Markdown

Fixes the BeginFrameArgs that SendExternalBeginFrame() hands to viz (time of the call, null deadline, fixed 60 Hz interval) and the dropping of a tick that arrives while the previous one is pending. Analysis and measurements in #4311.

What this does

  • SendExternalBeginFrame() keeps its signature. The interval now follows windowless_frame_rate and the deadline is frame_time + interval, as with Chromium's own BeginFrame sources.
  • New C++ overload SendExternalBeginFrame(int64_t tick_age_us) (C API send_external_begin_frame2), experimental: the age of the external clock tick at the time of the call. CEF dates the frame on the tick instead of on the wake-up time of the thread that observed it, derives the interval from the successive tick times (median of the last five plausible deltas; windowless_frame_rate for the first tick) and sets the deadline one interval after the tick. A variable refresh rate is followed within a few ticks. 0 behaves like the no-argument form.
  • The external BeginFrame path of CefRenderWidgetHostViewOSR moves to a small CefExternalBeginFrameSourceOSR, modeled on ui::ExternalBeginFrameAdapter (Wayland): sequence numbers from BeginFrameArgsGenerator, wait_for_all_frame_sinks = false, vsync parameters following the interval.
  • One BeginFrame in flight per controller (viz reports a bad message otherwise). A tick that arrives while another is in flight is kept one deep with its own time stamps and issued at the acknowledgement if its interval is not half over, otherwise dropped like a missed vsync. The gate is closed exactly while the reply callback is alive: it reopens on the acknowledgement, or through a drop handler when the reply is destroyed without running (GPU process loss, or a tick that ui::Compositor kept pending until its frame sink existed and then replaced). It is not reset when the controller client is recreated, since the compositor issues its pending tick right after that. A warning is logged if a tick stays unacknowledged for more than a second.
  • The minimum capture period of the video consumer is 95 % of the tick interval, so that the capturer does not refuse a tick-paced frame when the tick dates drift.

Measured (details in #4311): DeckLink 1080i50 output under CPU load, 8 interleaved passes per arm, same binary. Plain call vs tick age from the card's clock: 29 → 7 animation hiccups, 16 → 0 missing SDI fields, 35 → 8 repeated fields. Idle, the two are indistinguishable.

Testing

Built and run on branch 8078 (156.0.7+chromium-156.0.8078.17), Windows 11, D3D12 shared textures, windowless browser clocked by the monitor vsync and by SDI cards (Blackmagic DeckLink, Deltacast). Cold starts (20) clean. On a GPU process loss the BeginFrames now resume with the restart of the GPU process (about 0.2 s) instead of staying blocked until the controller client is recreated (70 s observed with the reset); recovering the content after a GPU process crash (frames referencing shared images of the dead process for a while) is a separate matter, not addressed here. Not yet compiled against current master (same Chromium compatibility; the patch applies cleanly). The C API wrappers are generated at build time and are not part of this change.

…mbedded#4311)

SendExternalBeginFrame() handed viz a BeginFrame dated at the time of the
call, with a null deadline and an interval fixed to 60 Hz, and dropped a tick
that arrived while the previous one was pending. A <video> that is not at
the cadence of a 60 Hz tick stutters, and a page and a <video> that both
animate are drawn one tick apart.

- SendExternalBeginFrame() keeps its signature. The interval follows
  CefBrowserSettings.windowless_frame_rate and the deadline is
  frame_time + interval, as with Chromium's own BeginFrame sources.
- New C++ overload SendExternalBeginFrame(int64_t tick_age_us), experimental
  (C API send_external_begin_frame2): the age of the external clock tick at
  the time of the call. The frame is dated on the tick instead of on the
  wake-up time of the thread that observed it; the interval is derived from
  the successive tick times (median of the last five plausible deltas,
  windowless_frame_rate for the first tick) and the deadline is one interval
  after the tick. A variable refresh rate is followed within a few ticks.
  0 behaves like the no-argument form.
- The external BeginFrame path of CefRenderWidgetHostViewOSR follows
  ui::ExternalBeginFrameAdapter: sequence numbers from
  BeginFrameArgsGenerator, wait_for_all_frame_sinks = false, vsync
  parameters following the interval, and a tick arriving while another is
  in flight kept one deep and issued at the acknowledgement if its interval
  is not half over.
- The minimum capture period of the video consumer is 95 % of the tick
  interval, so that the capturer does not refuse a tick-paced frame when the
  tick dates drift.
@super-ienien super-ienien changed the title osr: Add SendExternalBeginFrameWithTiming (see #4311) osr: Fix external BeginFrame timing, optional tick age on SendExternalBeginFrame (see #4311) Oct 15, 2026
The gate that keeps one IssueExternalBeginFrame in flight reopened only on
the acknowledgement or when the controller client was recreated. A reply
destroyed without running, as on GPU process loss, kept it closed until
then, with the output frozen. Resetting the gate when the client is
recreated was also wrong: ui::Compositor keeps a tick received before its
frame sink exists and issues it right after creating the client, so a
second BeginFrame could overlap it, which viz reports as a bad message.

- Wrap the reply with mojo::WrapCallbackWithDropHandler, so that the gate
  is closed exactly while the reply is alive.
- Leave the gate, the queued tick and the sequence numbers untouched when
  the controller client is recreated.
- Log a warning when a tick stays unacknowledged for more than a second.

Assisted-by: Claude Code

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.

1 participant