diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 18:01:44 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 20:49:48 -0400 |
| commit | 32a472c6304ec4ec5e72c28bd0510173093c171b (patch) | |
| tree | 8053234243003323c2f784e12619fc198ae04cfe | |
| parent | d327881c44ada520d6ea939af59bdd420906b99f (diff) | |
nx2: support mapping modify so NVN2 places compressible textures
NVN2 gives a texture in a compressible memory pool its GPU address only
after NvRmGpuMappingModify switched the pool's mapping to the texture's
kind. NvRm fails that call unless the GPU characteristics report
NVGPU_GPU_FLAGS_SUPPORT_MAPPING_MODIFY, so such textures kept address 0
and draws into them went nowhere: title 1's cup-opening movie stayed on
its first frame (also its 640x360 and 512x512 passes).
Report the flag and answer the request nn::gpu::sdk then sends, gpu
sub-session 212 (NormalModifyMemoryKind4K), with the wanted kind and a
signalled job slot. The movie now plays on screen and the race follows.
| -rw-r--r-- | NX2_NOTES.md | 3 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp | 5 | ||||
| -rw-r--r-- | src/core/hle/service/nx2_gsv.h | 16 |
3 files changed, 23 insertions, 1 deletions
diff --git a/NX2_NOTES.md b/NX2_NOTES.md index 9daabcc461..8da5873a5c 100644 --- a/NX2_NOTES.md +++ b/NX2_NOTES.md @@ -209,7 +209,8 @@ With the patched decoder, games 1/3/4 only leave ~60-110 rare SIMD encodings in | 145 | is the split worth having on by default | clean A/B, nothing else running, 3 rounds each (profiler on in both): one thread 41,100 / 48,600 / 37,000 draws a second, frames 30.5 / 35.0 / 27.1 ms; `EDEN_NX2_SPLIT=2` 59,200 / 62,400 / 62,700 draws a second, frames 28.6 / 23.3 / 25.1 ms. Throughput 1.46x, frames 17% shorter (the back is now ~92% busy, ~53% of it draw setup); 0 crashes, 0 register differences in 57,089 checks after the front's fast paths (no guarded statics, inline ring writes) | on by default for Switch 2 titles (`EDEN_NX2_SPLIT=0` one thread, `=1` macros on the back). Default run without the profiler: 16.9 ms frames (~59 fps) on a light course | | 146 | title 4 aborts with InvalidHandle (0xE401) at its first movie, ~30 min into a new game | newer SDKs (`nn::codec::video::detail::InitializeServerContext`) open the decoder with gsv 906(u8, u64 library version 0x3'00030000) instead of 902; auto-stubbed, no handle came back | 906 hands out the 902 session (d8d11ec) | | 147 | title 1: Grand Prix cup-opening screen stays black, decoder registered but never started; then an abort with 2111-3604 (0x1c286f) when it did start | `nn::syncpt::sdk::CancelSyncPointEvent` sends 202 and always waits on the slot's event; we signalled it only for pending requests. Then the movie player reused slot 0, which NVN's fence waits had left signalled, read it as a decoded frame and found none | 202 always signals, 201 clears the slot's event first (b09629f) | -| 148 | title 1/4 movies have no picture | the video decoder service (nn::codec::video, gsv 902/906/1002 -> vdec -> vdecsvc) completed frames without decoding | FFmpeg decodes on a thread per decoder service (`video_core/host1x/nx2_video_decoder`): 101 names the codec ("video/vp9") in an A buffer, 109 submits frames (A buffer, pts us). 311/314 reply NativeSyncHandles {~syncpoint, 0, value} on a syncpoint the service allocates, incremented per decoded frame, frames filling images in register/release order. gsv 911 creates the pool's images, 961 returns the CpuUserImageDescriptor that `ReadDecoderOutputImage*` copy out with (pitch-linear NV12/RGBA/BGRA, kinds 10/8/9). Title 1's Mushroom Cup opening (2560x1440 VP9, 1423 frames) decodes in real time and the game goes on to the race. Open: the picture on screen stays on the first frame: the game memcpys each frame into GPU memory (0x546000-byte NV12 buffer registered with gpu 101 before Start) and the texture cache never takes the later CPU writes. `EDEN_NX2_VDEC_DUMP=<dir>` saves decoded frames | +| 148 | title 1/4 movies have no picture | the video decoder service (nn::codec::video, gsv 902/906/1002 -> vdec -> vdecsvc) completed frames without decoding | FFmpeg decodes on a thread per decoder service (`video_core/host1x/nx2_video_decoder`): 101 names the codec ("video/vp9") in an A buffer, 109 submits frames (A buffer, pts us). 311/314 reply NativeSyncHandles {~syncpoint, 0, value} on a syncpoint the service allocates, incremented per decoded frame, frames filling images in register/release order. gsv 911 creates the pool's images, 961 returns the CpuUserImageDescriptor that `ReadDecoderOutputImage*` copy out with (pitch-linear NV12/RGBA/BGRA, kinds 10/8/9). Title 1's Mushroom Cup opening (2560x1440 VP9, 1423 frames) decodes in real time and the game goes on to the race. The picture still stayed on the first frame: see 149. `EDEN_NX2_VDEC_DUMP=<dir>` saves decoded frames | +| 149 | title 1: the cup-opening movie's picture stays on its first frame although every frame decodes and reaches GPU memory | not the texture cache: the movie is drawn into a 2560x1440 render target whose NVN2 texture has GPU address 0 (also the 640x360 and 512x512 passes), so the draws go nowhere. NVN2's texture init (sdk+0x5ca240) places a texture in a compressible pool (flags 0x080000a1) only after `NvRmGpuMappingModify` switched the pool's mapping (0x815f0d0000, a gpu 214 region) to the texture's kind; NvRm fails that call with 2 unless the characteristics flags have NVGPU_GPU_FLAGS_SUPPORT_MAPPING_MODIFY (bit 34; ours were 0x4400155), and the texture keeps address 0 | report bit 34; the modify then runs through nn::gpu::sdk's VirtualMachine as gpu sub 212 (NormalModifyMemoryKind4K: words 3-6 range, 7 wanted kind, 8 fallback, 10 job slot; reply word 3 the kind applied), answered with the wanted kind and the slot signalled. The movie plays on screen | Run settings used (in `build/bin/user/config/sdl2-config.ini`): `memory_layout_mode=4` (12 GB). Debug knobs: `EDEN_NX2_TC_EXPECTED_MIB` / `EDEN_NX2_TC_CRITICAL_MIB` (collector thresholds), diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index df2421cd13..5db984b53d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -202,6 +202,11 @@ void ApplyNx2Characteristics(GC& gc) { gc.flags |= 0x100; // NVGPU_GPU_FLAGS_SUPPORT_TSG: NvRm binds channels to a TSG gc.flags |= 1ULL << 22; // NVGPU_GPU_FLAGS_SUPPORT_TSG_SUBCONTEXTS (see below) gc.flags |= 1ULL << 26; // NVGPU_GPU_FLAGS_SUPPORT_SYNCPOINT_ADDRESS: syncpoint shim + // NVGPU_GPU_FLAGS_SUPPORT_MAPPING_MODIFY: NVN2 gives a texture in a compressible pool its + // address only after NvRmGpuMappingModify switched the pool's mapping to the texture's kind. + // Without the flag NvRm fails that call and the texture stays at GPU address 0 (title 1's + // movie and the passes rendered into compressible pools never showed). + gc.flags |= 1ULL << 34; // The Switch 2 layout keeps nine s16 interface versions at 0x70..0x80 (like nvgpu's // *_ioctl_nr_last); its NvRm requires e.g. [0x78] >= 6 and enables features above 0xa, 0x1f diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index cfef3f1229..5eab9d420f 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -309,6 +309,7 @@ public: {101, &INx2GpuSession::SubRegisterMemory, "CreateGpuAccessibleMemory"}, {102, &INx2GpuSession::SubDestroyMemory, "DestroyGpuAccessibleMemory (guessed)"}, {211, &INx2GpuSession::SubSubmitJob, "NormalMapGpuMemory4K"}, + {212, &INx2GpuSession::SubModifyKind, "NormalModifyMemoryKind4K"}, {213, &INx2GpuSession::SubUnmapJob, "NormalUnmapGpuMemory4K"}, {214, &INx2GpuSession::SubCreateGpuOnlyRegion, "CreateSparseRegion"}, {215, &INx2GpuSession::SubDestroySparseRegion, "DestroySparseRegion"}, @@ -457,6 +458,21 @@ private: rb.Push(ResultSuccess); } + // Sub 212 (nn::gpu::sdk::NormalModifyMemoryKind4K, behind NvRmGpuMappingModify): word 2 the + // address space, 3-4 first GPU address, 5-6 last, 7 the wanted (compressible) kind, 8 the + // fallback kind, 9 a sync flag, 10 the job slot. The reply's word 3 is the kind applied. Kinds + // do not matter to the emulated GPU, so keep the mapping and report the wanted kind. + void SubModifyKind(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + const u64 first = u64{w[3]} | (u64{w[4]} << 32); + const u64 last = u64{w[5]} | (u64{w[6]} << 32); + const u32 kind = w[7] & 0xff; + LOG_DEBUG(Service, "nx2:gpu 212 modify [{:#x}, {:#x}] kind={:#x} fallback={:#x} slot={}", + first, last, kind, w[8] & 0xff, w[10]); + EventFor(w[10])->Signal(system.Kernel()); + TipcReplyWords(ctx, {kind}); + } + // Sub 213: words 3-4 first GPU address, 5-6 last, job slot in word 8. void SubUnmapJob(HLERequestContext& ctx) { const auto* w = ctx.CommandBuffer(); |
