Skip to content

A plain HMMA call works in matrices of its own: in slot 0 it overwrote the chain waiting there, which drew the dotted columns of agl's NDSR upscaler - #1544

Closed
lunks wants to merge 1 commit into
seedds:mainfrom
lunks:fix/ndsr-columns-main
Closed

lunks wants to merge 1 commit into
seedds:mainfrom
lunks:fix/ndsr-columns-main

Conversation

@lunks

@lunks lunks commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

For #1537 (and the stripes in #1265). Not run in game.

What and why

#1537 reports dotted columns across the whole picture of Ocarina of Time, vertical, in the same place in every frame (esdras-kyo traced them to the compute dispatches of agl's variable-resolution NDSR, agl_ndsr_vr.pck, with every render-resolution target clean).

Cause, from the HMMA chains of #1456: a chain keeps its D in an accumulator slot (acc[(flags >> 8) * 2]) until the call that takes it as C. A call in no chain has no slot in its flags, so HMMA_BODY worked in acc[0..1], and TensorPass hands out slot 0 first. A plain call while a chain waited in slot 0 replaced that chain's D, and the chain's end added to the wrong matrix. All 52 HMMA shaders of Ocarina of Time's NDSR packs have such a call; none of its 19 NDVE movie networks do, which is where the chains were measured.

Fix (dmsh_build.py, HMMA_BODY): a call with neither FlagCSlot nor FlagDSlot works in two matrices of its own, acc[16] and acc[17], past the 8 slots. No translator change. A chain change (844bf1a5ea8f to 5c852d7989ac).

Checked

Docs

  • The finding is in docs/reference/shaders.md ("A plain HMMA call in a chain's slot"); python3 dm/tools/docs_symptoms.py was run
  • A chain tool changed: dmsh_chain.py --header was run and dm/gfx/nvn/dmsh_chain.h is part of the change
  • No game files, keys or data extracted from a game in the change

…e the chain waiting there, which drew the dotted lines of agl's NDSR upscaler

A call in no chain has no slot in its flags, so HMMA_BODY worked in acc[0..1], and TensorPass hands out slot 0 first. A plain call while a chain waited in slot 0 replaced that chain's D, and the chain's end added to the wrong matrix. All 52 HMMA shaders of Ocarina of Time's NDSR packs have such a call, none of its 19 NDVE movie networks, on which the chains were measured.

fa3033d05c0a41d6 (1536x864 to 2048x1152, 48x24 input tile to 64x32 output tile), one dispatch offline with its own weights on a synthetic picture, M5 Pro: as built, columns 0 to 31 of each tile are off by 26 of 255 on average and columns 33 to 63 not at all, the shape of GitHub issue 1537. Fixed, the output equals the same shader with every flag off in all 7,077,888 channels, and a per-lane HMMA within 1 of 255. Not yet seen in the game.

test_chains_survive_plain_calls: the shader's calls 4 to 9 against a CPU reference, at four and six SIMD-groups. Chain 844bf1a5ea8f to 5c852d7989ac; no translator change. Not run in game.
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation shader chain The shader translation chain (DMSH_CHAIN): a change rebuilds every game's shaders labels Nov 6, 2026
@esdras-kyo

Copy link
Copy Markdown

Thank you so much for tracking this down so quickly, and for such a clear write-up! I tested it in game, and the dotted columns are gone. 🎉

How I tested: M5 Max (applegpu_g17s), macOS 26.6.2, Mac app on c229bd7 / chain 23a1ea1db092. I didn't rebuild the chain. I applied the HMMA_BODY change from this PR by hand to the MSL in the library (k = (f & 6) ? (f >> 8) * 2 : 16, acc[18], dm_acc[18]), recompiled with the chain's flags (metal -c -O1 -fpreserve-invariance), and put the libraries in a b-23a1ea1db092/ folder beside the image, which the host reads first. The unpatched shader recompiled this way is byte-identical to the packaged one, so the patch is the only change.

Result, Kokiri Forest, launcher defaults (DM_UPSCALER=game):

  • Only fa3033d05c0a41d6 patched: the vertical dotted columns are gone on screen. In a texraw of the 2048×1152 output, the spread of the high-passed column means in columns 0–31 against 32–63 of each 64-column tile went from about 6.5× (unpatched, several dumps) to 0.9–1.0× (4 dumps). Both halves of the tile now look the same.
  • I then applied the same change to all 71 shaders in Ocarina's library that have the helper (52 in the NDSR packs and 19 in the NDVE movie networks, the same counts as in your description). The game runs with them, and I'll keep playing on that build and report anything odd.

Not checked: the horizontal ~45 px lines from #1537's first report, the movies after the change, and regress.py.

Thanks again for the great work on nxnative. It's a real joy to play Ocarina on the Mac. Big hugs from Brazil! 🇧🇷

@lunks

lunks commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

For the record, what points at this fix so far, from three people on different Macs:

