Repository navigation
Conversation
… were left out, which hung the GPU in Ocarina of Time; chain 7006d127a074 The table had a fixed 4,096 entries. Loading Ocarina of Time's Link's house autosave made 4,097 to 4,522 pools; the host left the highest out, and shaders read 0 there. agl's task shader 99280b7b8aae78df reads both ends of a range through raw loads and loops (min(end - start, 256) - 1) >> 5 times: with both 0, 2^27 - 1 iterations, kIOGPUCommandBufferCallbackErrorHang (DM_NVN_HANGLOG put it and 47f9b38490dcf9a2 in both hung segments). The lookup itself stayed bounded. Upstream seedds#1265 saw the overflow eight minutes into gameplay, with floors missing; seedds#1326's logs have it too, and seedds#1347 reports that forcing the old size gives the hang. - Host (nvn_mtl.m gmem_map_gpu): a power of two of at least 4,096 entries, as many as the pools need (up to 2^20), the count in entry 0's delta word; freed pools leave at the next rebuild, which shrinks it again. The red line for pools still left out gives the first address and the size. - Translator (GlobalToStorage.TryGenerateRawStorageOp): the binary search loops log2(count) times from that count. Rebuilt translator, tool lock. - Cost: 128 KB of table up to 4,095 ranges, 256 KB at 4,522. Checked: t_pool_chunks (5,000 pools: every address found as the shader finds it, the table shrinks again), t_gmem_raw (the translated load on the GPU against that table, pools past entry 4,095 and an address in no pool), test_dmsh_ampere_mem.py RawGlobal, every test_dmsh_*.py with the new translator, test_dmsh_abi.py, test_nsp_chain.py, test_docs.py, lint_c tidy on the three C files (no new finding). Not run in the game from this branch: it needs the rebuilt shaders.
…, replacing A-1 Ports the code of lunks' commit 574a38f (github.com/lunks/nxnative, fix/gmem-pool-table; seedds#1326, comment 6664549683) and reverts A-1's code (d7f5b40) in the same change. Its parent ded89d4 is lunks' own stack, so the commit is not cherry-picked whole: only nvn_mtl.m (gmem_map, gml_read, gml_take), dmsh_abi.h, GlobalToStorage.cs, t_gmem_raw.m (new), the tests Makefile, t_pool_chunks.m and test_dmsh_ampere_mem.py. Their game README, dlls, lock and chain header are left out (rebuilt in A-9). lunks' design: a power-of-two table of at least 4,096 entries, as many as the pools need (up to 2^20), the count in entry 0's delta word, shrinking again when pools are freed; the search loops log2(count) times; a hint is two words (32-bit chunk number, entry index); the red line names the first unresolved address. lunks found with DM_NVN_HANGLOG what hung: agl task shader 99280b7b8aae78df loops (min(end - start, 256) - 1) >> 5 times over a range whose ends it reads through raw loads, 2^27 - 1 times with both 0. Adjusted while porting: test_dmsh_ampere_mem.py's RawGlobal used the storage-slot setup of lunks' own AtomgCas test (from their stack), so it carries those three instructions itself; its Metal fixture compiles without -fbracket-depth, as MAC_METAL does here (A-3b). A-1's RawGlobalMemory test and test_dmsh_abi's pool-table check go with A-1's code; the 0x1000 ratchet entry comes back with DMSH_GMEM_MAP_ENTRIES. A-1b's docs are kept, reworded: the reference entry's Fix is lunks' design, credited to lunks; the game note says so. Checked: t_pool_chunks 94 checks, t_gmem_raw 61 checks (8,192 entries, 55 probes, on the GPU), test_dmsh_ampere_mem and test_dmsh_abi pass with a translator built from this source. Not run in the game yet. Why 574a38f and not lunks' upstream PR seedds#1360: seedds#1360 is the same fix written for upstream main, whose pool table still has the page-hash write log and no chunk hint; this branch has seedds#1299's bitmap write log and chunk hint, and every source file of seedds#1360 conflicts with them. 574a38f is lunks' own version on seedds#1299's table. Against our A-1 it keeps the same search and speed, and adds a simpler two-word hint, a table that shrinks again, the unresolved address in the red line, a GPU test of the translated lookup, and lunks' in-game runs (3 clean save loads, about 15 minutes of play). Once seedds#1299 and seedds#1360 land upstream, this should merge with little conflict.
…7c132d dotnet build -c Release of dmsh_xlate from this branch's source: A-2 (ATOMG.CAS), seedds#1359's float add atomics (A-6), lunks' 574a38f pool table (A-8), upstream main (A-5). seedds#1359's and lunks' own dlls are not taken. dmsh_xlate.dll, its pdb and Ryujinx.Graphics.Shader.dll copied into dmsh_xlate_bin (launcher kept; Ryujinx.Common.dll and Spv.Generator.dll, whose sources did not change, kept as committed). tool_lock.py --update-lock (Metal unchanged at 32023.922), dmsh_chain.py --header: chain a5bef9d6ad60 -> cd1da67c132d. Checked with this binary: every dm/gfx/tests/test_dmsh_*.py, t_gmem_raw (61 checks on the GPU), t_pool_chunks (94), test_nsp_chain, test_dmsh_abi, test_docs, tool_lock.py --check-tools. Not merged: seedds#1357 (-fbracket-depth on every compile; A-3b keeps the compile cache), seedds#1360 (see A-8).
|
Tested in game on a second machine: Apple M4, 24 GB, macOS 27.2, Xcode 27.2, Ocarina of Time v131072 from a NUP. This is the earlier commit 574a38f ( A new game from the title, through the prologue and the owl, into the first 3D scene:
One non-blocking note from reading the diff: the table is still a new buffer on every rebuild, whenever a pool comes or goes, so at about 4,300 pools that is a 256 KB allocation plus a residency commit each time. A small ring of reused table buffers, each reused once its fence has passed, would avoid that churn. It isn't needed for this fix. Drafted with Claude; reviewed by me. |
|
A longer run on the same build (574a38f, M4 24 GB), this time into gameplay: a new game through the opening until Link is controllable in his house, where the game autosaved.
Then the save load itself, the case that wedged in your runs. Same build, "Kokiri Forest, Autosave" loaded through Continue → Load, in handheld mode at upscale 2.0:
Drafted with Claude; reviewed by me. |
|
Thanks for testing on the M4 24 GB, including Continue → Load of the Kokiri Forest autosave, the case that wedged for us. On the ring of table buffers: I'd rather keep this PR to the table growth and look at the ring as a follow-up if rebuilding the table shows up in a profile. |
…tries (Ocarina of Time's GPU hang) Combined with seedds#1299's lookup hints and entry bitmap write log: the table's count is in entry 0 (as seedds#1360), hints name only entries below 4,096 (the rest go by the search), and the write log is as long as the table. Translator binaries and chain lock to be rebuilt.
…dds#1376 (float add atomics, pool table count, LDSM); chain lock and header regenerated (65ed9f9547f9) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
From upstream f3ac5ea (the Ocarina integration that seedds/nxnative draft PR seedds#1386 sits on): the game's streaming allocator keeps about 5,100 address regions in the alias region, spread up to +4.95 GB, and with the profile's 2.38 GB VammManager::AllocateAddressRegion failed after 5-15 minutes outdoors (nn::diag abort). mem_alias goes to 8 GB and mem_reserve grows by the same 5.6 GB so the heap keeps its size. Profile values only; the rest of that commit (pool tables skipping retired IDs, a 8,191-entry raw global table instead of seedds#1360's growing one, translator changes) waits for its own upstream pull requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
…nly the pools the game took a buffer address in: every pool was wired for it, which slowed Ocarina of Time's frames to 60-65 ms Its marks go into the pool table that seedds#1360 sized to the pools (gmem_map here); t_pool_chunks checks the mark, then the chunk hints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
|
Tested on a third machine, and a note on combining this with #1299, which changes the same table. Setup: Apple M3 Max, 36 GB, macOS 27.0.1, Xcode 27.2. Ocarina of Time v131072 from a NUP, Mac build ( Before (without this PR): 34 s after the load, on walking out of Link's house, After (this PR merged as below): Merging with #1299: the two conflict in
Branch: Checked on that branch: This only matters for whichever of #1299 and #1360 merges second; I'm happy to rebase the merge onto that one. #1394, #1404 and #1417 also change |
|
This PR and #1299 (at ee9b776) both change the raw global memory pool lookup, and they don't merge as they stand. Anyone who tests Ocarina of Time on #1299 currently has to leave this fix out. On a 24 GB M4 Pro, that build hit this PR's overflow line at 6:54 into play, hung the GPU at about 9:27, and ended in a kernel panic (report in #1265). Text conflicts (merging eaa7a1d into #1299's head, both on current
Design conflict. #1299 adds a 2 MiB chunk hint in front of the binary search, and its encoding assumes a table of exactly 4,096 entries:
This PR makes the table a power of two from 4,096 up to 2^20 entries, with the count in entry 0's delta low word, and makes the search follow that count. As I read the two together:
A possible combined layout. Words 6 and 7 of an entry are both unused under this PR:
That keeps #1299's NDVE speed-up (title movie 47.6 → 16.67 ms) for any table size. Whether the hint needs to cover more than the first I haven't built or played a merged version: the above is from reading both diffs, and from the run in #1265, which has #1299 without this PR. I can test a combined branch on the same Mac if one of you pushes it. |
|
@LucyferDev a combined branch is already up, from my comment above: It uses your layout with one difference:
On whether the hints should cover #1427 (table words rewritten at commit) touches the same table: it repoints argument blocks at |
table_has walked DMSH_GMEM_MAP_ENTRIES entries. A table that grows with its pools (seedds#1360) has more, and a pool past entry 4,095 would not have been found. It walks the buffer's length now, which is the same 4,096 entries on main. Pointed out by deanpektas27 on seedds#1360. Checked: check_t_gmem_words, 13 checks, 0 failed.
Main's translator changed (8feddfe: F2FP, I2FP, BindlessToArray and others); this branch's GlobalToStorage.cs merges without a text conflict. The translator was rebuilt from the merged source (dotnet publish, .NET 10.0.403), dmsh_chain.lock rewritten by tool_lock.py --update-lock and dmsh_chain.h by dmsh_chain.py --header: chain 6fe832f2db50. Checked: test_dmsh_ampere_mem (66) with DMSH_TEST_XLATE, test_nsp_chain, test_xlate_anycpu, test_docs, test_dmsh_pool_arrays, docs_symptoms.py --check; t_pool_chunks (85 checks) and t_gmem_raw (61 checks, an 8,192-entry table) with GAME=01007EF00011E000.
…ol table grows with the pools: pools past 4,095 were left out and hung the GPU in Ocarina of Time Translator binaries, lock and chain header kept until the translator is rebuilt on the Mac. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
…dds#1300 seedds#1359 seedds#1360; chain lock and header regenerated (c5757270bc73) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
…ss over IDs of another type (Ocarina of Time's residency commits 12 times fewer) Resolved against NXForge's pool table (seedds#1360's patching, seedds#1393's pool_skip_retired): - pool_entry passes over an ID whose registration resolved to another texture type (treg_t.tt) after the gone check, so both the full walk and the patch path skip it - the full build and the patch path take their buffers from the spares (pool_buf) and retire the replaced one (pool_buf_retire); pool_snap keeps its sub field - the pool table log line keeps NXForge's why/patched/kept counts and adds made/reused/passed over - t_int_float_view.m keeps main's checks; the Makefile has t_gmem_raw and t_pool_table - t_pool_table marks the 2D table out of date before its second rebuild, since a patch on registrations alone would not walk the 3D ID Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
… texture object holds each shared texture in one lookup (Ocarina of Time's cutscene texture creation 2.4 times cheaper) Resolved against NXForge's serial table (seedds#1360's program generation, seedds#1300's g_ghost_active): - serial_put / serial_del keep the program generation and add seedds#1439's held counts - tex_put keeps the same-object check and the g_ghost_active ring, and counts g_by_serial's names as seedds#1439 does - t_int_float_view.m keeps main's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
) replaces the pull request heads Upstream landed Ocarina of Time in main (chain adec7d81d970: "holds 60 fps in Kokiri Forest, 4 GB less") and closed the pull requests it folded in, with reworded and partly different code. Mixing a pull request head with its landed version hunk by hunk silently broke things (two GlobalToStorage tables, a duplicate AF2ip, two mtk_pso definitions), so the rule here is one version per feature: - Pull requests upstream closed (seedds#1299, seedds#1300, seedds#1312, seedds#1343, seedds#1345, seedds#1353, seedds#1354, seedds#1359, seedds#1360, seedds#1373, seedds#1428, seedds#1430, seedds#1434): their hunks are taken out of our side before the three-way merge, so upstream's landed version stands alone. Gone with them: bindless pool table patching (reg_bump), the ghost table's per-submission hold, the growing raw pool table and its write-log sizing, seedds#1428's pool_buf spares (upstream's pool_table_take is the same idea), the ixc-based indirect mesh draw (upstream uses mdx), and their tests (t_pool_table, t_mesh_indirect, t_gmem_raw, finalized_bindless_pool, test_bnsh_nvn2, test_ndve). - Open pull requests that upstream's landing supersedes: seedds#1394 (upstream's mtl_pool_addressed) and seedds#1393 pool_skip_retired (upstream's budgeted pool walk; the profile does not use it) are dropped with their tests and docs. seedds#1368's mesh pipeline prewarm is dropped too: upstream kept the background build in nvn_geometry.inc, and the prewarm's mtk_build / mtk_claim / g_warm_mps went with it. - Kept, as upstream does not have them: NXForge's own features (GPU watchdog, present filters and anti-aliasing, aniso floor, quick settings, pipeline archive compaction, upload breakdown, memory report lines) and the still open pull requests seedds#1304, seedds#1305, seedds#1308, seedds#1320, seedds#1331, seedds#1332, seedds#1355, seedds#1356, seedds#1369, seedds#1376, seedds#1377, seedds#1379, seedds#1382, seedds#1390, seedds#1391, seedds#1392, seedds#1414, seedds#1420, seedds#1436, seedds#1439, seedds#1446, seedds#1453. - Combined where both sides changed the same lines: GW_N 1 << 16 with seedds#1320's wide-write intervals (GW_PAGES = SBW_WPAGES, 128 slots), the CPU upload log with upstream's linear upload route, arch_open_end before the compaction limit, cbn_prog in geo_gs_one (seedds#1382). - Translator: upstream's sources and binaries, plus seedds#1414's InstEmitAmpereMem diff and the LDSM SubgroupBarrier in InstEmitAmpereTensor.cs. The binaries and dmsh_chain.lock are upstream's until the Mac rebuilds them (dotnet publish, tool_lock --update-lock, dmsh_chain --header); dmsh_chain.h is regenerated for now. - RED_ONCE keys: upstream took 153 and 154, so the present anti-aliasing lines move to 156 and 157 and seedds#1420's packaged-entry line to 158 (array 159). - Host: hle_net.c and t_net_eventfd.c are upstream's (its eventfd poll waits out the whole timeout, which our 5d7920c fixed another way). - Kept ours: dm/tools/romfs_image.py and its test, so the 26 GB RomFS image is not packed again. dm/tools tests on Linux: same failures as on 875449e (Linux-only quirks), plus test_nlg4 and test_dmsh_pool_arrays, which fail on upstream main too. Not yet built on the Mac. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
What and why
Ocarina of Time (Switch 2,
0400486024DEC000, v131072; profile in #1299) hangs the GPU, and in longer sessions floors go missing. Reports: #1265 (the red line eight minutes in, floors missing), #1326 (its first hang,kIOGPUCommandBufferCallbackErrorHangwith a GPU restart for an MMU interrupt; both of its logs have the red line), #1347 (forcing the old table size gives the hang).Cause: the raw global memory pool table had a fixed 4,096 entries. The game's save load makes 4,097 to 4,522 pools. The host left the highest ones out (
[RED] raw global memory: N more memory pools than the 4095 entries of the pool table) and shaders read 0 for addresses in them. agl's task shader99280b7b8aae78df(and47f9b38490dcf9a2) reads both ends of a range through raw loads and loops(min(end - start, 256) - 1) >> 5times, so with both ends 0 it loops 2^27 - 1 times.DM_NVN_HANGLOGnamed both in the two segments that hung. The lookup itself stays bounded; the zeros reach the game's arithmetic, so every pool has to stay in the table.Fix: the host sizes the table to its pools, a power of two of at least 4,096 entries up to 2^20, with the count in entry 0's delta word (
gmem_map_gpu). The translator's binary search loopslog2(count)times from that count (GlobalToStorage.TryGenerateRawStorageOp). The table is rebuilt when pools come or go, which also shrinks it. The red line for pools still left out gives the first address and the size. The translator is rebuilt from these sources and the chain version changes.Fixes the hang the overflow caused. Does not cover: #1326's second run, which ended in GPU write faults at 15.5 GB of GPU allocations; nor the hang dacONJA reports on #1326 with the grown table in (4,098 ranges, no overflow line, hang several minutes later, GPU about 32 ms a frame), which is still open; nor the memory growth in #1347.
stan69b's
oot-b-wipbranch (d7f5b40) has the same design; it has no PR, and they said on #1326 to go ahead with ours. Thanks to them for the comparison. Our comment there: #1326 (comment)Reference entry: More memory pools than the raw pool table had entries.
Checked
t_pool_chunks(85 checks: 5,000 pools, every address found as the shader finds it, the table is the least again once they go),t_gmem_raw(new, 61 checks: the translated load on the GPU against the host's table, pools past entry 4,095, 0 outside any pool),test_dmsh_ampere_mem.py(newRawGlobal), everytest_dmsh_*.pywith the rebuilt translator,test_dmsh_abi.py,test_nsp_chain.py,test_docs.py.lint_c.py tidyonnvn_mtl.mand the two tests: no finding above the baseline. The rest of the Metal suite (make -C dm/gfx/tests run) was not run.dotnet publish, the 5 dlls copied,tool_lock.py --update-lock,dmsh_chain.py --header). Every game's shaders are rebuilt; only shaders with raw global memory change their MSL (Donkey Kong Bananza has two), and I did not compare other games' MSL.regress.pywas not run.Docs
docs/reference/shaders.mdandpython3 dm/tools/docs_symptoms.pywas run (the symptom index)dmsh_chain.py --headerwas run anddm/gfx/nvn/dmsh_chain.his part of the change