aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorkraibse <33869366+kraibse@users.noreply.github.com>2026-11-01 03:20:42 +0100
committerkraibse <33869366+kraibse@users.noreply.github.com>2026-11-02 00:11:25 +0100
commitcce6d44af23f08a7287a66d745fec34e38c9eb25 (patch)
tree6d26989c997978d71a2ea771be4dba28155b0845 /src
parent803ece5f90bbdc5ef9751f559cbe8b2ecbdb2aac (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.cpp25
-rw-r--r--src/video_core/dma_pusher.cpp16
-rw-r--r--src/video_core/dma_pusher.h4
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).