Still open: the horizontal stripes some reports show (#1566, FrankieJLyons on #1537), which no one has tested with this change yet.

@aksmith-git

Copy link
Copy Markdown
Contributor

I found the same cause and made the same HMMA_BODY change (k = (f & 6) ? (f >> 8) * 2 : 16, acc[18]) on our Ocarina of Time builds before I saw this PR. It's byte-identical to yours, so I won't open a second PR. Here are numbers from an in-game run with a rebuilt chain, which may help close the "Not run in game" gap.

Symptom on our side. After merging bd9f422 (#1456), every gameplay frame on the Mac had a 32-px dotted column in every 64 px of the 1920x1080 scene, with the UI clean on top. DM_UPSCALER=metalfx was clean. That's the same shape as #1537 and #1547.

Bisection before the fix. I recompiled the MSL of the 52 NDSR HMMA shaders and laid the metallibs over the library:

  • all flags off: clean
  • only FlagCSlot / FlagDSlot off: clean
  • only FlagADirect, FlagAReuse or FlagCZero off: still striped
  • D-slot calls also returning D (no early return): still striped, so the C read from the slot is the wrong one

That fits the slot-0 cause: the culprit is the slot bookkeeping alone, not the A or C=0 shortcuts.

In game, full rebuild (M4 Pro, macOS 27.0.1, host and translator with this body, Ocarina's Mac library (14,122 shaders, all built OK) rebuilt for the new chain, launcher defaults so DM_UPSCALER=game, Kokiri Forest bridge and village). I measured the strength of the column pattern on the screenshots (lower is cleaner):

Offline, per shader. I ran each of the 52 NDSR HMMA shaders alone on random input and weights and compared it with the same shader built with every flag off. With the #1456 body, up to 252,485 pixels differ. With this body, the difference stays within what two runs of identical builds differ by (< 200 px, edge pixels). 15 of the 52 hang on the synthetic input with every variant, flags-off build included, so this covers 37.

Not checked: the horizontal lines (#1566, the first report on #1537), movies after the change, iOS, regress.py.

One more thing: Donkey Kong Bananza's NDSR has 3,996 C = 0 calls beside 1,325 chain calls, so its gameplay picture is probably affected the same way. I haven't tested it.

@PucaVaz

PucaVaz commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Tested in game on an M4 MacBook Air (24 GB, macOS 27.2), chain 5c852d7989ac: main b2d46b8 + this PR, --seen-shaders Mac app, handheld (DM_OPMODE=0), DM_UPSCALER=game, windowed. Scripted runs from the Kokiri Forest autosave in Link's house: the house, out of the door, Saria's greeting and the balcony, with the same shots in each run.

On this Mac in handheld the bug shows as horizontal dotted rows, 24 across the 1920x1080 picture (44.9 px apart), in the house and outside; no vertical 64-px columns. With this PR they are gone in every shot.

Before: the same app with main's form of the 62 network shaders that have the HMMA helper (all 52 NDSR ones and 10 Ndve), i.e. this PR's three HMMA_BODY / acc[18] edits undone in the library MSL, compiled with the chain's flags and laid over the image as loose files (DM_MSL_DIR). Recompiling the unchanged MSL the same way gives byte-identical libraries, so the overlay is what main builds.

Measure: on luma, each row's mean |I(x) − (I(x−1)+I(x+1))/2| minus a 15-row running median, then the Fourier amplitude at the 45-row period (luma levels, 0-255). Over 8 shots on the balcony (~270 frames): 0.262 before, 0.016 after (band power 56.1 vs 11.4, the 45-row period's power over the rest of the spectrum). Single shots: house 0.098 / 0.013, door 0.080 / 0.013, balcony after Saria 0.269 / 0.022. This is not bryanjeal's amplitude (I couldn't find its definition), so compare the ratios, not the values.

GPU time is unchanged: GPU p50 25.5 vs 26.0 ms in the house, 36.8 vs 37.0 ms on the balcony. Frame times with the PR: house 41.9 fps (p50 / p95 / p99 22.7 / 33.9 / 46.1 ms), 180 s walk 38.0 fps (24.0 / 38.5 / 51.1 ms, 78 frames over 50 ms; the chain's first run on this Mac, and the screen was locked, which made the display link drop out now and then). No GPU errors; [RED] lines only the partial-library notes.

Not checked: docked mode (2048x1152 output, where the M5 reports see columns), MetalFX, the movies, regress.py.

@WillLehmann04

Copy link
Copy Markdown

Same speckles here on a different chip, with screenshots as requested.

Setup

  • MacBook Pro 14-inch (2023), Apple M2 Pro (applegpu_g14s), 16 GB, macOS 27.0.1
  • The Legend of Zelda: Ocarina of Time, 0400486024DEC000, v131072, built from one NUP (base + update, no DLC)
  • Mac app (--platform mac-app), built and launched through NX Play (nx-native-wrapper)
  • Build line from the log: [dm] build: commit <COMMIT>, shader chain <CHAIN>, profile 0400486024DEC000

What I see
Small red, green and purple dots and short horizontal dashes scattered over the picture. They are on the title-screen backdrop (night sky and grass), on the tapestry intro, and they persist into gameplay (grass and foliage, screenshot). They don't move with the camera like geometry; they look like per-pixel noise in the final image.

What I tried

Before the b1d7c70 / 6121fd7 landing, the title movie on this Mac showed the 32×8 block lattice from #1343 instead; that is gone now and the speckles are what remains.

Possibly separate
Attached screenshot of speckles. With occasional large tears (speckle but a large portion of the screen)

Screenshot 2026-11-07 at 8 25 49 am

Happy to run any DM_CTL dumps or test a chain on this M2 Pro.

@lunks

lunks commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing: 13b432b on main fixes the same bug the same way (the plain HMMA call works in its own matrices instead of slot 0; chain d866f9700d78), so this is superseded. Thank you to everyone who tested it, esdras-kyo, aksmith-git, PucaVaz, jonpage0 and bryanjeal in particular.

@WillLehmann04: thanks for the screenshots and the M2 Pro details. A rebuild that says "reused (inputs unchanged)" has not picked up a chain change, so that test of #1544 did not exercise it. If you can rebuild at 13b432b or later with a full shader rebuild and the dots remain, please add it to #1568, which tracks the speckles on M1/M2 Pro; it looks like a separate cause from the columns and rows this fixed.

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) shader chain The shader translation chain (DMSH_CHAIN): a change rebuilds every game's shaders

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants