From ca3492570ce86cd6722a1dc675d59cda038a951b Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 9 Jun 2023 14:45:08 +0200 Subject: [PATCH] [dxvk] Add functionality to wait for a given present operation --- src/d3d11/d3d11_swapchain.cpp | 2 +- src/d3d9/d3d9_swapchain.cpp | 2 +- src/dxvk/dxvk_presenter.cpp | 96 ++++++++++++++++++++++++++++++++--- src/dxvk/dxvk_presenter.h | 54 ++++++++++++++++---- src/dxvk/dxvk_queue.cpp | 2 +- 5 files changed, 138 insertions(+), 18 deletions(-) diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 873c0f3d..89eee344 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -479,7 +479,7 @@ namespace dxvk { presenterDesc.numFormats = PickFormats(m_desc.Format, presenterDesc.formats); presenterDesc.fullScreenExclusive = PickFullscreenMode(); - m_presenter = new Presenter(m_device, presenterDesc); + m_presenter = new Presenter(m_device, nullptr, presenterDesc); m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate); } diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index da440775..8cc5e954 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -894,7 +894,7 @@ namespace dxvk { presenterDesc.numFormats = PickFormats(EnumerateFormat(m_presentParams.BackBufferFormat), presenterDesc.formats); presenterDesc.fullScreenExclusive = PickFullscreenMode(); - m_presenter = new Presenter(m_device, presenterDesc); + m_presenter = new Presenter(m_device, nullptr, presenterDesc); m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate); } diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 27247e18..ef4d757e 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -8,18 +8,32 @@ namespace dxvk { Presenter::Presenter( - const Rc& device, - const PresenterDesc& desc) - : m_device(device), + const Rc& device, + const Rc& signal, + const PresenterDesc& desc) + : m_device(device), m_signal(signal), m_vki(device->instance()->vki()), m_vkd(device->vkd()) { - + // If a frame signal was provided, launch thread that synchronizes + // with present operations and periodically signals the event + if (m_device->features().khrPresentWait.presentWait && m_signal != nullptr) + m_frameThread = dxvk::thread([this] { runFrameThread(); }); } Presenter::~Presenter() { destroySwapchain(); destroySurface(); + + if (m_frameThread.joinable()) { + { std::lock_guard lock(m_frameMutex); + + m_frameQueue.push(PresenterFrame()); + m_frameCond.notify_one(); + } + + m_frameThread.join(); + } } @@ -51,9 +65,15 @@ namespace dxvk { } - VkResult Presenter::presentImage(VkPresentModeKHR mode) { + VkResult Presenter::presentImage( + VkPresentModeKHR mode, + uint64_t frameId) { PresenterSync sync = m_semaphores.at(m_frameIndex); + VkPresentIdKHR presentId = { VK_STRUCTURE_TYPE_PRESENT_ID_KHR }; + presentId.swapchainCount = 1; + presentId.pPresentIds = &frameId; + VkSwapchainPresentModeInfoEXT modeInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT }; modeInfo.swapchainCount = 1; modeInfo.pPresentModes = &mode; @@ -65,8 +85,11 @@ namespace dxvk { info.pSwapchains = &m_swapchain; info.pImageIndices = &m_imageIndex; + if (m_device->features().khrPresentId.presentId && frameId) + presentId.pNext = const_cast(std::exchange(info.pNext, &presentId)); + if (m_device->features().extSwapchainMaintenance1.swapchainMaintenance1) - info.pNext = &modeInfo; + modeInfo.pNext = const_cast(std::exchange(info.pNext, &modeInfo)); VkResult status = m_vkd->vkQueuePresentKHR( m_device->queues().graphics.queueHandle, &info); @@ -93,6 +116,29 @@ namespace dxvk { } + void Presenter::signalFrame( + VkResult result, + uint64_t frameId) { + if (m_signal == nullptr || !frameId) + return; + + if (m_device->features().khrPresentWait.presentWait) { + std::lock_guard lock(m_frameMutex); + + PresenterFrame frame = { }; + frame.result = result; + frame.frameId = frameId; + + m_frameQueue.push(frame); + m_frameCond.notify_one(); + } else { + m_signal->signal(frameId); + } + + m_lastFrameId.store(frameId, std::memory_order_release); + } + + VkResult Presenter::recreateSurface( const std::function& fn) { if (m_swapchain) @@ -572,6 +618,9 @@ namespace dxvk { void Presenter::destroySwapchain() { + if (m_signal != nullptr) + m_signal->wait(m_lastFrameId.load(std::memory_order_acquire)); + for (const auto& img : m_images) m_vkd->vkDestroyImageView(m_vkd->device(), img.view, nullptr); @@ -596,4 +645,39 @@ namespace dxvk { m_surface = VK_NULL_HANDLE; } + + void Presenter::runFrameThread() { + env::setThreadName("dxvk-frame"); + + while (true) { + std::unique_lock lock(m_frameMutex); + + m_frameCond.wait(lock, [this] { + return !m_frameQueue.empty(); + }); + + PresenterFrame frame = m_frameQueue.front(); + m_frameQueue.pop(); + + lock.unlock(); + + // Use a frame ID of 0 as an exit condition + if (!frame.frameId) + return; + + // If the present operation has succeeded, actually wait for it to complete. + if (frame.result >= 0) { + VkResult vr = m_vkd->vkWaitForPresentKHR(m_vkd->device(), + m_swapchain, frame.frameId, std::numeric_limits::max()); + + if (vr < 0 && vr != VK_ERROR_OUT_OF_DATE_KHR && vr != VK_ERROR_SURFACE_LOST_KHR) + Logger::err(str::format("Presenter: vkWaitForPresentKHR failed: ", vr)); + } + + // Always signal even on error, since failures here + // are transparent to the front-end. + m_signal->signal(frame.frameId); + } + } + } diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 1f2a441c..c2822a48 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -10,6 +10,8 @@ #include "../util/util_math.h" #include "../util/util_string.h" +#include "../util/sync/sync_signal.h" + #include "../vulkan/vulkan_loader.h" #include "dxvk_format.h" @@ -68,6 +70,14 @@ namespace dxvk { VkSemaphore present; }; + /** + * \brief Queued frame + */ + struct PresenterFrame { + uint64_t frameId; + VkResult result; + }; + /** * \brief Vulkan presenter * @@ -80,8 +90,9 @@ namespace dxvk { public: Presenter( - const Rc& device, - const PresenterDesc& desc); + const Rc& device, + const Rc& signal, + const PresenterDesc& desc); ~Presenter(); @@ -123,10 +134,27 @@ namespace dxvk { * an error, the swap chain must be recreated, * but do not present before acquiring an image. * \param [in] mode Present mode + * \param [in] frameId Frame number. + * Must increase monotonically. * \returns Status of the operation */ VkResult presentImage( - VkPresentModeKHR mode); + VkPresentModeKHR mode, + uint64_t frameId); + + /** + * \brief Signals a given frame + * + * Waits for the present operation to complete and then signals + * the presenter signal with the given frame ID. Must not be + * called before GPU work prior to the present submission has + * completed in order to maintain consistency. + * \param [in] result Presentation result + * \param [in] frameId Frame number + */ + void signalFrame( + VkResult result, + uint64_t frameId); /** * \brief Changes and takes ownership of surface @@ -196,6 +224,7 @@ namespace dxvk { private: Rc m_device; + Rc m_signal; Rc m_vki; Rc m_vkd; @@ -210,12 +239,19 @@ namespace dxvk { std::vector m_dynamicModes; - uint32_t m_imageIndex = 0; - uint32_t m_frameIndex = 0; + uint32_t m_imageIndex = 0; + uint32_t m_frameIndex = 0; - VkResult m_acquireStatus = VK_NOT_READY; + VkResult m_acquireStatus = VK_NOT_READY; - FpsLimiter m_fpsLimiter; + FpsLimiter m_fpsLimiter; + + dxvk::mutex m_frameMutex; + dxvk::condition_variable m_frameCond; + dxvk::thread m_frameThread; + std::queue m_frameQueue; + + std::atomic m_lastFrameId = { 0ull }; VkResult recreateSwapChainInternal( const PresenterDesc& desc); @@ -251,12 +287,12 @@ namespace dxvk { uint32_t maxImageCount, uint32_t desired); - VkResult createSurface(); - void destroySwapchain(); void destroySurface(); + void runFrameThread(); + }; } diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index de230901..3c434af9 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -118,7 +118,7 @@ namespace dxvk { if (entry.submit.cmdList != nullptr) status = entry.submit.cmdList->submit(); else if (entry.present.presenter != nullptr) - status = entry.present.presenter->presentImage(entry.present.presentMode); + status = entry.present.presenter->presentImage(entry.present.presentMode, 0); if (m_callback) m_callback(false);