fix(host): create the vdisplay at the multiplied rate up front #497

Merged
enricobuehler merged 1 commits from fix/vdisplay-hz-mult-arrival-mode into main 2026-09-01 17:50:54 +00:00
Owner

Symptom

Field report (2026-09-01, Windows host, native client, PUNKTFUNK_VDISPLAY_HZ_MULT=2): every stream start crashed immediately; a host restart made no difference. Log shows two fresh sessions dying identically.

Root cause

Two callers in build_pipeline_with_retry disagreed about the virtual display's mode:

  1. The IDD-push retry-hold lease created the monitor at the plain session mode (vd.create(mode) → arrives at 3840x2160@120).
  2. build_pipeline's acquire then asked for display_mode_for(mode) (@240), which the manager satisfied with a live in-place resize on the monitor created milliseconds earlier.

Windows pins a monitor's settable mode set at arrival, so the 240 Hz set was rejected (DISP_CHANGE_BADMODE in the log) — and the IddCxMonitorUpdateModes2 mode-list churn landed under the opening capture ring. From then on the driver dropped every composed frame as a size/format mismatch ("driver attached and DWM composed 142 frame(s), but none matched the ring"), the build burned all 8 retries, and the session ended.

Fix

One line: the retry-hold lease requests the same display_mode_for(mode) the build acquires. The monitor arrives at the multiplied rate — no mid-open churn, and the multiplied refresh is in the arrival mode list so it actually commits. A driver/OS that still refuses it falls back through the existing achieved-rate path (pacing_hz), which already handles a clamped refresh cleanly — it was the churn that killed the session, not the clamp.

Not touched: the GameStream plane never applies the multiplier (consistent within itself), and the Linux registry pool doesn't use the Windows refcount manager, so neither path has this mismatch.

Verification

  • Traced the field log against the code end to end; the log's sequence (create @120 → in-place resize 120→240 → BADMODE → ring mismatch ×8 → session end) matches the diagnosed path exactly.
  • WRITING_BASE=origin/main scripts/ci/check-writing.py passes.
  • Compile: the host crate doesn't build on macOS (target-gated deps) and Windows cross-compile stops at openh264's C build, so CI is the compile gate. Runtime confirmation needs the reporter's host on a build from this branch.
## Symptom Field report (2026-09-01, Windows host, native client, `PUNKTFUNK_VDISPLAY_HZ_MULT=2`): every stream start crashed immediately; a host restart made no difference. Log shows two fresh sessions dying identically. ## Root cause Two callers in `build_pipeline_with_retry` disagreed about the virtual display's mode: 1. The IDD-push **retry-hold lease** created the monitor at the plain session mode (`vd.create(mode)` → arrives at `3840x2160@120`). 2. `build_pipeline`'s acquire then asked for `display_mode_for(mode)` (`@240`), which the manager satisfied with a live **in-place resize** on the monitor created milliseconds earlier. Windows pins a monitor's settable mode set at arrival, so the 240 Hz set was rejected (`DISP_CHANGE_BADMODE` in the log) — and the `IddCxMonitorUpdateModes2` mode-list churn landed under the opening capture ring. From then on the driver dropped every composed frame as a size/format mismatch ("driver attached and DWM composed 142 frame(s), but none matched the ring"), the build burned all 8 retries, and the session ended. ## Fix One line: the retry-hold lease requests the same `display_mode_for(mode)` the build acquires. The monitor arrives at the multiplied rate — no mid-open churn, and the multiplied refresh is in the arrival mode list so it actually commits. A driver/OS that still refuses it falls back through the existing achieved-rate path (`pacing_hz`), which already handles a clamped refresh cleanly — it was the churn that killed the session, not the clamp. Not touched: the GameStream plane never applies the multiplier (consistent within itself), and the Linux registry pool doesn't use the Windows refcount manager, so neither path has this mismatch. ## Verification - Traced the field log against the code end to end; the log's sequence (create @120 → in-place resize 120→240 → BADMODE → ring mismatch ×8 → session end) matches the diagnosed path exactly. - `WRITING_BASE=origin/main scripts/ci/check-writing.py` passes. - Compile: the host crate doesn't build on macOS (target-gated deps) and Windows cross-compile stops at openh264's C build, so CI is the compile gate. Runtime confirmation needs the reporter's host on a build from this branch.
enricobuehler added 1 commit 2026-09-01 17:46:30 +00:00
fix(host): create the vdisplay at the multiplied rate up front
ci / bun-nix (pull_request) Successful in 41s
ci / docs-site (pull_request) Successful in 1m7s
macos-host / check (pull_request) Successful in 1m6s
secret-scan / gitleaks (pull_request) Successful in 24s
ci / docs-drift (pull_request) Failing after 45s
ci / web (pull_request) Successful in 1m47s
ci / rust-arm64 (pull_request) Successful in 2m36s
android / android (pull_request) Successful in 6m53s
ci / rust (pull_request) Successful in 10m25s
52d76ab56e
With PUNKTFUNK_VDISPLAY_HZ_MULT set, every Windows IDD-push session died
at bring-up: the retry-hold lease created the monitor at the session
rate, then the build's acquire asked for the multiplied rate, forcing an
in-place mode churn on the just-arrived monitor. The OS pins settable
modes at arrival, so the multiplied rate was BADMODE-rejected — and the
mode-list churn landed under the opening capture ring, whose frames then
all dropped as size/format mismatches until the retries ran out.

Make the lease request the same display_mode_for(mode) the build
acquires. The monitor arrives at the multiplied rate — no mid-open
churn, and the rate is in the arrival list so it actually commits. A
driver that still cannot run it falls back through the existing
achieved-rate path.
enricobuehler merged commit 0ffd1111de into main 2026-09-01 17:50:54 +00:00
enricobuehler deleted branch fix/vdisplay-hz-mult-arrival-mode 2026-09-01 17:50:55 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#497