Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dm/gfx/nvn/dmsh_chain.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Generated file (dm/gfx/tools/dmsh_chain.py --header): the B shader library translation chain version this source tree expects (default options), one for every game;
// the host loads only <library dir>/b-<V>/. Run dmsh_chain.py --header after changing the chain's tools and commit this file with the change
#define DMSH_CHAIN "d24f05cf9d78"
#define DMSH_CHAIN "3df0ed1c9c0a"
19 changes: 19 additions & 0 deletions dm/gfx/tests/test_dmsh_ampere_alu.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,5 +347,24 @@ def test_imad_bit_75_of_the_c_slot_forms_is_no_negation(self):
self.assertEqual(self.stored(self.MOV_R5_3, imad), '10u')


class FragmentSize(Alu):
"""S2UR of special register 0x84, which only pixel shaders read: Nintendo Switch Sports Resort's sea shader 01a6b85e2f6ea3d4 takes its bytes 1 and 2 (the fragment's width and
height under variable-rate shading), multiplies them and divides by the product. Read as 0 the division gave +Inf, the sea's NaN spread over the picture and the island's sea was
black. The host shades every pixel: a 1 x 1 fragment, so the product is 1. The encodings are the game's (+0x29b0, +0x2c40, +0x2c80, +0x2ca0)"""
S2UR = (0x000ea20000008400, 0x00000000000479c3) # S2UR UR4, SR 0x84
BYTE2 = (0x004fe2000800003f, 0x0000777204057896) # UPRMT UR5, UR4, 0x7772: byte 2
BYTE1 = (0x000fc4000800003f, 0x0000777104047896) # UPRMT UR4, UR4, 0x7771: byte 1
PRODUCT = (0x000fe2000f8e023f, 0x00000004050472a4) # UIMAD UR4, UR5, UR4, URZ
MOV_UR4 = (0x000fe20000000f00, 0x0000000400067c02) # MOV R6, UR4: the value stored

def test_one_pixel_fragment(self):
msl = self.kept(self.S2UR, self.BYTE2, self.BYTE1, self.PRODUCT, self.MOV_UR4)
self.assertRegex(msl, r'= 1u;') # the product, folded to the 1 x 1 fragment
self.assertNotRegex(msl, r'= 0u;')

def test_register_value(self): # the whole register: bytes 1 and 2 are 1, the others 0
self.assertIn('65792u', self.kept(self.S2UR, self.MOV_UR4))


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion dm/gfx/tools/dmsh_chain.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Translation chain tool fingerprints (written by tool_lock.py --update-lock; regenerate after changing tools and commit it)
dmsh_xlate 90c1264b7831364c
dmsh_xlate 09dfad6902250b49
xlate_source fcb37fccd2f53012
spirv-cross e37945d8e441d34d
metal_macosx Apple metal version 32023.922 (metalfe-32023.922.4)
Expand Down
Binary file modified dm/gfx/tools/dmsh_xlate_bin/dmsh_xlate
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -730,12 +730,31 @@ public static void ACs2r(EmitterContext context)
}
}

// Special register 0x84, read only by pixel shaders: its bytes 1 and 2 are the fragment's size in pixels under variable-rate shading (inferred from use: Nintendo
// Switch Sports Resort's 612 reads, all S2UR, take bytes 1 and 2 with PRMT, multiply them and divide by the product). The host shades every pixel, so the
// fragment is 1 x 1. Read as 0 (EmitSReg's default), 1 / (0 x 0) was +Inf: the sea's pixel shaders made NaN at the horizon, which the auto-exposure spread
// over the whole picture (a black sea and blocks of noise above it in the main-menu island)
private const int SRegFragmentSize = 0x84;
private const int FragmentSizeOneByOne = 0x00010100;

// S2R Rd, SR / S2UR URd, SR (72..80)
public static void AS2r(EmitterContext context)
{
AmpereOp x = new(context.CurrOp);
int sr = x.Bits(72, 80);

if (sr == SRegFragmentSize)
{
ASet(context, x, Const(FragmentSizeOneByOne), AUniform(context));
return;
}

if (!Enum.IsDefined(typeof(SReg), sr))
{
context.TranslatorContext.GpuAccessor.Log($"S2R of special register 0x{sr:x2}, which the translator does not know: read as 0");
}

ASet(context, x, EmitSReg(context, (SReg)x.Bits(72, 80)), AUniform(context));
ASet(context, x, EmitSReg(context, (SReg)sr), AUniform(context));
}

