From d6759fb2a4d46f3902ce9715a3c22f264c6c3726 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:04:22 -0400 Subject: Switch 2: title 4 menu text renders upright - 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. --- NX2_NOTES.md | 82 ++++++++++------------ src/core/hle/service/nx2_gsv.h | 5 +- .../frontend/ampere/lower_to_maxwell.h | 34 ++++++++- src/video_core/engines/fermi_2d.cpp | 13 ++++ src/video_core/engines/maxwell_dma.cpp | 18 +++++ src/video_core/renderer_vulkan/vk_rasterizer.cpp | 44 +++++++++--- 6 files changed, 139 insertions(+), 57 deletions(-) diff --git a/NX2_NOTES.md b/NX2_NOTES.md index 29cdd8868e..5c5858cdc6 100644 --- a/NX2_NOTES.md +++ b/NX2_NOTES.md @@ -100,66 +100,58 @@ With the patched decoder, games 1/3/4 only leave ~60-110 rare SIMD encodings in | 42 | GPU memory model | gpu sub 101 = `CreateGpuAccessibleMemory` (reply word 3 = GpuMemoryHandle); 211 = `NormalMapGpuMemory4K` (words 3-4 first VA, 5-6 last, 7 handle, 8-9 offset, 17 slot), also into sparse regions at 0x81_xxxx_xxxx; 102/213 (`NormalUnmapGpuMemory4K`)/214 (`CreateSparseRegion`) complete through slot events (words 3/8/11) | real handles, `Nx2GpuMemoryRegistry::MapAt` with SMMU range reuse, slot signals | | 43 | (title 1) `nn::syncpt::sdk::Initialize` abort | newer SDK opens syncpt with TIPC 0(u64 token) and aborts unless it is echoed | handshake echo | | 44 | (title 1) NVN closes the GPU device after GetVARegions | newer SDK asks gpu 201/202(id) for the user/gsv partitions (two u64s each, stored as min/max) instead of using 203 | 201 = [0x8000000, 0xC000000000), 202 empty | +| 45 | NVN command buffer with no command memory (~12 s, titles 1 and 4) | `NvRmGpuTSGSubcontextCreate` failed (0x11): characteristics reported one VEID | flags bit 8/22 (TSG, TSG_SUBCONTEXTS) and 64 subcontexts at characteristics +0xb4 (12125bc) | +| 46 | NVN asserts in `GetSyncpointShimRoAddress` | Switch 2 reads syncpoints through a read-only GPU page (flag bit 26, nvhost-as ioctl 0xB) | shim at the top of the VA range; GPU reads of it return the syncpoint value (12125bc, from changes.patch) | +| 47 | window/image creation | gsv 111/112/114/423/429/430/435/1510 native memory and images; svc 0x80 is SetGpuMemoryAttribute | native object registry + layout calc (from changes.patch), real svc 0x80 (12125bc) | +| 48 | presents go nowhere | vi2 229 (word 5 image id, ~word 22/word 24 fence) | composite the block-linear image with native GOB tiling (f63ff90, from changes.patch) | +| 49 | crash in `DmaPusher::CallMethod` (null engine) | NVN uses subchannel 2 (inline-to-memory) without binding a class | bind KeplerMemory there by default; drop methods on unbound subchannels (9f30683) | +| 50 | GPU parses garbage, then stalls (~14-19 s) | NVN2 gates each command buffer with a SEMAPHORED acquire the CPU releases after writing it; Eden read past unmet acquires and ran stale commands (`EDEN_NX2_PB_CHECK` confirmed entries changed after submit) | suspend the channel on an unmet acquire and resume it from the same word (9f30683, approach from changes.patch) | +| 51 | desync after a called command buffer | NVN2 ends a GPFIFO entry inside a method's data, calls another buffer, and continues at the next address | set the unfinished method aside and resume it when the stream continues (9f30683) | +| 52 | semaphore words next to releases get clobbered | 4-byte releases (SEMAPHORED bit 24) were written as 16 bytes | 4-byte releases write 4 bytes (9f30683) | +| 53 | no draws: 3D macros skipped | NVN2 drives 3D/compute state and draws through Turing MME64 macros; compute dispatches through QMD 3.0 | changes.patch's MME interpreter (one macro context shared by the process's channels, also on the compute class), native compute and QMD scheduler (d7826ce) | +| 54 | draws hit Eden's Maxwell decoder ("Invalid insn") | shaders are SM86 SASS | changes.patch's Ampere-to-Maxwell lowering, widened until all 32 of title 4's programs lower; `EDEN_NX2_SHADER_DUMP` + `C:\dev\nx2work\lowertest` test it offline (d7826ce) | +| 55 | GPU desyncs again after ~15-30 s | the game ran a full ring of command memory ahead of Eden's GPU thread: the vi2 229 reply reports all buffers free, so nothing paced it | vi2 229 waits (bounded) for the frame's GPU fence (325cac7) | +| 56 | macro arguments lost | NVN interleaves several command buffers, so more than one split method can be pending | several parked methods keyed by resume address, dropped at the end of the submission (325cac7) | +| 57 | frames all black although draws run | gpu 214 creates GPU-only memory (six 300 MB regions at 0x81_0000_0000) that the graphics server backs itself; the render targets live there and were unmapped | back each region with application-pool pages mapped into the alias code region (325cac7). Title 4 now shows its loading screen and a menu screen | Run settings used (in `build/bin/user/config/sdl2-config.ini`): `memory_layout_mode=4` (12 GB). Debug knobs: `EDEN_NX2_AUTO_STUB`, `EDEN_NX2_IPC_TRACE`, `EDEN_NX2_SVC_TRACE`, -`EDEN_NX2_PULLER_TRACE`, `EDEN_NX2_METHOD_TRACE`, `EDEN_NX2_HIDE_WINDOW`. +`EDEN_NX2_PULLER_TRACE`, `EDEN_NX2_METHOD_TRACE`, `EDEN_NX2_HIDE_WINDOW`, +`EDEN_NX2_FRAME_CAPTURE=` (every 120th presented frame as TGA), `EDEN_NX2_PB_CHECK` +(pushbuffer entries changed between submit and execution), `EDEN_NX2_QUERY_TRACE` (every GPU +report/semaphore write). `C:\dev\crashrun.sh` runs eden-cli under cdb and prints the crash stack; +`C:\dev\stackdump.sh
` dumps host thread stacks of a running test. ## Where it stops now All runs: `EDEN_NX2_AUTO_STUB=1`, 12 GB memory layout, loose ExeFS with the sibling `romfs/`. -**Title 4** (`fourth switch game\exefs`, 0400380020056000) **runs its main loop**: it stays up for -the whole 90 s test, polls input (`hid` 214, auto-stubbed) every 16 ms and drives the audio -renderer every frame. Boot order now: nn::init -> nn::oe -> fs/RomFS -> graphics system (gsv, -nvsrv, syncpt, gpu, vi2) -> NVN device, two GPU channels, window with 4K swapchain images -> -fonts (its own `.bfotf` plus system fonts via pl:u 2010) -> heaps (incl. 300 MB sound) -> aud:u -audio renderer -> Gyml sequence files -> main loop. -- GPU submissions are executed by Eden's puller/engines (Ampere classes on Maxwell engines), - syncpoints and queue semaphores advance, but Turing+ macros are skipped and nothing is presented. -- Only six GPU submissions happen (all during init). At ~11.5-12.4 s the main thread hands NVN - a command buffer (from a per-context table at game `main+0x320149c`, used at `main+0x32014e8`) - that was initialised but never given command memory or a memory callback: the whole state at - cmdbuf+0x2be8.. is zero, so NVN writes its first methods to address 0 and then calls a null - function. Some earlier game-side setup of that command buffer (its command-memory pool or the - frame-begin that adds memory) silently failed or never ran; that is the next thing to find. - Another thread keeps polling input afterwards. +**Title 4** (`fourth switch game\exefs`, 0400380020056000) **renders**: after boot it shows its +animated cloud loading screen (with its ambient audio) for about 30 s, then a menu-style screen +with three painted panels on dark green. Frames are paced on the GPU fence, ~12 presents/s. +Still missing on the menu screen: text and the logo band at the top. Shaders that appear there +need two more lowered forms (LDG.E register address, SHF.R.U32 low word; added, being tested), +and one macro (start 0x142) fails with "parameter FIFO underflow" (needs a 4th parameter). **Title 3**: last checked before the graphics work; should now track title 4. -**Title 1** (`first switch game\exefs`, 0400C3F00006E000, `Drive.nss`) now gets as far as -title 4: the larger memory budget (it sees ~9.5 GB) cleared the old null-allocator stop, then -blockers 43-44. It boots graphics, NVN and several GPU channels, polls input every frame, and -hits the same empty NVN command buffer at ~14 s (identical writes to address 0), so that stop is -shared, not title-specific. +**Title 1** (`first switch game\exefs`, 0400C3F00006E000, `Drive.nss`): reached its main loop and +the empty-command-buffer stop that blocker 45 fixed for both titles; not retested since the +macro/shader/present work. **Title 2**: main/sdk/subsdk0 fail LZ4 and SHA-256 checks; can't be run from these files. -## What a picture needs - -1. **Present path**: gsv 315/423/429/435/111 (window buffer create/register) and vi2 present must - hand the swapchain image to Eden's display, so whatever the GPU wrote appears. -2. **Turing+ macro engine (MME64)**: NVN2 uses macros for much of its 3D state; Eden only has the - Fermi-era macro ISA. Mesa's `nouveau/mme` (tu104) documents the encoding. -3. **Ampere shader frontend**: NVN2 ships Ampere SASS (128-bit Volta+ encoding); Eden's shader - recompiler only decodes Maxwell. This is the largest piece. -4. **Ampere 3D-class method differences** versus Maxwell B197, texture/RT formats from gsv image - preferences. - ## Prioritized next steps -0. **Empty command buffer** at ~12 s (title 4): find where the game normally adds command memory - to the per-context command buffers (`main+0x320149c` table) and which earlier call failed. - Break on NVN's command-buffer init and on the game's frame-begin, compare with the stores to - cmdbuf+0x2be8 (53 sites in sdk). -1. **Present**: decode gsv 315/423/429/435/111 and vi2 present/acquire, and blit the presented - image through Eden's renderer. First visible milestone: the game's clear colour. -2. **MME64 interpreter** behind `g_nx2_ampere` (replace the skip in `Maxwell3D::CallMacroMethod`). -3. **Ampere SASS decoder** for the shader recompiler. -4. **Title 1 / title 3** retests with the current tree. -5. Confirm the guesses: syncpt page layout, semaphore mirror page, gsv 1510 pixel formats, - aud:u command numbers, pl:u built-in font type numbering, svcGetInfo 29-33. -6. **Six cores** instead of folding 4/5 onto 0/1; rare SIMD encodings still undecoded. -7. Upstream-worthy pieces independent of Switch 2: MSVC 14.51 build fixes, eden-cli SDL3 +1. **Title 4 menu text/logo**: test the LDG / SHF.R lowering on the menu shaders + (`EDEN_NX2_SHADER_DUMP`, lowertest), then the macro at 0x142 that wants a fourth parameter + (dump its code with `EDEN_NATIVE_DRAW_DIAGNOSTICS` and check the interpreter against Mesa's + mme_tu104 semantics). +2. **Title 1 / title 3** retests with the current tree; dump their shaders and widen the lowering. +3. Replace guesses with real protocol knowledge: syncpt page layout, the semaphore mirror page, + gsv 214 (GPU-only memory) and vi2 present buffer availability (real buffer release instead of + the fence wait), gsv 1510 formats, svcGetInfo 29-33. +4. **Six cores** instead of folding 4/5 onto 0/1; rare SIMD encodings still undecoded. +5. Upstream-worthy pieces independent of Switch 2: MSVC 14.51 build fixes, eden-cli SDL3 lifetime fixes and `_setmaxstdio`, ExeFS loader fixes, FAC/FAH offset fix, GetPseudoDeviceId without NACP. diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 1e78cd39d3..b027ef535d 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -632,7 +632,10 @@ private: .height = layout.height, .stride = static_cast(layout.pitch / 4), .pixel_format = android::PixelFormat::Rgba8888, - .transform_flags = android::BufferTransformFlags::Unset, + // Title 4 renders with a lower-left origin (negative viewport scale), so its image is + // stored bottom-up and the console's compositor shows it flipped. Whether vi2 carries + // this per present is not known yet; flip unconditionally for now. + .transform_flags = android::BufferTransformFlags::FlipV, .crop_rect = {0, 0, static_cast(layout.width), static_cast(layout.height)}, .blending = Tegra::BlendMode::Opaque, .native_tiling = true, diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 524688f036..2c3ef28751 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -882,6 +882,16 @@ inline LoweredProgram LowerToMaxwell(std::span code) { emit(0x38f0000000070000ULL | (u64{253} << 8) | (u64{mask} << 20)); break; } + case 0x381: { // LDG.E of 32/64/128 bits through a 64-bit register address. + const u32 size = insn.Bits(73, 3); + if ((insn.words[2] & ~0xe00U) != 0x001e8100 || size < 4 || size > 6 || + insn.Bits(32, 8) || (insn.Bits(24, 8) & 1) || insn.Bits(24, 8) >= 246 || + (size == 5 && (dest & 1)) || (size == 6 && (dest & 3))) + return fail(index, "native global load mode"); + emit(base(0xeed0000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | + (u64{insn.Bits(40, 24)} << 20) | (u64{1} << 45) | (u64{size} << 48)); + break; + } case 0x386: case 0x986: { // Global store of 32/64/128 bits, 64-bit address. const u32 data = insn.Bits(32, 8); const u32 size = insn.Bits(73, 3); @@ -1083,6 +1093,22 @@ inline LoweredProgram LowerToMaxwell(std::span code) { (u64{insn.Bits(90, 1)} << 49) | (u64{depth_compare} << 50) | (u64{7} << 51) | (u64{lod} << 55)); const u32 components = static_cast(std::popcount(mask)); + if (insn.Bits(79, 1)) { + // FP16 results: (c0, c1) packed into the first destination, (c2, c3) into + // the second (F2F writes the half in the low 16 bits; ISCADD merges). + const auto pack = [&](u32 target, u32 low, u32 high) { + if (target == 255) return; + emit(0x5ca8000000000000ULL | predicate | 246 | (u64{1} << 8) | (u64{2} << 10) | (u64{low} << 20)); + emit(0x5ca8000000000000ULL | predicate | 247 | (u64{1} << 8) | (u64{2} << 10) | (u64{high} << 20)); + emit(0x5c18000000000000ULL | predicate | target | (u64{246} << 8) | (u64{247} << 20) | (u64{16} << 39)); + }; + pack(static_cast(dest), 250, components > 1 ? 251U : 255U); + if (components > 2) { + pack(split ? second_dest : (dest == 255 ? 255U : static_cast(dest) + 1), + 252, components > 3 ? 253U : 255U); + } + return true; + } for (u32 i = 0; i < components; ++i) { u32 target = 255; if (!split) target = dest == 255 ? 255U : static_cast(dest) + i; @@ -1114,7 +1140,7 @@ inline LoweredProgram LowerToMaxwell(std::span code) { break; } result.required_texture_buffer = 2; - emit(0xd800000000000000ULL | predicate | dest | (u64{insn.Bits(24, 8)} << 8) | + emit((0xd800000000000000ULL & ~(u64{insn.Bits(79, 1)} << 59)) | predicate | dest | (u64{insn.Bits(24, 8)} << 8) | (u64{insn.Bits(32, 8)} << 20) | (u64{insn.Bits(64, 8)} << 28) | (u64{insn.Bits(40, 14)} << 36) | (u64{insn.Bits(90, 1)} << 49) | (u64{swizzle} << 50) | (u64{dimension == 5 ? (lod ? 8U : 7U) : depth_compare ? (lod ? 6U : 4U) : (lod == 3 ? 3U : lod ? 2U : 1U)} << 53)); @@ -1386,6 +1412,12 @@ inline LoweredProgram LowerToMaxwell(std::span code) { emit(base(0x3848000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | (u64{insn.words[1]} << 20)); break; } + if (!insn.Bits(80, 1) && insn.Bits(76, 1) && insn.Bits(64, 8) == 255 && + (insn.Bits(73, 2) == 3 || is_signed) && !insn.Bits(75, 1) && insn.words[1] <= 32) { + // SHF.R low word with a zero high word: a logical right shift. + emit(base(0x3828000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | (u64{insn.words[1]} << 20)); + break; + } if ((insn.Bits(73, 2) != 3 && !is_signed) || insn.Bits(75, 1) || !insn.Bits(80, 1) || insn.words[1] > 32) return fail(index, "native funnel-shift mode"); const u32 low = insn.Bits(24, 8), high = insn.Bits(64, 8), shift = insn.words[1]; if (insn.Bits(76, 1)) { diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index a9b3355476..35b2c69801 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -60,6 +60,19 @@ void Fermi2D::ConsumeSinkImpl(Core::System& system) { } void Fermi2D::Blit() { + if (static const u64 trace_destination = [] { + const char* value = std::getenv("EDEN_NX2_TRACE_DST"); + return value != nullptr ? std::strtoull(value, nullptr, 16) : 0ULL; + }(); + trace_destination != 0 && regs.dst.Address() >= trace_destination && + regs.dst.Address() < trace_destination + (32ULL << 20)) { + static u32 traced_destination{}; + if (traced_destination++ < 40) { + LOG_INFO(HW_GPU, "nx2 2D blit to traced dst: {:#x} {}x{} -> {:#x} {}x{}", + regs.src.Address(), regs.src.width, regs.src.height, regs.dst.Address(), + regs.dst.width, regs.dst.height); + } + } if (static const bool trace = std::getenv("EDEN_NX2_RT_TRACE") != nullptr; trace) { static u32 traced{}; if (traced++ < 64) { diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 5b6efabca0..0b0541799b 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -64,6 +64,24 @@ void MaxwellDMA::CallMultiMethod(Core::System& system, u32 method, const u32* ba void MaxwellDMA::Launch() { LOG_TRACE(Render_OpenGL, "DMA copy {:#x} -> {:#x}", static_cast(regs.offset_in), GPUVAddr(regs.offset_out)); + // EDEN_NX2_TRACE_DST=: also log copies writing within 32 MiB above that GPU address. + static const u64 trace_destination = [] { + const char* value = std::getenv("EDEN_NX2_TRACE_DST"); + return value != nullptr ? std::strtoull(value, nullptr, 16) : 0ULL; + }(); + if (trace_destination != 0 && GPUVAddr(regs.offset_out) >= trace_destination && + GPUVAddr(regs.offset_out) < trace_destination + (32ULL << 20)) { + static u32 traced_destination{}; + if (traced_destination++ < 40) { + LOG_INFO(HW_GPU, + "nx2 DMA to traced dst: {:#x} -> {:#x} lines={} length={} pitch in={:#x} " + "out={:#x} launch={:#x} src_layout={} dst_layout={}", + static_cast(regs.offset_in), GPUVAddr(regs.offset_out), + regs.line_count, regs.line_length_in, regs.pitch_in, regs.pitch_out, + regs.reg_array[0xc0], regs.launch_dma.src_memory_layout.Value() == LaunchDMA::MemoryLayout::PITCH ? "pitch" : "block", + regs.launch_dma.dst_memory_layout.Value() == LaunchDMA::MemoryLayout::PITCH ? "pitch" : "block"); + } + } if (static const bool trace = std::getenv("EDEN_NX2_RT_TRACE") != nullptr; trace) { static u32 traced{}; if (traced++ < 64) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 7177dd77f3..1d9965a9a5 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -320,7 +321,19 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory static const bool trace = std::getenv("EDEN_NX2_WINDOW_TRACE") != nullptr; static u32 traced{}; const auto& regs = maxwell3d.regs; - if (!trace || regs.rt[0].width != 3840 || traced >= 400) { + static const u64 skip = [] { + const char* value = std::getenv("EDEN_NX2_WINDOW_TRACE_SKIP"); + return value != nullptr ? std::strtoull(value, nullptr, 10) : 0ULL; + }(); + static u64 seen_events{}; + static const u64 only_ps = [] { + const char* value = std::getenv("EDEN_NX2_TRACE_PS"); + return value != nullptr ? std::strtoull(value, nullptr, 16) : 0ULL; + }(); + const u64 pixel_shader = + (u64{regs.pipelines[5].reserved[0] & 0xff} << 32) | regs.pipelines[5].reserved[1]; + if (!trace || regs.rt[0].width != 3840 || traced >= 400 || seen_events++ < skip || + (only_ps != 0 && pixel_shader != only_ps)) { return; } ++traced; @@ -349,7 +362,7 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory fmt::join(values, " ")); } static u32 texture_traces{}; - if (texture_traces++ > 40) { + if (texture_traces++ > 200) { return; } // Texture used by the pixel shader's first bound TEX (handle in c[2][0x20]) and its TIC. @@ -370,6 +383,15 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory memory.ReadBlockUnsafe(texture, texels.data(), sizeof(texels)); LOG_INFO(HW_GPU, "nx2 window texture {:#x} {}x{}: {:08x}", texture, (tic[4] & 0xffff) + 1, (tic[5] & 0xffff) + 1, fmt::join(texels, " ")); + // EDEN_NX2_TEXTURE_DUMP=: save the first 4 MiB of the last traced texture's memory. + if (const char* path = std::getenv("EDEN_NX2_TEXTURE_DUMP"); path != nullptr) { + std::vector bytes(2048u * 10240u); + memory.ReadBlockUnsafe(texture, bytes.data(), bytes.size()); + if (std::FILE* file = std::fopen(path, "wb")) { + std::fwrite(bytes.data(), 1, bytes.size(), file); + std::fclose(file); + } + } } } // Anonymous namespace @@ -379,14 +401,16 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { : maxwell3d->draw_manager.draw_state.vertex_buffer.count); // EDEN_NX2_RT_TRACE: log the first draw to every distinct colour target (debug aid). if (static const bool trace = std::getenv("EDEN_NX2_RT_TRACE") != nullptr; trace) { - static std::set> seen; - const auto& rt = maxwell3d->regs.rt[0]; - const auto key = std::make_tuple(rt.Address(), rt.width, rt.height, - static_cast(rt.format)); - if (rt.width >= 640 && seen.size() < 400 && seen.insert(key).second) { - LOG_INFO(HW_GPU, "nx2 draw target {:#x} {}x{} format={:#x} tile={:#x} (targets so far {})", - rt.Address(), rt.width, rt.height, static_cast(rt.format), - static_cast(maxwell3d->regs.rt[0].tile_mode.block_height.Value()), seen.size()); + static std::set> seen; + const auto& regs = maxwell3d->regs; + const auto& rt = regs.rt[0]; + const u64 vs = (u64{regs.pipelines[1].reserved[0] & 0xff} << 32) | regs.pipelines[1].reserved[1]; + const u64 ps = (u64{regs.pipelines[5].reserved[0] & 0xff} << 32) | regs.pipelines[5].reserved[1]; + const auto key = std::make_tuple(rt.Address(), rt.width, static_cast(rt.format), vs, ps); + if (rt.width >= 640 && seen.size() < 600 && seen.insert(key).second) { + LOG_INFO(HW_GPU, "nx2 draw target {:#x} {}x{} format={:#x} vs={:#x} ps={:#x} blend={} (pairs so far {})", + rt.Address(), rt.width, rt.height, static_cast(rt.format), vs, ps, + regs.blend.enable[0], seen.size()); } } PrepareDraw(is_indexed, [this, is_indexed, instance_count] { -- cgit v1.2.3