perf(linux): hand guest memory to the GPU as udmabuf dma-bufs, not host pointers #448
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!448
Loading…
Reference in a new issue
No description provided.
Delete branch "linux/udmabuf-guest-memory"
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?
What this fixes
On Linux/RADV,
vkQueueSubmitcosts grow with the guest memory imported: 15-45 ms per submit in Kokiri Forest, 3-5 fps with about 11 ms of GPU time (#312, #413).On amdgpu a
VK_EXT_external_memory_hostimport is a userptr buffer object. RADV lists every buffer object in every submission, and at every CS ioctl the kernel re-validates every page of every userptr BO. Batching (#413) cuts the number of submits; this change removes the per-page cost itself.Change
The guest window becomes a memfd's (
MAP_SHARED, size sealed) whenever/dev/udmabufcan be opened.Each pool is then imported as a dma-buf of the window's own pages:
UDMABUF_CREATEover the memfd range;VK_KHR_external_memory_fd+VK_EXT_external_memory_dma_buf, both optional extensions;vkGetMemoryFdPropertiesKHR.These are the same pages the game reads and writes. Nothing is copied, the pages are pinned once, and there's no page walk at submit.
Supporting changes:
MADV_REMOVEon the shared window (hos_posix_guest_discard), becauseMADV_DONTNEEDdoes not zero shmem.FreeMemoryPagesalready does for imports.RX_VK_LOG_ADDR: also logs dma-buf pools.No setup needed. udmabuf refuses a dma-buf larger than its
size_limit_mb(64 MB by default), and pools are up to 1 GB. A pool larger than the limit therefore becomes a sparse buffer (VK_BUFFER_CREATE_SPARSE_BINDING_BIT), with each limit-sized piece bound to its own dma-buf of the window:vkQueueBindSparsewhen the pool is made, underdev.present_mu, waited on a fence;Raising the limit (as root) only makes the large pools single dma-bufs. Devices without sparse binding on the queue keep larger pools as host-pointer imports, which is logged.
A shared mapping cannot be a host-pointer import on RADV (
VK_ERROR_INVALID_EXTERNAL_HANDLE), which is why the memfd is only used when/dev/udmabufopens.Switches:
HOS_GUEST_MEMFD=0/1forces the window's kind.RX_VK_NO_DMABUF=1keeps host-pointer imports.RX_VK_DMABUF_PIECE_MB=<n>caps the pieces, to try the sparse path with the limit raised.This is complementary to #413's batching and independent of the retired-imports PR (#446), though the two combine. It is also unrelated to the THP change for #312: the dma-buf path doesn't use THP.
Tested
RX_VK_DMABUF_PIECE_MB=64. Pools over 64 MB (80-160 MB among those logged) were bound from 64 MB pieces, and retired ones were given back. There were no bind failures, device loss or crashes, and the picture was correct.foliage-linux-updaterrelease with this patch.main: the patch is rebased onto currentmain,hookshot_vkbuilds there, and the host files compile with the release toolchain. I have not run a fullmainbuild.Analysis and patch made with Claude (Anthropic); I ran every test on my machine.
🤖 Generated with Claude Code
Closing: on current main (
7e9bf73) this no longer shows a benefit on the setup it was written for (RX 7800 XT, RADV). Two runs each in Kokiri: 33.7 and 35.1 fps without it, 32.3 and 30.6 fps with it, no flashes either way. The same build can vary by about 10 fps between runs, so that is no measurable gain. The 3-4 fps this was meant to fix no longer reproduces on main. The branch stays on my fork in case it turns out to be useful later.Reopening, and correcting my closing comment: its numbers came from a broken test setup, and its claim that the 3-4 fps no longer reproduces on main is wrong.
The "without it" runs used an executable that still had this PR's host side (the guest window as a memfd) with a renderer that cannot use it, a combination no real install has. Host-pointer imports cannot work on that shared mapping, so those runs had no pool imports at all and came out at about 34 fps.
Re-measured with
HOS_GUEST_MEMFD=0(anonymous memory, which is what main does), same main renderer (7e9bf73). RX 7800 XT, RADV, Kokiri, two runs each:So this PR is still needed on this setup. The branch has also been updated: merged with main, every piece of a sparse pool is now freed (retired pools from #446 and dropped read-ahead pools), and the
.origbackup files I committed by mistake are gone.Heads-up for when this branch is rebased onto main: main now has #446 (
f2203e4), and its giveback frees onlyp.bufferandp.memory. On this branch a pool can be imported as several dma-bufs (Pool::pieces), so every piece after the first is never freed — the giveback then frees less than it reports and leaves the rest pinned.Two-line fix, tested here on a Steam Deck OLED (RADV VANGOGH) for ~3 minutes of world, no device lost:
With it the log reads
imports: 49 live (1328 MB), 0 retired still imported (0 MB), 39 retired given back (1104 MB)on a 14 GB Deck, and a world scene that used to fail withradv/amdgpu: Not enough memory for command submission(5991 of 6037 MB allowed) runs without it.Happy to test a rebased branch on the Deck if that helps.
Thanks @RufflezAU. This is already on the branch:
d23c572("free every piece of a sparse pool when it goes") makes the same change as your patch, withpiecesinDeadImport, freed inreap_mirrors()and moved over inretired_imports_give_back(). It also frees the pieces on the dropped-prefetch path inbg_drain(). The branch head ise317563, which includes #446.Your Deck result is useful, since it shows the leak causing a real
Not enough memory for command submissionfailure. A run of the current head on the Deck would be welcome if you have time. It is merged up to7e9bf73, not the very latest main.Tested on RDNA 3 / RADV (RX 7900 XTX, Mesa 26.2.6, NixOS, Hyprland):
a598b9f+ this PR, Kokiri Forest, same spot in every run (scripted withHOS_INPUT_LIVE),RX_VK_BATCH_POLL_US=2000:submit/ frameguest memory as dma-bufs: yes)It also merges cleanly onto v2.0.1 (with #438 and #485), which is the build I will play on. Full numbers and the other settings in #528.
🤖 Measured and written with Claude Code