[dxvk] Add functionality to wait for a given present operation

This commit is contained in:
Philip Rebohle 2023-06-09 14:45:08 +02:00
parent 215c4f8f6f
commit ca3492570c
5 changed files with 138 additions and 18 deletions

View File

@ -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);
}

View File

@ -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);
}

View File

@ -8,18 +8,32 @@
namespace dxvk {
Presenter::Presenter(
const Rc<DxvkDevice>& device,
const PresenterDesc& desc)
: m_device(device),
const Rc<DxvkDevice>& device,
const Rc<sync::Signal>& 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<dxvk::mutex> 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<void*>(std::exchange(info.pNext, &presentId));
if (m_device->features().extSwapchainMaintenance1.swapchainMaintenance1)
info.pNext = &modeInfo;
modeInfo.pNext = const_cast<void*>(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<dxvk::mutex> 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<VkResult (VkSurfaceKHR*)>& 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<dxvk::mutex> 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<uint64_t>::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);
}
}
}

View File

@ -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<DxvkDevice>& device,
const PresenterDesc& desc);
const Rc<DxvkDevice>& device,
const Rc<sync::Signal>& 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<DxvkDevice> m_device;
Rc<sync::Signal> m_signal;
Rc<vk::InstanceFn> m_vki;
Rc<vk::DeviceFn> m_vkd;
@ -210,12 +239,19 @@ namespace dxvk {
std::vector<VkPresentModeKHR> 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<PresenterFrame> m_frameQueue;
std::atomic<uint64_t> 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();
};
}

View File

@ -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);