aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 06:22:54 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:47 -0400
commitbe449da3db512f790c71d60a58e027a5898a0446 (patch)
treef6b654821a27b50f248a409ac714b50015dfe327 /src
parent8d72c1f92ac12ff5992b92b2e27b84aa31468879 (diff)
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.
Diffstat (limited to 'src')
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h8
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h19
-rw-r--r--src/video_core/gpu.cpp7
3 files changed, 32 insertions, 2 deletions
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<P>::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<P>::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 <class P>
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 <algorithm>
+#include <cstdlib>
#include <array>
#include <bit>
#include <functional>
@@ -514,6 +515,24 @@ private:
MemoryTracker memory_tracker;
Common::RangeSet<DAddr> 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<DAddr> gpu_modified_ranges;
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 6441b9511a..53f8f063f7 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -221,6 +221,13 @@ struct GPU::Impl {
return raster_area;
}
raster_area.preemtive = true;
+ // A guest CPU read of GPU-written memory (reactive flushing): count them, they decide
+ // whether buffer data must be copied back at every fence (EDEN_NX2_BUFFER_DOWNLOADS).
+ if (static std::atomic<u64> reads{}; (reads.fetch_add(1, std::memory_order_relaxed) & 63) == 0) {
+ LOG_INFO(HW_GPU, "CPU read of GPU-written memory [{:#x}, {:#x}) ({} so far)",
+ raster_area.start_address, raster_area.end_address,
+ reads.load(std::memory_order_relaxed));
+ }
const u64 fence = RequestSyncOperation([this, &raster_area]() {
renderer->ReadRasterizer()->FlushRegion(raster_area.start_address, raster_area.end_address - raster_area.start_address);
});