diff options
| author | PastaLaPate <pastalapate@proton.me> | 2026-11-02 20:29:43 +0100 |
|---|---|---|
| committer | PastaLaPate <pastalapate@proton.me> | 2026-11-02 20:29:43 +0100 |
| commit | 7d25dbcea1e08e09c952096671b5fe2196d7ac79 (patch) | |
| tree | f0b1ba079e2442daafb25812157100d3409bc564 /src/video_core/buffer_cache/buffer_cache.h | |
| parent | 0d0e7788f8c86bcdbbef505654e9489808f38183 (diff) | |
| parent | 62214094d6c698b70f51016e5c12af415f025be7 (diff) | |
Merge remote-tracking branch 'upstream/nx2' into nx2
Diffstat (limited to 'src/video_core/buffer_cache/buffer_cache.h')
| -rw-r--r-- | src/video_core/buffer_cache/buffer_cache.h | 259 |
1 files changed, 253 insertions, 6 deletions
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index e90af3214d..cd176dec27 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -17,6 +17,7 @@ #include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/guest_memory.h" #include "video_core/host1x/gpu_device_memory_manager.h" +#include "video_core/nx2.h" #include "video_core/nx2_profile.h" #include "video_core/texture_cache/util.h" @@ -100,6 +101,12 @@ void BufferCache<P>::TickFrame() { return; } runtime.TickFrame(slot_buffers); + if (frame_tick % 600 == 0 && !uploaded_buffer_ranges.empty()) { + for (const auto& [address, size] : uploaded_buffer_ranges) { + device_memory.HintCold(address, size); + } + uploaded_buffer_ranges.clear(); + } if (static u64 frames{}; ++frames % 600 == 0) { u64 total{}; size_t count{}; @@ -141,9 +148,10 @@ void BufferCache<P>::TickFrame() { // title 1's buffers were ~200 MiB of an 8 GB GPU's ~6.8 GB in use, yet this collector // ran aggressively every frame on the device-wide figure, evicting up to 64 buffers (and // downloading the GPU-written ones, ~5% of the GPU thread) that were recreated later. - // Collect when the buffers themselves are large. - if (buffer_bytes >= 1_GiB) { - RunGarbageCollector(buffer_bytes >= 2_GiB); + // Collect when the buffers themselves are large. At 1 GiB it held geometry of scenes + // long gone: title 1's title screen grew to ~0.9 GB of buffers in ten minutes. + if (buffer_bytes >= 512_MiB) { + RunGarbageCollector(buffer_bytes >= 1_GiB); } } else if (total_used_memory >= minimum_memory) { RunGarbageCollector(total_used_memory >= critical_memory); @@ -166,6 +174,7 @@ void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size template <class P> void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) { + Nx2NoteListHostWrite(device_addr, size); if (IsRegionGpuModified(device_addr, size)) { ClearDownload(device_addr, size); gpu_modified_ranges.Subtract(device_addr, size); @@ -186,6 +195,7 @@ void BufferCache<P>::CachedWriteMemory(DAddr device_addr, u64 size) { return; } + Nx2NoteListHostWrite(device_addr, size); tmp_buffer.resize_destructive(size); device_memory.ReadBlockUnsafe(device_addr, tmp_buffer.data(), size); @@ -291,6 +301,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am const bool has_new_downloads = tmp_intervals.size() != 0; for (const auto& pair : tmp_intervals) { gpu_modified_ranges.Add(pair.first, pair.second); + if (Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) { + Tegra::Nx2MarkGpuWritten(pair.first, pair.second); + Nx2NoteListWrite(buffer_b, pair.first, pair.second); + } } const auto& copy = copies[0]; src_buffer.MarkUsage(copy.src_offset, copy.size); @@ -379,6 +393,10 @@ template <class P> void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) { const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr); + if (!device_addr) { + channel_state->uniform_buffers[stage][index] = NULL_BINDING; + return; + } const Binding binding{ .device_addr = *device_addr, .size = size, @@ -389,7 +407,11 @@ void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr template <class P> void BufferCache<P>::DisableGraphicsUniformBuffer(size_t stage, u32 index) { - channel_state->uniform_buffers[stage][index] = NULL_BINDING; + auto& current = channel_state->uniform_buffers[stage][index]; + if (current.device_addr == 0 && current.size == 0 && !current.buffer_id) [[likely]] { + return; + } + current = NULL_BINDING; } template <class P> @@ -994,6 +1016,26 @@ void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) { template <class P> void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind) { ++channel_state->uniform_cache_shots[0]; + // Bank 24 (root table 0, NVN2's push constants) is read from bank 17, which such games do not + // bind: stream the table's current words as that buffer. + if (index == 17 && maxwell3d->root_tables.loaded && channel_state->uniform_buffers[stage][index].size == 0) { + const auto& words = maxwell3d->root_tables.tables[0]; + const u32 size = (*channel_state->uniform_buffer_sizes)[stage][index]; + const u32 copied = (std::min)(size, static_cast<u32>(sizeof(words))); + if constexpr (IS_OPENGL) { + std::array<u8, sizeof(words)> data{}; + std::memcpy(data.data(), words.data(), copied); + runtime.BindFastUniformBuffer(stage, binding_index, copied); + runtime.PushFastUniformBuffer(stage, binding_index, std::span<const u8>(data.data(), copied)); + } else { + const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size); + std::memset(span.data(), 0, span.size()); + std::memcpy(span.data(), words.data(), copied); + } + channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; + channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; + return; + } const Binding& binding = channel_state->uniform_buffers[stage][index]; const DAddr device_addr = binding.device_addr; const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]); @@ -1494,6 +1536,9 @@ void BufferCache<P>::UpdateDrawIndirect() { template <class P> void BufferCache<P>::UpdateUniformBuffers(size_t stage) { + if (channel_state->enabled_uniform_buffer_masks[stage] == 0) [[likely]] { + return; + } ForEachEnabledBit(channel_state->enabled_uniform_buffer_masks[stage], [&](u32 index) { Binding& binding = channel_state->uniform_buffers[stage][index]; if (binding.buffer_id) { @@ -1511,6 +1556,9 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) { template <class P> void BufferCache<P>::UpdateStorageBuffers(size_t stage) { + if (channel_state->enabled_storage_buffers[stage] == 0) [[likely]] { + return; + } ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { // Resolve buffer Binding& binding = channel_state->storage_buffers[stage][index]; @@ -1523,6 +1571,9 @@ void BufferCache<P>::UpdateStorageBuffers(size_t stage) { template <class P> void BufferCache<P>::UpdateTextureBuffers(size_t stage) { + if (channel_state->enabled_texture_buffers[stage] == 0) [[likely]] { + return; + } ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { Binding& binding = channel_state->texture_buffers[stage][index]; binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false); @@ -1610,14 +1661,205 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3 } else { ArmReadTraps(device_addr, size); } + if (Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) { + Tegra::Nx2MarkGpuWritten(device_addr, size); + Nx2NoteListWrite(buffer_id, device_addr, size); + } +} + +template <class P> +void BufferCache<P>::Nx2NoteListWrite(BufferId buffer_id, DAddr device_addr, u64 size) { + if constexpr (!IS_OPENGL) { + if (size == 0 || !Tegra::g_nx2_has_command_list_pages.load(std::memory_order_relaxed)) { + return; + } + constexpr u64 page_bits = Tegra::Nx2WrittenPageBits; + const u64 last = (std::min<u64>)((device_addr + size - 1) >> page_bits, + Tegra::g_nx2_command_list_pages.size() * 64 - 1); + for (u64 page = device_addr >> page_bits; page <= last;) { + // Storage buffers can be large: skip 64 pages at a time where none is a list page. + const u64 word = + Tegra::g_nx2_command_list_pages[page / 64].load(std::memory_order_relaxed) >> + (page % 64); + if (word == 0) { + page = (page / 64 + 1) * 64; + continue; + } + if (word & 1) { + const DAddr page_addr = page << page_bits; + { + std::scoped_lock lock{nx2_list_mutex}; + Nx2ListPage& entry = nx2_list_pages[page_addr]; + ++entry.writes; + if (!nx2_collect_list_copies && !entry.draw_written) { + entry.draw_written = true; + LOG_INFO(HW_GPU, "Command list page {:#x} is written by draws too: its " + "lists drain the split", + page_addr); + } + } + if (nx2_collect_list_copies) { + nx2_list_copy_requests.emplace_back(buffer_id, page_addr); + } + } + ++page; + } + } +} + +template <class P> +bool BufferCache<P>::Nx2CopyListPages(u64 tick) { + if constexpr (IS_OPENGL) { + return false; + } else { + if (nx2_list_copy_requests.empty()) { + return false; + } + std::ranges::sort(nx2_list_copy_requests, {}, &std::pair<BufferId, DAddr>::second); + constexpr DAddr page_size = DAddr{1} << Tegra::Nx2WrittenPageBits; + bool recorded = false; + for (size_t i = 0; i < nx2_list_copy_requests.size(); ++i) { + const auto [buffer_id, page] = nx2_list_copy_requests[i]; + if (i + 1 < nx2_list_copy_requests.size() && + nx2_list_copy_requests[i + 1].second == page) { + continue; + } + Buffer& buffer = slot_buffers[buffer_id]; + const DAddr begin = (std::max<DAddr>)(page, buffer.CpuAddr()); + const DAddr end = + (std::min<DAddr>)(page + page_size, buffer.CpuAddr() + buffer.SizeBytes()); + if (begin >= end) { + continue; + } + Nx2Staging staging = runtime.DownloadStagingBuffer(page_size, true); + const BufferCopy copy{ + .src_offset = buffer.Offset(begin), + .dst_offset = staging.offset + (begin - page), + .size = end - begin, + }; + buffer.MarkUsage(copy.src_offset, copy.size); + runtime.CopyBuffer(staging.buffer, buffer, std::span{©, 1}, true); + recorded = true; + std::scoped_lock lock{nx2_list_mutex}; + Nx2ListPage& entry = nx2_list_pages[page]; + if (entry.copy) { + nx2_retired_list_copies.emplace_back(entry.copy_tick, std::move(*entry.copy)); + } + entry.copy = staging; + entry.copy_writes = entry.writes; + entry.copy_tick = tick; + entry.copy_begin = static_cast<u32>(begin - page); + entry.copy_end = static_cast<u32>(end - page); + } + nx2_list_copy_requests.clear(); + std::erase_if(nx2_retired_list_copies, [this](auto& retired) { + if (!runtime.IsFree(retired.first)) { + return false; + } + runtime.FreeDeferredStagingBuffer(retired.second); + return true; + }); + return recorded; + } +} + +template <class P> +auto BufferCache<P>::Nx2ReadListCopy(DAddr device_addr, std::span<u8> out) -> Nx2ListRead { + if constexpr (IS_OPENGL) { + return Nx2ListRead::Unknown; + } else { + constexpr DAddr page_size = DAddr{1} << Tegra::Nx2WrittenPageBits; + const DAddr page = device_addr & ~(page_size - 1); + const u64 offset = device_addr - page; + if (offset + out.size() > page_size) { + return Nx2ListRead::Unknown; + } + const auto current = [&](const Nx2ListPage& entry) { + return entry.copy && !entry.draw_written && entry.copy_writes == entry.writes && + offset >= entry.copy_begin && offset + out.size() <= entry.copy_end; + }; + u64 tick{}; + u64 writes{}; + { + std::scoped_lock lock{nx2_list_mutex}; + const auto it = nx2_list_pages.find(page); + if (it == nx2_list_pages.end()) { + return Nx2ListRead::Unknown; + } + const Nx2ListPage& entry = it->second; + // Pages the CPU writes the lists of (GPU-written once, long before) stay synced. + if (!entry.draw_written && entry.synced_writes == entry.writes) { + return Nx2ListRead::Synced; + } + if (!current(entry)) { + return Nx2ListRead::Unknown; + } + // The lists after the first of a batch find the copy done: read it at once. + if (runtime.IsFree(entry.copy_tick)) { + std::memcpy(out.data(), entry.copy->mapped_span.data() + offset, out.size()); + return Nx2ListRead::Copied; + } + tick = entry.copy_tick; + writes = entry.copy_writes; + } + // Submitted with the work that wrote the page (Nx2CopyListPages's caller flushes). + runtime.WaitSubmittedTick(tick); + std::scoped_lock lock{nx2_list_mutex}; + const auto it = nx2_list_pages.find(page); + if (it == nx2_list_pages.end() || !current(it->second) || it->second.copy_tick != tick || + it->second.copy_writes != writes) { + return Nx2ListRead::Unknown; + } + std::memcpy(out.data(), it->second.copy->mapped_span.data() + offset, out.size()); + return Nx2ListRead::Copied; + } +} + +template <class P> +void BufferCache<P>::Nx2NoteListSynced(DAddr page) { + if constexpr (!IS_OPENGL) { + std::scoped_lock lock{nx2_list_mutex}; + Nx2ListPage& entry = nx2_list_pages[page]; + entry.synced_writes = entry.writes; + } +} + +template <class P> +void BufferCache<P>::Nx2NoteListHostWrite(DAddr device_addr, u64 size) { + if constexpr (!IS_OPENGL) { + if (size == 0 || !Tegra::g_nx2_has_command_list_pages.load(std::memory_order_relaxed)) { + return; + } + constexpr u64 page_bits = Tegra::Nx2WrittenPageBits; + const u64 last = (std::min<u64>)((device_addr + size - 1) >> page_bits, + Tegra::g_nx2_command_list_pages.size() * 64 - 1); + for (u64 page = device_addr >> page_bits; page <= last; ++page) { + if (!((Tegra::g_nx2_command_list_pages[page / 64].load(std::memory_order_relaxed) >> + (page % 64)) & + 1)) { + continue; + } + std::scoped_lock lock{nx2_list_mutex}; + const auto it = nx2_list_pages.find(page << page_bits); + if (it == nx2_list_pages.end()) { + continue; + } + Nx2ListPage& entry = it->second; + const bool synced = entry.synced_writes == entry.writes; + ++entry.writes; + if (synced) { + entry.synced_writes = entry.writes; + } + } + } } template <class P> BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible) { - if (device_addr == 0) { + const u64 page = device_addr >> CACHING_PAGEBITS; + if (device_addr == 0 || page >= page_table.size()) { return NULL_BUFFER_ID; } - const u64 page = device_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page]; if (buffer_id) { Buffer& buffer = slot_buffers[buffer_id]; @@ -1927,6 +2169,11 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer, u8* const src_pointer = staging_pointer.data() + copy.src_offset; const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset; device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size); + // Large uploads are mostly geometry the host GPU now has its own copy of: TickFrame + // marks their guest memory cold (see TextureCache::RefreshContents). + if (copy.size >= 256_KiB) { + uploaded_buffer_ranges.emplace_back(device_addr, copy.size); + } // Apply the staging offset copy.src_offset += upload_staging.offset; } |
