From be449da3db512f790c71d60a58e027a5898a0446 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Thu, 29 Oct 2026 06:22:54 -0400 Subject: buffer_cache: no buffer downloads at every fence for Switch 2 titles Eden copied every GPU-written buffer range back to guest memory at the next fence. Title 1 signals a fence per compute dispatch, so that was 250-330 MB/s in 13,000-20,000 ranges a second, with the fencing thread holding the buffer cache while it wrote them. Measured: the native MME's DMA reads never touched those pages (they read query words written on the CPU side), and with reactive flushing the guest CPU read GPU-written memory at most 64 times in 200 s. Without them, for Switch 2 titles (native tiling): fences ~1.1 us instead of ~5, draw setup 6.1 us instead of 6.4-6.7, macro calls 160 ns instead of 167-170, GPU-thread lock spinning 1.8% (was 5.5%). Regions stay marked GPU written and are still flushed on demand. EDEN_NX2_BUFFER_DOWNLOADS=0/1 forces either way. Reactive flushing reads of GPU-written memory are now counted in the log. --- src/video_core/buffer_cache/buffer_cache.h | 8 ++++++-- src/video_core/buffer_cache/buffer_cache_base.h | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) (limited to 'src/video_core/buffer_cache') diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index b909971e42..0a6b53b33b 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -252,7 +252,9 @@ bool BufferCache

::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am const DAddr diff = base_address - *cpu_src_address; const DAddr new_base_address = *cpu_dest_address + diff; tmp_intervals.push_back({new_base_address, size}); - uncommitted_gpu_modified_ranges.Add(new_base_address, size); + if (AsyncBufferDownloads()) { + uncommitted_gpu_modified_ranges.Add(new_base_address, size); + } }; gpu_modified_ranges.ForEachInRange(*cpu_src_address, amount, mirror); // This subtraction in this order is important for overlapping copies. @@ -1574,7 +1576,9 @@ void BufferCache

::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3 } memory_tracker.MarkRegionAsGpuModified(device_addr, size); gpu_modified_ranges.Add(device_addr, size); - uncommitted_gpu_modified_ranges.Add(device_addr, size); + if (AsyncBufferDownloads()) { + uncommitted_gpu_modified_ranges.Add(device_addr, size); + } } template diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 57011752cc..6d6d873b75 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -514,6 +515,24 @@ private: MemoryTracker memory_tracker; Common::RangeSet uncommitted_gpu_modified_ranges; + + /// Whether GPU-written buffer ranges are copied back to guest memory at the next fence; + /// otherwise only when something flushes them (as upstream yuzu below High GPU accuracy). + /// Not for Switch 2 titles: title 1 had 250-330 MB/s copied back (a fence per compute + /// dispatch, the fencing thread holding the buffer cache while it wrote them), while its CPU + /// read GPU-written memory at most 64 times in 200 s (counted with reactive flushing) and + /// the native MME's DMA reads never touched those pages. Without them a fence costs ~1 us + /// instead of ~5 and draw setup ~7% less. EDEN_NX2_BUFFER_DOWNLOADS=0/1 forces it. + bool AsyncBufferDownloads() const { + static const int forced = [] { + const char* value = std::getenv("EDEN_NX2_BUFFER_DOWNLOADS"); + return value == nullptr ? -1 : (value[0] == '0' ? 0 : 1); + }(); + if (forced >= 0) { + return forced == 1; + } + return gpu_memory == nullptr || !gpu_memory->UsesNativeTiling(); + } Common::RangeSet gpu_modified_ranges; std::deque> committed_gpu_modified_ranges; -- cgit v1.2.3