Skip to content

A GPU-written span's sliver inside a texture does not make it the GPU's: Ocarina of Time's terrain tiles no longer vanish or show another tile's surface (#1566) - #1920

Closed
nawangwe wants to merge 1 commit into
seedds:mainfrom
nawangwe:gws-sliver

Conversation

@nawangwe

@nawangwe nawangwe commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Symptom. In The Legend of Zelda: Ocarina of Time (0400486024DEC000 v131072) on the Mac, after some walking a whole terrain tile goes missing (the ground vanishes or sits flat and low) or shows another tile's surface with a hard square edge: a square of grass across the cobbled path out of Castle Town, a grass patch on Death Mountain Trail. A reload makes it right for a while. The same class as the Death Mountain Trail and Kakariko reports in #1566 on current main (fixes #1566's remaining cases of this kind; the horizontal-stripes part of that issue is separate).

Cause. gws_owned asks every GPU-written span that overlaps a texture whether the CPU has rewritten it since, through the span's whole pages (gws_cpu_since). An overlap that holds no whole page of the texture cannot be answered that way, and its silence counted as the GPU's, so the texture became a GPU texture of zeros at its first use instead of being uploaded from its storage. A first-use trace of every 260x260 texture (which way the decision went and why, with the overlapping spans, their stamps and the dirty generation) showed two slivers doing this to the CPU-filled height (R32F) and hole (RGBA8) maps:

  • the span of a neighbouring 260x260 RG8 normal map (laid out at 214,528 bytes) reaching 1,536 or 13,824 bytes past the map's start. Textures are 512-byte aligned, and a span outlives the texture it was recorded for, so the game hands part of that storage to the next tile's map. 10 of the 12 cases caught.
  • a 2,560-byte remnant of an old span left between two newer ones (gws_set keeps the parts outside a new span), lying inside the map. 2 of 12.

