The GPU ASTC decoder unswizzles block-linear data itself and never applied
the native (Switch 2) GOB swizzle that the other unswizzle paths use, so
ASTC textures came out with 16-byte blocks swapped wherever the image
varies. Title 4's UI is ASTC (10x5, 4x4, 8x8): portraits, buttons and the
squid cursor had hatched edges and clean interiors. Pass the native tiling
flag to the decoder and swap offset bits 5 and 6 as
block_linear_unswizzle_2d.comp does.
nx2:gpu 213 (NormalUnmapGpuMemory4K) and 102 (destroy GPU-accessible
memory) were no-ops, so Eden kept freed memory mapped for the GPU. Title 4
frees its customization-room memory right after saving and reuses it as
ordinary heap; pending GPU->CPU write-backs for the old buffers then landed on
the new objects, and two threads crashed on garbage pointers ~5 s later
(the post-customization freeze).
Unmap the range from every address space (the GMMU unmap drops cached
buffers/images and their pending downloads), keep the registry's mapping
list in step, re-map 211 requests even at the memory's own address, and
drop freed regions from the semaphore mirror table, which every 8 KiB
allocation used to fill for good after 32 entries.
EDEN_NX2_KEY_RECORD records keyboard transitions as "f<frame>:code" against a
global count of guest presents; EDEN_NX2_KEY_SCRIPT replays them on the same
frames (or by seconds, as before), so a played session reproduces exactly.
Debug aids: the PC/LR/SP of the first unmapped reads, NSO load addresses,
EDEN_NX2_IPC_SUMMARY (request counts every 5 s), EDEN_NX2_FORCE_LOD0.
Unmapped-access logging is rate limited and the per-draw/semaphore spam is
demoted, so long sessions stay under the log size cap.
- 64-bit adds: IADD3/UIADD3 with a carry-out predicate (unsigned compare of
the sum) and IADD3.X/UIADD3.X with a carry-in predicate (SEL + add); uniform
predicates go through R239 like UISETP. Used by a compute pass the game runs
thousands of times while loading the next area.
- TMML (0xb69) LOD query, 2D: 8.8 fixed-point results as on Maxwell.
- Vertex stores to a[0x5c] (an Ampere per-primitive hint, likely shading rate)
are dropped; Eden had no attribute for it and refused the pipelines.
All 864 shaders from a 5-minute scripted session past the intro now lower,
except the opt-in tensor-core upscaler (bilinear stand-in).
Native shader capture counted CALL.REL.NOINC as a function call, so a program
using it waited for a RET after its EXIT and read on into the following
shaders until a garbage branch target made it fail. NOINC calls push no
return address (the compiler uses them as jumps; the lowering already treats
them so): count them as forward branches.
That shader builds title 4's 3D colour-grading LUT once per scene; with it
rejected the LUT stayed empty and the tonemap turned every frame black. The
intro cutscene now renders lit and graded, with subtitles, through the
bilinear stand-in for the tensor-core upscaler.
Also: rejected captures dump to EDEN_NX2_SHADER_DUMP as rejected-<addr>.bin
and report the offending instruction.
- Render area falls back to colour target 0 when the surface clip is unset
(Switch 2 titles may never program it); it feeds the synthesized blit block.
- BlitConstantsPass also covers fragment shaders: UV clamp rectangle 0..1
(0x30-0x3c) and zero dither bias.
- Debug: EDEN_NX2_SKIP_VS=<addr,...>, EDEN_NX2_TRACE_VS=<addr>,
EDEN_NX2_UPSCALE_SOURCE[_FORMAT], EDEN_NX2_TRACE_AFTER_UPSCALE,
EDEN_NX2_SAMPLE_AT, skipped-draw reasons, clear tracing.
Showing the raw HDR scene colour through the upscale fallback gives the
correct lit cockpit scene; the game's tonemap pass still outputs black.
- When a compute program is rejected for tensor-core math, blit its input
image (handle at cbuf0+0x90) to its output (cbuf0+0xa0) with bilinear
filtering. Title 4 renders at 2560x1440 and upscales to 3840x2160 with a
DLSS-style network; this keeps the 4K output fed. EDEN_NX2_TENSOR=stub keeps
the program with HMMA as D = C (for experiments).
- BlitConstantsPass: vertex shaders reading bank 24 (lowered to 17) get the
console-provided blit block synthesized from the render area (texel size,
size, UV scale 1, rectangle 0..W-1,H-1).
- Debug: EDEN_NX2_CB24_TRACE, upscaler resource description and render target
sampling at the fallback.
The scene behind the intro dialogue is still black: the tonemap output that
feeds the upscaler has no colour although earlier targets do.
- Tensor-core ops for its neural compute pass: HMMA.16816.F16 (SHFL + HFMA2),
MOVM.16.MT88 and LDSM .MT88. Opt-in via EDEN_NX2_TENSOR: the expansion
(372 HMMAs) takes the host driver minutes to compile.
- HSETP2 with an immediate half pair; skip @!PT instructions.
- Constant bank 24, read only by three NVN2 fullscreen vertex shaders and never
bound by the game, reads from bank 17 (also never bound: zeros, as on hw).
- Debug dumps of the vertex stage's bound constant buffers on rejection.
All 214 shaders captured after the menu lower (213 without EDEN_NX2_TENSOR).
- Native QMD: a launch whose shader Eden can't translate still completes and
fires its releases (the console never fails a launch; queues wait on them).
- QMD releases with flag bit 19 are max releases: NVN2 tracks QMD completion
in a word other releases also advance, so a plain write moved it backwards.
- Ampere lowering: LDSM.16.M88.{1,2,4} (SHFL + LDS), IMAD with a negated
uniform addend, strong LDG (bit 77), 2D TLD4 gather, RED MIN/MAX/..., AST of
RZ. 210 of the 214 shaders title 4 uses after its menu now lower.
- Maxwell3D: ignore constant buffer binds to slots >= 18 instead of writing
past the state array.
- Debug: EDEN_NX2_SEM_WATCH covers QMD and compute-macro releases and logs
address spaces; EDEN_NX2_3D_METHODS logs the first write to each 3D method;
constant buffer binds are logged once per stage/slot.
- MME: implement MME_DMA_READ / MME_DMA_READ_FIFOED (Turing macros read query
results into their load FIFO; the conditional-render macro at 0x142 needs it).
- vi2 present waits up to 4 s for the frame fence (was 250 ms) so shader
compile stalls don't let the game lap its equal-value gate semaphores.
- Native QMD: fire the third release slot at word 52, which NVN2 patches for
queue-to-queue syncs (compute queue releasing a gate the 3D queue waits on).
- Compute class SET_REPORT_SEMAPHORE (0x6c0-0x6c3) releases.
- Ampere lowering: AOFFI, any LOP3 truth table, STG RZ, LDG 0x981, IABS,
I2FP rounding, IMAD negated addend, SHFL 0x989 (all 77 title-4 shaders lower).
- Debug: EDEN_NX2_SEM_WATCH=<addr> traces acquires, releases, method
arguments and compute macro emits for one semaphore block;
EDEN_NX2_KEY_SCRIPT scripted key presses; per-submit logs moved to TRACE.
The vertical flip at present was a misreading: EDEN_NX2_FRAME_CAPTURE wrote the
TGA origin bit backwards (invert_y means the data is bottom-up), so captures
were upside down while the screen was right. Present unflipped again.
- Present flips the image vertically: title 4 renders with a lower-left origin
(negative viewport scale), so its frames are stored bottom-up.
- Lowering: TEX/TEXS FP16 results (bit 79, packed half pairs) - the glyph
shaders unpack them with HADD2.F32; LDG.E with a register address;
SHF.R.U32 low word with a zero high word.
- Debug aids: EDEN_NX2_TRACE_PS / EDEN_NX2_WINDOW_TRACE_SKIP for the window
trace, EDEN_NX2_TEXTURE_DUMP, EDEN_NX2_TRACE_DST for copies; RT trace keyed
by shader pair. NX2_NOTES: blockers 53-57, next steps.
- gpu 214 creates GPU-only memory the graphics server backs itself (title 4
renders everything into six 300 MB regions at 0x81_0000_0000). Back each
with application-pool pages mapped into the alias code region and map that
for the GPU. This is what made title 4's frames appear.
- vi2 229 waits (bounded) for the presented frame's GPU fence: the reply
reports every buffer free, so without this the game laps Eden's GPU thread
and rewrites command memory it has not read yet.
- Split methods: keep several parked methods keyed by resume address (NVN
interleaves command buffers) and drop them when the submission ends.
- hid 214 (per-frame vibration send) answered quietly.
- Debug aids: EDEN_NX2_WINDOW_TRACE / EDEN_NX2_RT_TRACE (render targets,
clears, textures), EDEN_NX2_AUDIO_LEVEL (output peak), EDEN_NX2_PB_DUMP
(command list for offline replay), parsed-entry change check.
From changes.patch: the TU104/GA10x MME interpreter (shared macro context for
the process's channels, also on the compute class), native compute with QMD
3.0 dependency scheduling, the Ampere-to-Maxwell shader lowering, native
vertex stream sizes, and the console's GOB swizzle for textures, DMA copies
and the host unswizzle shaders. Switch 2 channels turn all of it on at init.
The lowering is widened for title 4 (all 32 of its programs lower): general
TEX (any dimension/LOD/mask, operand split 1+1/2+1/2+2/4+n, any but consistent
descriptor buffer), cube/array, SULD channel masks, 128-bit STG, indexed IPA,
VOTE, SHFL with any immediate or predicate output, R2P/P2R byte select,
uniform ULOP3/ULEA/UBREV/UFLO.SH/UISETP signed/UMOV URZ, IMNMX cbuf, IADD3
and FFMA uniform operands, FMUL /4 /8 x8, and BSSY/BSYNC/BREAK/WARPSYNC as
no-ops. The capture stops at unmapped memory. EDEN_NX2_SHADER_DUMP dumps every
native program; tools in C:\dev\nx2work test the lowering offline.
NVN2 gates command buffers with semaphores the CPU releases after writing
them. Eden read past unmet SEMAPHORED acquires, so it executed stale commands
and desynchronised. Channels now suspend on an unmet acquire and resume from
the same word once it is met (scheduler keeps the waiting channels, the GPU
thread polls them), as in changes.patch.
Also: methods split across GPFIFO entries around a called command buffer are
set aside and resumed; subchannel 2 defaults to inline-to-memory; unbound
subchannels and out-of-range compute/DMA methods are dropped instead of
crashing; 4-byte semaphore releases no longer write 16 bytes; Ampere
SEM_PAYLOAD_HI/SEM_EXECUTE no longer spin as Kepler acquires; unmapped GPU
reads log instead of asserting. Debug aids: EDEN_NX2_PB_CHECK (pushbuffer
changed after submit), EDEN_NX2_QUERY_TRACE, a one-time pushbuffer dump.
vi2 229 composites the presented native image (block-linear RGBA8 in the
console's GOB layout, from changes.patch) at its SMMU address, which the GPU
memory registry now records per mapped range. EDEN_NX2_FRAME_CAPTURE=<dir>
saves every 120th presented frame as a TGA for hidden test runs. Also mirrors
releases for every two-page gsv semaphore region and logs gsv sub 101 ranges.
vi2 229/228 (PresentClientBuffer/CancelClientBuffer) reply with the mask of
slots the client may acquire next; syncpt 103 (QueryCounter) returns the host1x
value of ~counter. Names and reply shapes from changes.patch. Title 4 now runs
its render loop, presenting window images 1-3 in turn (730 GPU submissions in
60 s); nothing is shown yet.
- gsv 111/112/114/423/429/430/314/315/435 track native memory and images with
real ids (meanings from changes.patch); 1510/435 use its block-linear layout.
- svc 0x80 sets a real GpuAccessible (0x20) memory attribute (from changes.patch).
- Characteristics report TSG subcontexts (flag 22, 64 per TSG at 0xb4); NvRm
otherwise allows one subcontext and a second NVN queue fails its setup.
- Syncpoint shim: flag 26, address-space ioctl 0xB, and GPU reads of the shim
page return guest syncpoint values (scheme from changes.patch).
- EDEN_NX2_IPC_TRACE also logs TIPC commands; gdbrsp.py watch/mem improvements.
Newer SDKs open the syncpt session with TIPC 0(u64) and need it echoed, and
query the GPU address space partitions with gpu 201/202 instead of 203. With
these and the larger memory budget, title 1 reaches its main loop and the same
empty-command-buffer stop as title 4.
nx2:gpu 101 (CreateGpuAccessibleMemory) returns real GpuMemoryHandles; 211
(NormalMapGpuMemory4K) maps handle+offset at the requested GPU address,
including sparse regions, through Nx2GpuMemoryRegistry::MapAt (SMMU ranges are
reused). 102/213/214 complete through their job-slot events. gdbrsp.py waits
for a real thread before driving the stub and gains a write-watch op. Notes
record the current stop: an NVN command buffer with no command memory.
- aud:u (nn::audio::CreateUserClientSessionByHipc) command 2 returns Eden's
IAudioRendererManager; the renderer accepts Switch 2's 'REv?' update tag by
comparing revision numbers.
- nx2:gpu sub 211 signals the job slot in request word 17, not word 2; mapping
jobs no longer stall once the SDK rotates slots.
- eden-cli raises the CRT open-file limit (_setmaxstdio(8192)) like the Qt
frontend, so loose RomFS folders don't run out of handles.
- Per-mapping GPU logs moved to Debug.
- gsv 1510 CalculateRequirements: block-linear size/alignment for window images.
- nvhost-gpu: Switch 2 channel event 7 is a real (unsignalled) event.
- Puller mirrors NVN2 queue semaphore releases into the page the CPU and GPU
wait on (the first two-page nx2:gpu region); EDEN_NX2_METHOD_TRACE logs methods.
- pl:u 2000/2010 GetBuiltInFontSharedMemory/GetBuiltInFontData serve plain TTF
copies of the shared fonts.
- Switch 2 processes count the alias region in their max memory, so the game's
nninitStartup heap (total - 6.3 GiB) has room for its sound heap.
- gdbrsp.py: interrupt, threads, selthread, hits, continue_async, sleep.
- gsv 204: real syncpoint shared page (values mirrored from host1x, armed waits
acknowledged by a 500us poller); first handle is the writable request event.
- syncpt 22/201/202: per-slot events signalled through host1x guest actions.
- nx2:gpu sub 101 memory is identity-mapped (GPU VA == CPU VA) into every
nvhost-as-gpu GMMU via Nx2GpuMemoryRegistry; SMMU physical range widened to
34 bits for the 10/12 GiB layouts.
- Puller runs Ampere 3D/compute/copy classes on the Maxwell engines; class 0 unbinds.
- Turing+ macros are skipped (Eden only has the Fermi macro ISA).
- Submits with increment_value get Eden-side syncpoint increments, since NVN2
signals completion with semaphore releases only.
Once an nx2 NvRm identifies itself (0xD0-byte characteristics), nvhost-gpu and
nvhost-ctrl-gpu answer unknown ioctls with a logged input echo instead of
NotImplemented, ctrl-gpu reports GR + async-copy engines, and Ampere class numbers
(and class 0) map onto Eden's Maxwell object slots. gdbrsp.py gains trace/flow/
breakwhen/fp helpers.
Unknown /dev/nvhost-* nodes (first: /dev/nvhost-tsg-gpu, opened by the
Switch 2 NvRm during NVN device init) open as nx2_stub_device: every
ioctl succeeds with the input echoed and the rest zeroed, and is logged.
With this NVN device init completes and the title reaches
nn::wsi::ounce::native::NativeWindow::Acquire (first frame).
- svc 0x80 (address, size, mask 0x20, attr 0x20) marks memory GPU
accessible for nn::gpu::sdk::CreateGpuAccessibleMemory; accept it.
- gpu 22's sub-session is served by the gpu object; sub 101 registers
memory, sub 211 submits a job whose id event is signalled at once.
- gpu 203 reports [0x8000000, 0xC000000000) as the in-process
VirtualMachine range that serves AllocSpace (NVN reserves fixed VA at
0x8000000 up to 512 GiB).
- Report SM 0x808: NVN2's engine class table is only returned for it.
With this NVN device init allocates its GPU memory and opens
/dev/nvhost-tsg-gpu.
- AllocAsEx from Switch 2 NvRm passes flags 5, no big page size, range
[0x8000000, 0xC000000000) and split 0 (small pages only). Treat split 0
as small pages up to the end, keep a 4 GiB big-page region after it, and
give that allocator an absolute limit (Eden's length-as-limit breaks once
the split is past the region length).
- gpu TIPC 33 returns a count, 203 a kind plus a VA range used by
nn::gpu::sdk::CreateGpuAddressSpace.
- gdbrsp.py: unbreak, step, breaklr, unbreakpc, fp (frame walk), trace.
- gsv 1402's nvsrv session speaks the nvdrv INvDrvServices protocol;
serve it with an NVDRV on the shared module, initialised for the
calling process (Switch 2 doesn't call Initialize).
- GetCharacteristics3 callers asking for the 0xD0-byte Switch 2 layout
get chip 0x17E (Ampere; the Switch 2 NvRm rejects Maxwell for NVN),
Ampere classes, and s16 interface versions at 0x70..0x80.
- After that, unknown nvhost-ctrl-gpu Ioctl3 commands from such callers
return a zeroed success (logged).
ResponseBuilder::Push(Result) writes the CMIF two-word result, so TIPC
data pushed after it landed one word late and outside the copied size;
clients read zeros. TipcReplyWords writes the one-word TIPC result and
the data. vi2 TIPC 5 returns 7 (LayerHistory needs 7 recorders). With
this the title creates its layer and goes on to hid/acc/aoc and RomFS
asset loading.
gsv 305 returns the session nn::vi2::sdk::Initialize keeps. Reply shapes
read from the SDK: TIPC 1 -> u32 < 0x400, 3 -> u32 < 8, 11/31/32 -> one
handle then result (events here). With this nn::vi::Initialize returns and
the title opens the default display and starts creating a layer.
gsv 497 returns the session nn::gpu::sdk::BootWithRemoteMemory keeps.
On it: TIPC 11 returns an id range, 12(id) returns that id's event, 21(id)
starts an operation the SDK then waits for (signalled at once here), 22
returns another session. With this nn::gs::sdk::BootGraphicsSystem
returns and boot moves on to nn::vi.
- gsv 1402 returns an nvsrv session (handle at word 3, result at word 4);
nn::nvsrv::BootWithRemoteMemory keeps it.
- gsv 202 returns a syncpt session; gsv 204 takes two transfer memories
(0x1000 RW, 0x2000 RO) and must leave a header in the RO one (version 1,
count, five region offsets) and return two event handles.
- syncpt TIPC 1 returns a u32 < 0x400 (32 here).
RequestParser doesn't skip TIPC handle words, so 204 reads raw data words.
ISelfController 2000 hands nn::gsv::BootWithRemoteMemory a raw session
handle it then speaks TIPC on (4, 11, 101, 1501, 1502, 1402 during boot).
Return a session to INx2GraphicsSystemSession, which answers every
command with an empty success and logs it. ReturnRawSession creates the
session outside the caller's domain, since gsv wants a real handle.
nn::os::GetRemoteMemoryUsage returns {id 31, id 32}; nn::gsv and
nn::nvsrv BootWithRemoteMemory use it while booting the graphics
system. Return 0 for both for now (logged).
IPC trace logs every dispatched command and non-zero result words.
EDEN_NX2_AUTO_STUB auto-stubs unknown commands without relying on the
use_auto_stub setting, which eden-cli's config rewrite resets.
IApplicationProxy 60 returns the interface nn::oe::GetCurrentGraphicsScale
uses; its command 10 must return 10, 20 or 30, so return 10. Other
commands on it are auto-stubbed (nx2: prefix). IApplicationFunctions 321
is called right after 320; stub it.
The ExeFS directory loader never set its romfs member and, when used on
its own, never registered the process with the FileSystemController, so
fsp-srv SetCurrentProcess failed with ResultTargetNotFound (2002-1002).
Build the RomFS from a romfs/ directory next to exefs/ (or a .romfs file
in exefs/), and register the process when the loader isn't wrapped by
the NCA/NSP loaders.
Switch 2 titles get core mask 0x3f and nn::oe creates a thread on core 5.
Eden emulates four cores, so fold virtual cores 4..7 onto 0..3 in
CreateThread and SetThreadCoreMask (after validating against the real
process mask).