Skip to content

Hash shared pool pages before opening them for CPU writes - #2136

Closed
thehobbit85 wants to merge 1 commit into
seedds:mainfrom
thehobbit85:codex/oot-shared-page-write-tracking
Closed

thehobbit85 wants to merge 1 commit into
seedds:mainfrom
thehobbit85:codex/oot-shared-page-write-tracking

Conversation

@thehobbit85

Copy link
Copy Markdown
Contributor

What and why

Adjacent memory pools can share a host page. nvn_dirty_open previously hashed one pool and made its pages writable before hashing its neighbor. A write to that neighbor in the gap became part of its initial hash, so closing the mapping reported unchanged storage and a texture could stay stale.

Hash and claim every affected protected range before making any page writable, then open contiguous runs. The synthetic regression injects a real store immediately after the first protection change and reproduces the missed write on the original implementation.

Related to #2086. This fixes a reproduced tracker defect, but its connection to the reported OoT terrain corruption remains unverified in gameplay. Finding: writes while opening shared pool pages, linked from the OoT note.

Checked

  • Apple M2 Max, macOS 26.6; synthetic memory only, no game files.
  • Full standalone graphics suite: make -j4 -C dm/gfx/tests GAME=0400486024DEC000 run — passed.
  • Tool/documentation suite: python3 -m unittest discover -s dm/tools/tests — 1,435 tests; 1,411 passed, 23 skipped, one inherited failure. The inherited ABI-literal-ratchet failure in unchanged nvn_mtl.m also reproduces from pristine base revision 976fbd57f5cbee1f374453c2450c92ffd9f20dbc.
  • Deterministic shared-page regression: failed before the fix; all 30 dirty-race checks passed afterward, including unchanged-page behavior. Neighboring dirty-slot and retirement-race tests passed. Independent concurrency review found no remaining actionable issue.
  • Compilation database generated; clang-tidy was unavailable locally. git diff --check passed.
  • scripts/regress.py impact BASE..HEAD was run. Usage coverage is unknown; no gameplay regression run was possible without game files. Intended checks: OoT (0400486024DEC000, reported stale terrain), Pokopia (0400A3A01EE4C000, NVN2 pools), Monster Hunter Rise (0100B04011742000, RE Engine), Fire Emblem Engage (0100A6301214E000, Unity), and Super Mario Bros. Wonder (010015100B514000, Nintendo NVN). These cover different clients of the shared pool tracker; none is claimed gameplay-validated by this PR.

Docs

  • Durable reference finding, game-note link, and regenerated symptom index.
  • No shader-chain change; no game files, keys, extracted game data or agent work logs in this change.

@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation game: 0400486024DEC000 A game labels Nov 10, 2026
@bzwink

bzwink commented Nov 10, 2026

Copy link
Copy Markdown

Tested this in Ocarina of Time on an M2 Max (macOS 27.3), and it fixed the strobing ground textures in Goron City for me.

Before the patch I was on main 5e1c1aec plus #1379, #1459 and #1320 (shader chain d403eb0683df), 2560x1440 fullscreen with the MetalFX upscaler. On that build the ground textures in Goron City strobed during a 2 h 38 min session, and I had also seen the striped dirt floors described in #2086. I rebuilt with only this PR added (host-only change, same shader chain), went into Goron City, left, and came back, the route from #1865. The strobing did not come back.

That run was about 8 minutes. DM_NVN_DIRTYCHECK was not on in either session, so I have no miss counts to compare. No GPU hangs in either session.

seedds added a commit that referenced this pull request Nov 11, 2026
…ol buffer is made: nvn_dirty_open hashed one pool's protected pages and made them writable before it hashed the next pool's, and adjacent pools (4 KB aligned) share their first / last 16 KB host page, so a guest write to that page between the two went into the second pool's first hash, the close found that pool's page unchanged, and its texture stayed clean with stale content. Every range's protected pages are now hashed and taken (BUSY) before any page is opened, so each hash is of a read-only page; the pages are then opened in runs. A fault or host write on a taken page waits for the open as before, and neither holds a page of its own while it waits. The race reaches only pages two pools share: a write that also touches a texture's unshared pages faults there, so only a texture, or a write, that lies wholly in a shared page was missed. t_dirty_race (open_shared_page: two pools in one host page, a store to the second right after the first opens) fails on the old order (expected 1, got 0) and passes; t_dirty_slots and test_docs pass. regress.py check on OCTOPATH TRAVELER II: PASS (13,980 shaders, 59.3 fps, no new red line, a busy Mac), but that run opened no page for a pool buffer, so the new order is covered by the unit test only. Not run: Ocarina of Time or any game that opens pages, the full gfx suite, lint_c.py, the iPhone. A tester's M2 Max run of 8 minutes saw Ocarina's Goron City ground stop strobing with the PR, with no miss counts. Open: issue #2086's stale terrain map spans about 33 host pages, more than this race reaches by itself, so its cause is not found. The fix and the test are thehobbit85's (PR #2136)

