Skip to content

perf(capture): poll the framebuffer seed at 240 Hz - #115

Draft
szdziedzic wants to merge 1 commit into
szdziedzic-claude/webrtc-send-on-arrivalfrom
szdziedzic-claude/capture-poll-240hz
Draft

szdziedzic wants to merge 1 commit into
szdziedzic-claude/webrtc-send-on-arrivalfrom
szdziedzic-claude/capture-poll-240hz

Conversation

@szdziedzic

@szdziedzic szdziedzic commented Aug 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The IOSurface seed poll runs at 240 Hz instead of 60 Hz. SERVE_SIM_CAPTURE_POLL_HZ overrides the rate within [30, 480] for A/B runs; unparseable values keep the default.

Why

On the virtualized hosts EAS runs, SimulatorKit frame callbacks arrive below display cadence (#92 measured ~20 fps idle / ~36 fps scrolling delivered to the encoder), so the poll is often the real change detector. At 60 Hz it added 0–16.7 ms (~8 ms on average) between a framebuffer change and its capture. IOSurfaceGetSeed is a shared-memory read and the poll skips unchanged surfaces, so a faster timer is almost free and caps that delay at ~4 ms.

How

  • SimulatorCapturePollPolicy resolves the rate once from the environment (pollsPerSecond(environmentValue:), clamped) and exposes the interval; FrameCapture arms its strict DispatchSourceTimer from it and logs the rate at start.
  • The 5 fps idle floor and the poll-lateness accounting are unchanged; pollTicks simply advances four times faster.
  • Verified: policy tests for default, parsing and clamping; idle CPU with capture running on an M5 Pro went from 0.07 s to 0.11 s per 10 s wall (about 0.4% of one core).

Part of the tap-to-photon stack (bottom → top): latency probe → send frames on arrival → 240 Hz seed poll → bitrate floor → playout window → data-channel input → lossy moves lane. Everything is on by default, so a package built from any layer works with the unchanged EAS launch flags (--transport webrtc --webrtc-codec vp8 --max-dimension 960 --video-bitrate 6000000 --video-fps 60).

Measured with scripts/latency-probe (M5 Pro, loopback, iPhone 17 / iOS 26.5, 25 taps): 0.1.51 p50 32–37 ms / p95 ≈ 43 ms → top of stack p50 27 ms / p95 38 ms, no outliers above 40 ms.

🤖 Generated with Claude Code

SimulatorKit frame callbacks arrive below display cadence on the virtualized
hosts EAS runs (#92 measured ~20 fps idle / ~36 fps scrolling delivered to the
encoder), so on those hosts the IOSurface seed poll is often the real change
detector. At 60 Hz it added 0–16.7 ms (~8 ms on average) between a
framebuffer change and its capture. IOSurfaceGetSeed is a shared-memory read
and the poll skips unchanged surfaces, so the timer rate is almost free: poll
at 240 Hz by default and cap that delay at ~4 ms.

`SERVE_SIM_CAPTURE_POLL_HZ` overrides the rate within [30, 480] so a
deployment can A/B it without a release; unparseable values keep the default.
The idle floor (5 fps) and the poll-lateness accounting are unchanged; the
`pollTicks` counter simply advances four times faster.

Measured on an M5 Pro with an idle simulator and capture running: the
serve-sim process used 0.07 s CPU per 10 s at 60 Hz and 0.11 s at 240 Hz —
about 0.4% of one core for a 4x faster detector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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