aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/nx2.h
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-02 11:24:19 -0500
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-02 11:24:19 -0500
commit8f1435f8db104351fa3388551eee9d12d2043b40 (patch)
tree1cbaa0434f77ae07ba04ed41504ba48cbdd246a2 /src/video_core/nx2.h
parent75bdb925dfb77db2370cb027eda81352c9622bbb (diff)
nx2: read GPU-written command lists from copies instead of draining
The trees fix (#52) drains the split and flushes for every command list on a GPU-written page: in a Mario Kart World race ~13,500 lists and ~400 GPU round trips a second, during which the back has nothing to do. The replay ran at ~40 fps instead of ~45. The front now marks the pages those lists are on. The buffer cache notes writes to them; after a compute dispatch or DMA copy that writes one it copies the page into host memory and submits. The front waits for the back only up to the last compute or copy work it sent (not everything queued), then reads the copy once the GPU is done with it. Pages the GPU wrote once, long before, and the CPU now writes lists to have no copy: their first list drains and flushes the whole page, which is then read from guest memory until GPU work writes it again. CPU writes and inline uploads reach guest memory, so they keep such a page synced but make a copy stale. A page a draw writes always drains. Mario Kart World time-trial replay, Radeon 780M: ~40 -> ~45 fps, trees drawn, no list drains left.
Diffstat (limited to 'src/video_core/nx2.h')
-rw-r--r--src/video_core/nx2.h32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h
index db8c92adfd..92714d52d2 100644
--- a/src/video_core/nx2.h
+++ b/src/video_core/nx2.h
@@ -43,11 +43,43 @@ inline void Nx2MarkGpuWritten(u64 device_address, u64 size) {
}
}
+// GPU-written device pages the front has read command lists from (same layout as above). The
+// buffer cache copies these pages into host memory right after the compute or copy work that
+// writes them and submits, so the front reads the copy once the GPU is done instead of draining
+// the split and flushing the caches (BufferCache::Nx2ReadListCopy).
+inline std::array<std::atomic<u64>, (u64{1} << (36 - Nx2WrittenPageBits)) / 64> g_nx2_command_list_pages{};
+inline std::atomic<bool> g_nx2_has_command_list_pages{false};
+
+inline void Nx2MarkCommandListPage(u64 device_address) {
+ const u64 page = device_address >> Nx2WrittenPageBits;
+ if (page >= g_nx2_command_list_pages.size() * 64) {
+ return;
+ }
+ auto& word = g_nx2_command_list_pages[page / 64];
+ const u64 bit = u64{1} << (page % 64);
+ if ((word.load(std::memory_order_relaxed) & bit) == 0) {
+ word.fetch_or(bit, std::memory_order_relaxed);
+ g_nx2_has_command_list_pages.store(true, std::memory_order_relaxed);
+ }
+}
+
+[[nodiscard]] inline bool Nx2IsCommandListPage(u64 device_address) {
+ const u64 page = device_address >> Nx2WrittenPageBits;
+ if (page >= g_nx2_command_list_pages.size() * 64) {
+ return false;
+ }
+ return (g_nx2_command_list_pages[page / 64].load(std::memory_order_relaxed) >> (page % 64)) & 1;
+}
+
/// A new emulation session starts: no page has been written by its GPU yet.
inline void Nx2ClearGpuWrittenPages() {
for (auto& word : g_nx2_gpu_written_pages) {
word.store(0, std::memory_order_relaxed);
}
+ for (auto& word : g_nx2_command_list_pages) {
+ word.store(0, std::memory_order_relaxed);
+ }
+ g_nx2_has_command_list_pages.store(false, std::memory_order_relaxed);
}
[[nodiscard]] inline bool Nx2IsGpuWritten(u64 device_address) {