diff options
| author | kraibse <33869366+kraibse@users.noreply.github.com> | 2026-11-01 03:20:42 +0100 |
|---|---|---|
| committer | kraibse <33869366+kraibse@users.noreply.github.com> | 2026-11-02 00:11:25 +0100 |
| commit | cce6d44af23f08a7287a66d745fec34e38c9eb25 (patch) | |
| tree | 6d26989c997978d71a2ea771be4dba28155b0845 /src | |
| parent | 803ece5f90bbdc5ef9751f559cbe8b2ecbdb2aac (diff) | |
nx2: execute command lists as submitted
With the present waiting for the previous frame's fence, the game
refills command memory it already submitted while the GPU thread is
still running that frame, so some frames rendered into the wrong image.
Copy each entry's words at submit and execute the copy. Resumed entries
and ranges the GPU has newer words for still read memory.
EDEN_NX2_PB_SNAPSHOT=0 executes from memory.
Ported from Wael3rd/Eglantine-MKW (bd3119a86f).
Assisted-by: Claude Sonnet 5.5 (porting the fix)
(cherry picked from commit 408bb84d06ae0729697ef9bed2c2ba76ce6dff42)
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp | 25 | ||||
| -rw-r--r-- | src/video_core/dma_pusher.cpp | 16 | ||||
| -rw-r--r-- | src/video_core/dma_pusher.h | 4 |
3 files changed, 44 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 0cd72faed3..848c15d6f8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -24,6 +24,7 @@ #include "video_core/engines/puller.h" #include "video_core/gpu.h" #include "video_core/host1x/host1x.h" +#include "video_core/memory_manager.h" namespace Service::Nvidia::Devices { namespace { @@ -435,6 +436,30 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL entries.submit_hashes.push_back(Tegra::HashPushbufferWords(words)); } } + // The present returns before the GPU thread reaches this frame's work (nx2_gsv.h, + // frames ahead), and Mario Kart World then refills command memory it already + // submitted: the end of a frame ran with the next frame's present image as its render + // target, leaving an image three frames old to be shown. Keep the words as submitted. + // EDEN_NX2_PB_SNAPSHOT=0: execute from memory. + static const bool snapshot = [] { + const char* value = std::getenv("EDEN_NX2_PB_SNAPSHOT"); + return value == nullptr || value[0] != '0'; + }(); + if (snapshot && channel_state->memory_manager) { + size_t words = 0; + for (const auto& entry : entries.command_lists) { + words += entry.size; + } + entries.submitted_words.resize(words); + entries.submitted_offsets.reserve(entries.command_lists.size()); + size_t offset = 0; + for (const auto& entry : entries.command_lists) { + entries.submitted_offsets.push_back(static_cast<u32>(offset)); + channel_state->memory_manager->ReadBlockUnsafe( + entry.addr, entries.submitted_words.data() + offset, entry.size * sizeof(u32)); + offset += entry.size; + } + } } auto& flags = params.flags; diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 8c142b36db..cbbc7f26eb 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -155,7 +155,21 @@ bool DmaPusher::Step() { // (the split only starts with unsafe reads; the settings can change mid-game). const bool use_safe = !VideoCommon::GpuSplit::Get().Active() && (Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe()); - if (use_safe) { + // The words as submitted, unless this is a resumed entry or the GPU has newer ones the + // safe read below would flush (with the split, the front can't tell: it reads memory + // unflushed anyway). + const bool submitted = command_resume_offset == 0 && + dma_pushbuffer_subindex < command_list.submitted_offsets.size() && + !(use_safe && memory_manager.IsMemoryDirty(dma_state.dma_get, + header.size * sizeof(u32))); + if (submitted) { + const std::span<const CommandHeader> commands{ + command_list.submitted_words.data() + + command_list.submitted_offsets[dma_pushbuffer_subindex], + header.size}; + RecordParsedEntry(header, commands); + command_resume_offset += ProcessCommands(commands, 0); + } else if (use_safe) { Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers); const std::span<const CommandHeader> commands{headers.data(), headers.size()}; RecordParsedEntry(header, commands); diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index e8f2707096..d52aef821a 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h @@ -117,6 +117,10 @@ struct CommandList final { boost::container::small_vector<CommandHeader, 512> prefetch_command_list; /// EDEN_NX2_PB_CHECK: hash of each entry's words when it was submitted. std::vector<u64> submit_hashes; + /// The entries' words when they were submitted (NX2), each entry's at submitted_offsets[i]: + /// 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; }; /// FNV-1a over pushbuffer words (EDEN_NX2_PB_CHECK). |
