From bef001c1515a13a9b8e361a5a7d372fc2e01094c Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:39:08 -0400 Subject: nx2: read command lists the GPU wrote from the GPU's copy Mario Kart World runs small command lists (20-byte entries) that its culling dispatches fill in each frame. Accurate GPU mode reads every command list through the caches; Fast mode reads guest memory directly (on the split's front thread too), where those entries were still the words of an older frame, so the draws behind them were left out: the trees around the courses vanished in Fast mode only. The buffer cache now also marks the device pages the GPU writes (shader stores, copies of GPU-written data) in a bitmap the split's front can read. A command list on such a page waits for the back to run what was queued before it and is flushed when its words are still only in host buffers. In a race that is ~5% of command lists (13,500 a second of 262,000) and ~400 flushes a second. --- src/video_core/buffer_cache/buffer_cache.h | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/video_core/buffer_cache/buffer_cache.h') diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 42d7ba066d..a8f1edc68d 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -17,6 +17,7 @@ #include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/guest_memory.h" #include "video_core/host1x/gpu_device_memory_manager.h" +#include "video_core/nx2.h" #include "video_core/nx2_profile.h" #include "video_core/texture_cache/util.h" @@ -289,6 +290,9 @@ bool BufferCache

::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am const bool has_new_downloads = tmp_intervals.size() != 0; for (const auto& pair : tmp_intervals) { gpu_modified_ranges.Add(pair.first, pair.second); + if (Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) { + Tegra::Nx2MarkGpuWritten(pair.first, pair.second); + } } const auto& copy = copies[0]; src_buffer.MarkUsage(copy.src_offset, copy.size); @@ -1606,6 +1610,9 @@ void BufferCache

::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3 if (AsyncBufferDownloads()) { uncommitted_gpu_modified_ranges.Add(device_addr, size); } + if (Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) { + Tegra::Nx2MarkGpuWritten(device_addr, size); + } } template -- cgit v1.2.3 From 594e07ee5302098e417858608dd03c46336274f5 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Sun, 1 Nov 2026 01:11:09 -0400 Subject: buffer_cache: collect Switch 2 buffers from 512 MiB The collector ran on Switch 2 titles only once their buffers reached 1 GiB (aggressively at 2 GiB), so geometry of scenes long gone stayed in VRAM: Mario Kart World's title screen grew from 0.2 to 0.9 GB of buffers in ten minutes, most of a ~1.4 GB climb in VRAM in use. It now starts at 512 MiB (aggressive at 1 GiB); it only evicts buffers unused for 120 frames or more. (cherry picked from commit fb5d797636f2269591b68fda7c8d65787a9e3a9f) --- src/video_core/buffer_cache/buffer_cache.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/video_core/buffer_cache/buffer_cache.h') diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index e90af3214d..53874e75cc 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -141,9 +141,10 @@ void BufferCache

::TickFrame() { // title 1's buffers were ~200 MiB of an 8 GB GPU's ~6.8 GB in use, yet this collector // ran aggressively every frame on the device-wide figure, evicting up to 64 buffers (and // downloading the GPU-written ones, ~5% of the GPU thread) that were recreated later. - // Collect when the buffers themselves are large. - if (buffer_bytes >= 1_GiB) { - RunGarbageCollector(buffer_bytes >= 2_GiB); + // Collect when the buffers themselves are large. At 1 GiB it held geometry of scenes + // long gone: title 1's title screen grew to ~0.9 GB of buffers in ten minutes. + if (buffer_bytes >= 512_MiB) { + RunGarbageCollector(buffer_bytes >= 1_GiB); } } else if (total_used_memory >= minimum_memory) { RunGarbageCollector(total_used_memory >= critical_memory); -- cgit v1.2.3 From 86968cb5ce3195cb131eeb0ef68d2d8a4860f7df Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Sun, 1 Nov 2026 08:42:26 -0500 Subject: buffer_cache: mark guest memory large buffer uploads came from cold Like the texture cache's uploads: guest ranges of 256 KiB or more uploaded to the host GPU are mostly geometry the guest rarely reads again, so every 600 frames they are marked cold and the OS pages them out first when it needs RAM. (cherry picked from commit 5c942a5158a0f9769553eeba21b6121f277d0dc5) --- src/video_core/buffer_cache/buffer_cache.h | 11 +++++++++++ src/video_core/buffer_cache/buffer_cache_base.h | 2 ++ 2 files changed, 13 insertions(+) (limited to 'src/video_core/buffer_cache/buffer_cache.h') diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 42d7ba066d..633f8760e4 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -100,6 +100,12 @@ void BufferCache

::TickFrame() { return; } runtime.TickFrame(slot_buffers); + if (frame_tick % 600 == 0 && !uploaded_buffer_ranges.empty()) { + for (const auto& [address, size] : uploaded_buffer_ranges) { + device_memory.HintCold(address, size); + } + uploaded_buffer_ranges.clear(); + } if (static u64 frames{}; ++frames % 600 == 0) { u64 total{}; size_t count{}; @@ -1923,6 +1929,11 @@ void BufferCache

::MappedUploadMemory([[maybe_unused]] Buffer& buffer, u8* const src_pointer = staging_pointer.data() + copy.src_offset; const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset; device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size); + // Large uploads are mostly geometry the host GPU now has its own copy of: TickFrame + // marks their guest memory cold (see TextureCache::RefreshContents). + if (copy.size >= 256_KiB) { + uploaded_buffer_ranges.emplace_back(device_addr, copy.size); + } // Apply the staging offset copy.src_offset += upload_staging.offset; } diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 45a1836341..0db8bdb1eb 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -549,6 +549,8 @@ private: size_t immediate_buffer_capacity = 0; Common::ScratchBuffer immediate_buffer_alloc; + /// Guest memory large uploads came from since the last cold hint (device address, size). + std::vector> uploaded_buffer_ranges; struct LRUItemParams { using ObjectType = BufferId; -- cgit v1.2.3