Field report, 0.33.0, Win 11 client and Win 11 host. Streaming windowed is
fine. F11 to fullscreen quits the session with:
presenter: vkCreateSwapchainKHR: An unknown error has occurred, due to an
implementation or application bug
Fullscreen to windowed works. The shell renders it as "Couldn't connect",
which is wrong — the stream was live. That string is just how a non-zero
session exit is displayed.
Placing the failure
The reported text pins the call site without needing a log. clients/session/src/main.rs formats a run_sessionErr as presenter: {e:#}. A startup failure would read presenter: vulkan presenter: vkCreateSwapchainKHR: …, because Presenter::new carries .context("vulkan presenter"). One prefix only, so
this is the mid-session recreate_swapchain — the resize handler that
F11 drives, not swapchain creation at connect.
Cause
vk/reconfig.rs::recreate_swapchain drained the present-wait waiter after
calling vkCreateSwapchainKHR(oldSwapchain = old). The pf-present-wait
thread could therefore be parked inside vkWaitForPresentKHR(old) while the
driver retired that same swapchain.
vkCreateSwapchainKHR externally-synchronises oldSwapchain, so no other
thread may touch it for the duration of the call. The overlap is a spec
violation, and VK_ERROR_UNKNOWN is a normal Windows driver response to one.
present_timing.rs's own module doc stated only the destroy half of the
rule — "drain must run before any vkDestroySwapchainKHR". That is how the
drain came to sit on the wrong side of the create. The doc now states both
halves, so the next reader cannot re-break it the same way.
Why this fits the reported asymmetry. A mode change orphans its last
present, so that wait runs its full 250 ms cap instead of completing at
the next vblank. The overlap window is widest going into fullscreen, and
narrow coming back out. That is a fit, not a proof: no GPU-less test can
cover it, and neither maintainer box is the reporter's hardware.
Second hypothesis, if it survives this. Hybrid graphics. pick_device
prefers the discrete GPU, and a fullscreen swapchain on an adapter that does
not drive the display is a known Windows refusal. PUNKTFUNK_VK_DEVICE=<n>
moves it — note the index is raw vkEnumeratePhysicalDevices order, not --probe-decode's discrete-first display order. The richer failure text
below is what makes that call from a field log instead of a guess.
Changes
reconfig.rs — the drain, and the unclaimed-present claim drop, move
above the create. note_presented is the only producer and shares the
render thread, so nothing can hand the waiter a new job in between.
run.rs — a refused swapchain costs the fullscreen, not the
session. The resize arm's bare ? walked all the way out of run_session. It now warns, drops back to windowed, and rebuilds against
the geometry that was already working. A windowed failure still
propagates, since there is no smaller state to fall back to. This is right
regardless of which hypothesis above is correct: a transient driver
refusal during a mode transition should never end a stream.
reconfig.rs — the vkCreateSwapchainKHR failure text now carries
extent, format, colour space, present mode and image count. Those are the
first questions any swapchain report raises, and they cost nothing here.
Deliberately skipped
A PUNKTFUNK_PRESENT_WAIT=0 kill switch, matching the PUNKTFUNK_HDR10=0
explicit-off grammar already in that file, to A/B the waiter race in the
field without a rebuild. Add it if the fix does not hold; it costs a line in scripts/ci/docs-undocumented-env-baseline.txt or a docs entry.
Levers that already exist on 0.33.0, for anyone reproducing this: PUNKTFUNK_PRESENT_MODE=fifo (the default ladder picks MAILBOX first for
vsync-on without the VRR opt-in) and PUNKTFUNK_HDR10=0.
Verification
Check
Result
cargo fmt --all -- --check
clean, workspace-wide
Linux cargo clippy --all-targets -p pf-presenter -- -D warnings
Both clippy runs were checked for non-vacuity (Checking pf-presenter present
in each log), because cargo check -p pf-presenter on macOS compiles
nothing: every module in its lib.rs is #[cfg(any(target_os = "linux", windows))], so it "passes" in 0.3 s and reports 0 tests where the real count
is 54. scripts/xcheck.sh does not cover this crate either, so the Windows
run was done on the runner rather than inferred.
Neither behavioural change has an automated check. Both need a GPU and a
window. The real verification is the reporter's box, and this PR should be
treated as unconfirmed on glass until that happens.
User-facing fact changed? n/a — no install step, knob, port, limit or
documented behaviour changes. The only new user-visible string is the extra
detail appended to an existing error message.
Field report, 0.33.0, Win 11 client and Win 11 host. Streaming windowed is
fine. **F11 to fullscreen quits the session** with:
```
presenter: vkCreateSwapchainKHR: An unknown error has occurred, due to an
implementation or application bug
```
Fullscreen to windowed works. The shell renders it as **"Couldn't connect"**,
which is wrong — the stream was live. That string is just how a non-zero
session exit is displayed.
## Placing the failure
The reported text pins the call site without needing a log.
`clients/session/src/main.rs` formats a `run_session` `Err` as
`presenter: {e:#}`. A **startup** failure would read
`presenter: vulkan presenter: vkCreateSwapchainKHR: …`, because
`Presenter::new` carries `.context("vulkan presenter")`. One prefix only, so
this is the **mid-session `recreate_swapchain`** — the resize handler that
F11 drives, not swapchain creation at connect.
## Cause
`vk/reconfig.rs::recreate_swapchain` drained the present-wait waiter **after**
calling `vkCreateSwapchainKHR(oldSwapchain = old)`. The `pf-present-wait`
thread could therefore be parked inside `vkWaitForPresentKHR(old)` while the
driver retired that same swapchain.
`vkCreateSwapchainKHR` externally-synchronises `oldSwapchain`, so no other
thread may touch it for the duration of the call. The overlap is a spec
violation, and `VK_ERROR_UNKNOWN` is a normal Windows driver response to one.
`present_timing.rs`'s own module doc stated only the **destroy** half of the
rule — "`drain` must run before any `vkDestroySwapchainKHR`". That is how the
drain came to sit on the wrong side of the create. The doc now states both
halves, so the next reader cannot re-break it the same way.
**Why this fits the reported asymmetry.** A mode change orphans its last
present, so that wait runs its full **250 ms cap** instead of completing at
the next vblank. The overlap window is widest going *into* fullscreen, and
narrow coming back out. That is a fit, not a proof: no GPU-less test can
cover it, and neither maintainer box is the reporter's hardware.
**Second hypothesis, if it survives this.** Hybrid graphics. `pick_device`
prefers the discrete GPU, and a fullscreen swapchain on an adapter that does
not drive the display is a known Windows refusal. `PUNKTFUNK_VK_DEVICE=<n>`
moves it — note the index is raw `vkEnumeratePhysicalDevices` order, not
`--probe-decode`'s discrete-first display order. The richer failure text
below is what makes that call from a field log instead of a guess.
## Changes
1. **`reconfig.rs`** — the drain, and the unclaimed-present claim drop, move
above the create. `note_presented` is the only producer and shares the
render thread, so nothing can hand the waiter a new job in between.
2. **`run.rs`** — a refused swapchain costs the **fullscreen, not the
session**. The resize arm's bare `?` walked all the way out of
`run_session`. It now warns, drops back to windowed, and rebuilds against
the geometry that was already working. A windowed failure still
propagates, since there is no smaller state to fall back to. This is right
regardless of which hypothesis above is correct: a transient driver
refusal during a mode transition should never end a stream.
3. **`reconfig.rs`** — the `vkCreateSwapchainKHR` failure text now carries
extent, format, colour space, present mode and image count. Those are the
first questions any swapchain report raises, and they cost nothing here.
## Deliberately skipped
A `PUNKTFUNK_PRESENT_WAIT=0` kill switch, matching the `PUNKTFUNK_HDR10=0`
explicit-off grammar already in that file, to A/B the waiter race in the
field without a rebuild. Add it if the fix does not hold; it costs a line in
`scripts/ci/docs-undocumented-env-baseline.txt` or a docs entry.
Levers that already exist on 0.33.0, for anyone reproducing this:
`PUNKTFUNK_PRESENT_MODE=fifo` (the default ladder picks **MAILBOX** first for
vsync-on without the VRR opt-in) and `PUNKTFUNK_HDR10=0`.
## Verification
| Check | Result |
| --- | --- |
| `cargo fmt --all -- --check` | clean, workspace-wide |
| Linux `cargo clippy --all-targets -p pf-presenter -- -D warnings` | green |
| **Windows** `cargo clippy --all-targets -p pf-presenter -- -D warnings` | green, cold 3m01s on the CI runner |
| `cargo test -p pf-presenter` | 54 passed |
Both clippy runs were checked for non-vacuity (`Checking pf-presenter` present
in each log), because **`cargo check -p pf-presenter` on macOS compiles
nothing**: every module in its `lib.rs` is `#[cfg(any(target_os = "linux",
windows))]`, so it "passes" in 0.3 s and reports 0 tests where the real count
is 54. `scripts/xcheck.sh` does not cover this crate either, so the Windows
run was done on the runner rather than inferred.
**Neither behavioural change has an automated check.** Both need a GPU and a
window. The real verification is the reporter's box, and this PR should be
treated as unconfirmed on glass until that happens.
**User-facing fact changed?** n/a — no install step, knob, port, limit or
documented behaviour changes. The only new user-visible string is the extra
detail appended to an existing error message.
Toggling fullscreen mid-stream on Windows 11 quit the session with
`vkCreateSwapchainKHR: VK_ERROR_UNKNOWN`.
`recreate_swapchain` drained the present-wait waiter AFTER calling
`vkCreateSwapchainKHR(oldSwapchain = old)`, so the pf-present-wait
thread could sit inside `vkWaitForPresentKHR(old)` while the driver
retired that same swapchain. `vkCreateSwapchainKHR` externally-
synchronises `oldSwapchain`, so the overlap is a spec violation.
The drain (and the unclaimed-present drop) now run before the create.
`present_timing.rs` stated only the destroy half of the rule, which is
how the drain came to sit on the wrong side; it now states both.
The failure text also carries extent, format, colour space, present
mode and image count -- the first questions a field report raises.
A `recreate_swapchain` failure in the resize handler propagated out of
`run_session`, ending the stream; the shell then reported a live
session as "Couldn't connect".
A driver that refuses the fullscreen-sized swapchain now costs the
fullscreen only: warn, drop back to windowed, and rebuild against the
geometry that was already working. A windowed failure still
propagates, since there is no smaller state to fall back to.
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.
Field report, 0.33.0, Win 11 client and Win 11 host. Streaming windowed is
fine. F11 to fullscreen quits the session with:
Fullscreen to windowed works. The shell renders it as "Couldn't connect",
which is wrong — the stream was live. That string is just how a non-zero
session exit is displayed.
Placing the failure
The reported text pins the call site without needing a log.
clients/session/src/main.rsformats arun_sessionErraspresenter: {e:#}. A startup failure would readpresenter: vulkan presenter: vkCreateSwapchainKHR: …, becausePresenter::newcarries.context("vulkan presenter"). One prefix only, sothis is the mid-session
recreate_swapchain— the resize handler thatF11 drives, not swapchain creation at connect.
Cause
vk/reconfig.rs::recreate_swapchaindrained the present-wait waiter aftercalling
vkCreateSwapchainKHR(oldSwapchain = old). Thepf-present-waitthread could therefore be parked inside
vkWaitForPresentKHR(old)while thedriver retired that same swapchain.
vkCreateSwapchainKHRexternally-synchronisesoldSwapchain, so no otherthread may touch it for the duration of the call. The overlap is a spec
violation, and
VK_ERROR_UNKNOWNis a normal Windows driver response to one.present_timing.rs's own module doc stated only the destroy half of therule — "
drainmust run before anyvkDestroySwapchainKHR". That is how thedrain came to sit on the wrong side of the create. The doc now states both
halves, so the next reader cannot re-break it the same way.
Why this fits the reported asymmetry. A mode change orphans its last
present, so that wait runs its full 250 ms cap instead of completing at
the next vblank. The overlap window is widest going into fullscreen, and
narrow coming back out. That is a fit, not a proof: no GPU-less test can
cover it, and neither maintainer box is the reporter's hardware.
Second hypothesis, if it survives this. Hybrid graphics.
pick_deviceprefers the discrete GPU, and a fullscreen swapchain on an adapter that does
not drive the display is a known Windows refusal.
PUNKTFUNK_VK_DEVICE=<n>moves it — note the index is raw
vkEnumeratePhysicalDevicesorder, not--probe-decode's discrete-first display order. The richer failure textbelow is what makes that call from a field log instead of a guess.
Changes
reconfig.rs— the drain, and the unclaimed-present claim drop, moveabove the create.
note_presentedis the only producer and shares therender thread, so nothing can hand the waiter a new job in between.
run.rs— a refused swapchain costs the fullscreen, not thesession. The resize arm's bare
?walked all the way out ofrun_session. It now warns, drops back to windowed, and rebuilds againstthe geometry that was already working. A windowed failure still
propagates, since there is no smaller state to fall back to. This is right
regardless of which hypothesis above is correct: a transient driver
refusal during a mode transition should never end a stream.
reconfig.rs— thevkCreateSwapchainKHRfailure text now carriesextent, format, colour space, present mode and image count. Those are the
first questions any swapchain report raises, and they cost nothing here.
Deliberately skipped
A
PUNKTFUNK_PRESENT_WAIT=0kill switch, matching thePUNKTFUNK_HDR10=0explicit-off grammar already in that file, to A/B the waiter race in the
field without a rebuild. Add it if the fix does not hold; it costs a line in
scripts/ci/docs-undocumented-env-baseline.txtor a docs entry.Levers that already exist on 0.33.0, for anyone reproducing this:
PUNKTFUNK_PRESENT_MODE=fifo(the default ladder picks MAILBOX first forvsync-on without the VRR opt-in) and
PUNKTFUNK_HDR10=0.Verification
cargo fmt --all -- --checkcargo clippy --all-targets -p pf-presenter -- -D warningscargo clippy --all-targets -p pf-presenter -- -D warningscargo test -p pf-presenterBoth clippy runs were checked for non-vacuity (
Checking pf-presenterpresentin each log), because
cargo check -p pf-presenteron macOS compilesnothing: every module in its
lib.rsis#[cfg(any(target_os = "linux", windows))], so it "passes" in 0.3 s and reports 0 tests where the real countis 54.
scripts/xcheck.shdoes not cover this crate either, so the Windowsrun was done on the runner rather than inferred.
Neither behavioural change has an automated check. Both need a GPU and a
window. The real verification is the reporter's box, and this PR should be
treated as unconfirmed on glass until that happens.
User-facing fact changed? n/a — no install step, knob, port, limit or
documented behaviour changes. The only new user-visible string is the extra
detail appended to an existing error message.