A caught decision, the hole map behind the second kind of sliver (offsets relative to the texture, which is 417,792 bytes; the page tracker answers the two large spans, the 2,560-byte one is unanswerable and was taken as the GPU's):

first use ser=705240 260x260x2 fmt 0x25 mem 0x7e8cc47000 +835584 | cpu_content 0 → GPU TEXTURE | gpuw_overlaps 1 mem_any 1
  spans [-28672..+185856 stamp 890087 tracked cpu_since 1] [+185856..+188416 stamp 13347 tracked cpu_since 0] [+188416..+402944 stamp 890087 tracked cpu_since 1] ...

Fix. gws_sliver in nvn_mtl.m: a span whose overlap with the texture holds no whole page of it, and which does not cover the texture, does not decide in gws_owned. A span that covers the whole texture keeps the GPU's, so a texture smaller than a page over storage the GPU wrote stays a GPU texture, and a neighbour's CPU write in the same page is not its. 8 lines; no new state, no memory kept or freed.

Reference entry: A span's sliver inside a texture in docs/reference/nvn-objects.md.

Related. This is the third hole fix in this chain: #1558 made a texture the CPU rewrites after its first use follow the first-use rule, #1604 made the rule ask about the texture's own storage instead of its pool (the spans this change refines); both are on main and both symptoms stayed fixed in the play test. #1876 found the remnant case independently and keeps a split span's originally protected pages. Walked through on the traced cases, that leaves the neighbour-span case GPU-owned (the neighbour's span rounds down to the texture's start, so no page of the texture is in its protected range), which was 10 of the 12 catches here. #1447's address ranges are the mechanism these spans are built on, not this.

Checked

  • Tests: make -C dm/gfx/tests GAME=0400486024DEC000 check_t_cpu_storage (68 checks, 0 failed; Apple silicon, Metal). Three new cases: a neighbour's span reaching less than a page into a CPU-filled texture leaves it taken from its storage while the drawn neighbour stays the GPU's; a sub-page remnant of an old span inside a texture the CPU rewrote does not keep it the GPU's; a texture smaller than a page over storage the GPU wrote stays the GPU's though a neighbour in its page is CPU-written. With the fix reverted, exactly the four sliver checks fail and the two guarding the covering case pass. check_t_pool_chunks (146), check_t_cpu_tex (98), check_t_alias_takeover (28) pass. dm/tools/tests/test_docs.py passes (11); docs_symptoms.py run.

  • Games run: Ocarina of Time 0400486024DEC000 v131072, Mac app. Measured with the first-use trace, same save, same areas (Hyrule Field, Castle Town road, Kakariko, Death Mountain Trail):

    before with the fix
    play 26 min 26 min
    CPU-filled 260x260 maps first used 620 1,510
    of them resolved as GPU textures with data in storage (the bug) 12 0
    of them taken from storage past a sliver that would have blocked it — 162
    wrong tiles seen on screen 3 (two kinds, screenshots taken) 0

    Memory flat (no textures kept or released by the change), 60 fps as before.

  • Devices and OS: MacBook Pro M4 Pro 24 GB, macOS 26.6.

  • Not checked: iOS; other games (the rule only changes the answer for sub-page overlaps that do not cover the texture, which no other game is known to hit; scripts/regress.py check --games not run). The play test ran on a fork of main at 18cdc00 whose span code is identical; the port to current main is covered by the tests above.

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

…'s: Ocarina of Time's terrain tiles no longer vanish or show another tile's surface after walking (GitHub issue seedds#1566)

gws_owned asked every span overlapping a texture whether the CPU had rewritten it since the GPU wrote it, through the span's whole pages (gws_cpu_since). An overlap that holds no whole page of the texture cannot be answered that way, and its silence counted as the GPU's: the texture was made a GPU texture of zeros at its first use instead of being uploaded from its storage.

Two slivers did that to Ocarina of Time's CPU-filled 260x260 height (R32F) and hole (RGBA8) maps: the span of a 260x260 RG8 normal map (laid out at 214,528 bytes) reaching 1,536 or 13,824 bytes past the map's start, since textures are 512-byte aligned and a span outlives the texture it was recorded for, so the game hands part of that storage to the next tile's map; and a 2,560-byte remnant of an old span left between two newer ones (gws_set keeps the parts outside a new span). Whole terrain tiles vanished in Hyrule Field, a square of grass lay across the cobbled path from Castle Town, a grass patch sat on Death Mountain Trail; a reload made it right for a while.

gws_sliver: a span whose overlap with the texture holds no whole page of it, and which does not cover the texture, does not decide. A span that covers the whole texture keeps the GPU's, so a texture smaller than a page over storage the GPU wrote stays a GPU texture and a neighbour's CPU write in the same page is not its.

Traced on the Mac (M4 Pro, macOS 26.6) with a first-use trace of every 260x260 texture: 12 of 620 CPU-filled maps in 26 minutes were resolved as GPU textures, each with a sliver as the only span the dirty tracker could not answer for. With the rule, 1,498 of 1,510 tile maps in the next 26 minutes were taken from their storage, 162 of them past a sliver that would have made them GPU textures, none came up as zeros, and no tile was wrong on screen. t_cpu_storage: three new cases (68 checks); without the rule the four sliver checks fail and the two that guard the covering case pass. t_pool_chunks, t_cpu_tex and t_alias_takeover pass. Reference entry in docs/reference/nvn-objects.md.
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation labels Nov 9, 2026
Aznracer pushed a commit to Aznracer/Mighty-Green-Kokiri-Kid that referenced this pull request Nov 9, 2026
…'s: Ocarina of Time's terrain tiles no longer vanish or show another tile's surface (PR seedds#1920, issue seedds#1566)
@padsiwono

Copy link
Copy Markdown
Contributor

Play check on a second Mac: with this commit, the missing terrain tiles I had in Hyrule Field didn't come back.

Ocarina of Time 0400486024DEC000 v131072, M5 Max, macOS 26.6.2, 3024x1702 output, HDR, in a window. Host: main 270b6fd5 plus Ocarina of Time fixes and diagnostics; c4b7894 cherry-picked onto it cleanly.

  • Without it: about 2 minutes into a session from Kakariko towards the castle, Hyrule Field showed large grey see-through slabs with hard edges where tiles were missing (three frames below). More ground was missing further on; after dying and respawning it was all there, as your "a reload makes it right" says.
  • With it: 5.2 minutes from Hyrule Field to Hyrule Castle, its moat and the Castle Town market: no missing terrain seen.

One session each, so n=1.

One thing that fits your cause: both builds had a diagnostic that logs a 260x260 R32F height map uploaded with every value the same (we were after flat ground). It logged nothing in either session. With your trace that's expected: the broken maps were never uploaded, they became GPU textures of zeros.

Three frames from Hyrule Field at night without this change: large grey see-through vertical slabs with hard edges where terrain tiles are missing

seedds added a commit that referenced this pull request Nov 9, 2026
…'s: Ocarina of Time's terrain tiles no longer vanish or show another tile's surface after walking (PR #1920)

gws_owned asked every span overlapping a texture whether the CPU had rewritten it since the GPU wrote it, through the span's whole pages (gws_cpu_since). An overlap that holds no whole page of the texture cannot be answered that way, and its silence counted as the GPU's: the texture was made a GPU texture of zeros at its first use instead of being uploaded from its storage.

Two slivers did that to Ocarina of Time's CPU-filled 260x260 height (R32F) and hole (RGBA8) maps: the span of a 260x260 RG8 normal map (laid out at 214,528 bytes, the driver's size) reaching 1,536 or 13,824 bytes past the map's start, since a span outlives the texture it was recorded for and the game hands part of that storage to the next tile's map; and a 2,560-byte remnant of an old span left between two newer ones (gws_set keeps the parts outside a new span). Whole terrain tiles vanished in Hyrule Field, a square of grass lay across the cobbled path from Castle Town, a grass patch sat on Death Mountain Trail; a reload made it right for a while.

