Gives the shared Linux/Windows client (punktfunk-session → pf-presenter) the presenter care the Apple and Android clients already had. Six commits, all of design/desktop-presentation-rebuild.md WP1–WP6.
Why
The desktop client presented arrival-paced with no pacing layer: two depth-2 newest-wins hops into a drain-to-newest and an immediate present. That is the lowest-latency intent — but it was unnamed, unselectable, and had no alternative. VK_KHR_present_wait already produced true on-glass stamps and a latch grid was already computed, but write-only toward the host's phase lock: the client never used its own latch knowledge to schedule anything, or to explain what the display stage was spending.
What's in it
Settings cluster — present_priority / smooth_buffer / vsync / allow_vrr on Settings + SettingsOverlay, under the exact keys the Apple client already writes into the shared profile catalog (a mismatch would ride SettingsOverlay::extra, carried but never applied). PresentPriority::resolve mirrors the Android reference, so a profile means the same thing on all three clients.
The engine (present_pace.rs, pure state, unit-tested) — FrameStore (newest-wins, or smoothing FIFO with preroll / drop-oldest / underflow re-arm), LatchClock (panel grid from glass stamps, via the shared punktfunk_core::phase::PanelGrid), PresentGate (one undisplayed present in flight on queueing modes, 100 ms stale force-open). Samples drain every pass and the waiter pushes an SDL wake; smoothness serves one frame per latch slot; the adaptive margin starts at 0. PUNKTFUNK_PRESENTER=arrival disables the engine for field A/B.
V-Sync and VRR — present-mode selection is a preference ladder, logging requested-vs-active when a preference isn't offered. VRR detection is measured, never queried (no portable query exists on any desktop platform). The read-once native.refresh_hz was fixed: a display change relearns the grid.
The display stat splits into pace (decoded → submit: our pipeline) + latch (submit → glass: the vsync floor), plus a present: line naming the live mode, the VRR verdict and the engine counters. Deviation from plan: the specified display_adj twin is gone — with a real per-sample pace percentile it's the same quantity derived worse. paceis the Apple-comparable number.
Settings UI on GTK, WinUI, and the console screen — the only editor reachable in Gaming Mode, so the one that decides whether Deck users can reach any of this.
⭐ The headline: VK_EXT_present_mode_fifo_latest_ready
Found by making the client log what the surface actually offers instead of trusting a code comment. On NVIDIA/Wayland that read back [MAILBOX, 1000361000, FIFO], and 1000361000 is VK_PRESENT_MODE_FIFO_LATEST_READY_EXT: FIFO's tear-free vblank pacing that presents the latest ready image and retires the older ones — the driver doing what the software glass gate emulates.
Measured on .21, same box, back-to-back:
path
display
e2e
plain FIFO + glass gate
28.4 ms (pace 11.8 + latch 16.6)
30.6
FIFO_LATEST_READY
2.6 ms (0.6 + 2.0)
5.2
MAILBOX
1.4–2.0 ms
3.6–4.4
The extension postdates ash 0.38's headers, so it's hand-declared (mode, name, feature struct spliced into the device pNext chain). Trap worth knowing: the surface lists the mode even with the extension disabled, and using it on that basis is undefined — so the ladder only offers it once the device feature actually enabled.
This also let allow_vrr go automatic again wherever the device offers the mode (0.6 ms over MAILBOX rather than 27), retiring a dead switch an earlier revision had created.
WP6 — first on-glass session
Rig: .21 (CachyOS, RTX 5070 Ti, NVIDIA 610.43.03, GNOME/Wayland, 1080p60, VRR provably disabled), host + client on one box.
Five defects that unit tests and both CI gates passed over. Four share one root cause: the tests fed a data shape the live loop never produces — 40-stamp batches against a loop that delivers one stamp at a time.
windows(2) inside a batch meant the latch learner and VRR probe observed nothing; period_us read back the mode fallback, correct by luck on a 60 Hz panel and wrong the moment a mode lies.
The VRR reference was circular (compared against the learned period, which can't be learned from our own presents below panel rate) → reported VRR on a display with VRR off. Now referenced to the display mode's period.
The probe is meaningless outside FIFO — MAILBOX decouples from scanout. Same panel, same minute: FIFO no (right), MAILBOX yes (wrong). Now Unknown there.
Round evaluation was per-call, not per-sample, so the verdict depended on caller batching. Now closed inside the sample loop, with a test pinning bulk-vs-drip equivalence.
force_latency was dead code without pyrowave — a warning in the --no-default-features build CI ships (Windows ARM64). The gate now covers both feature sets.
Gates
punktfunk-rust-ci (linux/amd64), sources touched first so a warm target can't print a vacuous Finished: fmt; clippy --all-targets -- -D warnings over pf-client-core, pf-presenter, pf-console-ui, punktfunk-client-session, punktfunk-client-linux, plus the --no-default-features build; 165 client tests + the shared phase:: suite.
WinUI leg on Windows runner .133 (no Linux/macOS check reaches it): clippy=0 tests=0, against a tree proven by content. ⚠ Its first run this cycle was a false pass — the script printed a done-marker while the log carried a failure (STATUS_DLL_NOT_FOUND, environmental). The harness now echoes each phase's exit code.
Known-unverified (deliberately not claimed)
Windows / Intel / AMD driver support for FIFO_LATEST_READY. Nothing measured here carries over — Windows Vulkan WSI goes through DXGI, so exposing the enum and mapping it usefully are separate questions. Both facts are logged unconditionally now, so one run settles it; .221 (Intel Arc + RTX 3500, Windows) answers Windows+Intel+NVIDIA in one session.
The FIFO glass gate's own 11–13 ms headline. It never engaged on .21: the standing queue only forms when the stream rate approaches the panel rate, and an idle desktop is damage-driven at 40–50 fps on 60 Hz. That figure is still the code's inherited documentation. Needs AMD-on-Windows under load.
The VRR win itself — no VRR panel was available. The cost of attempting it is now measured and small.
Safe either way by construction: the new mode is only requested where the device feature enabled, and allow_vrr only goes automatic there — everywhere else the shipped MAILBOX-first behaviour is unchanged.
Gives the shared Linux/Windows client (`punktfunk-session` → `pf-presenter`) the presenter care the Apple and Android clients already had. Six commits, all of `design/desktop-presentation-rebuild.md` WP1–WP6.
## Why
The desktop client presented **arrival-paced with no pacing layer**: two depth-2 newest-wins hops into a drain-to-newest and an immediate present. That *is* the lowest-latency intent — but it was unnamed, unselectable, and had no alternative. `VK_KHR_present_wait` already produced true on-glass stamps and a latch grid was already computed, but write-only toward the host's phase lock: the client never used its own latch knowledge to schedule anything, or to explain what the display stage was spending.
## What's in it
**Settings cluster** — `present_priority` / `smooth_buffer` / `vsync` / `allow_vrr` on `Settings` + `SettingsOverlay`, under **the exact keys the Apple client already writes into the shared profile catalog** (a mismatch would ride `SettingsOverlay::extra`, carried but never applied). `PresentPriority::resolve` mirrors the Android reference, so a profile means the same thing on all three clients.
**The engine** (`present_pace.rs`, pure state, unit-tested) — `FrameStore` (newest-wins, or smoothing FIFO with preroll / drop-oldest / underflow re-arm), `LatchClock` (panel grid from glass stamps, via the shared `punktfunk_core::phase::PanelGrid`), `PresentGate` (one undisplayed present in flight on queueing modes, 100 ms stale force-open). Samples drain every pass and the waiter pushes an SDL wake; smoothness serves one frame per latch slot; the adaptive margin starts at 0. `PUNKTFUNK_PRESENTER=arrival` disables the engine for field A/B.
**V-Sync and VRR** — present-mode selection is a preference *ladder*, logging requested-vs-active when a preference isn't offered. VRR detection is **measured, never queried** (no portable query exists on any desktop platform). The read-once `native.refresh_hz` was fixed: a display change relearns the grid.
**The display stat splits** into `pace` (decoded → submit: our pipeline) + `latch` (submit → glass: the vsync floor), plus a `present:` line naming the live mode, the VRR verdict and the engine counters. *Deviation from plan:* the specified `display_adj` twin is gone — with a real per-sample `pace` percentile it's the same quantity derived worse. `pace` **is** the Apple-comparable number.
**Settings UI** on GTK, WinUI, and the console screen — the only editor reachable in Gaming Mode, so the one that decides whether Deck users can reach any of this.
## ⭐ The headline: `VK_EXT_present_mode_fifo_latest_ready`
Found by making the client log what the surface *actually* offers instead of trusting a code comment. On NVIDIA/Wayland that read back `[MAILBOX, 1000361000, FIFO]`, and `1000361000` is `VK_PRESENT_MODE_FIFO_LATEST_READY_EXT`: FIFO's tear-free vblank pacing that presents the **latest ready** image and retires the older ones — the driver doing what the software glass gate emulates.
Measured on `.21`, same box, back-to-back:
| path | display | e2e |
|---|---|---|
| plain FIFO + glass gate | 28.4 ms (pace 11.8 + latch 16.6) | 30.6 |
| **FIFO_LATEST_READY** | **2.6 ms** (0.6 + 2.0) | 5.2 |
| MAILBOX | 1.4–2.0 ms | 3.6–4.4 |
The extension postdates ash 0.38's headers, so it's hand-declared (mode, name, feature struct spliced into the device pNext chain). **Trap worth knowing:** the *surface* lists the mode even with the extension disabled, and using it on that basis is undefined — so the ladder only offers it once the device feature actually enabled.
This also let `allow_vrr` go automatic again wherever the device offers the mode (0.6 ms over MAILBOX rather than 27), retiring a dead switch an earlier revision had created.
## WP6 — first on-glass session
Rig: `.21` (CachyOS, RTX 5070 Ti, NVIDIA 610.43.03, GNOME/Wayland, 1080p60, **VRR provably disabled**), host + client on one box.
**Five defects that unit tests and both CI gates passed over.** Four share one root cause: *the tests fed a data shape the live loop never produces* — 40-stamp batches against a loop that delivers one stamp at a time.
1. `windows(2)` inside a batch meant the latch learner and VRR probe **observed nothing**; `period_us` read back the mode fallback, correct by luck on a 60 Hz panel and wrong the moment a mode lies.
2. The VRR reference was **circular** (compared against the *learned* period, which can't be learned from our own presents below panel rate) → reported VRR on a display with VRR off. Now referenced to the display mode's period.
3. The probe is **meaningless outside FIFO** — MAILBOX decouples from scanout. Same panel, same minute: FIFO `no` (right), MAILBOX `yes` (wrong). Now Unknown there.
4. Round evaluation was **per-call, not per-sample**, so the verdict depended on caller batching. Now closed inside the sample loop, with a test pinning bulk-vs-drip equivalence.
5. `force_latency` was **dead code without `pyrowave`** — a warning in the `--no-default-features` build CI ships (Windows ARM64). The gate now covers both feature sets.
## Gates
`punktfunk-rust-ci` (linux/amd64), sources touched first so a warm target can't print a vacuous `Finished`: fmt; `clippy --all-targets -- -D warnings` over `pf-client-core`, `pf-presenter`, `pf-console-ui`, `punktfunk-client-session`, `punktfunk-client-linux`, **plus the `--no-default-features` build**; **165 client tests + the shared `phase::` suite**.
WinUI leg on Windows runner `.133` (no Linux/macOS check reaches it): `clippy=0 tests=0`, against a tree proven by content. ⚠ Its first run this cycle was a **false pass** — the script printed a done-marker while the log carried a failure (`STATUS_DLL_NOT_FOUND`, environmental). The harness now echoes each phase's exit code.
## Known-unverified (deliberately not claimed)
- **Windows / Intel / AMD driver support for `FIFO_LATEST_READY`.** Nothing measured here carries over — Windows Vulkan WSI goes through DXGI, so exposing the enum and mapping it usefully are separate questions. Both facts are logged unconditionally now, so one run settles it; `.221` (Intel Arc + RTX 3500, Windows) answers Windows+Intel+NVIDIA in one session.
- **The FIFO glass gate's own 11–13 ms headline.** It never engaged on `.21`: the standing queue only forms when the stream rate approaches the panel rate, and an idle desktop is damage-driven at 40–50 fps on 60 Hz. That figure is still the code's inherited documentation. Needs AMD-on-Windows under load.
- **The VRR win itself** — no VRR panel was available. The *cost* of attempting it is now measured and small.
Safe either way by construction: the new mode is only requested where the device feature enabled, and `allow_vrr` only goes automatic there — everywhere else the shipped MAILBOX-first behaviour is unchanged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
enricobuehler
changed title from WIP: feat(client/present): desktop presenter intent model (WP1+WP2) to feat(client/present): desktop presenter intent model, display-stat split and settings UI (WP1/2/4/5)2026-08-02 17:58:54 +00:00
enricobuehler
marked the pull request as work in progress 2026-08-02 17:59:09 +00:00
WP1+WP2 of design/desktop-presentation-rebuild.md. The shared Linux/Windows
session client presented arrival-paced with no pacing layer at all: two depth-2
newest-wins hops into a drain-to-newest and an immediate present. That IS the
lowest-latency intent, but it was unnamed, unselectable, and had no alternative
— and on a surface without MAILBOX (AMD's Windows driver offers none, and any
compositor holding images does the same) the swapchain's own FIFO becomes a
standing queue worth a measured 11-13 ms at 60 Hz.
WP1 — the settings cluster, under the keys the Apple client already writes into
the shared profile catalog (present_priority / smooth_buffer / vsync /
allow_vrr): mismatched names would ride SettingsOverlay::extra, carried but
never applied. PresentPriority::resolve mirrors the Android reference exactly
(anything but an explicit "smooth" is latency; a buffer outside 1..=3 becomes
2), so a profile authored on any client means the same thing on all of them.
Only the first two are consumed here; vsync/allow_vrr land in WP3.
WP2 — the engine (present_pace.rs, pure state + arithmetic, 6 tests):
- FrameStore: newest-wins slot, or the smoothing FIFO with preroll-to-capacity,
drop-oldest overflow, and an underflow that re-arms the preroll (repeat by
omission) — the Apple/Android semantics, with qDrop/qDry counters.
- LatchClock: the panel grid learned from VK_KHR_present_wait glass stamps,
min positive spacing capped by the mode refresh (measured, never queried —
VRR and Android's per-uid refresh lie both punish trusting a reported rate).
It now also publishes the host-facing LatchGrid, so the phase-lock report and
the local scheduler cannot disagree about the grid.
- PresentGate: one undisplayed present in flight on FIFO surfaces, with the
100 ms stale force-open. This is the standing-queue killer, and it is inert
on MAILBOX/IMMEDIATE and without present timing — where behaviour stays
byte-for-byte the shipped arrival pacing.
Wiring: glass samples drain every pass (a 1 Hz batch would starve clock and
gate) and the waiter pushes an SDL wake, so a gate reopen never waits out the
event timeout; smoothness serves one frame per latch slot and tightens the
loop's wait to that deadline; the adaptive slot margin starts at 0 and widens
+500 us per missed window toward 2.5 ms (a fixed lead was measured to be pure
display tax). PUNKTFUNK_PRESENTER=arrival disables the whole engine for field
A/B without a rebuild.
PyroWave collapses smoothness to latency for the stream: its plane-ring
retirement accounting assumes the depth-2 newest-wins hand-off, and all-intra
frames make buffering moot anyway.
Gates (punktfunk-rust-ci, linux/amd64, sources touched first so a warm target
cannot print a vacuous Finished): clippy -D warnings across pf-client-core,
pf-presenter and punktfunk-client-session; 80 + 32 tests pass; rustfmt clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WP4 + WP5 of design/desktop-presentation-rebuild.md, on top of the WP1/WP2
engine. The engine shipped with no way to choose it and no way to see what it
cost; this closes both.
WP4 — the display stage splits into `pace` (decoded → present-submit, our own
pipeline) + `latch` (submit → on-glass, the presentation queue and the vblank
wait), off the `submitted_ns` stamp WP2 already carried. That split is what
makes a high `display` self-diagnosing: latch dominating is the vsync floor or
a standing queue, pace dominating is us. A `present:` line joins the Detailed
tier naming the live swapchain mode — the answer to most "why is my latch a
whole refresh" questions, since a MAILBOX request silently lands on FIFO
wherever the driver has no mailbox — plus the engine's counters, rendered only
when they are non-zero so a healthy latency session shows just the mode.
Deviation from the plan: the planned `display_adj` twin is NOT here. It was
specified as `display − latch_p50` for parity with the Apple HUD's shaved
figure, but with a real per-sample `pace` percentile that twin is the same
quantity derived worse (subtracting percentiles). `pace` IS the
Apple-comparable number — Apple subtracts its OS present floor, the latch is
ours — and the user docs now say exactly that.
WP5 — Prioritize + Smoothness buffer on all three surfaces: the GTK dialog (a
new Presentation group on the Display page), the WinUI settings page, and the
console settings screen, which is the ONLY editor reachable in Gaming Mode and
so the one that decides whether Deck users can reach this at all. The buffer
control follows the intent the way echo cancellation follows the mic: hidden on
the desktop shells, dimmed and inert on the console, where a row that vanished
mid-list would shift everything under the cursor.
The V-Sync and VRR rows are deliberately NOT here. Their settings exist and are
profile-routed, but the swapchain does not honour them until WP3, and a toggle
that does nothing is exactly how "Full chroma (4:4:4)" shipped inert on desktop
for three releases after being announced.
Buffer labels carry no millisecond hints (Apple/Android derive them from the
session refresh): under a Native mode the shells do not know the refresh at
settings time, so the captions state the cost as one refresh per frame rather
than a confident wrong number.
Docs: the stats page documents the split and the `present:` line, and stops
claiming Linux/Windows measure to the present instant (untrue since
present_wait); client-settings documents both new rows and drops the stale
claim that the desktop 4:4:4 toggle has no effect (it was wired to
VIDEO_CAP_444); configuration documents PUNKTFUNK_PRESENTER and
PUNKTFUNK_PRESENT_DEBUG.
Gates: punktfunk-rust-ci linux/amd64 — fmt, clippy -D warnings over
pf-client-core, pf-presenter, pf-console-ui, the session binary and the GTK
client, 158 tests. The WinUI leg cannot be reached by any Linux or macOS check,
so it was compiled on the Windows runner .133: clippy -D warnings and tests
both exit 0, against a tree proven by content to contain the edit. ⚠ The first
run there reported a false pass — the script printed its done-marker while the
log carried a test failure (a STATUS_DLL_NOT_FOUND launch failure, ffmpeg's
DLLs missing from PATH); the harness now echoes each phase's exit code so the
verdict is a fact in the log rather than an inference from a marker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WP3 of design/desktop-presentation-rebuild.md. The `vsync` and `allow_vrr`
settings have existed since WP1 but nothing consumed them — the swapchain picked
MAILBOX-or-FIFO once, from an env var, and froze. This makes them mean
something, which is also what unblocks their settings rows (deliberately
withheld from WP5 rather than shipped as dead switches).
Present-mode selection is now a preference ladder, not a constant:
* V-Sync off — IMMEDIATE, then FIFO_RELAXED, then the tear-free modes. Asking
to tear and silently getting vsync is a lie, so the mode that actually took is
named in the stats line and a refused preference is logged requested-vs-active.
* V-Sync on + VRR allowed + fullscreen — FIFO first. On a variable-refresh panel
with direct scanout the FIFO present IS the flip, so the panel follows the
stream's cadence instead of a fixed grid; MAILBOX would decouple presents from
scanout and re-quantize to the compositor's clock. This is only safe because
WP2's glass gate bounds the standing queue that historically made FIFO costly.
* Otherwise — MAILBOX then FIFO, the shipped default, unchanged.
`PUNKTFUNK_PRESENT_MODE` still pins a mode outright and now falls back to the
settings (rather than to mailbox) when the name is unknown.
VRR detection is MEASURED, never queried. No portable query exists — SDL exposes
none, Wayland does not report adaptive-sync state, Windows surfaces nothing
through Vulkan — and the platforms that do answer have been caught lying (see
the Android per-uid refresh-rate finding). The discriminator is quantization: on
a fixed-refresh panel every on-glass instant lands on the vblank grid, so the
spacing between presents is ~k×period for whole k even when the stream runs
slower than the panel (it just picks a larger k); under real VRR the panel
refreshes when we present, so the spacing follows our own cadence and sits off
the grid. `CadenceProbe` folds each delta to its distance from the nearest
multiple of the learned period and takes the median. Tri-state: it stays Unknown
below 24 deltas and after a display change, so `vrr` is reported only when it
has been measured — never inferred from what the display claims.
Also fixes the read-once refresh rate: `native.refresh_hz` was sampled at
startup and never revisited, so dragging the window to another monitor left a
60 Hz-seeded clock pacing a 144 Hz panel. `WindowEvent::DisplayChanged` now
relearns the latch grid, resets the cadence verdict, and clears the served-slot
latch.
Settings rows for both, on all three surfaces (GTK, WinUI, console). The
console's V-Sync row is reachable in Gaming Mode, which is the only editor a
Deck user has.
Gates: punktfunk-rust-ci linux/amd64 — fmt, clippy -D warnings over
pf-client-core, pf-presenter, pf-console-ui, the session binary and the GTK
client, 160 tests (the two new ones cover every ladder and both cadence
regimes, including the case that matters most: a stream slower than a FIXED
panel must still read as fixed). WinUI leg on the Windows runner .133:
clippy=0 tests=0, against a tree proven by content to contain the edit.
⚠ On-glass validation is still owed and is NOT claimed here: every box with a
real display was powered off when this landed, so the VRR ladder and the
detector have been exercised only against synthetic stamps in unit tests.
Rebase follow-up: `20de58a7` landed the same "panel grid can be wrong in both
directions" defect fix on Android and extracted the corrected learner into
`punktfunk_core::phase::PanelGrid` for the iOS and desktop presenters to share.
This clock had the identical bug — it capped the learned period at the display
mode's refresh, and the mode is only a CLAIM, so a display really running slower
than it advertises pinned a grid whose instants never arrive, for the session,
with no way back. Adopted the shared learner rather than carrying a second,
buggier copy; still fed the window's MIN spacing, which preserves the k×period
resistance the cap was actually aimed at while the streak requirement lets a
genuinely slower panel be discovered. New test: seed 120 Hz, real panel 60 Hz,
the clock must climb back out.
Took the same commit's third lesson too: the adaptive margin widened on a
latch over 1.5×period (a number picked here), and now widens on the latch
exceeding one period plus the lead already applied — the slot actually aimed at.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WP6 ran against .21 (CachyOS, RTX 5070 Ti, NVIDIA 610.43.03, GNOME/Wayland,
1080p60 HDMI, VRR provably disabled — `org.gnome.mutter experimental-features`
is empty), host and client on the same box, `VK_KHR_present_wait` available.
Five defects that unit tests and both CI gates had passed over:
1. The latch learner and the VRR probe observed NOTHING. Both derived spacings
with `windows(2)` inside a single batch, but the run loop drains present-wait
samples every pass, so a batch is normally ONE stamp. `period_us` read back
exactly the mode fallback — correct by luck on a 60 Hz panel, wrong the moment
a mode lies, which is the entire reason PanelGrid exists. The tests fed
40-stamp batches, a shape the live loop never produces. Spacings are now
measured against the previous stamp across calls.
2. The VRR reference was circular. It compared spacings against the LEARNED
period, but the grid cannot be learned from our own presents when the stream
runs below panel rate — we only ever observe multiples ≥ our frame interval,
so the learner adopts our own cadence and every delta is on-grid by
construction. It learned 18-22 ms from a 40-50 fps stream and reported VRR on
a display with VRR off. The reference is now the DISPLAY MODE's period, which
is the vblank grid presents actually quantize to.
3. The probe is meaningless outside FIFO. MAILBOX deliberately decouples presents
from scanout, so its stamps are never grid-quantized: same panel, same minute,
FIFO read `no` (correct, period 16.4 ms) and MAILBOX read `yes` (wrong).
Outside a FIFO-family mode the honest answer is Unknown, and that is now what
it reports.
4. Round evaluation was per-CALL rather than per-sample, so the verdict depended
on how the caller batched its stamps. Closed inside the sample loop now, with
a test pinning bulk-vs-one-at-a-time equivalence — the same invariant (1)
violated, in a second place.
5. `force_latency` was dead code without the `pyrowave` feature: a warning in the
`--no-default-features` build CI actually ships (the Windows ARM64 leg). The
gate only ever tested default features; it now tests both.
DESIGN REVERSAL — the VRR FIFO-first ladder is opt-in (`PUNKTFUNK_VRR_FIFO=1`),
no longer default. It shipped default-on for `allow_vrr` + fullscreen, which is
the default configuration. Measured A/B, same box, back to back, reproduced
across three runs: FIFO+engine `display 28.4 ms (pace 11.8 + latch 16.6)` versus
MAILBOX `1.4 ms (0.2 + 1.2)`. Under a compositor the FIFO present's on-glass
confirmation arrives a whole refresh later and the presenter serialises behind
it. The VRR upside is real in principle but UNMEASURED — no VRR panel was
available — and a default that is measurably ~27 ms worse on the hardware we
could test, bought against an unproven win on hardware we could not, is the
wrong way round. A test pins the default to MAILBOX; flip it back when a VRR
panel confirms the win.
NOT measured, and not claimed: the FIFO glass gate's own headline. The standing
queue only forms when the stream rate approaches the panel rate, and an idle
GNOME desktop is damage-driven at 40-50 fps on a 60 Hz panel, so `gated`/`forced`
read 0 in every mode and the mechanism never engaged. The 11-13 ms figure is
still the code's inherited documentation, not a fresh measurement. It needs its
actual target: AMD-on-Windows (no MAILBOX, direct scanout) under load.
Rig caveats recorded rather than smoothed over: host and client shared one GPU,
so absolute latencies are contended and run-to-run variance was large, and it
could not be visually confirmed what the physical screen showed. Mode selection,
the fallback ladder, the VRR verdict and the counter plumbing are robust to
that; absolute numbers are not.
Gates: fmt, clippy -D warnings over the five client crates AND the
`--no-default-features` build (added because defect 5 hid there), 160 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"AMD's Windows driver offers no MAILBOX" is the premise the FIFO glass gate is
built on, and it has been carried in a code comment rather than measured. Present
modes are a property of the (surface, device) pair — they vary by platform
surface, driver version and fullscreen state — so the only way to settle it is to
read them back from real machines. One unconditional log line makes every field
log answer the question.
First reading, .21 (NVIDIA 610.43.03, GNOME/Wayland):
surface present modes available=[MAILBOX, 1000361000, FIFO]
Two things fall out. No IMMEDIATE and no FIFO_RELAXED on this surface, which is
why a PUNKTFUNK_PRESENT_MODE=immediate run reported mode=fifo — the pin was not
offered and the ladder fell through; previously that looked like a puzzling
result and is now evidence. And 1000361000 is
VK_PRESENT_MODE_FIFO_LATEST_READY_EXT: FIFO's tear-free vblank pacing that
presents the LATEST READY image instead of draining a queue — the driver-native
version of what the glass gate emulates in software, and a candidate to replace
it wherever the driver exposes it (needs VK_EXT_present_mode_fifo_latest_ready
enabled at device creation, so a work package rather than a tweak).
Also documents PUNKTFUNK_VRR_FIFO, which the previous commit introduced without
a docs entry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`VK_PRESENT_MODE_FIFO_LATEST_READY_EXT` is FIFO's tear-free vblank pacing that
presents the LATEST READY image at each refresh and retires the older ones,
instead of draining a queue. That is precisely what the software glass gate
emulates — so where the driver offers it, the driver does the job, and it does
it exactly where the gate matters most: a surface with no MAILBOX gets
newest-wins behaviour back without the app holding frames.
Found by asking the surface what it actually offers rather than trusting a
comment: the previous commit's `surface present modes` line read back
`[MAILBOX, 1000361000, FIFO]` on NVIDIA/Wayland, and 1000361000 is this mode.
The extension postdates the Vulkan headers ash 0.38 is generated from (1.3.281),
so there is no binding — hence the bare number in the log. It is hand-declared
here: mode value, extension name, and
`VkPhysicalDevicePresentModeFifoLatestReadyFeaturesEXT` spliced into the device
pNext chain. One trap worth naming: the SURFACE advertises the mode even with
the extension disabled, and using it on that basis is undefined — so the ladder
only offers it when the device feature actually came back true and we enabled it.
The gate/probe predicate had to split in two, and the distinction is the point:
* `needs_glass_gate()` — FIFO and FIFO_RELAXED only. NOT this mode: gating on
top of a driver that already retires stale images would hold frames back to
emulate something the presentation engine is doing, paying the serialisation
twice, which is the ~27 ms the last commit measured.
* `vblank_locked()` — the whole FIFO family INCLUDING this mode, because it
still presents on the refresh boundary, so the VRR cadence probe's premise
("with VRR off, a present waits for vblank") still holds.
Ranking: MAILBOX first (measured good at 1.4 ms), then LATEST_READY, then plain
FIFO — so a MAILBOX-less surface reaches newest-wins in the driver rather than
in our gate.
MEASURED ON GLASS (.21, NVIDIA 610.43.03, GNOME/Wayland): the extension probe,
feature enable and swapchain creation all succeed with a mode ash has no binding
for. Default ladder selects MAILBOX with `fifo_latest_ready=true`; the VRR ladder
selects `present_mode=1000361000` and measures `display 2.6 ms (pace 0.6 + latch
2.0)` — against 13-28 ms for plain FIFO + gate on the same box. The vblank-locked
path is now MAILBOX-class.
That changes the previous commit's reversal. The VRR ladder was reverted to
opt-in because it led with plain FIFO and cost ~27 ms; led with LATEST_READY it
costs 0.6 ms over MAILBOX. So `allow_vrr` is automatic again WHERE THE DEVICE
OFFERS THE MODE, and stays behind `PUNKTFUNK_VRR_FIFO=1` where it does not — on
those drivers the ladder would fall back to plain FIFO and the regression
returns. Both branches are pinned by tests. This also retires a dead switch: the
"Follow variable refresh rate" row did nothing at all after the reversal, and now
does something real on any driver with the extension.
⚠ Still unverified off this box: whether Windows and Intel drivers expose the
mode at all. Nothing measured here carries over — Windows Vulkan WSI goes through
DXGI, so exposing the enum and mapping it usefully onto flip-model semantics are
separate questions, and Intel is a different vendor stack again. Both facts are
logged unconditionally now (`surface present modes` + `fifo_latest_ready=`), so
one run on any box settles it. The code is safe either way: the mode is only
requested where the device feature enabled, and `allow_vrr` only goes automatic
there — everywhere else the shipped MAILBOX-first behaviour is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Gives the shared Linux/Windows client (
punktfunk-session→pf-presenter) the presenter care the Apple and Android clients already had. Six commits, all ofdesign/desktop-presentation-rebuild.mdWP1–WP6.Why
The desktop client presented arrival-paced with no pacing layer: two depth-2 newest-wins hops into a drain-to-newest and an immediate present. That is the lowest-latency intent — but it was unnamed, unselectable, and had no alternative.
VK_KHR_present_waitalready produced true on-glass stamps and a latch grid was already computed, but write-only toward the host's phase lock: the client never used its own latch knowledge to schedule anything, or to explain what the display stage was spending.What's in it
Settings cluster —
present_priority/smooth_buffer/vsync/allow_vrronSettings+SettingsOverlay, under the exact keys the Apple client already writes into the shared profile catalog (a mismatch would rideSettingsOverlay::extra, carried but never applied).PresentPriority::resolvemirrors the Android reference, so a profile means the same thing on all three clients.The engine (
present_pace.rs, pure state, unit-tested) —FrameStore(newest-wins, or smoothing FIFO with preroll / drop-oldest / underflow re-arm),LatchClock(panel grid from glass stamps, via the sharedpunktfunk_core::phase::PanelGrid),PresentGate(one undisplayed present in flight on queueing modes, 100 ms stale force-open). Samples drain every pass and the waiter pushes an SDL wake; smoothness serves one frame per latch slot; the adaptive margin starts at 0.PUNKTFUNK_PRESENTER=arrivaldisables the engine for field A/B.V-Sync and VRR — present-mode selection is a preference ladder, logging requested-vs-active when a preference isn't offered. VRR detection is measured, never queried (no portable query exists on any desktop platform). The read-once
native.refresh_hzwas fixed: a display change relearns the grid.The display stat splits into
pace(decoded → submit: our pipeline) +latch(submit → glass: the vsync floor), plus apresent:line naming the live mode, the VRR verdict and the engine counters. Deviation from plan: the specifieddisplay_adjtwin is gone — with a real per-samplepacepercentile it's the same quantity derived worse.paceis the Apple-comparable number.Settings UI on GTK, WinUI, and the console screen — the only editor reachable in Gaming Mode, so the one that decides whether Deck users can reach any of this.
⭐ The headline:
VK_EXT_present_mode_fifo_latest_readyFound by making the client log what the surface actually offers instead of trusting a code comment. On NVIDIA/Wayland that read back
[MAILBOX, 1000361000, FIFO], and1000361000isVK_PRESENT_MODE_FIFO_LATEST_READY_EXT: FIFO's tear-free vblank pacing that presents the latest ready image and retires the older ones — the driver doing what the software glass gate emulates.Measured on
.21, same box, back-to-back:The extension postdates ash 0.38's headers, so it's hand-declared (mode, name, feature struct spliced into the device pNext chain). Trap worth knowing: the surface lists the mode even with the extension disabled, and using it on that basis is undefined — so the ladder only offers it once the device feature actually enabled.
This also let
allow_vrrgo automatic again wherever the device offers the mode (0.6 ms over MAILBOX rather than 27), retiring a dead switch an earlier revision had created.WP6 — first on-glass session
Rig:
.21(CachyOS, RTX 5070 Ti, NVIDIA 610.43.03, GNOME/Wayland, 1080p60, VRR provably disabled), host + client on one box.Five defects that unit tests and both CI gates passed over. Four share one root cause: the tests fed a data shape the live loop never produces — 40-stamp batches against a loop that delivers one stamp at a time.
windows(2)inside a batch meant the latch learner and VRR probe observed nothing;period_usread back the mode fallback, correct by luck on a 60 Hz panel and wrong the moment a mode lies.no(right), MAILBOXyes(wrong). Now Unknown there.force_latencywas dead code withoutpyrowave— a warning in the--no-default-featuresbuild CI ships (Windows ARM64). The gate now covers both feature sets.Gates
punktfunk-rust-ci(linux/amd64), sources touched first so a warm target can't print a vacuousFinished: fmt;clippy --all-targets -- -D warningsoverpf-client-core,pf-presenter,pf-console-ui,punktfunk-client-session,punktfunk-client-linux, plus the--no-default-featuresbuild; 165 client tests + the sharedphase::suite.WinUI leg on Windows runner
.133(no Linux/macOS check reaches it):clippy=0 tests=0, against a tree proven by content. ⚠ Its first run this cycle was a false pass — the script printed a done-marker while the log carried a failure (STATUS_DLL_NOT_FOUND, environmental). The harness now echoes each phase's exit code.Known-unverified (deliberately not claimed)
FIFO_LATEST_READY. Nothing measured here carries over — Windows Vulkan WSI goes through DXGI, so exposing the enum and mapping it usefully are separate questions. Both facts are logged unconditionally now, so one run settles it;.221(Intel Arc + RTX 3500, Windows) answers Windows+Intel+NVIDIA in one session..21: the standing queue only forms when the stream rate approaches the panel rate, and an idle desktop is damage-driven at 40–50 fps on 60 Hz. That figure is still the code's inherited documentation. Needs AMD-on-Windows under load.Safe either way by construction: the new mode is only requested where the device feature enabled, and
allow_vrronly goes automatic there — everywhere else the shipped MAILBOX-first behaviour is unchanged.🤖 Generated with Claude Code
1f1c6fbb9dto56e3eef78756e3eef787to28c6f752d0WIP: feat(client/present): desktop presenter intent model (WP1+WP2)to feat(client/present): desktop presenter intent model, display-stat split and settings UI (WP1/2/4/5)28c6f752d0toe35981d70fWIP: feat(client/present): desktop presenter intent model, display-stat split and settings UI (WP1/2/4/5)to WIP: feat(client/present): desktop presenter rebuild — intent engine, V-Sync/VRR, display-stat split, settings UI (WP1–WP5)e35981d70fto8b3058e286WIP: feat(client/present): desktop presenter rebuild — intent engine, V-Sync/VRR, display-stat split, settings UI (WP1–WP5)to WIP: feat(client/present): desktop presenter rebuild — intent engine, V-Sync/VRR, display-stat split, settings UI (WP1–WP6)`VK_PRESENT_MODE_FIFO_LATEST_READY_EXT` is FIFO's tear-free vblank pacing that presents the LATEST READY image at each refresh and retires the older ones, instead of draining a queue. That is precisely what the software glass gate emulates — so where the driver offers it, the driver does the job, and it does it exactly where the gate matters most: a surface with no MAILBOX gets newest-wins behaviour back without the app holding frames. Found by asking the surface what it actually offers rather than trusting a comment: the previous commit's `surface present modes` line read back `[MAILBOX, 1000361000, FIFO]` on NVIDIA/Wayland, and 1000361000 is this mode. The extension postdates the Vulkan headers ash 0.38 is generated from (1.3.281), so there is no binding — hence the bare number in the log. It is hand-declared here: mode value, extension name, and `VkPhysicalDevicePresentModeFifoLatestReadyFeaturesEXT` spliced into the device pNext chain. One trap worth naming: the SURFACE advertises the mode even with the extension disabled, and using it on that basis is undefined — so the ladder only offers it when the device feature actually came back true and we enabled it. The gate/probe predicate had to split in two, and the distinction is the point: * `needs_glass_gate()` — FIFO and FIFO_RELAXED only. NOT this mode: gating on top of a driver that already retires stale images would hold frames back to emulate something the presentation engine is doing, paying the serialisation twice, which is the ~27 ms the last commit measured. * `vblank_locked()` — the whole FIFO family INCLUDING this mode, because it still presents on the refresh boundary, so the VRR cadence probe's premise ("with VRR off, a present waits for vblank") still holds. Ranking: MAILBOX first (measured good at 1.4 ms), then LATEST_READY, then plain FIFO — so a MAILBOX-less surface reaches newest-wins in the driver rather than in our gate. MEASURED ON GLASS (.21, NVIDIA 610.43.03, GNOME/Wayland): the extension probe, feature enable and swapchain creation all succeed with a mode ash has no binding for. Default ladder selects MAILBOX with `fifo_latest_ready=true`; the VRR ladder selects `present_mode=1000361000` and measures `display 2.6 ms (pace 0.6 + latch 2.0)` — against 13-28 ms for plain FIFO + gate on the same box. The vblank-locked path is now MAILBOX-class. That changes the previous commit's reversal. The VRR ladder was reverted to opt-in because it led with plain FIFO and cost ~27 ms; led with LATEST_READY it costs 0.6 ms over MAILBOX. So `allow_vrr` is automatic again WHERE THE DEVICE OFFERS THE MODE, and stays behind `PUNKTFUNK_VRR_FIFO=1` where it does not — on those drivers the ladder would fall back to plain FIFO and the regression returns. Both branches are pinned by tests. This also retires a dead switch: the "Follow variable refresh rate" row did nothing at all after the reversal, and now does something real on any driver with the extension. ⚠ Still unverified off this box: whether Windows and Intel drivers expose the mode at all. Nothing measured here carries over — Windows Vulkan WSI goes through DXGI, so exposing the enum and mapping it usefully onto flip-model semantics are separate questions, and Intel is a different vendor stack again. Both facts are logged unconditionally now (`surface present modes` + `fifo_latest_ready=`), so one run on any box settles it. The code is safe either way: the mode is only requested where the device feature enabled, and `allow_vrr` only goes automatic there — everywhere else the shipped MAILBOX-first behaviour is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>7eec3c3e94to6b3c582eb1WIP: feat(client/present): desktop presenter rebuild — intent engine, V-Sync/VRR, display-stat split, settings UI (WP1–WP6)to feat(client/present): desktop presenter rebuild — intent engine, V-Sync/VRR, display-stat split, settings UI