fix(vk): recover presentation after surface loss #1995
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1995
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/vulkan-surface-loss"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
After Vulkan reported a lost window surface, the picture could stop indefinitely. The renderer now retires the old swapchain and retries with a replacement surface after loss during acquire, present or swapchain setup.
Closes #1977.
Technical changes
Validation
Current head:
b52079d5, rebased onto main49f75dc8(recorder test ordering fix). All authored commits are unchanged by range-diff. Fresh hosted checks run on this head; the local and native results below remain tied to their earlier tested heads.Earlier local and native evidence:
Earlier head:
8c6b593d, rebased onto mainebbba2ea. Focused Windows checks: 4 passed / 1 failed. Swapchain recovery, presentation semaphores, Anti-Lag and the new FPS statistics pass. The recorder check fails its existing destructor negative control on both this head and the exact clean base. Earlier native evidence remains labeled below.cda37c2, the upstream source suite passed with 323 passed / 0 failed / 24 skipped on Windows and 337 / 0 / 10 on Linux. Windows includes the temporary bake skips from #2013.Earlier native smoke test and Linux surface probe
cc80fde(API 14), a Windows RTX 5070 Ti run logged 6,688 presents at 2× frame generation with pacing off. The Kokiri scene returned after maximize, restore and minimize, and the game exited normally.Thanks. This is still a draft, so I'm leaving a comment rather than an approval. I read it at d526a52. There are no earlier reviews or comments.
The recovery logic looks right to me. I checked these points:
retire_swapchain()drains the device (device_wait_idle_locked, which also takespresent_queue_mu, so an in-flight present on the dedicated queue has returned). Then it drops the views and theswap_renderedsemaphores, and destroys the chain. Only after that doesinvalidate_lost_surface()destroy the surface. Increate_swapchainthe old chain is still retired aftervkCreateSwapchainKHRbefore the surface goes, so the spec's "swapchains before their surface" holds on every path, including loss during creation.f.acquiredunsignalled andf.doneun-reset, so both can be reused. Every successful acquire still reaches the submit that waits onf.acquired. I found no new early return between acquire and submit. The new swap_spare return is before the acquire. A present that fails with SURFACE_LOST still runs its semaphore wait, and theswap_renderedsemaphores of a rejected deferred frame belong to the chain being retired, so they are destroyed after the idle.present_commit.swap_sparebecoming atomic fixes the display thread's unlocked read. The< 2recheck inpresent_imagesits after the recreate, so a pair that was chosen against the old chain cannot over-acquire on a smaller one. That pair is dropped (usedstays false) and the next set falls back to ordinary presents.present_image_mu→present_mu→present_queue_mu.dev.surfaceandpresent_queue_supportedare only touched in vk_main.cpp/vk_replay.cpp underpresent_mu, and nothing outside those files reads the surface orpresent_family. Not resettingpresent_queue/present_familyinchoose_present_queueis fine, because every user now goes throughpresent_queue_supported.test_present_semaphores.py(acquire block up to#ifndef _WIN32, present block up toif (res < 0),test_antilag.pyandtest_vk_swapchain_compat.pymatch the head source. They compile the realretire_swapchain/invalidate_lost_surface/present_commitrather than copies.Things to sort out before it leaves draft:
VK_ERROR_SURFACE_LOST_KHRthe picture stopped for good: nothing replaced the surface (#1977)." Then the rest as it is.SDL_VIDEODRIVER=x11with a remapped window. Linux hasn't been run in the game at all. Also, on Linux the recreate now callscreate_surfacefrom whichever thread presents (display/painter) rather than the thread that calledstart(). I haven't seen that fail with SDL on Wayland/X11, but a Linux run would cover it.present_commitnow returns false for an OUT_OF_DATE present too, so the frame overlay and pacing stats leave those frames out. I think that's more correct, but it's worth a line in the description.CI: everything has passed except Python source checks (windows-2022), which is still pending. The branch is 2 commits behind main and merges cleanly. Nothing in
vk_api.hchanged, so no API bump is needed.During recovery this runs every 250 ms from
create_swapchainfor as long as the surface can't be made, e.g. a window that is going away, or a compositor that's gone. This log and the present-support one below would then fill the log at 4 lines a second. Could they be capped likeinvalidate_lost_surface's (logged++ < 8), or logged only fromstart()?Nit: this also returns false for
VK_ERROR_OUT_OF_DATE_KHR. Before,present_commitreturned true there, so the deferred path now leaves an out-of-date present out of the overlay counts andhist. That's probably what we want (the frame wasn't shown), just worth noting in the description since it isn't about surface loss.Thanks for the review.
c5aec41keeps both surface startup diagnostics at startup only, so a failed 250 ms retry won't print them repeatedly. The existing capped surface-loss message remains. I also rewrote the opening, noted the deferred OUT_OF_DATE accounting, and clarified that the native resize smoke test exercised ordinary window transitions. Surface loss itself was injected. Linux gameplay and driver-reported native loss are still untested.The previous Windows source run matched the exact upstream base: 322 passed, 2 failed, 21 skipped, with the same log timing failure and skeleton bake timeout. The three new fork workflows are linked in the Validation table for
c5aec41.Thanks, I re-read it at cda37c2. The branch was rebased onto main, so I compared the old and new patches file by file. The only code change since d526a52 is the log fix. The rest of the patch is the same as what I checked last time.
All my earlier points are sorted:
create_surfacerunning on the presenting thread is still only covered by the WSLg probe, not by a game run. It isn't a blocker, but it would be the first thing to look at if someone on Linux reports a problem after this lands.open_window_surface(bool log_failure)logs only whenstart()calls it. The 250 ms retry increate_swapchainpassesfalse, so a window that can't be recreated stays quiet apart from the capped surface-lost line (8 at most).choose_present_queuestill logs, but only after a surface was actually made, so that's once per recovery.test_vk_swapchain_compat.pynow checks that both diagnostics are guarded and that the retry passesfalse.CI is green on the head, including both Python source runs. It merges cleanly and
vk_api.his untouched. Looks good to me.Fixed in c5aec41 (now cda37c2): the retry passes
log_failure=false, so onlystart()logs these, and the test checks it. Thanks.Re-read at
b52079d. This is a rebase onto main (49f75dc) with no code changes: I compared the old and new patches file by file against cda37c2, and all ten files are identical. My approval at cda37c2 still holds. The log spam fix, the problem-first description, the honest testing scope and the OUT_OF_DATE accounting note are all still in place. Thetest_recorder_sync.pyhunk still applies on top of main's ordering fix.CI: the source checks (Linux and Windows, both parts), the Windows builder and the title check pass. The AppImage job was cancelled at the 1 h limit in its "System packages" step, before anything was built. That is the runner, not this PR, and main's AppImage runs are red/cancelled right now too. A re-run should clear it. It merges cleanly and
vk_api.his untouched.Same caveat as before: on Linux, recovery calls
create_surfacefrom the presenting thread, and that is only covered by the WSLg probe. Looks good to me.github-sync referenced this pull request2026-11-12 06:22:54 +00:00