aboutsummaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 15:14:37 -0500
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 15:14:37 -0500
commit91b7400a27a89f1ff9bef7f148913aa76f0a39ad (patch)
tree1d2ff177cb93ed6af25ad011bf3b06c0bb9a89c2 /src/video_core
parent76812d0bd86c3dfbef473ad6236a68045ad3d4b5 (diff)
parentbef001c1515a13a9b8e361a5a7d372fc2e01094c (diff)
Merge remote-tracking branch 'origin/fix/nx2-gpu-written-command-lists' into test/linux-all-prs-plus
# Conflicts: # src/core/hle/service/nvdrv/nvdrv.cpp
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h7
-rw-r--r--src/video_core/dma_pusher.cpp27
-rw-r--r--src/video_core/dma_pusher.h2
-rw-r--r--src/video_core/nx2.h38
4 files changed, 74 insertions, 0 deletions
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 70d79c3535..a030b563d4 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"
@@ -298,6 +299,9 @@ 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);
+ }
}
const auto& copy = copies[0];
src_buffer.MarkUsage(copy.src_offset, copy.size);
@@ -1617,6 +1621,9 @@ 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);
+ }
}
template <class P>
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 88d571ae5f..8c142b36db 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -162,6 +162,7 @@ bool DmaPusher::Step() {
command_resume_offset +=
ProcessCommands(commands.subspan(command_resume_offset), command_resume_offset);
} else {
+ FlushGpuWrittenCommands(dma_state.dma_get, size_t{header.size} * sizeof(u32));
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::UnsafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
const std::span<const CommandHeader> commands{headers.data(), headers.size()};
RecordParsedEntry(header, commands);
@@ -445,6 +446,32 @@ void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) {
}
}
+void DmaPusher::FlushGpuWrittenCommands(GPUVAddr address, size_t size) {
+ // Title 1 runs small command lists its culling dispatches fill in each frame. Accurate GPU
+ // mode reads every command list through the caches (safe reads); here only the ones on pages
+ // the GPU has written are, or their draws run with an older frame's words (trees vanish).
+ if (!Tegra::g_nx2_ampere.load(std::memory_order_relaxed) || size == 0) {
+ return;
+ }
+ bool written = false;
+ for (GPUVAddr page = address & ~GPUVAddr{0xfff}; page < address + size; page += 0x1000) {
+ if (const auto device = memory_manager.GpuToCpuAddress(page);
+ device && Tegra::Nx2IsGpuWritten(*device)) {
+ written = true;
+ break;
+ }
+ }
+ if (!written) {
+ return;
+ }
+ // The caches belong to the split's back: it first runs what the front queued (the dispatch
+ // that writes these words among it).
+ VideoCommon::GpuSplit::Get().Drain();
+ if (memory_manager.IsMemoryDirty(address, size, VideoCommon::CacheType::BufferCache)) {
+ memory_manager.FlushRegion(address, size, VideoCommon::CacheType::BufferCache);
+ }
+}
+
void DmaPusher::ReportUnboundSubchannel() {
// Methods sent to a subchannel with no engine (no SetObject yet) are dropped.
static std::atomic<u32> reported{};
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 82d29edb4b..e8f2707096 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -182,6 +182,8 @@ public:
void DumpCurrentEntry(const char* reason);
void CheckSubmitHash(const CommandListHeader& header, u64 submit_hash);
void RecordParsedEntry(const CommandListHeader& header, std::span<const CommandHeader> commands);
+ /// Brings command list words the GPU wrote (still only in host buffers) to guest memory.
+ void FlushGpuWrittenCommands(GPUVAddr address, size_t size);
private:
diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h
index 318607384b..1fb49e15a6 100644
--- a/src/video_core/nx2.h
+++ b/src/video_core/nx2.h
@@ -20,6 +20,44 @@ namespace Tegra {
// Ampere classes and the Turing+ macro ISA, which Eden's Maxwell engines don't implement.
inline std::atomic<bool> g_nx2_ampere{false};
+// Device pages the GPU has written through the buffer cache (shader stores, copies), ever. Title 1
+// runs command lists the GPU writes each frame (20-byte entries its culling fills in); read
+// without a flush they are stale and the draws behind them (trees) go missing. The bits never
+// clear: the dispatch that rewrites a page may still be queued on the split's back when its front
+// reads the command list. One bit per 4 KiB of the 36-bit device address space (2 MiB).
+inline constexpr u64 Nx2WrittenPageBits = 12;
+inline std::array<std::atomic<u64>, (u64{1} << (36 - Nx2WrittenPageBits)) / 64> g_nx2_gpu_written_pages{};
+
+inline void Nx2MarkGpuWritten(u64 device_address, u64 size) {
+ if (size == 0) {
+ return;
+ }
+ const u64 last = std::min<u64>((device_address + size - 1) >> Nx2WrittenPageBits,
+ g_nx2_gpu_written_pages.size() * 64 - 1);
+ for (u64 page = device_address >> Nx2WrittenPageBits; page <= last; ++page) {
+ auto& word = g_nx2_gpu_written_pages[page / 64];
+ const u64 bit = u64{1} << (page % 64);
+ if ((word.load(std::memory_order_relaxed) & bit) == 0) {
+ word.fetch_or(bit, std::memory_order_relaxed);
+ }
+ }
+}
+
+/// A new emulation session starts: no page has been written by its GPU yet.
+inline void Nx2ClearGpuWrittenPages() {
+ for (auto& word : g_nx2_gpu_written_pages) {
+ word.store(0, std::memory_order_relaxed);
+ }
+}
+
+[[nodiscard]] inline bool Nx2IsGpuWritten(u64 device_address) {
+ const u64 page = device_address >> Nx2WrittenPageBits;
+ if (page >= g_nx2_gpu_written_pages.size() * 64) {
+ return false;
+ }
+ return (g_nx2_gpu_written_pages[page / 64].load(std::memory_order_relaxed) >> (page % 64)) & 1;
+}
+
// NVN2 queues release their progress values into the second page of a two-page GPU-accessible
// region and GPU acquires read the same offsets in the first page (the CPU polls the second).
// Something on the console copies one into the other; until that is known, the puller mirrors