Low FPS on Windows due to recv_timeout quantization
Environment
- OS: Windows 11
- Weylus: 0.11.4
- Client: iPad
Summary
On Windows the video frame loop runs at roughly half the intended rate (~23 fps instead of 60). Capture and encoding are NOT the bottleneck — the pacing sleep is. recv_timeout on the video command channel is quantized by the default 15.6 ms Windows timer tick, so the idle wait that should be ~6.5 ms pushing the frame period from 16.67 ms to ~27–31 ms.
Per-frame breakdown (measured on Windows 11)
| metric | before fix | after `timeBeginPer |
|---|---|---|
| fps | 23.1 | 52.2 |
| period avg | 27.1 ms | 16.8 ms |
| wait avg | ~16–27 ms | 7.3 ms |
| capture avg | 5.4 ms | 4.9 ms |
| encode avg | 4.8 ms | 5.2 ms |
Capture + encode ≈ 10 ms, well inside the 16.67 ms budget. The entire deficit is the wait.
Root cause
The video loop paces frames via receiver.recv_timeout(timeout) (std::sync::mpsc → WaitForSingleObject on Windows). Timed waits round up to the current system timer tick, which is 15.6 ms by default. Measured in isolation on the same machine:
| requested timeout | actual, before | actual, after timeBeginPeriod(1) |
|---|---|---|
| 6 ms | 15.77 ms | 6.50 ms |
| 11 ms | 15.60 ms | 11.55 ms |
| 16 ms | 29.58 ms | 16.58 ms |
So a 6–11 ms pacing sleep lands on one tick (~15.6 ms) or two (~31 ms), roughly halving the frame rate.
Proposed fix
Raise the timer resolution once at startup and keep it for the process lifetime (the standard approach used by games/rendering apps):
// main.rs, at the top of main(), Windows only
#[cfg(target_os = "windows")]
{
winapi::um::timeapi::timeBeginPeriod(1);
}
Cargo.toml (winapi features):
winapi = { version = "...", features = [ ..., "mmsystem", "timeapi" ] }
Optionally pair with timeEndPeriod(1) on process exit.Verification
After the change the wait drops to ~7 ms and the while capture/encode times are unchanged —confirming the deficit was purely the quantized sleep.
Source: H-M-H/Weylus