aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 23:04:22 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 23:04:22 -0400
commitd6759fb2a4d46f3902ce9715a3c22f264c6c3726 (patch)
tree4de9a1e5cdefc1a214cf2e252c00c8583fa6f53e /src
parent81eaf7ccbf07bfc73553d8e752a256aa3719bcb9 (diff)
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.
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/nx2_gsv.h5
-rw-r--r--src/shader_recompiler/frontend/ampere/lower_to_maxwell.h34
-rw-r--r--src/video_core/engines/fermi_2d.cpp13
-rw-r--r--src/video_core/engines/maxwell_dma.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp44
5 files changed, 102 insertions, 12 deletions
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<u32>(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<int>(layout.width), static_cast<int>(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<const Instruction> 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<const Instruction> code) {
(u64{insn.Bits(90, 1)} << 49) | (u64{depth_compare} << 50) | (u64{7} << 51) |
(u64{lod} << 55));
const u32 components = static_cast<u32>(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<u32>(dest), 250, components > 1 ? 251U : 255U);
+ if (components > 2) {
+ pack(split ? second_dest : (dest == 255 ? 255U : static_cast<u32>(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<u32>(dest) + i;
@@ -1114,7 +1140,7 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> 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<const Instruction> 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<GPUVAddr>(regs.offset_in), GPUVAddr(regs.offset_out));
+ // EDEN_NX2_TRACE_DST=<hex>: 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<GPUVAddr>(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 <algorithm>
#include <array>
#include <memory>
+#include <cstdio>
#include <cstdlib>
#include <tuple>
#include <set>
@@ -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=<file>: 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<u8> 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<std::tuple<GPUVAddr, u32, u32, u32>> seen;
- const auto& rt = maxwell3d->regs.rt[0];
- const auto key = std::make_tuple(rt.Address(), rt.width, rt.height,
- static_cast<u32>(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<u32>(rt.format),
- static_cast<u32>(maxwell3d->regs.rt[0].tile_mode.block_height.Value()), seen.size());
+ static std::set<std::tuple<GPUVAddr, u32, u32, u64, u64>> 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<u32>(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<u32>(rt.format), vs, ps,
+ regs.blend.enable[0], seen.size());
}
}
PrepareDraw(is_indexed, [this, is_indexed, instance_count] {