gws_sliver: a span whose overlap with the texture holds no whole page of it, and which does not cover the texture, does not decide. A span that covers the whole texture keeps the GPU's, so a texture smaller than a page over storage the GPU wrote stays a GPU texture and a neighbour's CPU write in the same page is not its.

Traced on the Mac (M4 Pro, macOS 26.6) with a first-use trace of every 260x260 texture: 12 of 620 CPU-filled maps in 26 minutes were resolved as GPU textures, each with a sliver as the only span the dirty tracker could not answer for. With the rule, 1,498 of 1,510 tile maps in the next 26 minutes were taken from their storage, 162 of them past a sliver that would have made them GPU textures, none came up as zeros, and no tile was wrong on screen. A second Mac (M5 Max) had tiles missing in Hyrule Field within 2 minutes without it and none in 5.2 minutes with it. t_cpu_storage: three new cases (68 checks); t_pool_chunks, t_cpu_tex and t_alias_takeover pass. regress.py check --quick on NieR:Automata: PASS. Reference entry in docs/reference/nvn-objects.md, a line in Ocarina of Time's note. The horizontal stripes in issue #1566 are separate.

Refs #1566

Co-authored-by: Nawangwe <4419194+nawangwe@users.noreply.github.com>
@seedds

seedds commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Landed on main as c06d1e7, with you as co-author. Thank you: the first-use trace pinned it down exactly, and the covering-span guard is the right boundary.

On top of your commit: the reference entry's "Found on" now only credits #1876 (it no longer describes #1876's code, which isn't on main) and adds padsiwono's M5 Max check; Ocarina of Time's note gets a line under "Found on this game". The commit says "Refs #1566", since the stripes part of that issue is separate.

Checked: the 214,528-byte RG8 layout matches the driver's size (t_tex_layout), so the overlap is storage reuse, not an oversized layout. t_cpu_storage 68/68, t_pool_chunks, t_cpu_tex, t_alias_takeover and test_docs pass; regress.py check --quick on NieR:Automata passes. The red CI checks on this PR were already red on main (t_pso_archive's clang-tidy count, the iOS IOBluetooth link fixed by 9983d65, test_dmsh_abi's "?" count).

@seedds seedds closed this Nov 9, 2026
ivan-leschinsky pushed a commit to ivan-leschinsky/nxnative that referenced this pull request Nov 9, 2026
…carry: seedds#1876's split-span entry came along with seedds#1824's README context when the branch moved onto main, where seedds#1920 (c06d1e7) fixes the same terrain holes instead

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U35V38c6UYLbzyyPx6Up7P
Know-It-All-Brother pushed a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
…'s: Ocarina of Time's terrain tiles no longer vanish or show another tile's surface after walking (PR seedds#1920)

gws_owned asked every span overlapping a texture whether the CPU had rewritten it since the GPU wrote it, through the span's whole pages (gws_cpu_since). An overlap that holds no whole page of the texture cannot be answered that way, and its silence counted as the GPU's: the texture was made a GPU texture of zeros at its first use instead of being uploaded from its storage.

Two slivers did that to Ocarina of Time's CPU-filled 260x260 height (R32F) and hole (RGBA8) maps: the span of a 260x260 RG8 normal map (laid out at 214,528 bytes, the driver's size) reaching 1,536 or 13,824 bytes past the map's start, since a span outlives the texture it was recorded for and the game hands part of that storage to the next tile's map; and a 2,560-byte remnant of an old span left between two newer ones (gws_set keeps the parts outside a new span). Whole terrain tiles vanished in Hyrule Field, a square of grass lay across the cobbled path from Castle Town, a grass patch sat on Death Mountain Trail; a reload made it right for a while.

gws_sliver: a span whose overlap with the texture holds no whole page of it, and which does not cover the texture, does not decide. A span that covers the whole texture keeps the GPU's, so a texture smaller than a page over storage the GPU wrote stays a GPU texture and a neighbour's CPU write in the same page is not its.

