aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/renderer_vulkan/vk_scheduler.cpp
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 11:16:43 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:48 -0400
commit7a251d3a92f870649dab6068007ca3a1c910bbb5 (patch)
tree556633cb13fc4dc2f181f33989a3395919886b78 /src/video_core/renderer_vulkan/vk_scheduler.cpp
parent00c059cd39f4fd9d1244345d6727a171acec4f86 (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/renderer_vulkan/vk_scheduler.cpp')
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp9
1 files changed, 9 insertions, 0 deletions
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index aa8511a5cb..e1e989c79f 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -55,6 +55,9 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
Scheduler::~Scheduler() = default;
u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
+ if (VideoCommon::GpuSplit::check_owner) [[unlikely]] {
+ VideoCommon::GpuSplit::CheckOwner("Scheduler::Flush");
+ }
// When flushing, we only send data to the worker thread; no waiting is necessary.
const u64 signal_value = SubmitExecution(signal_semaphore, wait_semaphore);
AllocateNewContext();
@@ -62,6 +65,9 @@ u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
}
void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
+ if (VideoCommon::GpuSplit::check_owner) [[unlikely]] {
+ VideoCommon::GpuSplit::CheckOwner("Scheduler::Finish");
+ }
// When finishing, we need to wait for the submission to have executed on the device.
const u64 presubmit_tick = CurrentTick();
SubmitExecution(signal_semaphore, wait_semaphore);
@@ -83,6 +89,9 @@ void Scheduler::WaitWorker() {
}
void Scheduler::DispatchWork() {
+ if (VideoCommon::GpuSplit::check_owner) [[unlikely]] {
+ VideoCommon::GpuSplit::CheckOwner("Scheduler::DispatchWork");
+ }
if (chunk && !chunk->Empty()) {
// Waking the worker is a system call (~2% of the GPU thread at one dispatch per 8
// draws): skip it while the worker is still busy, it pops the queue before waiting.