diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 11:16:43 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 20:49:48 -0400 |
| commit | 7a251d3a92f870649dab6068007ca3a1c910bbb5 (patch) | |
| tree | 556633cb13fc4dc2f181f33989a3395919886b78 /src/video_core/gpu_thread.cpp | |
| parent | 00c059cd39f4fd9d1244345d6727a171acec4f86 (diff) | |
video_core: split the GPU thread in two (EDEN_NX2_SPLIT, off by default)
The GPU thread (front) takes the CPU's commands, parses command lists
and runs the puller; engine method calls and everything behind them
(registers, draws, dispatches, the rasterizer and its caches) run in
order on a second thread (back), fed through a single-producer
single-consumer ring (gpu_split.h). Work the front did on back-owned
state (end of command list flushes, channel binds, semaphore releases,
syncpoint increments, flush/invalidate requests, presentation) is
deferred into the same ring; the front waits for the back only where it
needs a result from that state: semaphore acquires and MME DMA reads.
EDEN_NX2_SPLIT=1 runs macros on the back. EDEN_NX2_SPLIT=2 runs the 3D
and compute macros on the front against copies of the registers they
read (Maxwell3D/KeplerCompute Front*), drops state writes that keep a
register's value there, and sends the rest. EDEN_NX2_SPLIT_VERIFY=1
compares the copies with the back's registers every 4096 macro calls
(0 differences in 40,193 checks in title 1); EDEN_NX2_SPLIT_CHECK=1
logs the stack of any other thread using the Vulkan scheduler while the
back runs. Switch 2 titles, Vulkan, non-accurate GPU only.
Title 1, 3D attract loop: ~44,000 draws a second on one thread, ~47,400
with EDEN_NX2_SPLIT=1, 50,000-66,500 with EDEN_NX2_SPLIT=2.
Diffstat (limited to 'src/video_core/gpu_thread.cpp')
| -rw-r--r-- | src/video_core/gpu_thread.cpp | 55 |
1 files changed, 48 insertions, 7 deletions
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 72da1904e1..55200b0647 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -17,12 +17,28 @@ #include "video_core/dma_pusher.h" #include "video_core/gpu.h" #include "video_core/gpu_thread.h" +#include "video_core/gpu_split.h" #include "video_core/nx2_profile.h" #include "video_core/host1x/host1x.h" #include "video_core/renderer_base.h" namespace VideoCommon::GPUThread { +namespace { + +// Tells a CPU thread waiting in PushCommand that its command ran. +void SignalCommand(SynchState& state, u64 fence, bool block) { + state.signaled_fence.store(fence); + if (block) { + // We have to lock the write_lock to ensure that the condition_variable wait not get a + // race between the check and the lock itself. + std::scoped_lock lk{state.write_lock}; + state.cv.notify_all(); + } +} + +} // Anonymous namespace + ThreadManager::ThreadManager(Core::System& system_) : system{system_} {} @@ -42,8 +58,10 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten // When a pending channel last moved or a command arrived (see the polling below). auto last_progress = std::chrono::steady_clock::now(); namespace Profile = VideoCommon::Nx2Profile; + auto& split = VideoCommon::GpuSplit::Get(); while (!stop_token.stop_requested()) { Profile::Report(); + split.MaybeStart(system); if (scheduler.HasPendingChannels()) { // Suspended channels wait on semaphores the CPU releases: poll them meanwhile. { @@ -71,6 +89,34 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten break; } last_progress = std::chrono::steady_clock::now(); + if (split.Active()) { + // Only command lists are parsed here; everything else runs on the back thread + // in order, and so does the signal that the command ran. + auto* const rasterizer_ptr = renderer.ReadRasterizer(); + if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) { + Profile::Scope scope{Profile::Zone::Push}; + scheduler.Push(system.GPU(), submit_list->channel, std::move(submit_list->entries)); + } else if (std::holds_alternative<GPUTickCommand>(next.data)) { + split.Defer([gpu = &system.GPU()] { gpu->TickWork(); }); + } else if (auto* run = std::get_if<RunCommand>(&next.data)) { + split.DeferFunction(std::move(run->func)); + } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) { + split.Defer([rasterizer_ptr, addr = flush->addr, size = flush->size] { + rasterizer_ptr->FlushRegion(addr, size); + }); + } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) { + split.Defer([rasterizer_ptr, addr = invalidate->addr, size = invalidate->size] { + rasterizer_ptr->OnCacheInvalidation(addr, size); + }); + } else { + ASSERT(false); + } + split.Defer([state_ptr = &state, fence = next.fence, block = next.block] { + SignalCommand(*state_ptr, fence, block); + }); + split.Publish(); + continue; + } if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) { Profile::Scope scope{Profile::Zone::Push}; scheduler.Push(system.GPU(), submit_list->channel, std::move(submit_list->entries)); @@ -89,13 +135,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten } else { ASSERT(false); } - state.signaled_fence.store(next.fence); - if (next.block) { - // We have to lock the write_lock to ensure that the condition_variable wait not get a - // race between the check and the lock itself. - std::scoped_lock lk{state.write_lock}; - state.cv.notify_all(); - } + SignalCommand(state, next.fence, next.block); } }); } @@ -161,6 +201,7 @@ void ThreadManager::NotifyShutdown() { thread.request_stop(); thread.join(); } + VideoCommon::GpuSplit::Get().Stop(); } } // namespace VideoCommon::GPUThread |
