Repository navigation
Special register 0x84 reads as a 1 x 1 fragment, so a pixel shader that divides by its fragment's size no longer makes +Inf and a black sea - #2373
Closed
bymaxmarc wants to merge 1 commit into
Conversation
…s Resort's island sea is no longer black Pixel shaders read special register 0x84 (S2UR) and divide by the product of its bytes 1 and 2, the fragment's size under variable-rate shading (inferred from the reads in Nintendo Switch Sports Resort, which all take bytes 1 and 2 with PRMT, multiply them and divide by the product). The register is not in Ryujinx's SReg list and EmitSReg read it as 0, so 1 / (0 x 0) gave +Inf: the sea shaders (agl AnnaSea, 01a6b85e2f6ea3d4, b3203175c76cda20, 2271b2e0293b3c55) made NaN at the horizon, the auto-exposure spread it, and the main menu's sea was black with a band of coloured noise above it. The avatar material c30f0edd6c669a61 takes the register into a shading factor, and the avatar's hair and clothes drew flat black. - Translator: AS2r answers 0x84 with 0x00010100 (the host shades every pixel); any other special register the SReg list lacks leaves a translator note instead of a silent 0 - dmsh_xlate rebuilt with dm/tools/native_tools.sh dmsh_xlate on main ccd834e (Microsoft .NET SDK 10.0.403, universal arm64 + x86_64, no weak imports, --source-sha fcb37fccd2f53012 on both as before); lock updated with tool_lock.py --update-lock (dmsh_xlate 90c1264b7831364c -> 09dfad6902250b49) and dmsh_chain.h regenerated (chain d24f05cf9d78 -> 3df0ed1c9c0a) - Test: test_dmsh_ampere_alu.py FragmentSize (the game's S2UR / UPRMT / UIMAD encodings); its two tests fail with main's translator, and all 47 pass with this one - docs/reference/shaders.md entry (the sea and the avatar); symptom index regenerated Translator output against main's on 304 of the game's shaders (4 that read 0x84 and 300 vertex and pixel shaders drawn at random; dmsh_build.py with each tree's translator): the MSL of 412 of 486 outputs is identical, and the other 74 differ only in the folded constant of that read, float(0) -> float(1); the reflection JSON differs only in its chain field, and the metallibs only for those 74 shaders. Checked in the game on the fork's work branch with an earlier main: the sea is blue, the noise and the washed-out exposure are gone, and default avatars show their colours. Needs a full shader rebuild of every game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 11, 2026
biosed
pushed a commit
to biosed/nxnative-android
that referenced
this pull request
Nov 11, 2026
Report Ampere special register 0x84 as a 1 x 1 fragment and make other unknown special registers visible in translator notes. Cover S2R, S2UR, byte extraction, and the diagnostic with Linux SPIR-V tests.\n\nAdapted from seedds/nxnative#2373.
seedds
added a commit
that referenced
this pull request
Nov 12, 2026
…so Nintendo Switch Sports Resort's sea is no longer black and its avatars' hair and clothes are no longer flat black (PR #2103): S2UR of system register 0x84 (Mesa NAK's NAK_SV_VARIABLE_RATE) is the fragment's size in pixels, X in bits 8..15 and Y in bits 16..23, which variable rate shading makes 2 or 4. The translator did not know the register and gave 0, the default for an unknown one; 612 of the game's 11,059 shaders, all pixel shaders, read it, and the sea shaders (01a6b85e2f6ea3d4, b3203175c76cda20, 2271b2e0293b3c55) divide a blend weight by the fragment's area: an infinite weight, NaN, written as 0 by blend_zero, with black dots of NaN noise spread by the auto-exposure; the avatar material c30f0edd6c669a61 takes it into a shading factor. SReg.VariableRate (132, Decoders/InstDecoders.cs) reads 0x10100 in EmitSReg (Instructions/InstEmitMove.cs), which Maxwell's S2R and Ampere's S2R / S2UR (InstEmitAmpereAlu.AS2r) both go through: the host shades every pixel. Chosen over bymaxmarc's PR #2373 (the same value in AS2r only, with the avatars' material found); FifteenPen found the black sea first. The translator was rebuilt with Microsoft's .NET SDK 10.0.404 (native_tools.sh; Homebrew's links its own OpenSSL and is refused), dmsh_chain.lock updated: chain change, DMSH_CHAIN 3856a9225d68, every game's shaders are translated again once. New reference entry (The fragment's shading-rate size reads 1 x 1), symptom index regenerated. Checked: test_dmsh_ampere_mem with DMSH_TEST_XLATE, 86 tests (ShadingRate: float(65792) for colour 0). Co-authored-by: FifteenPen <354289956+FifteenPen@users.noreply.github.com>
3 tasks done
Owner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Pixel shaders of a Switch 2 game read special register 0x84 with S2UR and take its bytes 1 and 2 with PRMT, multiply them and divide by the product. Every one of Nintendo Switch Sports Resort's (04008E001F098000) 612 reads, in 611 pixel shaders, does this. The bytes are the fragment's width and height under variable-rate shading; that meaning is inferred from this use. The register is not in Ryujinx's
SReglist, andEmitSRegread it as 0, so1 / (0 x 0)gave +Inf. The sea shaders (aglAnnaSea:01a6b85e2f6ea3d4,b3203175c76cda20,2271b2e0293b3c55) made NaN at the horizon and the auto-exposure spread it: the main menu's sea was black with a band of coloured noise above it. The avatar materialc30f0edd6c669a61takes the register into a shading factor, and the avatar's hair and clothes drew flat black.The fix, in the translator (
InstEmitAmpereAlu.csAS2r): register 0x84 answers a 1 x 1 fragment (0x00010100), since the host shades every pixel. Any other special register theSReglist does not know now leaves a translator note (S2R of special register 0x.., which the translator does not know: read as 0) instead of a silent 0.This is a chain change.
dmsh_xlatewas rebuilt withdm/tools/native_tools.sh dmsh_xlatefrom main's sources plus this change (Microsoft's .NET SDK 10.0.403, universal arm64 + x86_64, no weak imports;--source-shafcb37fccd2f53012on both, as main, since that digest covers onlyProgram.csand the csproj). The lock was updated withtool_lock.py --update-lock(dmsh_xlate90c1264b7831364c->09dfad6902250b49), anddmsh_chain.py --headergivesd24f05cf9d78->3df0ed1c9c0a. The build is not byte-reproducible (native_tools.sh: each link gets its own UUID). If you would rather build the binary yourself, take the source and test commit and rebuild withnative_tools.sh,tool_lock.py --update-lockanddmsh_chain.py --header.Reference entry: The fragment size register (special register 0x84) reads 1 x 1, symptom index regenerated.
This PR and the uniform-tests PR (#2372) each change
dm/gfx/nvn/dmsh_chain.h, so whichever lands second needsdmsh_chain.py --headerrun again on top of the first.Checked
DMSH_TEST_XLATE=dm/gfx/tools/dmsh_xlate_bin/dmsh_xlate python3 dm/gfx/tests/test_dmsh_ampere_alu.py: 47 tests pass,FragmentSizeamong them (the game's own S2UR, UPRMT and UIMAD encodings; the product folds to 1, and the register is 65792). With main's translator, the twoFragmentSizetests fail and the other 45 pass.python3 dm/gfx/tools/tool_lock.py --check-tools: passes.dm/tools/tests/test_nsp_chain.py: 17 tests OK.python -m unittest discover -s dm/tools/tests: 1,469 tests OK, 27 skipped, as on main.python -m unittest discover -s dm/gfx/tests -p 'test_*.py'withDMSH_TEST_XLATEset: 491 tests, the 489 that main has plus the 2 new ones. The same 10 modules fail to import on this branch and on main, because they load a game profile at import andDM_GAMEwas not set (test_gfpak,test_kpka,test_monolith,test_mt_framework,test_needle,test_pg_shader,test_pkz,test_shader_split_progress,test_snowdrop,test_sumo). No other failure.make -k -C dm/gfx/tests GAME=010015100B514000 -j8 run(M2 Max): exit 0, every check passed.native_tools.shasks: I randmsh_build.pyon 304 of Nintendo Switch Sports Resort's shaders once in a main tree and once in this branch, each with its own translator. The 304 were the 4 named above and 300 vertex and pixel shader keys drawn at random from its 10,718. The MSL of 412 of the 486 outputs is identical. The other 74 shaders all read 0x84. That is more than the 611 above suggests. The 612 reads were counted in the code stored in the game's shader packages, so the likely reason is that many shader keys share one code body; this was not checked. Their MSL differs in one line only, the folded value of that read:float _N = float(0);becomesfloat(1). The reflection JSON differs only in itschainfield, the metallibs only for those 74, and no new translator note appears.regress.py: not run. Only this game has a build on this Mac. The change reaches only shaders that read special register 0x84 (Ampere, NVN2), so it most likely reaches the other Switch 2 games (Mario Kart World, Donkey Kong Bananza, PRAGMATA). Their shaders were not rebuilt here.Docs
docs/reference/shaders.md, andpython3 dm/tools/docs_symptoms.pywas run (the symptom index)dmsh_chain.py --headerwas run anddm/gfx/nvn/dmsh_chain.his part of the change, withdmsh_chain.lockand the rebuiltdmsh_xlate