From 598bb7750c71ea1378724ddfe9418f27330fa279 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Wed, 28 Oct 2026 07:47:45 -0400 Subject: nx2: unmap GPU memory in submission order NormalUnmapGpuMemory4K and destroying GPU memory are jobs the console's graphics server completes behind the work already submitted. Run the GMMU unmaps on the GPU thread after the command lists queued before them (GPU::RunInOrder) and signal the job's event only then, instead of unmapping and signalling at once while Eden's GPU thread may still be behind. Also EDEN_NX2_FRAMES_AHEAD (experimental): wait for the previous frame's fence at present instead of this one's. --- src/video_core/gpu.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/video_core/gpu.cpp') diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 1621b24125..2a61a66d82 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -206,6 +206,10 @@ struct GPU::Impl { gpu_thread.SubmitList(channel, std::move(entries), is_async); } + void RunInOrder(std::function&& func) { + gpu_thread.Run(std::move(func), is_async); + } + /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory void FlushRegion(DAddr addr, u64 size) { gpu_thread.FlushRegion(addr, size, is_async); @@ -530,6 +534,10 @@ void GPU::FlushRegion(DAddr addr, u64 size) { impl->FlushRegion(addr, size); } +void GPU::RunInOrder(std::function&& func) { + impl->RunInOrder(std::move(func)); +} + void GPU::InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) { impl->InvalidateRegion(addr, size, preserve_gpu_writes); } -- cgit v1.2.3 From 8393e2912616ce475619900be8cb29e551ff2997 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:19:44 -0400 Subject: Fix the P6 scratch save in R2UR/MATCH/HFMA2.RELU; GPU memory dump tool The colour cast on title 1 (magenta/cyan, then red/green, changing per course) came from the sky/atmosphere volume shader (a pixel shader drawing a 32^3 RGBA16F table slice by slice), which starts with an unguarded R2UR. R2UR, MATCH and HFMA2.RELU use P6 as scratch and saved/restored it with the uniform-guard mask, 1 << (6 - 7) for an unguarded instruction: undefined, in practice bit 31, which shifted into the opcode bits and turned the save into another instruction. They now always save and restore P6. Debugging aids used to find it: - EDEN_NX2_DUMP=::,...: flush GPU memory ranges at a present (on the GPU thread) and save them next to the frame captures; nx2work/vol3d.py untiles Switch 2 3D textures from such dumps. - GPU::RunOnGpuThread. - Logs of Switch 2 3D images and descriptors. --- src/core/hle/service/nx2_gsv.h | 42 ++++++++++++++++++++++ .../frontend/ampere/lower_to_maxwell.h | 16 +++++---- src/video_core/gpu.cpp | 4 +++ src/video_core/gpu.h | 3 ++ src/video_core/texture_cache/image_info.cpp | 15 +++++++- src/video_core/texture_cache/texture_cache.h | 6 ++++ 6 files changed, 78 insertions(+), 8 deletions(-) (limited to 'src/video_core/gpu.cpp') diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index b7f0e9261a..fc7de1fae7 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -21,6 +21,7 @@ #include "common/alignment.h" #include "common/settings.h" #include "core/core.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "core/frontend/framebuffer_layout.h" #include "core/perf_stats.h" @@ -758,6 +759,7 @@ private: } system.GPU().RequestComposite(std::move(layers), std::move(fences)); CaptureFrame(); + DumpRanges(); system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs()); system.GetPerfStats().EndSystemFrame(); system.GetPerfStats().BeginSystemFrame(); @@ -797,6 +799,46 @@ private: layout); } + // EDEN_NX2_DUMP=::[,:...]: at that + // present, flush those GPU ranges back from the host GPU and save their bytes as + // nx2-dump-
.bin in the EDEN_NX2_FRAME_CAPTURE directory (to inspect what a pass + // rendered, e.g. a 3D volume, offline). + void DumpRanges() { + static const std::string spec = [] { + const char* text = std::getenv("EDEN_NX2_DUMP"); + return text ? std::string{text} : std::string{}; + }(); + if (spec.empty() || m_presents != std::strtoull(spec.c_str(), nullptr, 10)) { + return; + } + const char* const directory = std::getenv("EDEN_NX2_FRAME_CAPTURE"); + size_t position = spec.find(':'); + while (position != std::string::npos) { + char* end{}; + const u64 address = std::strtoull(spec.c_str() + position + 1, &end, 16); + const u64 size = *end == ':' ? std::strtoull(end + 1, &end, 16) : 0; + position = spec.find(',', position + 1); // the next "address:size" + const std::string path = fmt::format("{}/nx2-dump-{:x}.bin", directory ? directory : ".", address); + // On the GPU thread: waiting for it here can deadlock with a GPU wait on this present. + static_cast(system.GPU().RunOnGpuThread([this, address, size, path] { + std::vector bytes(size); + for (u64 current = address; current < address + size;) { + const u64 chunk = std::min((current | 0xfff) + 1, address + size) - current; + if (const auto device = Nvidia::Devices::Nx2GpuMemoryRegistry::Get().DeviceAddressOf(current)) { + system.GPU().Renderer().ReadRasterizer()->FlushRegion(*device, chunk); + system.Host1x().MemoryManager().ReadBlock(*device, bytes.data() + (current - address), chunk); + } + current += chunk; + } + if (std::FILE* file = std::fopen(path.c_str(), "wb")) { + std::fwrite(bytes.data(), 1, bytes.size(), file); + std::fclose(file); + } + LOG_INFO(Service, "nx2: dumped {:#x} bytes at {:#x} to {}", size, address, path); + })); + } + } + // 228(u64 token, u32 slot): return a buffer without presenting it. void CancelClientBuffer(HLERequestContext& ctx) { constexpr u64 AvailableSlots = 0x7fff; diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 0cebaa4363..1390951ef5 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -161,15 +161,17 @@ inline LoweredProgram LowerToMaxwell(std::span code) { }; std::string_view operand_error; const auto emit = [&](u64 word) { result.instructions.push_back(word); }; - const u32 guard_mask = 1U << guard_slot; - const auto save_predicate = [&] { - emit(0x38e8000000070000ULL | 253 | (u64{255} << 8) | (u64{guard_mask} << 20)); + // P(guard slot) while a uniform guard is swapped in; P6 for lowerings that use it as + // scratch (R2UR, MATCH, HFMA2.RELU), which save and restore it with the defaults below. + const u32 guard_mask = guard_slot < 7 ? 1U << guard_slot : 0U; + const auto save_predicate = [&](u32 mask = 1U << 6) { + emit(0x38e8000000070000ULL | 253 | (u64{255} << 8) | (u64{mask} << 20)); }; - const auto restore_predicate = [&] { - emit(0x38f0000000070000ULL | (u64{253} << 8) | (u64{guard_mask} << 20)); + const auto restore_predicate = [&](u32 mask = 1U << 6) { + emit(0x38f0000000070000ULL | (u64{253} << 8) | (u64{mask} << 20)); }; if (guarded_uniform) { - save_predicate(); + save_predicate(guard_mask); emit(0x38f0000000070000ULL | (u64{239} << 8) | (u64{guard_mask} << 20)); } const u32 output_mask = uniform_output == 7 ? 0U : 1U << (6 - uniform_output); @@ -2229,7 +2231,7 @@ inline LoweredProgram LowerToMaxwell(std::span code) { emit(0x38e8000000070000ULL | 239 | (u64{239} << 8) | (u64{output_mask} << 20)); emit(0x38f0000000070000ULL | (u64{254} << 8) | (u64{output_mask} << 20)); } - if (guarded_uniform) restore_predicate(); + if (guarded_uniform) restore_predicate(guard_mask); } return fail(static_cast(code.size()), main_exited ? "native program lacks captured library return" : "native program has no unconditional EXIT"); } diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 2a61a66d82..6441b9511a 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -411,6 +411,10 @@ u64 GPU::RequestFlush(DAddr addr, std::size_t size) { }); } +u64 GPU::RunOnGpuThread(std::function&& action) { + return impl->RequestSyncOperation(std::move(action)); +} + u64 GPU::CurrentSyncRequestFence() const { return impl->CurrentSyncRequestFence(); } diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 41ba186cd3..3a6546b41b 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -164,6 +164,9 @@ public: /// Request a host GPU memory flush from the CPU. [[nodiscard]] u64 RequestFlush(DAddr addr, std::size_t size); + /// Runs an action on the GPU thread between command lists (returns its sync fence). + u64 RunOnGpuThread(std::function&& action); + /// Obtains current flush request fence id. [[nodiscard]] u64 CurrentSyncRequestFence() const; diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 8b59eb6841..7e86bf00cf 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -4,9 +4,13 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include #include #include "common/assert.h" +#include "common/logging.h" #include "common/settings.h" #include "video_core/surface.h" #include "video_core/texture_cache/format_lookup_table.h" @@ -109,14 +113,23 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept { size.height = config.Height(); resources.layers = config.BaseLayer() + config.Depth() * 6; break; - case TextureType::Texture3D: + case TextureType::Texture3D: { ASSERT(config.BaseLayer() == 0); type = ImageType::e3D; size.width = config.Width(); size.height = config.Height(); size.depth = config.Depth(); resources.layers = 1; + // A few distinct 3D descriptors, raw (to check Switch 2 volume fields). + static std::vector> logged; + if (logged.size() < 16 && std::find(logged.begin(), logged.end(), config.raw) == logged.end()) { + logged.push_back(config.raw); + LOG_INFO(HW_GPU, "3D TIC {:016x} {:016x} {:016x} {:016x}: {}x{}x{} block_depth {} levels {}", + config.raw[0], config.raw[1], config.raw[2], config.raw[3], size.width, size.height, + size.depth, config.block_depth.Value(), config.max_mip_level.Value() + 1); + } break; + } case TextureType::Texture1DBuffer: type = ImageType::Buffer; size.width = config.Width(); diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index f3da5684a5..75005f0731 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1569,6 +1569,12 @@ ImageId TextureCache

::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr={:#x}", gpu_addr); ImageInfo storage_info = info; storage_info.native_tiling = gpu_memory->UsesNativeTiling(); + if (storage_info.native_tiling && info.type == ImageType::e3D) { + // Switch 2 3D images (lighting/fog volumes) are rare: log each one's shape. + LOG_INFO(HW_GPU, "3D image at {:#x}: {}x{}x{} levels {} format {} block {}x{}x{}", + gpu_addr, info.size.width, info.size.height, info.size.depth, info.resources.levels, + static_cast(info.format), info.block.width, info.block.height, info.block.depth); + } const ImageId image_id = JoinImages(storage_info, gpu_addr, *cpu_addr); const Image& image = slot_images[image_id]; // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different -- cgit v1.2.3