Add frame pacing against the headset display clock - #22
Conversation
|
Those numbers are incredible :O I'll start review that tomorrow and merge it as soon as you're ready, thanks! |
FramePacer aligns server frame release with the headset display. It phase-locks a release timeline to the predicted display times reported by the client and wakes the application frame loop one render lead ahead of each display tick. Release ticks fire from a dedicated pacing thread that runs under the platform real-time scheduling class. The render lead requires no configuration. The pacer learns the required lead from client frame feedback, raises it when frames miss their display tick, and maintains a standing margin on links with recurring delivery bursts. A pose target servo tracks the observed display lag so that poses are predicted to the tick a frame actually displays on. Protocol additions: - Tracking timing fields that drive the timeline lock. - Per-frame feedback that carries the measured decode slack. - Timesync messages that establish the server-to-client clock offset. - A per-frame display target in the video headers. Client changes: - Decoded frames are latched by their intended display slot instead of the latest available image. - Every composited frame outcome is reported back to the server. Server changes: - The VideoToolbox RealTime rate-control hint is no longer requested, which shortens the encode callback.
cecaf74 to
656c60c
Compare
|
@demonixis @dingyifei did you have a chance to try it? Just want to know it works fine for you. :D |
|
It seems to work on my end after some tweaking and I think my game got smoother. The latency issues with my setup require some changes to be made for the frame pacing to work correctly. I tested with modified ALVR server and client so there are some confounding variables there. I'm in the process of moving encoding out of Rosetta, which will allow me to use H265 and reduce pipeline latency for gaming. Tried the frame pacer on a higher-latency streaming path and hit a case where the closed loop never engages. Sharing a root cause, a fix that works, and numbers, in case it's useful upstream. SetupEmbedded
So the display lag the pacer must model (predicted-display minus release tick) is ~55–70 ms = 4–5 display periods, versus the few-ms lag on a low-latency USB path. SymptomIn closed-loop mode the pacer never leaves the warming state:
Grid lock itself is fine ( Root causeThree constants (
What workedWidening the three envelopes so a deep pipeline can bootstrap, without changing shallow-path behavior (wider bounds are strictly permissive):
Result on the same hardware: floor Suggestion
Great news: After I move encode from rosetta to native and switching to HEVC, encode delay and decode queue delay are greatly reduced I think it is approaching usable level M2P (USB helping is because I've been using Wifi -> Router -> Wifi). Given most setups have m2p at 50ms~80ms, it is really close to that without fine tuning Btw the HEVC data produced didn't use your pacer, but a naive implementation, so your pacer implementation should result in even better perf! |
tl;dr Adds a server-side pacer that phase-locks frame release to the headset's vsync grid.
Results
Wi-Fi
ADB
Tested on Quest 3 + macOS 15 with default settings.
TODO