Repository navigation
Conversation
…d of freezing the Mac A session of Ocarina of Time ended in a GPU out-of-memory and a stuck game, and seedds#1326 reports a Mac frozen until the power button: GPU memory is wired, IOGPUScheduler's freeToWireMemory fails at recommendedMaxWorkingSetSize, and the submit thread then waits in the kernel where the process cannot be killed. Nothing in the host looked at the working set. res_commit now reads the larger of the set's allocatedSize and the device's currentAllocatedSize before it commits; the set's size leaves out what was never committed (measured: a new 64 MB texture adds 0 to it until the commit), the device's counts it from its creation. From 80% of the working set the submit thread lets go of what can go: idle sharing entries nothing holds, finished deferred releases, spares and the upload slots' staging. From 95%, if that was not enough, the game stops with a [RED] report of what the set holds, by kind. A commit that adds to the set is not made at the working set itself. A 100 ms timer reads the sizes too, as a stuck submit thread never comes back to look. The stop reaches the launcher as a normal exit: the Mac app closes the graphics gate, leaves full screen, shows an alert and quits as Cmd-Q does, the path PucaVaz's PR seedds#1332 takes for a revoked GPU (front_stop_alert is written so front_gpu_lost can call it); without a window the host takes its graceful exit. DM_NVN_RES_LIMIT_MB and the report's distinct pages and texture formats come from JioChoi's PR seedds#1319, moved from after the commit to before it. iOS only evicts unless the switch sets a limit: no iPhone has tried the stop. t_res_guard covers the levels, the commit refused and removals passing, what stays (a live object, the parent of a live sRGB view, a recent lookup), the second look at the stop level, and the timer with the lock held.
… the likely cause of the freeze after a window switch Ocarina of Time out of focus went from 11 to 20 GB of GPU allocated in 12 s with the residency set flat, and swap grew 6.7 GB in 20 s until boot.sh killed the run. Out of focus the game's main thread waits in oe::PopNotificationMessage (main+0x3bc0354) and submits nothing, while its streaming threads remake a run of about 10 MB of a virtual pool 70 times a second. Each old buffer went to the deferred queue behind a fence no submission would pass, and only a submission's feedback drains that queue: 770 MB a second, 881 buffers at the end of the log. A user's session froze for good at a window switch, frames not back at focus 1, audio on, no GPU error: past the working set the next commit is what the driver refuses, and DM_FOCUS_KEEP=1, which keeps the game submitting, avoids it. It also fits the sawtooth of GPU allocated noted in earlier logs. A pool's buffer retired with no submission open now waits only for what was committed, as none can reach it once it left the table, and the guard's 100 ms timer drains the queue while no feedback is due (defer_idle). The timer runs with the guard off too. The guard also counts what res_add added since the last commit, as the set's size leaves that out and the device's size could lag it. DM_CTL "focus <n>" sets the focus state, which a windowless boot needs to test this. t_res_guard: a retired pool buffer goes at the next tick between submissions, and behind the submission with one open.
…fers no longer pile up when a game stops submitting (upstream PRs seedds#1391 and seedds#1392)
… piles up retired pool buffers, the likely cause of the freeze after a window switch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g # Conflicts: # dm/gfx/nvn/nvn_mtl.m
Only the generated symptom index conflicted; regenerated (docs_symptoms.py). Checked: test_docs.py, docs_symptoms.py --check. No code conflict.
Only the generated symptom index conflicted; regenerated (docs_symptoms.py). Checked: test_docs.py, docs_symptoms.py --check. No code conflict.
…red pool buffers no longer pile up) over seedds#1447, seedds#1453 and seedds#1455 alias_let_go also clears seedds#1455's lookup stamps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SzykpcKm2eZPvPjGPKx1C
) 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
# Conflicts: # dm/gfx/nvn/nvn_mtl.m
# Conflicts: # docs/agents/symptoms.md
|
Bounded M3 Max window-switch check (2026-11-06): a local Ocarina build carrying #1392 together with #1391/#1482 loaded Kokiri Forest, switched to Finder, returned, accepted movement/camera input and exited cleanly. No freeze occurred. I did not establish that this game's submissions stopped for the entire out-of-focus interval, so this is only a window-switch smoke check, not proof of the retired-buffer stall path or a long suspension stress test. |
… for another one: with no submission open it waits only for the last committed fence, is drained at once off the submit thread, and the residency set's removal is committed then too (PR #1392) Only a submission drained the deferred-release queue (at its start or in its feedback handler), and a finalized pool's buffer waited for the next submission's fence. A game that stops submitting while its other threads go on piles them up: PR #1392 measured Ocarina of Time out of focus going from 11 to 20 GB of GPU allocated in 12 s (its streaming threads remade a virtual pool run of about 10 MB 70 times a second), the likely cause of a session that stayed frozen after a window switch. - mtl_pool_del bumps the pool generation first, then reads g_sub_on (set in mtl_sub_begin before any lookup, cleared once the commit has its fence), with a full fence on each side: a submission either is seen open, and the buffer waits for its fence, or sees the bump and cannot reach the buffer through the table or the per-thread cache. - An entry queued with its fence already passed starts one background drain (defer_kick); entries handed back while g_smu was busy are tried again 10 ms later. - A drain's removals free nothing until the residency set's next commit: a feedback handler, or a submission closing, that finds none open has it committed the same way (defer_idle_commit). Unlike the PR, nothing runs on #1391's 100 ms timer, the guard accounting stays with #1391, and DM_OE_SCRIPT already sets the focus state without a window. Checked: t_pool_retire (new, 11 checks, 20 runs) and t_pool_chunks, t_pool_notes, t_defer_chain, t_gmem_words, t_readback, t_feedback, t_rt_recycle, t_alias_takeover; test_docs.py; lint_c.py tidy at the baseline. Games, base against fix on hosts pinned to the chains on disk: Ocarina of Time with 40 s out of focus (the game stopped submitting but remade no pool run on the village-deck route, so the pile-up was not reproduced; the same red lines, no GPU error, the picture back at focus) and Fire Emblem Engage's 240 s attract demo (about 45 pool deletions, the same pictures, residency commits per 300 frames as before). Co-authored-by: Pedro Nascimento <91118+lunks@users.noreply.github.com>
|
Thank you for the diagnosis and the measurements. The fix landed in 1d37720, rewritten so it no longer depends on #1391, with you credited as co-author. What changed from this PR:
On an M6 the pile-up was not reproduced. On the village-deck route Ocarina stopped submitting for 40 s out of focus, but made no pool deletions, both during the load and on the deck, so GPU allocated stayed flat on main too. If you know the scene where the 770 MB a second happened, a route to it would let us confirm the fix in the game. Fire Emblem Engage's attract demo, which deletes pools, behaved the same before and after. |
… for another one: with no submission open it waits only for the last committed fence, is drained at once off the submit thread, and the residency set's removal is committed then too (PR seedds#1392) Only a submission drained the deferred-release queue (at its start or in its feedback handler), and a finalized pool's buffer waited for the next submission's fence. A game that stops submitting while its other threads go on piles them up: PR seedds#1392 measured Ocarina of Time out of focus going from 11 to 20 GB of GPU allocated in 12 s (its streaming threads remade a virtual pool run of about 10 MB 70 times a second), the likely cause of a session that stayed frozen after a window switch. - mtl_pool_del bumps the pool generation first, then reads g_sub_on (set in mtl_sub_begin before any lookup, cleared once the commit has its fence), with a full fence on each side: a submission either is seen open, and the buffer waits for its fence, or sees the bump and cannot reach the buffer through the table or the per-thread cache. - An entry queued with its fence already passed starts one background drain (defer_kick); entries handed back while g_smu was busy are tried again 10 ms later. - A drain's removals free nothing until the residency set's next commit: a feedback handler, or a submission closing, that finds none open has it committed the same way (defer_idle_commit). Unlike the PR, nothing runs on seedds#1391's 100 ms timer, the guard accounting stays with seedds#1391, and DM_OE_SCRIPT already sets the focus state without a window. Checked: t_pool_retire (new, 11 checks, 20 runs) and t_pool_chunks, t_pool_notes, t_defer_chain, t_gmem_words, t_readback, t_feedback, t_rt_recycle, t_alias_takeover; test_docs.py; lint_c.py tidy at the baseline. Games, base against fix on hosts pinned to the chains on disk: Ocarina of Time with 40 s out of focus (the game stopped submitting but remade no pool run on the village-deck route, so the pile-up was not reproduced; the same red lines, no GPU error, the picture back at focus) and Fire Emblem Engage's 240 s attract demo (about 45 pool deletions, the same pictures, residency commits per 300 frames as before). Co-authored-by: Pedro Nascimento <91118+lunks@users.noreply.github.com>
Stacked on #1391 (the guard): review only the top commit. Its test is
t_res_guard, and it builds on the guard's 100 ms timer.What and why
Ocarina of Time (Switch 2,
0400486024DEC000) out of focus went from 11 to 20 GB of GPU allocated in 12 s with the residency set flat, and swap grew 6.7 GB in 20 s until the run was killed. Out of focus the game's main thread waits inoe::PopNotificationMessageand submits nothing, while its streaming threads remake a run of about 10 MB of a virtual pool 70 times a second. Each old buffer went to the deferred queue behind a fence no submission would pass, and only a submission's feedback drains that queue: 770 MB a second, 881 buffers at the end of the log. A user's session froze for good at a window switch (frames not back at focus, audio on, no GPU error). That this pile-up is what froze it is inferred, not shown: the old host was not reproduced. What was seen is that a 43 s window switch resumed, with retired buffers flat where they had grown from 11 to 20 GB in 12 s out of focus, so the cause is called likely.Change: a pool's buffer retired with no submission open waits only for what was committed, as none can reach it once it left the table, and the guard's 100 ms timer drains the queue while no feedback is due (
defer_idle, the timer runs with the guard off too). The guard also counts whatres_addadded since the last commit, as the set's size leaves that out and the device's size could lag it.DM_CTL "focus <n>"sets the focus state, which a windowless boot needs to test this.Checked
check_t_res_guard26 checks and thestuckrun 2 checks, 0 failed (new: a retired pool buffer goes at the next tick between submissions, and behind the submission when one is open);build/t_hid(host,dm/host/tests) 156 checks, 0 failed;test_docs.py,test_env_switches.py,test_gfx_tests_makefile.pyOK;docs_symptoms.py --checkcurrent;lint_c.py tidyonnvn_mtl.m,hle_hid.c,t_hid.c,t_res_guard.m: 0 pairs above the baseline.regress.py, any game.big_lookup) and editsmtl_pool_del; this PR changes how a retired pool buffer waits (mtl_pool_del,big_moved).git apply --3way --checkof PRAGMATA's opening load fits an 8 GB Mac's GPU working set: large-pool chunks retire, pools from 128 MB are chunked, and DM_NVN_TEX_MIP_SKIP #1319 on this branch conflicts innvn_mtl.m,docs/env_switches.tsv,docs/reference/memory.md,docs/agents/symptoms.md(the first three through the guard PR).Docs
docs/reference/memory.mdanddocs_symptoms.pywas run