fix(vk): retain guest pages during import handoff and retirement #468
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!468
Loading…
Reference in a new issue
No description provided.
Delete branch "zeldaootr/Longshot:import-ownership"
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?
Type: fix · Risk: low · Tested on: Steam Deck OLED, SteamOS 3.9.5, RADV (ours and a tester's runs); Linux x86-64 (game-free) · Needs: Windows, any GPU
Why
When the game frees a range that Vulkan still owns through an import that is queued, being made, made but not yet published, or retired but waiting for the GPU, the renderer can report the pages as free. The host can then decommit pages that a pending GPU allocation still references.
How to reproduce
Seen on: Linux x86-64, game-free (the real
memory_guest_imported()with its import tables filled by hand); not seen as a fault in the game, where the race does not show in the counters (see Measurements)Settings and switches: defaults
Steps: on
mainaa95d35fwith this PR's test added (cd7e4de1), runpython -I -B tests/test_pending_guest_import.py --cxx g++: it asks about a range held by a queued import, one being made, one made but not published and one retired but not yet destroyedExpected: each is reported as imported
Actual:
Assertion 'memory_guest_imported(0x2000, 0x3000)' failed: a range only the import thread holds is reported freeWhat changed
memory_guest_imported()now also counts the imports the background thread has queued, is making or has made but not yet published, and the retired ones still waiting for the GPU, so the host keeps those pages instead of decommitting them under a live import.memory_guest_imported()ingpu/vk/vk_memory.cppalso checks the ranges the import thread has queued or is importing (bg_inflight, now[lo, hi)per range, underbg_mu), the ranges imported but not yet published (bg_ready), and the retired imports waiting for the GPU before they are destroyed (dead_imports, which now keeps the guest start,DeadImport::lo). An empty range answers 0 before taking the lock.tests/test_pending_guest_import.py(new) cutsmemory_guest_imported()out of the source and checks deferred, in-flight and ready ranges plus a disjoint query;tests/texel_buffer_ownership_test.cppandnx2/test_import_windows.pyfollow the newDeadImportandbg_inflightshapes, and the latter now also checks the end recorded for each queued range.Measurements
Verdict: passes: no change in fps, frame times or memory on a Steam Deck at the Field transition (20.60 against 20.53 fps, inside the run-to-run spread; no fault in 4 runs each), as expected for a correctness fix; the game-free test fails on
mainand passes here. A tester's Deck run with and without this PR also saw no difference.Hardware: Steam Deck OLED, AMD Custom APU 0932 (RADV VANGOGH), Mesa 26.2.0-devel (git-5c65d4fc31), 16 GB shared, SteamOS 3.9.5 (kernel 7.2.7-valve2)
Scene: Hyrule Field (child), arriving from Kokiri Forest by teleport to the Lost Woods bridge entrance, day; 1280×720 output, render resolution option 4, no frame generation, precompile off, 1536 MiB texture budget, 256 MiB mirror budget, headless
Method: one
scripts/dev_packs.pybase at main9c9d69ed(release optimisation, x86-64-v3); main is the base's runtime with no pack, this PR a renderer pack (longshot_vk.soonly; the host binary is byte-identical). One warm-up pass of main made the caches, copied fresh into every run. Each run: child Kokiri save settled 20 s, teleport to HyruleField_Child, 20 s warm-up, 45 s stationary sample. Four rounds in one session, interleaved main, #468, #469, #472; medians of the 4 runs per arm. FPS and present intervals fromHOS_PRESENT_TRACEin the 45 s window; GPU clock and busy from sysfs every 0.5 s; guest memory backing from the guest memfd's allocated blocks.Rebased since: the branch is now on main
aa95d35f, the fix atd2b39b8f(the same change as 46b467d0,git patch-id). Not measured again onaa95d35f. Since9c9d69edmainchangedgpu/vk/vk_memory.cpponly in how the mirror budget is set (#735), not inmemory_guest_imported()or the import tables; everything else it changed both arms would take alike, so these numbers still compare the change. The game-free test was run again onaa95d35f(below).How to reproduce: build a base at main and a pack of this branch (
python -I scripts/dev_packs.py base|pack|rt, docs/development.md "Testing a change against a base"), start each runtime withHOS_CONTROLand a child save at Kokiri spawn, thenlongshot_ctl.py teleport HyruleField_Child, wait 20 s and sample 45 s.9c9d69edPer run, FPS: main 20.56, 20.54, 20.69, 20.64; this PR 20.44, 20.71, 20.62, 20.30. The difference is inside the run-to-run spread: no frame-rate change, as expected for a correctness fix. The game's own log shows the same import traffic on both (
vk: imports:about 50 live imports, 9-13 retired given back). The live game does not exercise the race this fixes in a way the counters show; the game-free test is the evidence for the fix itself.A tester's Deck run, with and without this PR: dukenheimer, 2026-11-14 (#468 (comment)), not a test request (T-HHWD asks for Windows). Steam Deck OLED (AMD Custom APU 0932, RADV VANGOGH, 16 GB; SteamOS 3.9.5 build 20261023.100, kernel 7.2.7-valve2, Mesa 26.2.0-devel git-5c65d4fc31), stock clocks, Game Mode, docked. Build: a Deck user build of main
543846ebwith #227, #225, #223, #232 and #303, with #384, this PR (e5889e76), #488 and #496 merged on top; "without" is the same tree with this PR's merge reverted, onlylongshot_vk.soswapped in one warm test folder. That owner's settings: the handheld profile, output 1280x800, render resolution 100%, the game's own upscaler, frame generation 3x, a 60 fps cap, vsync on,RX_VK_READBACK_EARLY=1andHOS_VIDEO_LOG=1. A scripted moving route from a Kokiri Forest save, 80 s measured, warm caches; runs in a mirrored order, 2 each way.No crash, exception, device lost,
vk: device fault,the GPU has finished nothingor read-back timeout line in 9 route runs (7 with this PR, among them the build's cold first start) and 4 title-screen movie runs with it. GPU memory is the process'sdrm-memory-gtt+drm-memory-vramfrom/proc/<pid>/fdinfo, every 5 s; it grows in every run as textures stream in, and no growth is peculiar to either arm. The tester's reading: no difference in faults, memory or fps; the p95 gap follows the run order (rising toward the middle of the series whatever ran there), not the build. Its limits, as the tester put them: the routes stay in Kokiri Forest (in run 4 the route went into a house), so they may never reach the import handoff this PR guards; the Field transition is not tested there.Flags and settings
None
Testing
Tested: On the Steam Deck OLED above, 4 runs of this PR interleaved with 4 of main through the Kokiri Forest to Hyrule Field transition: Field reached every time (status map
HyruleField_Child, 914-932 presents in each 45 s sample), no device fault, noVK_ERROR, no crash record, the run's low-memory guard never tripped, every run exited 0. dukenheimer's Deck route runs with and without this PR (in Measurements): no fault, no difference. Game-free, on this branch rebased ontomainaa95d35f:tests/test_pending_guest_import.pyfails at the test-only commitcd7e4de1and passes at2273c8d3, twice each, alternating, and undertests/run_all.pywith g++ 13.3 and with clang 23.1.5 at both commits;nx2/test_import_windows.pypasses at the tip with both. The 43 checks that readgpu/vk/vk_memory.cppor the changed tests pass at the tip, 43 of 43. With #469 and #472, as rebased, the branch merges cleanly (git merge-tree).Not tested: A forced background handoff or an induced GPU fault in the live game; the Field transition with this PR in anyone's run but ours; Windows and other GPUs and drivers in the game.
Must be tested:
VK_ERROR, no crash record inlongshot_log.txtand fps like main at the same spot (by hand, test request T-HHWD: post the fps, thevk: imports:lines and the GPU, driver and OS)Risks
What could break: this query recognizes logical ranges held at the time it runs; the host releases the renderer lock before performing the discard. Sparse-buffer padding beyond the logical binding is outside the test. Lock order is
pools_muthenbg_mu, as inbg_drainandpool_lookup; the import thread never takespools_mu. A range held by a pending import is now kept backed a little longer than before.Turning it off: revert the fix commit
Compatibility: Nothing changes
Issues
None filed; found while tracing guest memory on the Steam Deck. Overlaps two other PRs in
memory_guest_imported: #469 adds the same empty-range guard, and #431 changes the 1 GiB stop in the same loop. This and #469 merge with each other cleanly; whichever of these lands after #431 needs a small rebase.Checklist
Everyone
type(scope): description) of the status line's type.main. Onto mainaa95d35f, no conflict; the test, fix and docs commits are the same changes as before (git patch-id).python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). The full suite on mainaa95d35f(Linux, g++ 13.3, the Vulkan headers set): 528 passed, 15 failed, 50 skipped; on this branch the new test and the 43 checks that read the changed files ran, with no failure thatmaindoes not have (see Testing). The 15 that fail on main:test_appimage_player_build,test_bake_portability,test_cpm_discovery,test_crash_dialog,test_env_on,test_lift_cache,test_lift_cleanup_error,test_make_game_cache,test_mods_size_table,test_options_budget_once,test_pipeline_tour,test_readback_late,test_vk_pgo,test_walk_keyandnx2/test_texture_init_barrier.py.third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md).mainand passing here.tests/that reads changed source updated in this PR, not weakened.RX_VK_ONLY=1 RX_RELEASE=1 RX_TARGET=windowswith llvm-mingw 20261104 linkedlongshot_vk.dll, and the same withoutRX_TARGETlinkedlongshot_vk.so; after the rebase,gpu/vk/vk_memory.cppat2273c8d3checked whole (-fsyntax-only -Wall -Wextra) with clang 23.1.5 for Linux and llvm-mingw 20261104 for Windows: the same diagnostics asmainaa95d35f's (54 and 43), none new. The diff has no path handling.e5889e76058fd6d885d6Rebased onto main
1be60eaeand measured on a Steam Deck OLED against main: four interleaved rounds at the Kokiri Forest to Hyrule Field transition, medians 20.60 fps on main and 20.53 here (within the run-to-run spread), no fault in any run. The run records are in docs/tested. Still WIP: not built for Windows yet; the test request asks for that.Built for Windows from Linux with llvm-mingw 20261104 (the renderer alone, RX_VK_ONLY, as the CI job does) and for Linux: both link, and the changed file has no new warning under -Wall -Wextra on either. The Windows box is ticked; the Windows test request stays open for a run on real hardware.
WIP: fix(vk): retain guest pages during import handoff and retirementto fix(vk): retain guest pages during import handoff and retirementSteam Deck OLED test, with and without this PR (dukenheimer's Deck)
Mesa 26.2.0-devel git-5c65d4fc31. Stock clocks. Game Mode, docked: the game gets a 1280x720 surface.
543846eb+ #2270fc2e8c9(with #225) + #22352a61f40+ #2323ccb48a8+ #303995aab15, with#384
a9b0c880, #468e5889e76, #488abf2227fand #4967fb4e2c1merged on top. All four merged clean. Linux suite: 544 passed,main's 5 known failures. Built with x86-64-v3 and the release play profile, LLVM 23.1.5 (glibc 2.35).
git revert -m 1), built the same way. Only that file is swappedin one warm test folder: the host
longshotfor #496,longshot_vk.sofor #384 and #468.upscaler, frame generation 3x, frame rate cap 60, vsync on.
RX_VK_READBACK_EARLY=1(#303) andHOS_VIDEO_LOG=1.In runs 2 (-#496) and 4 (-#468) the scripted route went into a house after its start, so most of those two windows is an interior.
Results for this PR (#468)
Faults. None with this PR and none without. Over 9 route runs (7 with it, among them the build's cold first start; 2 without)
and 4 title-screen movie runs with it, there were none of these:
vk: device faultlines;the GPU has finished nothinglines;Memory.
drm-memory-gtt+drm-memory-vramfrom/proc/<pid>/fdinfo, every 5 s. It grows in every run astextures stream in along the route.
Reading. No difference in faults, memory or fps. The p95 gap follows the run order: the runs without this PR were the two in the
middle, and by position in the series p95 was 73.4, 78.3, 79.4, 95.2, 100.7, 99.7, 79.1, 73.6 ms, rising toward the middle
whatever ran there. These routes stay in Kokiri Forest (the first run without the PR went into a house), so they may never reach
the import handoff this PR guards. Your repro is a Field transition, and nothing here tests that.
Thank you, dukenheimer, for the Deck runs with and without this PR: they are in the description as you reported them, with your reading (no difference in faults, memory or fps) and your limit (the Kokiri routes may never reach the import handoff). Rebased onto
mainaa95d35fwith no conflict (the same three changes,git patch-id) and the description moved to the current template.The game-free test was run again on the new base (fails at the test-only commit, passes at the tip, two rounds, g++ and clang; the 43 checks that read the changed files pass); the Field numbers still hold because
mainchangedvk_memory.cppsince9c9d69edonly in how the mirror budget is set (#735), not inmemory_guest_imported()or the import tables. T-HHWD (Windows) still waits for thetesting wantedlabel.8fd6d885d62273c8d36eView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.