From cdcf384df2ccc07b737e5be0a17fac4b0282bffc Mon Sep 17 00:00:00 2001 From: jpolo1224 Date: Mon, 10 Aug 2026 00:04:14 -0400 Subject: [PATCH] Block on the GPU fence instead of spinning on it An unbounded wait polled vkGetFenceStatus in a tight loop with nothing but a pause hint between calls. command_buffer::flush() takes that path for the submit fence, so it is what a frame does while it waits on the GPU: a core pinned at 100% for the whole wait, hammering a driver entry point while the driver is trying to do the work being waited on. Cheap on a desktop with cores to spare. Not here, where it competes with the SPU and PPU threads for a handful of cores. Arkham City measured 24ms of a 53ms frame in this function with the GPU only 71-77% busy, which is what a stall looks like when the waiter is too busy spinning to prepare the next submission. Polls briefly first, since most waits are for a fence about to signal and blocking would cost a syscall and a wake-up for nothing, then hands the wait to vkWaitForFences so the driver can sleep the thread. The blocking call was already there, three lines up, used only when a finite timeout was supplied. Same shape as wait_for_event below, which already had this treatment. --- rpcs3/Emu/RSX/VK/vkutils/sync.cpp | 37 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/vkutils/sync.cpp b/rpcs3/Emu/RSX/VK/vkutils/sync.cpp index 88cb04153..9c66f3a5a 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/sync.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/sync.cpp @@ -575,20 +575,43 @@ namespace vk } else { - while (auto status = vkGetFenceStatus(*g_render_device, pFence->handle)) + // An unbounded wait used to poll vkGetFenceStatus in a tight loop with nothing + // but a pause hint between calls. That is a core pinned at 100% for the whole + // duration of a GPU wait, plus a driver entry point hammered while the driver is + // trying to do the very work being waited on. + // + // It costs little on a desktop with cores to spare. It is expensive here: this + // is reached from command_buffer::flush() for the submit fence, so it is the + // path a frame takes when it waits on the GPU, and it competes with the SPU and + // PPU threads for a handful of cores. Arkham City measured 24ms of a 53ms frame + // in this function with the GPU only 71-77% busy, which is what a stall looks + // like when the waiter is too busy spinning to prepare the next submission. + // + // Poll briefly first, since most waits here are for a fence that is about to + // signal and blocking would cost a syscall and a wake-up for no reason, then + // hand the wait to the driver, which can sleep the thread and free the core. + // Same shape as wait_for_event below, which already got this treatment. + constexpr u64 hot_polls = 512; + + for (u64 poll = 0; poll < hot_polls; poll++) { - switch (status) + const VkResult status = vkGetFenceStatus(*g_render_device, pFence->handle); + + if (status == VK_SUCCESS) + { + return VK_SUCCESS; + } + + if (status != VK_NOT_READY) { - case VK_NOT_READY: - utils::pause(); - continue; - default: die_with_error(status); return status; } + + utils::pause(); } - return VK_SUCCESS; + return vkWaitForFences(*g_render_device, 1, &pFence->handle, VK_FALSE, UINT64_MAX); } }