Co-authored-by: Eliran Zach <4012041+thehobbit85@users.noreply.github.com>
@seedds

seedds commented Nov 11, 2026

Copy link
Copy Markdown
Owner

Landed on main as 1e69c18, with you as co-author: your fix and your test as written. Thank you.

Changes when landing: the code comment is in the file's one-line style and says why holding the taken pages cannot deadlock (a fault or host write waits for the open and holds no page of its own meanwhile), and the reference entry and the Ocarina note now state the race's reach.

On the reach: only a pool's first or last host page can be shared with a neighbour, so only a texture, or a write, that lies wholly in a shared page was missed. A write that also touches a texture's unshared pages faults there and marks it written. The map in #2086 is 260x260x2 RGBA8, about 33 host pages, so this race does not explain it by itself, and #2086 stays open.

Checked here (M4 Pro): t_dirty_race fails on the old order with your test (expected 1, got 0) and passes with the fix; t_dirty_slots and test_docs pass; regress.py check on OCTOPATH TRAVELER II passes, but that run opened no page for a pool buffer, so the new order is covered by the unit test only. Not run: Ocarina of Time.

@bzwink thanks for the Goron City run. If the strobing comes back on main, a log with DM_NVN_DIRTYCHECK=1 (the [nvn] packaged textures: ... line, with opened for pool buffers N (changed while open M) and missed K) would tell whether a missed write is behind it.

@seedds seedds closed this Nov 11, 2026
neko973 pushed a commit to neko973/nxnative that referenced this pull request Nov 11, 2026
…ol buffer is made: nvn_dirty_open hashed one pool's protected pages and made them writable before it hashed the next pool's, and adjacent pools (4 KB aligned) share their first / last 16 KB host page, so a guest write to that page between the two went into the second pool's first hash, the close found that pool's page unchanged, and its texture stayed clean with stale content. Every range's protected pages are now hashed and taken (BUSY) before any page is opened, so each hash is of a read-only page; the pages are then opened in runs. A fault or host write on a taken page waits for the open as before, and neither holds a page of its own while it waits. The race reaches only pages two pools share: a write that also touches a texture's unshared pages faults there, so only a texture, or a write, that lies wholly in a shared page was missed. t_dirty_race (open_shared_page: two pools in one host page, a store to the second right after the first opens) fails on the old order (expected 1, got 0) and passes; t_dirty_slots and test_docs pass. regress.py check on OCTOPATH TRAVELER II: PASS (13,980 shaders, 59.3 fps, no new red line, a busy Mac), but that run opened no page for a pool buffer, so the new order is covered by the unit test only. Not run: Ocarina of Time or any game that opens pages, the full gfx suite, lint_c.py, the iPhone. A tester's M2 Max run of 8 minutes saw Ocarina's Goron City ground stop strobing with the PR, with no miss counts. Open: issue seedds#2086's stale terrain map spans about 33 host pages, more than this race reaches by itself, so its cause is not found. The fix and the test are thehobbit85's (PR seedds#2136)

Co-authored-by: Eliran Zach <4012041+thehobbit85@users.noreply.github.com>
Fasen24-AI pushed a commit to Fasen24-AI/swordboiMacNative that referenced this pull request Nov 11, 2026
…ol buffer is made: nvn_dirty_open hashed one pool's protected pages and made them writable before it hashed the next pool's, and adjacent pools (4 KB aligned) share their first / last 16 KB host page, so a guest write to that page between the two went into the second pool's first hash, the close found that pool's page unchanged, and its texture stayed clean with stale content. Every range's protected pages are now hashed and taken (BUSY) before any page is opened, so each hash is of a read-only page; the pages are then opened in runs. A fault or host write on a taken page waits for the open as before, and neither holds a page of its own while it waits. The race reaches only pages two pools share: a write that also touches a texture's unshared pages faults there, so only a texture, or a write, that lies wholly in a shared page was missed. t_dirty_race (open_shared_page: two pools in one host page, a store to the second right after the first opens) fails on the old order (expected 1, got 0) and passes; t_dirty_slots and test_docs pass. regress.py check on OCTOPATH TRAVELER II: PASS (13,980 shaders, 59.3 fps, no new red line, a busy Mac), but that run opened no page for a pool buffer, so the new order is covered by the unit test only. Not run: Ocarina of Time or any game that opens pages, the full gfx suite, lint_c.py, the iPhone. A tester's M2 Max run of 8 minutes saw Ocarina's Goron City ground stop strobing with the PR, with no miss counts. Open: issue seedds#2086's stale terrain map spans about 33 host pages, more than this race reaches by itself, so its cause is not found. The fix and the test are thehobbit85's (PR seedds#2136)

Co-authored-by: Eliran Zach <4012041+thehobbit85@users.noreply.github.com>
(cherry picked from commit 1e69c18)
@thehobbit85
thehobbit85 deleted the codex/oot-shared-page-write-tracking branch November 13, 2026 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0400486024DEC000 A game 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.

3 participants