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.
Environment
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_timeouton 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)
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:timeBeginPeriod(1)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):
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.