diff options
| author | Sssnowyyy <78863999+Sssnowyyy@users.noreply.github.com> | 2026-11-02 11:34:40 -0500 |
|---|---|---|
| committer | Sssnowyyy <78863999+Sssnowyyy@users.noreply.github.com> | 2026-11-02 14:53:47 -0500 |
| commit | 256b1dc346e19bc60c2884d4af875fc53ef0a99a (patch) | |
| tree | 1af39de66d5b0aa451b533d339706e3094939cef /src | |
| parent | 690f80e15f411d8fa46a2eecdfc50ddadc8ed5e0 (diff) | |
video_core: don't execute command lists copied before their channel waited
cce6d44af2 executes each command list entry from a copy of its words taken
at submit. Games submit command lists before writing them: the lists wait
behind a semaphore acquire, and the game writes their commands just before
it releases the acquire (Mario Kart World after present). The copies of
those lists were stale. Pokopia stopped after its first frame on macOS: its
GPU never reached the fence the game spun on (EDEN_NX2_PB_SNAPSHOT=0 made it
run again).
Count each channel's suspensions on an acquire and the resumes after them,
and record the count with each copy. A list copied while its channel was
suspended, or whose channel suspended since, reads guest memory instead.
Lists submitted and run without a wait in between keep the copy.
EDEN_NX2_PB_SNAPSHOT=0 still disables it altogether.
Assisted-by: Claude Sonnet 5.5 (first version); Claude Opus 5.5 (bisect, suspension count, testing and commit message)
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp | 5 | ||||
| -rw-r--r-- | src/video_core/dma_pusher.cpp | 3 | ||||
| -rw-r--r-- | src/video_core/dma_pusher.h | 9 | ||||
| -rw-r--r-- | src/video_core/engines/puller.cpp | 2 |
4 files changed, 18 insertions, 1 deletions
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 848c15d6f8..79586446d5 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -445,7 +445,10 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL const char* value = std::getenv("EDEN_NX2_PB_SNAPSHOT"); return value == nullptr || value[0] != '0'; }(); - if (snapshot && channel_state->memory_manager) { + if (snapshot && channel_state->memory_manager && channel_state->payload) { + // Before the copy: a suspension during it must count as one after it. + entries.snapshot_suspensions = + channel_state->payload->dma_pusher.suspensions.load(std::memory_order_acquire); size_t words = 0; for (const auto& entry : entries.command_lists) { words += entry.size; diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 6b93bb7f60..6a79e0570e 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -162,6 +162,9 @@ bool DmaPusher::Step() { // read: their words at submit time are an older frame's. const bool submitted = command_resume_offset == 0 && dma_pushbuffer_subindex < command_list.submitted_offsets.size() && + (command_list.snapshot_suspensions & 1) == 0 && + command_list.snapshot_suspensions == + suspensions.load(std::memory_order_acquire) && !IsGpuWrittenCommands(dma_state.dma_get, size_t{header.size} * sizeof(u32)) && !(use_safe && memory_manager.IsMemoryDirty(dma_state.dma_get, diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index 289786bb9e..feab652bb5 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h @@ -7,6 +7,7 @@ #pragma once #include <array> +#include <atomic> #include <condition_variable> #include <optional> #include <span> @@ -121,6 +122,10 @@ struct CommandList final { /// executed instead of the memory, which the game may already be refilling for a later frame. std::vector<CommandHeader> submitted_words; std::vector<u32> submitted_offsets; + /// DmaPusher::suspensions when the words were copied. A list copied during a suspension, or + /// that waited behind one, reads memory instead: games write the commands it runs before they + /// release the acquire. + u64 snapshot_suspensions = 0; }; /// FNV-1a over pushbuffer words (EDEN_NX2_PB_CHECK). @@ -139,6 +144,10 @@ inline u64 HashPushbufferWords(std::span<const u32> words) { /// details on this implementation. class DmaPusher final { public: + /// Counts suspensions on a semaphore acquire and the resumes after them, so it is odd while + /// the channel is suspended (see CommandList::snapshot_suspensions). + std::atomic<u64> suspensions{0}; + explicit DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_); ~DmaPusher(); diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 0bb1809aa3..37ac3ec851 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -116,6 +116,7 @@ bool Puller::ResumeAcquire(DmaPusher& dma_pusher) { return false; } pending_acquire.reset(); + dma_pusher.suspensions.fetch_add(1, std::memory_order_release); Nx2SemaphoreWaiters::Get().Remove(this); return true; } @@ -257,6 +258,7 @@ void Puller::ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher) { dma_pusher.rasterizer->ReleaseFences(); if (!waiters.Latched(this) && !acquire.Matches(ReadSemaphoreWord(dma_pusher, acquire.address))) { pending_acquire = acquire; + dma_pusher.suspensions.fetch_add(1, std::memory_order_release); VideoCommon::Nx2Profile::Add(VideoCommon::Nx2Profile::Counter::AcquireWaits, 1); } else { waiters.Remove(this); |
