diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 13:41:36 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 13:41:49 -0400 |
| commit | ddd367a8152f1c37bfc972475b40afece2da31ac (patch) | |
| tree | 062c859069aead4a429c2ecb2d836bab4812fb05 /src | |
| parent | ea33ad1865970ee731e5f74dbcc26ca472798afe (diff) | |
core: end device page runs where pages are unmapped or mapped again
WalkBlock copies a run of physically contiguous device pages at once, using
each page's continuity_tracker. Unmap left the counts as they were, Map
without tracking did not touch them, and the pages before a changed range
kept counting into it. A read starting on such a page then copied the host
memory that followed the old mapping, or, starting on an unmapped page,
zero-filled the whole old run, mapped pages after it included.
Splatoon Raiders lost its dialogue text this way: the glyph vertex shader
reads its corner table from constant buffer 1 (the program's constants, at
device 0x5617d700), and the buffer cache uploaded the range from 0x5617c000,
an unmapped page still counted as the start of a long run, so the table
reached the GPU as zeros and every glyph sampled one empty texel.
Unmap and Map now reset the counts of their pages (Map recounts them when
tracking) and cut the runs of the pages before them at the range start.
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/device_memory_manager.h | 2 | ||||
| -rw-r--r-- | src/core/device_memory_manager.inc | 21 |
2 files changed, 23 insertions, 0 deletions
diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 46cda14e78..9c034f01a1 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -187,6 +187,8 @@ private: void WalkBlock(const DAddr addr, const std::size_t size, auto on_unmapped, auto on_memory, auto increment); + void CutContinuityAt(size_t page); + void InnerGatherDeviceAddresses(Common::ScratchBuffer<u32>& buffer, PAddr address); std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl; diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index d78a229e1f..c45bc71656 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -211,9 +211,13 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size std::scoped_lock lk(mapping_guard); tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages); + CutContinuityAt(start_page_d); for (size_t i = 0; i < num_pages; i++) { const VAddr new_vaddress = virtual_address + i * Memory::YUZU_PAGESIZE; auto* ptr = process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress)); + // One page per run unless tracked below: a run left from an earlier mapping of these + // device pages would make WalkBlock copy the host memory that followed that mapping. + tracked_entries.GetUnchecked(start_page_d + i).continuity_tracker = 0; if (ptr == nullptr) [[unlikely]] { tracked_entries.GetUnchecked(start_page_d + i).compressed_physical_ptr = 0; continue; @@ -248,11 +252,13 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) { std::scoped_lock lk(mapping_guard); tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages); // should already be committed, but just in case + CutContinuityAt(start_page_d); for (size_t i = 0; i < num_pages; i++) { auto& entry = tracked_entries.GetUnchecked(start_page_d + i); auto phys_addr = entry.compressed_physical_ptr; entry.compressed_physical_ptr = 0; entry.cpu_backing_address = 0; + entry.continuity_tracker = 0; if (phys_addr != 0) [[likely]] { u32& base_dev = compressed_device_addr.GetAndFault(phys_addr - 1U); if ((base_dev >> MULTI_FLAG_BITS) == 0) [[likely]] { @@ -270,6 +276,21 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) { } translation_generation.fetch_add(1, std::memory_order_release); } + +template <typename Traits> +void DeviceMemoryManager<Traits>::CutContinuityAt(size_t page) { + // Runs counted from the pages before `page` end there: the pages from `page` on are being + // mapped again or unmapped. A run is counted down page by page (TrackContinuityImpl), so the + // first page whose run already ends before `page` ends the walk. + for (size_t index = page; index-- > 0;) { + const size_t last_count = page - 1 - index; + if (tracked_entries[index].continuity_tracker <= last_count) { + break; + } + tracked_entries.GetUnchecked(index).continuity_tracker = static_cast<u32>(last_count); + } +} + template <typename Traits> void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtual_address, size_t size, Asid asid) { |
