aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/nx2.h
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 01:03:37 -0500
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 15:18:01 -0500
commit40902b09c7b86de7cf12f26839241f26925d04b5 (patch)
tree1a0403bb25a8ab44f37e270564fb4a105ed1e6b9 /src/video_core/nx2.h
parent81746294f8114f93d82de1e1984b00d0194f25ba (diff)
nx2: leave unwritten sparse blocks without memory when they get zeros
Title 1 clears about 1 GB of its sparse regions at boot through the GPU memory manager. Every block the clear touched got 4 MiB of guest memory, which the clear itself then committed on the host, and counted as written, so the texture cache gave the images over it device memory too. A block nobody wrote reads zero already: when the part of a write that lands on it is all zero, leave it as it is. (cherry picked from commit 0d7ff63fd1a373cecff6ba91022c9027e801e53f)
Diffstat (limited to 'src/video_core/nx2.h')
-rw-r--r--src/video_core/nx2.h18
1 files changed, 14 insertions, 4 deletions
diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h
index 1fb49e15a6..db8c92adfd 100644
--- a/src/video_core/nx2.h
+++ b/src/video_core/nx2.h
@@ -97,8 +97,9 @@ inline void Nx2ClearSemaphoreRegions() {
// Switch 2 sparse regions (nx2:gpu 214, word 7 bit 0x200: title 1's terrain texture arrays and
// layer pools, 170 MB-5.8 GB each, of which the game fills a little) get memory per block when the
// GPU first writes there, not in full. nx2:gpu installs the handler and the span its regions cover;
-// the GPU memory manager calls Nx2BackSparseWrite before each write.
-using Nx2SparseBackingHandler = std::function<void(u64 gpu_address, u64 size)>;
+// the GPU memory manager calls Nx2BackSparseWrite before each write, with the bytes it writes
+// (blocks nobody wrote that only get zeros need no memory: they read zero already).
+using Nx2SparseBackingHandler = std::function<void(u64 gpu_address, u64 size, const u8* data)>;
inline std::recursive_mutex g_nx2_sparse_backing_mutex;
inline Nx2SparseBackingHandler g_nx2_sparse_backing;
inline std::atomic<u64> g_nx2_sparse_low{~u64{0}};
@@ -122,14 +123,14 @@ inline void Nx2AddSparseSpan(u64 gpu_address, u64 size) {
}
}
-inline void Nx2BackSparseWrite(u64 gpu_address, u64 size) {
+inline void Nx2BackSparseWrite(u64 gpu_address, u64 size, const void* data) {
if (gpu_address + size <= g_nx2_sparse_low.load(std::memory_order_relaxed) ||
gpu_address >= g_nx2_sparse_high.load(std::memory_order_relaxed)) [[likely]] {
return;
}
std::scoped_lock lk{g_nx2_sparse_backing_mutex};
if (g_nx2_sparse_backing) {
- g_nx2_sparse_backing(gpu_address, size);
+ g_nx2_sparse_backing(gpu_address, size, static_cast<const u8*>(data));
}
}
@@ -174,6 +175,15 @@ inline void Nx2SparseBlockWritten(u64 region_address, u64 block) {
}
}
+/// True unless the block of the region at region_address is known to hold nothing (no write from
+/// the CPU or the GPU yet).
+inline bool Nx2SparseBlockIsWritten(u64 region_address, u64 block) {
+ std::scoped_lock lk{g_nx2_sparse_state_mutex};
+ const auto it = g_nx2_sparse_state.find(region_address);
+ return it == g_nx2_sparse_state.end() || block >= it->second.written.size() ||
+ it->second.written[block];
+}
+
/// The game maps its own memory over [offset, +size) of the region at region_address (mapped), or
/// unmaps it there.
inline void Nx2SparseTilesMapped(u64 region_address, u64 offset, u64 size, bool mapped) {