Problem
On the 16 GB Apple M5, the macOS libmpv render path drives sustained CPU and GUI pressure during an 8-cell wall. MpvGLWidget._on_mpv_frame() currently records and emits a queued Qt signal for each libmpv update callback, while paintGL() invokes the native render API. We need to measure and bound that queue explicitly.
Evidence
- Software 8-cell run
20260828_161931: 1,191 main-loop stalls over about 3,000 active seconds, 32 cache-starvation freezes, and 206,131 VO frame-drop events.
- Hardware trial
20260828_172945: about 118,000 render calls in 600 seconds; the Hyperwall process averaged 2.14 CPU cores in powermetrics.
- Corrected normalized total render CPU was about 0.8275 core-equivalents for software versus 0.8313 for hardware, so a decoder toggle is not a demonstrated render-CPU fix.
Scope
- Add a thread-safe one-pending-update gate per
MpvGLWidget.
- Preserve newest-frame semantics without unbounded queued GUI work.
- Count callbacks, coalesced callbacks, queued notifications, paints, renders, and errors.
- Preserve the libmpv callback trampoline through teardown.
Acceptance criteria
- Tests cover callback bursts, callback/GUI races, shutdown gating, and newest-frame handling.
- A 60-120 second M5 profile shows bounded queued notifications.
- No render-context teardown regression or increase in maximum paint gap.
- The full repository test suite passes.
Problem
On the 16 GB Apple M5, the macOS libmpv render path drives sustained CPU and GUI pressure during an 8-cell wall.
MpvGLWidget._on_mpv_frame()currently records and emits a queued Qt signal for each libmpv update callback, whilepaintGL()invokes the native render API. We need to measure and bound that queue explicitly.Evidence
20260828_161931: 1,191 main-loop stalls over about 3,000 active seconds, 32 cache-starvation freezes, and 206,131 VO frame-drop events.20260828_172945: about 118,000 render calls in 600 seconds; the Hyperwall process averaged 2.14 CPU cores inpowermetrics.Scope
MpvGLWidget.Acceptance criteria