fix(vk): fall back to host pointers on udmabuf rejection and retry mirror allocation #2482
Open
Erfboom wants to merge 2 commits from
Erfboom/fix/vk-udmabuf-fallback into main
pull from: Erfboom/fix/vk-udmabuf-fallback
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
2 participants
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2482
Loading…
Reference in a new issue
No description provided.
Delete branch "Erfboom/fix/vk-udmabuf-fallback"
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?
Problem
When
/dev/udmabufexists on the host system but fails to import guest memory (e.g. kernel/driver rejection withEINVAL,EBADFD, orEPERM, or when the driver reports no valid memory type), the Vulkan backend continuously attempts imports on every buffer pool allocation. This causes continuous ioctl failure logging and wasted kernel transitions instead of cleanly falling back to host-pointer imports (VK_EXT_external_memory_host).In addition, pools that failed device mirror creation under transient VRAM pressure remained permanently marked as
MIRROR_REFUSED. If memory was later freed or other mirrors were reclaimed, these pools would never be retried, leaving hot buffers in slower system memory permanently.Solution
disable_dmabuf_imports(const char* why): if udmabuf creation or driver import is rejected before any successful import has occurred, close the udmabuf handle and permanently fallback to host-pointer imports for subsequent allocations.mirror_make, if device buffer creation fails due to VRAM pressure, triggermirrors_give_back()and retry once.mirror_retry_epoch = mirror_epoch + 120rather than permanently refusing mirrors for the pool. Ingpu_buffer_bind, allow pools to exitMIRROR_REFUSEDonce the retry epoch is reached.Impact
ioctl(..., kUdmabufCreate)syscalls and log spam on Linux configurations where/dev/udmabufis present but unsupported or restricted.tests/run_all.py(359 passed, 0 failed).I ran the suite merged into main and it passes, but:
mirror_makenow callsmirrors_give_back(n)(vk_memory.cpp:3060), which is reached fromgpu_buffer_bindmid-recording (:2752).mirror_freethen rewrites the page table and bumpspools_genwhile the recording is still open. That used to happen only at the submission boundary.mirror_epoch + 120(:3063) repeats free-and-retry every 120 submissions, freeing idle mirrors for an allocation that keeps failing. Low-VRAM cards churn.:204), so one transientVK_ERROR_OUT_OF_DEVICE_MEMORYdisables dma-buf imports for good. The log was already capped at 8 lines.I'd split out or drop the retry, and attach a run from the game.
Thanks. The udmabuf half makes sense to me:
EBADFD/EINVALfrom udmabuf on a memfd it won't take are permanent, and so is "no memory type", so giving up on the first one is fine there. Two things I'd still change, plus I agree with @Joey-Magnus's review:vkImportMemoryFdKHR/bind failing withVK_ERROR_OUT_OF_DEVICE_MEMORYis not proof the driver can't import, so I'd only disable on the ioctl errnos and the missing memory type, and keep the import-failure path as it was.mirrors_give_backreached fromgpu_buffer_bindmid-recording, page table rewritten andpools_genbumped outside the submission boundary) is a real behaviour change and not mentioned in the description. More below on why the immediate retry rarely helps anyway, which makes it easy to just drop.MIRROR_REFUSEDpools on its own is reasonable, but I'd split it from the udmabuf fix into its own PR with a run from the game on a low-VRAM card, as Joey asked.Nit: the description says the failures meant "continuous ioctl failure logging"; the log was already capped at 8 lines, so this is about the wasted ioctls/imports, not log spam.
CI has no checks reported on this branch yet.
@ -158,0 +159,4 @@void disable_dmabuf_imports(const char* why) {if (dmabuf_memfd >= 0) logf("vk: disabling udmabuf pool imports (%s); falling back to host-pointer imports", why);dmabuf_memfd = -1;Minor:
dma_buf_memorycan run on the background import thread (bg_maincallsimport_poolwithoutpools_mu) as well as on the recording thread, so closingdmabuf_devhere can race the other thread'sioctlon the same fd (or a reused fd number). The background imports are opt-in (RX_VK_BG_IMPORT,RX_VK_ASYNC_IMPORT, prefetch), so not a default-path problem; settingdmabuf_memfd = -1and leaving the fd open would avoid it.We only get here after the budget check above passed, so inside
mirrors_give_backthemirror_bytes + want_bytes > mirror_budget()clause is false: it frees only retired mirrors and ones idle pastkLongIdle, whichmemory_begin_submissionalready does every 64 submissions. So the secondcreate_device_bufferis usually the same allocation against the same VRAM, and the cost is the mid-recordingmirror_freeJoey pointed out. I'd drop the give-back-and-retry here and keep just themirror_retry_epochpart (in its own PR).I tested this PR in a real play session. It fixes the stuck refused mirrors I reported in #2412 (#2412).
Setup: RTX 3070 Laptop 8 GB, NVIDIA 620.70.06, CachyOS (Linux). I merged this PR into main at
57ed3aa(v2.1.0-rc.3-177-gf302c495). DLSS (built with the SDK), output 1920x1080,RX_VK_VRAM_MB=3072,RX_VK_COMPACT=1, default mirror budget (2048 MB). In the game: the whole Water Temple, then Lake Hylia, Hyrule Field, and burning Kakariko. Before the merge,tests/scripts that readvk_memory.cpppassed: 26 run, one skipped because it needs Windows.Timeline
At Lake Hylia, VRAM ran out the same way as in my earlier report. The log showed
image over budget(x10), 40 textures evicted, and pools left in system memory (0x7fb8000000 (176 MB) stays in system memory: the mirrors have 1920 MB of 2048). This time the pools went back to state 1 (MIRROR_WANTED), got mirrors again, and the PCIe reads dropped to almost nothing within about 3 minutes. Over the whole session, pools appeared in state 3 in only 4 summaries. My bad session on7759a6fhad 23,649.Same scene size, four sessions (same PC and settings)
7759a6f, good session7759a6f, bad session (mirrors stuck refused)940eb9e,RX_VK_VRAM_MB=256057ed3aa+ #2482memory synca frame(fps: mean of the
vi:lines, grouped by the draw count in the nearestpresentsline.)Lowering the texture budget to 2560 did not leave room for the mirrors. VRAM still filled up to 6.4-6.7 GB, textures were missing or blurry in some areas, and fps was worse than with 3072. With this PR and the budget back at 3072, the session at up to 1800 draws a frame matched my best earlier one,
memory syncstayed under 1 ms the whole time, and nothing was missing in the temple.The lower fps in Hyrule Field comes from the size of the scene, not from memory. At 1500-2200 draws a frame, both the replay (
submit13-17 ms) and GPU time (13-16 ms) grow with the draw count, while PCIe reads and memory sync stay low. That is #995's territory.On NVIDIA the udmabuf part of the PR doesn't come into play: the log has no
disabling udmabuf pool importsline, since #2340 already keeps dma-buf imports off there.Tested this on Windows with an 8 GB card and it fixes the slowdown for me.
Setup: RTX 5060 Ti 8 GB, driver 610.88, Core Ultra 7 265F, Windows 11, 1080p. Built on main at
497060fwith this PR merged on top. My build also has a few other local changes (#1377 and some switches), but those were identical in both runs below. The only difference between them is this PR.Same route both times: Graveyard, Kakariko, Hyrule Field, then into the Market from the field, then back out. Without the PR, the Market entry pushed VRAM over the limit and two pools (160 MB and 240 MB) ended up in state 3 for the rest of the session. The GPU read them over PCIe the whole time and the game never recovered, even after leaving. With the PR they got their mirrors back.
The Market itself still drops to around 22 fps, but that one looks CPU-bound (#995), not this.
(I used Claude to help read the logs; the playing and testing were mine.)
Thanks @Joey-Magnus and @hckrman101 for the feedback, and @YakuzaFanA for the benchmark run on Windows!
Pushed an update addressing all points:
dma_buf_memory,VK_ERROR_OUT_OF_DEVICE_MEMORYis explicitly guarded so a temporary allocation failure during a VRAM spike will not disableudmabufimports for the remainder of the session.mirrors_give_back(n)entirely from insidemirror_make. Evictions and page table rewrites continue to only happen safely at submission boundaries.gpu_buffer_bind, mirror retry is gated on budget headroom (mirror_bytes + (p->hi - p->lo) <= mirror_budget()). If VRAM is genuinely full and no room exists, the retry epoch is deferred by 120 submissions without evicting active mirrors or churning on low-VRAM GPUs. Pools > 1 GB remain permanently refused.Tested locally on Linux (6 GB hardware):
udmabufremains active across heavy transitions (80 live imports, 2.9 GB imported, clean lifecycle).The update addresses my first round: no
mirrors_give_backmid-recording, the retry is budget-gated and frees nothing, and an out-of-device-memory import no longer turns dma-buf imports off (gpu/vk/vk_memory.cpp:213).I merged it into current main and ran
tests/run_all.py. One failure is this PR's:tests/test_direct_buffer_writes.pycutsgpu_buffer_bindout, and the retry block (:3042) usesmirror_bytesandmirror_budget(), whichtests/direct_buffer_writes_test.cppdoesn't declare, so it no longer compiles. Please add the two stubs. The other CI reds are main's from early Nov 12.Still open:
:163:disable_dmabuf_importsclosesdmabuf_devwhile the background import thread (RX_VK_BG_IMPORT,RX_VK_ASYNC_IMPORT) can be inioctlon it. Settingdmabuf_memfd = -1and leaving the fd open avoids that.2fa7431b; a short re-run on993f7f02would confirm the budget-gated retry still recovers.