diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 01:03:37 -0500 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 15:18:01 -0500 |
| commit | 40902b09c7b86de7cf12f26839241f26925d04b5 (patch) | |
| tree | 1a0403bb25a8ab44f37e270564fb4a105ed1e6b9 /src/core | |
| parent | 81746294f8114f93d82de1e1984b00d0194f25ba (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/core')
| -rw-r--r-- | src/core/hle/service/nx2_gsv.h | 47 |
1 files changed, 45 insertions, 2 deletions
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index e037b820d6..9d0fd597b2 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -340,7 +340,9 @@ public: }); if (LazySparseRegions()) { Tegra::Nx2SetSparseBacking( - [this](u64 gpu_address, u64 size) { BackSparseWrite(gpu_address, size); }); + [this](u64 gpu_address, u64 size, const u8* data) { + BackSparseWrite(gpu_address, size, data); + }); } } @@ -735,6 +737,23 @@ private: TipcReplyWords(ctx, {static_cast<u32>(count), static_cast<u32>(u64{count} >> 32)}); } + static bool IsAllZero(const u8* data, size_t size) { + size_t i = 0; + for (; i + sizeof(u64) <= size; i += sizeof(u64)) { + u64 word; + std::memcpy(&word, data + i, sizeof(word)); + if (word != 0) { + return false; + } + } + for (; i < size; ++i) { + if (data[i] != 0) { + return false; + } + } + return true; + } + // Returns the pages of destroyed regions the GPU thread is done with to the kernel. void FreeReleasedRegions() { auto* const process = system.ApplicationProcess(); @@ -842,7 +861,7 @@ private: // usually on the GPU thread): the blocks the write touches get memory, cleared, behind the // region's device pages. The GPU mapping stays as it is, so the images over the region keep // their storage and only see the write. - void BackSparseWrite(u64 gpu_address, u64 size) { + void BackSparseWrite(u64 gpu_address, u64 size, const u8* data) { struct Pending { u64 region; u64 block; @@ -865,6 +884,7 @@ private: } }; bool waits_for_other = false; + u64 zero_bytes = 0; { std::unique_lock lk{m_region_mutex}; for_regions([&](u64 start, SparseRegion& region, u64 first, u64 last) { @@ -875,6 +895,18 @@ private: region.blocks[block] = BlockWritten; Tegra::Nx2SparseBlockWritten(start, block); } else if (region.blocks[block] == BlockEmpty) { + // Zeros over a block nobody wrote change nothing: it reads zero without + // memory. Title 1 clears ~1 GB of them at boot. + const u64 block_begin = start + block * SparseBlockSize; + const u64 write_begin = (std::max)(gpu_address, block_begin); + const u64 write_end = (std::min)( + {end, block_begin + SparseBlockSize, start + region.size}); + if (data != nullptr && + IsAllZero(data + (write_begin - gpu_address), write_end - write_begin) && + !Tegra::Nx2SparseBlockIsWritten(start, block)) { + zero_bytes += write_end - write_begin; + continue; + } region.blocks[block] = BlockPending; todo.push_back({start, block}); } else if (region.blocks[block] == BlockPending) { @@ -883,6 +915,16 @@ private: } }); } + if (zero_bytes != 0) { + std::scoped_lock lk{m_region_mutex}; + const u64 before = m_sparse_zero_bytes; + m_sparse_zero_bytes += zero_bytes; + if (before == 0 || (before >> 28) != (m_sparse_zero_bytes >> 28)) { + LOG_INFO(Service, "nx2:gpu {} MiB of zeros over unwritten sparse blocks left out " + "(they read zero without memory)", + m_sparse_zero_bytes >> 20); + } + } // Allocating and mapping happens outside the lock (see AllocateGpuOnlyBacking). for (const auto& [start, block] : todo) { u64 block_size = SparseBlockSize; @@ -993,6 +1035,7 @@ private: std::map<u64, SparseRegion> m_sparse_regions; ///< By GPU address u64 m_sparse_blocks{}; ///< Sparse blocks backed so far u64 m_sparse_backed_bytes{}; ///< Sparse region memory given out so far + u64 m_sparse_zero_bytes{}; ///< Zeros written over unwritten sparse blocks, left out u64 m_sparse_released_bytes{}; ///< Blocks given back once the game mapped tiles there // nn::gpu::sdk::SparseMapRequest (216), filled from NvRm's nvgpu_as_remap_op. |
