Repository navigation
Open
Description
Palworld (PPSA23126, 01.102.999), character creation. Buffers that cross from one registered guest range into the next get no host import, so GuestBufferMemory::copyRegion copies them on the CPU at every upload. The [buffers] profile line (APS5_PROFILE_DRAW) counts them as "outside imports":
| 10 s window at character creation | Copied outside imports |
|---|---|
| 1st | 15.5 GB |
| 2nd | 15.4 GB |
| 3rd | 16.1 GB |
| later in the same run | 38–56 GB |
The scene runs at 6–7 presents per 10 s. Some of the regions, named by a local diagnostic in UploadFinish:
0x3024fc0000+0xee40000(238 MiB): copied once per frame, by aDISPATCH_DIRECTbuffer upload.0x3016970000+0x2f0000(3 MiB) and its neighbours: the regions in0x3010000000–0x301fffffffwere copied 9076 times in one run, 26.7 GB.0x3137d50000+0x2000000,0x3030a40000+0x1f15320,0x31a6710000+0x620000and about 40 more, 256 KiB to 32 MiB.
Two causes, one after the other:
- No span over the crossing. Since perf(agc-driver): join host-import spans only over whole aligned windows #3425 (for perf(agc-driver): import adjacent registered ranges as one host-import span #3158), adjacent ranges are joined into one import only when they fill a whole aligned window (8 MiB). A region that crosses two ranges whose window is not full, or that crosses a window edge, fails
runCoversinUploadFinishand takes the copy path.importWindowTestschecks this on purpose ("a view crossing a window edge gets no import"). perf(agc-driver): import adjacent registered ranges as one host-import span #3158's reason still holds: a span that is re-imported on every heap growth cost Ratchet & Clank about 1.5 TiB while loading. - On Windows, the span's alias fails. Direct memory is mapped as shared views of a section, so a span import needs one read-write alias (
GuestArenaMapAlias).WindowsMappings::MapAliasaccepts only one contiguous run of offsets in one section. Neighbouring ranges in Palworld map pages at offsets that do not continue, so it refuses with "the range is not one contiguous run of views of a section". Even when cause 1 is fixed locally, 43 of these spans still fail here and stay copied (2–56 GB per 10 s).
With both fixed (one import over the ranges a crossing region touches, and an alias made of one view per run of offsets), the same scene copies 0 bytes outside imports and runs at 9–13 presents per 10 s.
Proposed fix (a PR follows):
- Spans only where needed. A span is made only for a region that crosses ranges, over the whole contiguous ranges it touches (grown over the imports it overlaps so that two spans never retire each other). Complete windows keep perf(agc-driver): join host-import spans only over whole aligned windows #3425's behaviour.
- perf(agc-driver): import adjacent registered ranges as one host-import span #3158's protection is kept by a budget. The bytes imported by spans count against a budget per 10 s (
APS5_HOST_IMPORT_SPAN_MIB, default 1 GiB). A title that grows a heap in small steps can re-import spans at most up to the budget, and past it crossing regions are copied as today. - The alias is made of placeholders. It covers several runs of section offsets: a placeholder reserved over the whole span, split into one view per run, as
WindowsMappingsalready does for the guest's own views.
Setup
- Windows 11 Pro 26300, RTX 2080 Ti (NVIDIA 610.74), i5-13600K
- MinGW-w64 GCC 15.2.0 (WinLibs r7), RelWithDebInfo
mainplus the open PRs Palworld needs to reach character creation (palworld/integration, listed in Palworld (PPSA23126): next start-up stop is unresolved import oiOi7vgPfAY (sceAudioOut2EnableChat) #2648)
No game files are shared, only log lines. AI-assisted (Claude Code).
Metadata
Metadata
Assignees
Labels
No labels
Activity
oneandonlydean commentedon Oct 12, 2026
up4/host-import-bind-joinon oneandonlydean/AnyPS5 (7787130, see #3425) joins the imports under a buffer bound across them, which is your cause 1. Could you run it on this scene withAPS5_TRACE_HOST_IMPORT=1before opening yours? It does not touchWindowsMappings::MapAlias, so the alias over several view runs (cause 2) is still missing there; a PR for that part alone would be welcome.MarcZX10 commentedon Oct 12, 2026
Draft PR #4367 carries Angel21PC’s alias-run commit from closed #2254 with the original commit author preserved. It covers only
WindowsMappings::MapAliasfor multiple section/offset runs; it does not add cross-registered-range imports. The original author reported the three Windowsguest_memoryalias tests passing on the earlier #2254 base, but I could not run Windows tests on this current-main head. If you have a Windows build available, could you run the focusedguest_memorytest on #4367? The draft is open pending that current-head validation.queeeef commentedon Oct 12, 2026
@oneandonlydean ran
up4/host-import-bind-join(778713032) with #4367 on the Palworld scene,APS5_TRACE_HOST_IMPORT=1,APS5_PROFILE_DRAW=1.app.exe+0x575bdc0, the Windows: resuming a write fault overwrites the guest red zone when AVX state is live #361 red-zone read.What it fixes
joins 2 registered rangesandjoins 3 registered rangesfor binds up to0x1fe0000).0x3020000000range any more, where the per-frame 238 MiB region was.What is still copied: 116 binds are refused with
a piece lies in a partly registered window. They are 147 MiB in total, counted once each. The largest:0x301f130000+0x15600000x3127c20000+0x8c00000x3139d40000+0x8700000x31a1e20000+0x6800000x319fe20000+0x5300000x400000They are used often enough to dominate the copies:
By region over the run: 18.0 GB in
0x3140000000, 17.5 GB in0x31a0000000, 3.6 GB in0x3190000000, 2.1 GB in0x3170000000.For comparison, on the same scene:
So in this scene, the partly registered windows carry most of the remaining traffic. If it helps, I can run a variant of your branch that also joins those pieces when every byte of the bind is registered, and report the same numbers.
Two more numbers from the trace, without a baseline to compare against:
host import ... oklines;retired: the range was mapped again.AI-assisted (Claude Code)
oneandonlydean commentedon Oct 14, 2026
#4515, #4517 and #4367 are on main now, so binds across adjacent registered ranges are imported, including exact runs of small pieces, and Windows aliases cover several view runs. Could you rerun the Palworld character-creation scene on current main and post the copied-outside-imports numbers? The partly registered windows from your last run may still be copied.
EdouardSence commentedon Oct 15, 2026
Ratchet & Clank (PPSA01474) on current
maind92a8dad3: a buffer inside a single registered range is still CPU-copied on every dispatch. Here the cause is the held-import check inimportRunAt, not a crossing between registered ranges.During the black loading screen after the logos, one indirect compute dispatch (250,038 SPIR-V words) binds
0x111c190000+0x1fe00000(510 MiB) about 20 times per second. Every bind copies it on the CPU:That is about 100 GiB copied per 10 s.
resources: B uploadtakes 260 s of a 420 s run, and the AGC worker spends most of its time incopyRegion→memmove.Why it is not imported. I added local prints to the decision path; they are not part of any PR.
0x10acc00000+0xb7800000(2936 MiB, readable, writable, gpu,/memfd:direct memory). SorunCoversis true and no join is attempted (UploadFinish,GuestBufferMemory.cpp:3447).importAccessthen asksimportRunAtfor the windowed run0x1118c00000+0x28000000(640 MiB). That run overlaps an existing import,0x1118c00000+0x4000000(64 MiB), which another descriptor of the same build has already looked up.GuestBufferMemory.cpp:957-961therefore returnsnullptrthroughheld(!joined && pointerHeld(...)). The call returns nothing, so the region falls back tocopyRegion.APS5_TRACE_HOST_IMPORT=1shows no attempt or failure for the range, only the smaller imports nearby succeeding.The comment above that loop exempts joins because "a retired import stays alive through the hold (KeepThroughHold). Refusing it there would keep the run copied for good." This is the same "copied for good" outcome on the non-joined path. Both imports would map the same memfd pages. I haven't tried changing the check, since whether a confirmed pointer may be retired mid-build is your call.
Setup: CachyOS, RX 9070 XT, RADV Mesa 26.2.4, 32 GB RAM,
udmabuf.size_limit_mb=8192. Host import budget 15614 MiB, about 4.7 GiB live when this happens. #6191's stop is not reached in 900 s on this machine, with or without #6232, presumably because of this slowdown.Tested and written by Claude (AI assistant) on a contributor's machine; Gemini ran the game and the instrumented build. AI-assisted: yes.
EdouardSence commentedon Oct 15, 2026
Follow-up with the check changed locally: on a probe branch,
heldinimportRunAt(GuestBufferMemory.cpp:957) always returns false, so the non-joined path may replace held imports the way joins already do. Both runs are Ratchet & Clank PPSA01474, 900 s each, same scene and same scripted input:maind92a8dad3held→ falseresources: B upload[buffers]outside imports)journalctl -k)The 510 MiB buffer is imported now:
With that change, full ctest matches
mainon this machine: 1222 tests, the same two failures, no new ones.agc_host_import_sparseis a test budget issue, fixed in test(agc): lift the host import budget in the sparse dma-buf import test #6261.agc_driver_tessellationis the Mesa bug from agc_driver_tessellation segfaults on RADV with Mesa 26.2+: nir_opt_varyings corrupts the TCS input offset's use list (Mesa bug) #5444.No test exercises the
heldrefusal, so its removal is not covered either way.The screen stays black for the whole 900 s even at 3× the throughput, and #6191's stop does not appear. This change only removes the copies; it does not make the game progress further here.
If you want this as a PR, I'd need to know what the check protects. The pointers looked up earlier in the same build would point to a retired import kept alive through the hold, which maps the same memfd pages. I can then write a test that fails on
main(a run inside one registered range, where an overlapping smaller import is already held, ends up copied).Tested and written by Claude (AI assistant) on a contributor's machine; Gemini ran the builds and the game. AI-assisted: yes.
MarcZX10 commentedon Oct 16, 2026
I checked the current
mainpath while tracing this follow-up.importRunAt()now has anexpandedWindowcase: when a larger import covers an existing import from the same single registered range, and a recorder is active, it permits that promotion despite the smaller import being held.HostImportRetireHold::WindowPromotionOutlivesTheHoldchecks that the old buffer remains alive through the batches that may still use it.That appears to address the held-import mechanism in the 510 MiB example, but I cannot confirm whether
expandedWindowis true or a recorder is active at that call site in your run. On a current-main rerun, could you log those two conditions around the attempted import and report whether that range now imports or still falls back tocopyRegion()? That would tell us whether the current exception covers this case or whether a distinct safe change is still needed.EdouardSence commentedon Oct 16, 2026
@MarcZX100 Confirmed on current
main: the range now imports, and nothing goes throughcopyRegion().Ratchet & Clank PPSA01474, plain
maineb901bcf4(no local changes), 240 s withAPS5_TRACE_HOST_IMPORT=1, RX 9070 XT, RADV Mesa 26.2.4:importRunAt()used to refuse is now promoted over the held 64 MiB import, soexpandedWindowheld and a recorder was active at that call:0x1118c00000+0x2c000000, alsook.outside importsCPU-copy line at all, andresources: B uploadtotals 1 s for the run. Ond92a8dad3it was 260 s out of 420.held→ false experiment, the same 600 per 10 s needed a patch; on thismainit doesn't.So 7b06e75 covers this case, and no further change is needed for this path. On this machine the title still shows a black screen after the logos, but that is the separate stall I described in #2172, not this copy.
Tested and written by Claude (AI assistant) on a contributor's machine. AI-assisted: yes.