aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/buffer_cache
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-11-02 22:58:00 +0100
committercirakg <cirakg@cirakg.xyz>2026-11-02 22:58:00 +0100
commitcf0502ad274022480cb264e0e75390863cd49608 (patch)
treefd672b27a4cbddffb3acbb09ee59d3dd96ba489c /src/video_core/buffer_cache
parentff234849d6a41d2c2d29cf09408ac66e202d68e7 (diff)
parent0da830e94169b9040007b69c8147ab00b6574e36 (diff)
Merge branch 'nx2' into ldn/room-handshake
Diffstat (limited to 'src/video_core/buffer_cache')
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h191
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h51
2 files changed, 242 insertions, 0 deletions
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 53bbae7610..cd176dec27 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -174,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);
@@ -194,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);
@@ -301,6 +303,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
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];
@@ -1660,6 +1663,194 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3
}
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{&copy, 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;
+ }
+ }
}
}
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index 47f342932d..1f0c6cf10f 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -14,7 +14,9 @@
#include <memory>
#include <mutex>
#include <numeric>
+#include <optional>
#include <span>
+#include <unordered_map>
#include <vector>
#include "common/container/unordered_map.h"
@@ -321,6 +323,28 @@ public:
bool DMAClear(GPUVAddr src_address, u64 amount, u32 value);
+ /// Switch 2 GPU-written command lists (nx2.h, g_nx2_command_list_pages). While collecting (a
+ /// compute dispatch or a DMA copy being recorded), writes to command list pages are noted;
+ /// Nx2CopyListPages then records copies of those pages into host memory after the work.
+ void Nx2CollectListCopies(bool collect) {
+ nx2_collect_list_copies = collect;
+ if (!collect) {
+ nx2_list_copy_requests.clear();
+ }
+ }
+ /// True when it recorded copies, which the GPU completes at `tick` (the caller submits).
+ bool Nx2CopyListPages(u64 tick);
+ enum class Nx2ListRead { Unknown, Copied, Synced };
+ /// Front thread: Synced when guest memory holds the page `device_addr` lies in (flushed and
+ /// written by no GPU work since), else Copied with `out` filled from the copy of its last
+ /// write once the GPU is done with it. Unknown otherwise: the caller drains and flushes.
+ Nx2ListRead Nx2ReadListCopy(DAddr device_addr, std::span<u8> out);
+ /// Front thread, the split drained: the command list page was flushed to guest memory.
+ void Nx2NoteListSynced(DAddr page);
+ /// CPU and inline writes: they reach guest memory, so a synced page stays synced, but its copy
+ /// no longer holds the page.
+ void Nx2NoteListHostWrite(DAddr device_addr, u64 size);
+
/// Return true when a CPU region is modified from the GPU
[[nodiscard]] bool IsRegionGpuModified(DAddr addr, size_t size);
@@ -440,6 +464,10 @@ private:
void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size, bool needs_sync = false);
+ /// Counts a GPU write to the command list pages in the range (invalidating their copies) and,
+ /// while collecting, asks for new copies.
+ void Nx2NoteListWrite(BufferId buffer_id, DAddr device_addr, u64 size);
+
[[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible);
void SynchronizeBufferWrites(Buffer& buffer);
@@ -604,6 +632,29 @@ private:
// Covers the device address space (MaxwellDeviceTraits::device_virtual_bits).
std::array<BufferId, ((1ULL << 36) >> CACHING_PAGEBITS)> page_table;
Common::ScratchBuffer<u8> tmp_buffer;
+
+ /// A command list page (Nx2CopyListPages): the GPU writes noted to it and the last copy.
+ using Nx2Staging = decltype(std::declval<Runtime&>().DownloadStagingBuffer(size_t{}, true));
+ struct Nx2ListPage {
+ u64 writes{}; ///< GPU writes noted to the page
+ u64 copy_writes{}; ///< `writes` when the copy was made: the copy is current while equal
+ u64 copy_tick{}; ///< GPU tick that completes the copy
+ /// `writes` when the front last flushed the page: guest memory holds it while equal
+ u64 synced_writes{~u64{0}};
+ u32 copy_begin{}; ///< Bytes of the page the copy holds (a buffer can end inside it)
+ u32 copy_end{};
+ /// Written outside a compute dispatch or a DMA copy (a draw's storage writes) at least
+ /// once: the front can't tell from what it sent whether such a write is still queued, so
+ /// the page's lists always drain the split instead of reading a copy.
+ bool draw_written{};
+ std::optional<Nx2Staging> copy;
+ };
+ std::mutex nx2_list_mutex; ///< nx2_list_pages: the split's front reads the copies
+ std::unordered_map<DAddr, Nx2ListPage> nx2_list_pages;
+ /// Copies replaced while the GPU may still write them: freed once it is past their tick.
+ std::vector<std::pair<u64, Nx2Staging>> nx2_retired_list_copies;
+ boost::container::small_vector<std::pair<BufferId, DAddr>, 8> nx2_list_copy_requests;
+ bool nx2_collect_list_copies{};
};
} // namespace VideoCommon