Traced on the Mac (M4 Pro, macOS 26.6) with a first-use trace of every 260x260 texture: 12 of 620 CPU-filled maps in 26 minutes were resolved as GPU textures, each with a sliver as the only span the dirty tracker could not answer for. With the rule, 1,498 of 1,510 tile maps in the next 26 minutes were taken from their storage, 162 of them past a sliver that would have made them GPU textures, none came up as zeros, and no tile was wrong on screen. A second Mac (M5 Max) had tiles missing in Hyrule Field within 2 minutes without it and none in 5.2 minutes with it. t_cpu_storage: three new cases (68 checks); t_pool_chunks, t_cpu_tex and t_alias_takeover pass. regress.py check --quick on NieR:Automata: PASS. Reference entry in docs/reference/nvn-objects.md, a line in Ocarina of Time's note. The horizontal stripes in issue seedds#1566 are separate.

Refs seedds#1566

Co-authored-by: Nawangwe <4419194+nawangwe@users.noreply.github.com>
Know-It-All-Brother added a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
Main's terrain fix (span slivers, PR seedds#1920) replaces the earlier split-span fix here, and main's pool release (no wait for another submission) replaces the open-fence release; the GPU watchdog's shader ring, alias_has and reg_storage are kept. Chain edbb339d8660.
Fasen24-AI added a commit to Fasen24-AI/swordboiMacNative that referenced this pull request Nov 10, 2026
…ctions compiled out, DMSH_BORDER_FC), seedds#1930 (native float atomic add), seedds#1920 (GPU-written sliver does not own a texture: terrain tiles), seedds#1917 (depth attachment sampled by a depth-writing draw reads a copy), seedds#1928 (enc_thread on), seedds#1942 (idle-GPU late frames do not step the hold up), seedds#1940 (prewarm lines after the first frame), seedds#1936 (resolution families uncompressed)

Hand-resolved: image_texture keeps the virtual pools we admit; fb_prepare keeps our pass split and local dread flag after the depth snapshot; the compute key keeps the NDVE direct draw before the border constant.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7

Upstream pull requests applied in this commit (their authors' work, brought onto this tree with a three-way merge; the description is in each PR):
- seedds#1774 by uncraftablexd (closed): Compute threads that exit before a barrier keep arriving at it, and every shader loop has a cap (GPU hang defences), chain change
- seedds#1917 by FifteenPen (closed): A draw that writes depth and samples its own depth attachment reads a copy of it: Ocarina of Time's light-blue dashes over the rock at Death Mountain Trail's cave entrance are gone
- seedds#1920 by nawangwe (closed): A GPU-written span's sliver inside a texture does not make it the GPU's: Ocarina of Time's terrain tiles no longer vanish or show another tile's surface (seedds#1566)
- seedds#1928 by FeReuss (open): Ocarina of Time: enc_thread on, the replay encodes on a thread of its own
- seedds#1929 by FeReuss (open): Unused border corrections are compiled out of pipelines that bind no sampler with a twin (function constant DMSH_BORDER_FC)
- seedds#1930 by FeReuss (open): A float add to storage memory with no result read is one native atomic add instead of a compare-and-swap loop
- seedds#1936 by TheGoldfish11 (closed): Write resolution-family image textures uncompressed
- seedds#1940 by uncraftablexd (open): A prewarm list's lines after its #@after_first_frame line are built after the first frame, in the background, instead of in the prepare step (issue seedds#1747)
- seedds#1942 by uncraftablexd (open): Frame step: late frames with the GPU idle do not step up, and a longer hold the GPU does not explain is probed
Fasen24-AI added a commit to Fasen24-AI/swordboiMacNative that referenced this pull request Nov 10, 2026
…1909 seedds#1897 seedds#1917 seedds#1920 seedds#1936) and seedds#1902, ahead of merging main

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7

Upstream pull requests applied in this commit (their authors' work, brought onto this tree with a three-way merge; the description is in each PR):
- seedds#1897 by Know-It-All-Brother (closed): HMMA with an f16 accumulator rounds once per 8 products, as Ampere does (chain change)
- seedds#1902 by fxd0h (open): HFMA2 rounds once to half, as Ampere does (chain change)
- seedds#1909 by oz-rinkliff (open): Bound translated fragment uniform reads to the NVN binding length
- seedds#1917 by FifteenPen (closed): A draw that writes depth and samples its own depth attachment reads a copy of it: Ocarina of Time's light-blue dashes over the rock at Death Mountain Trail's cave entrance are gone
- seedds#1920 by nawangwe (closed): A GPU-written span's sliver inside a texture does not make it the GPU's: Ocarina of Time's terrain tiles no longer vanish or show another tile's surface (seedds#1566)
- seedds#1936 by TheGoldfish11 (closed): Write resolution-family image textures uncompressed
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.

Ocarina of Time (M5 Mac): blue holes in Kokiri Forest terrain, and full-screen horizontal stripes since 0c7e88b9

3 participants