feat(host): a frame limiter for the game, not for the stream
PUNKTFUNK_MAX_FPS caps how fast the compositor lets the game render. It deliberately does NOT touch the session: the client still negotiates and receives its full rate, because the encode loop re-encodes the held frame whenever the compositor produced no new one, and a repeat of an unchanged picture is an almost-empty P-frame. So a 60-capped game on a 120 Hz session still puts 120 frames a second on the wire — and the GPU time the game gives up goes to capture and encode instead, which is the point (on a laptop or a handheld, it goes to heat and battery too). Capping the stream would be a different and mostly unwanted feature: it hands the client fewer frames than it asked for and saves the game's GPU nothing. gamescope is the compositor that has the lever, so it is the one that gets it: the rate becomes --nested-refresh, which is what gamescope clamps the game to. All three sessions we own take it — the bare spawn's -r, the managed gamescope-session-plus wrapper's PF_HZ, and the SteamOS PATH shim's. In the managed path the limit lands on PF_HZ alone and NOT on CUSTOM_REFRESH_RATES, so the mode the session advertises stays the client's — that is what makes games see the real refresh rather than the box's EDID. Two scoping notes. Under gamescope the cap is the nested output's rate, so everything it composites moves at it, not the game alone; there is only the one output. And the attach path (PUNKTFUNK_GAMESCOPE_NODE) mirrors a gamescope we do not own, so it has no lever to pull and is untouched. Closes #13 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,25 @@ PUNKTFUNK_VIDEO_SOURCE=virtual
|
||||
# VAAPI/AMF/QSV decline (4:2:0). GameStream/Moonlight always stays 4:2:0.
|
||||
#PUNKTFUNK_444=1
|
||||
#PUNKTFUNK_10BIT=1 # HEVC Main10 / HDR (when the client advertises 10-bit)
|
||||
|
||||
# Frame limiter for the GAME — how fast the compositor lets it render. Unset (or 0) = no limit,
|
||||
# the default. It does NOT cap the stream: the client still negotiates and receives its full rate,
|
||||
# because the encode loop re-encodes the held frame when the compositor produced no new one (an
|
||||
# almost-empty P-frame). So a 60-capped game on a 120 Hz session still sends 120 frames a second,
|
||||
# and the GPU time the game gives up goes to capture and encode instead — and to heat and battery
|
||||
# on a laptop or handheld.
|
||||
# gamescope only, today: it takes this as --nested-refresh, the rate it clamps the game to. That is
|
||||
# the nested output's rate, so everything gamescope composites moves at it. Other compositors have
|
||||
# no equivalent lever and ignore it.
|
||||
#PUNKTFUNK_MAX_FPS=60
|
||||
|
||||
# Run the virtual display at a MULTIPLE of the session's frame rate, without sending a single
|
||||
# extra frame. A compositor paints on its own vblank, so a frame finished just after the capture
|
||||
# sampled waits nearly a whole interval to be picked up — the jittery part of the latency budget.
|
||||
# At 2 that worst case halves. Costs the compositor and GPU the extra composites, so it's opt-in;
|
||||
# 1 (default) is off, 4 is the ceiling. If the backend won't give the multiplied rate it reports
|
||||
# what it achieved and the stream paces to that, exactly as it would for any other refusal.
|
||||
#PUNKTFUNK_VDISPLAY_HZ_MULT=2
|
||||
#RUST_LOG=info
|
||||
# Management API bearer token. The mgmt API is HTTPS + token-authenticated ALWAYS (even on
|
||||
# loopback); if unset it is auto-generated + persisted to ~/.config/punktfunk/mgmt-token (which the
|
||||
|
||||
Reference in New Issue
Block a user