// ULDC URd, c[bank][offset] (0xab9) or c[bank][URa + offset] (0xabb, URa at 24..30): a byte, short, word or word pair (size 73..76)
Expand Down
1 change: 1 addition & 0 deletions docs/agents/symptoms.md
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,7 @@ One line per symptom in the [subsystem references](../reference/): what you see
- a deferred scene with part of its materials unlit or black: grass, ground and trees dark while wood and characters look right, whole areas of a map black. There may be no red line. A texture dump shows an integer G-buffer target (`fmt=73`, RGBA8Uint) holding only zeros, and the lighting pass reads it. Since this entry, `[RED] shader … writes colour target …, an integer format, as a float`. → [Integer outputs told from float bits](../reference/shaders.md#integer-outputs-told-from-float-bits)
- `failed to create render pipeline … Cannot convert attribute …`; draws missing (a pipeline that fails to build). → [Integer-typed vertex inputs fed normalized formats](../reference/shaders.md#integer-typed-vertex-inputs-fed-normalized-formats)
- outdoors the whole screen blurred (depth of field out of focus everywhere). → [Depth mode near = 0 needs the per-mode entry points](../reference/shaders.md#depth-mode-near--0-needs-the-per-mode-entry-points)
- a Switch 2 game's sea, an avatar's clothes, or another pixel shader that uses its fragment's size, comes out black, with blocks of coloured noise along the horizon and a scene washed out around it; `DM_NVN_MAGSCAN` finds a few non-finite pixels first in that shader's pass. The translated MSL has `float _N = float(0);` and a `1.0 / _N` after it. → [The fragment size register (special register 0x84) reads 1 x 1](../reference/shaders.md#the-fragment-size-register-special-register-0x84-reads-1-x-1)
- `SPIRV-Cross threw an exception: Subgroup builtins are not available in this type of function`. → [Subgroup operations rewritten for a one-lane subgroup](../reference/shaders.md#subgroup-operations-rewritten-for-a-one-lane-subgroup)
- the tone mapper read an empty LUT and the screen came out a flat blue. → [Geometry shaders that only pick the layer](../reference/shaders.md#geometry-shaders-that-only-pick-the-layer)
- `[RED] NVN unimplemented function called: nvnCommandBufferDrawMeshTasks`, and a build that fails with `Ampere instruction 927 ... is not implemented` in shaders the map calls vertex or domain; what they draw is missing. → [NVN2 task and mesh shaders](../reference/shaders.md#nvn2-task-and-mesh-shaders)
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/shaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,14 @@ Finding, keying and translating shaders (`dm/gfx/tools/shader_split.py`, `dmsh_b
- The register is −1 under a lower-left origin. The Ryujinx recompiler answers a constant +1 (a TODO in `InstEmitMove.cs`; LASRecomp, a static recompilation of Link's Awakening, answers −1). Answering it per profile means a translator change, i.e. every game's chain.
- **Found on:** [The Legend of Zelda: Link's Awakening](../../dm/games/01006BB00C6F0000/README.md): 2,487 of its pixel shaders read it; in the room shader the value only scales a depth slope under `abs()`; other shaders may use its sign (`dFdy`-based effects).

### The fragment size register (special register 0x84) reads 1 x 1

- **Symptom:** a Switch 2 game's sea, an avatar's clothes, or another pixel shader that uses its fragment's size, comes out black, with blocks of coloured noise along the horizon and a scene washed out around it; `DM_NVN_MAGSCAN` finds a few non-finite pixels first in that shader's pass. The translated MSL has `float _N = float(0);` and a `1.0 / _N` after it.
- **Cause:** special register 0x84, which only pixel shaders read (with S2UR), holds the fragment's width and height in pixels in bytes 1 and 2 under variable-rate shading: every read in Nintendo Switch Sports Resort (612 in 611 pixel shaders) takes those two bytes with PRMT, multiplies them and divides by the product (the meaning is inferred from that use). The register is not in Ryujinx's `SReg` list, and `EmitSReg` reads an unknown one as 0, so the product was 0 and its reciprocal +Inf. The sea's horizon pixels became NaN, the auto-exposure spread them over the frame, and the sea's own colour came out black.
- **Fix:** `InstEmitAmpereAlu.cs` `AS2r` answers register 0x84 with a 1 x 1 fragment (`0x00010100`): the host shades every pixel. Any other register the `SReg` list does not know now leaves a translator note (`S2R of special register 0x.., which the translator does not know: read as 0`) in the shader's build notes. A chain change.
- **Check:** `test_dmsh_ampere_alu.py` `FragmentSize` (the game's S2UR, PRMT and IMAD encodings: the product folds to 1; the register is 65792); it fails on the translator before. Nintendo Switch Sports Resort's main menu with only the three sea shaders' constant set to 1 in their MSL (the translator's output for them with this fix): the island's sea is blue, the noise band and the washed-out exposure are gone.
- **Found on:** Nintendo Switch Sports Resort (`04008E001F098000`, no profile yet): the main menu's island, sea shaders `01a6b85e2f6ea3d4`, `b3203175c76cda20` and `2271b2e0293b3c55` (agl `AnnaSea`). 608 other pixel shaders read the register too. Among them the avatar material `c30f0edd6c669a61` takes it into a shading factor: the avatar's hair and clothes were flat black in the avatar editor, user selection and the HUD icons, and show their colours on the rebuilt app (chain 592225da745b).

### Subgroup operations rewritten for a one-lane subgroup

- **Symptom:** `SPIRV-Cross threw an exception: Subgroup builtins are not available in this type of function`.
Expand Down