aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 18:01:44 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:48 -0400
commit32a472c6304ec4ec5e72c28bd0510173093c171b (patch)
tree8053234243003323c2f784e12619fc198ae04cfe
parentd327881c44ada520d6ea939af59bdd420906b99f (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.md3
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp5
-rw-r--r--src/core/hle/service/nx2_gsv.h16
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();