Skip to content

A sparse mapping rebuilds the texture tables and starts a new ID cache epoch only when it is new - #2408

Closed
Sennbon-99 wants to merge 1 commit into
seedds:mainfrom
Sennbon-99:pool-tables-sparse-gen
Closed

Sennbon-99 wants to merge 1 commit into
seedds:mainfrom
Sennbon-99:pool-tables-sparse-gen

Conversation

@Sennbon-99

Copy link
Copy Markdown

What and why

In a long Ocarina of Time (0400486024DEC000, v131072) session, area loads (Lake Hylia, the Kakariko graveyard, the Kakariko gate from Hyrule Field) stalled in runs of 150 ms to 1 s frames. A sample of the host during ten of those stalls had the submit thread in pool_gpu → pool_entry → tex_by_id → tex_mtl for half of its time, and the [nvn] pool tables up to frame … lines showed the 33,024-entry texture table built whole up to 3 times a frame (8.4 ms).

Cause: pool_gpu patches a table over storage notes unless a sparse texture's page table may have changed, and the test for that was nvn_sparse_gen() being non-zero, i.e. whether any sparse mapping had ever been made. ser_sync started a new epoch of the texture ID cache on the same test. Ocarina of Time maps its virtual pool once at boot (one MapVirtual line), so for the rest of the session every storage note built the texture table whole and sent every ID down the slow path.

Change (nvn_mtl.m, every game; no chain change): a table keeps the sparse generation it was built at (pool_snap.vgen) and ser_sync the one of its last sync (g_ser_vgen); only a sparse mapping made since then builds the table whole or starts a new epoch, as before. The pool table log line counts those builds (a sparse mapping since the build).

Reference entry: A sparse mapping made once kept the texture tables from being patched; the two entries it amends now say "made since" instead of "any".

Checked

  • Tests: make -C dm/gfx/tests GAME=0400486024DEC000 run passes (OK, no FAIL). make -k -C dm/host/tests GAME=0400486024DEC000 run: the tests pass but two do not build on this Mac, t_hid (undefined dm_gfx_addr_note, dm_gfx_bufsnap, probe_atrace) and t_codec_video; this branch changes nothing under dm/host. t_pool_notes gains 9 checks: a storage note after a new sparse mapping builds the table whole and starts an epoch; one with no mapping since builds nothing and keeps the epoch; the kept table matches a table built whole. t_sparse_tiles (4,253 checks), t_pool_retire, t_pool_standin, pool_seen_replace and the alias tests pass.
  • Games run: Ocarina of Time v131072 (NUP, built with --allow-damaged for three damaged .bntx.zs), played by hand through Lake Hylia and Kakariko, Dynamic resolution on before, off after:
  • Not shown: fewer slow frames overall. Frames under 35 fps were 6.1 % before and 7.4 % after; the after session also ran nvn/pool: age read marks once they cover a quarter of the working set (Ocarina of Time: late-session area-load freezes) #2344 and DM_NVN_POOL_CHUNKS=0, the places played differ, and at full resolution the GPU sits near 16.7 ms at the lake. A 50 s scripts/bench.py route from the same save at Lake Hylia (3 runs each, base host against this one) measured the same (about 38 fps, GPU p50 16.6–17 ms) and showed almost no whole builds either way (0.01 a frame): the rebuilds pile up with storage notes over a long session, which a short route does not reach.
  • Devices and OS: MacBook Pro M4 Pro (16-core GPU, 24 GB), macOS 26.7.2, Xcode 27.2, Metal 32023.922.
  • regress.py check --games: not run; Ocarina of Time is the only game built on this Mac. The change can only make a table be patched where it was built whole; a game with sparse pools that maps while it runs keeps rebuilding on each new mapping, as before.

Docs

  • The finding is in its docs/reference/nvn-objects.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • No game files, keys or data extracted from a game in the change or the attachments

…e epoch only when it is new

pool_gpu refused to patch a texture table over any storage note once any sparse
mapping existed (nvn_sparse_gen() non-zero), and ser_sync started a new epoch of
the texture ID cache on every change for the same reason. Ocarina of Time maps
its virtual pool once at boot, so every storage note after that rebuilt its
33,024-ID table whole and sent every ID down the slow path.

Both now compare the sparse generation with the one seen when the table was
built (pool_snap.vgen) or at the last sync (g_ser_vgen). The pool table log line
counts the rebuilds a sparse mapping caused. t_pool_notes covers both cases.
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation labels Nov 11, 2026
@Sennbon-99

Copy link
Copy Markdown
Author

The same change came independently in #2489 (bymaxmarc), found on Nintendo Switch Sports Resort's two-player skate: the texture table built whole about 3 times a frame after the game's first sparse mapping (M2 Max). Two games hit it; either PR fixes both.

@Sennbon-99

Copy link
Copy Markdown
Author

Closing: superseded by df9ca26 (#1721). A past sparse mapping no longer starts a new ID cache epoch or rebuilds the texture table whole, which covers the Ocarina of Time case this PR fixed.

@Sennbon-99 Sennbon-99 closed this Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant