1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-02-23 19:54:16 +01:00

[dxvk] Fix lack of forward progress guarantee in presenter

Turns out that relying on two threads to unblock another was a bad idea.
Fixes a potential deadlock w.r.t. swapchain recreation with Reflex.
This commit is contained in:
Philip Rebohle 2025-02-11 11:57:47 +01:00 committed by Philip Rebohle
parent ac17d5f9b1
commit efeb15edbd
2 changed files with 39 additions and 23 deletions

View File

@ -259,9 +259,16 @@ namespace dxvk {
return;
if (m_device->features().khrPresentWait.presentWait) {
std::lock_guard lock(m_frameMutex);
bool canSignal = false;
{ std::unique_lock lock(m_frameMutex);
m_lastSignaled = frameId;
m_frameCond.notify_one();
canSignal = m_lastCompleted >= frameId;
}
if (canSignal)
m_signal->signal(frameId);
} else {
m_fpsLimiter.delay();
m_signal->signal(frameId);
@ -1203,24 +1210,25 @@ namespace dxvk {
void Presenter::runFrameThread() {
env::setThreadName("dxvk-frame");
std::unique_lock lock(m_frameMutex);
while (true) {
PresenterFrame frame = { };
// Wait for all GPU work for this frame to complete in order to maintain
// ordering guarantees of the frame signal w.r.t. objects being released
{ std::unique_lock lock(m_frameMutex);
m_frameCond.wait(lock, [this] {
return !m_frameQueue.empty() && m_frameQueue.front().frameId <= m_lastSignaled;
return !m_frameQueue.empty();
});
// Use a frame ID of 0 as an exit condition
PresenterFrame frame = m_frameQueue.front();
frame = m_frameQueue.front();
if (!frame.frameId) {
m_frameQueue.pop();
return;
}
lock.unlock();
}
// If the present operation has succeeded, actually wait for it to complete.
// Don't bother with it on MAILBOX / IMMEDIATE modes since doing so would
@ -1240,20 +1248,27 @@ namespace dxvk {
frame.tracker = nullptr;
}
// Apply FPS limtier here to align it as closely with scanout as we can,
// Apply FPS limiter here to align it as closely with scanout as we can,
// and delay signaling the frame latency event to emulate behaviour of a
// low refresh rate display as closely as we can.
m_fpsLimiter.delay();
// Always signal even on error, since failures here
// are transparent to the front-end.
m_signal->signal(frame.frameId);
// Wake up any thread that may be waiting for the queue to become empty
lock.lock();
bool canSignal = false;
{ std::unique_lock lock(m_frameMutex);
m_frameQueue.pop();
m_frameDrain.notify_one();
m_lastCompleted = frame.frameId;
canSignal = m_lastSignaled >= frame.frameId;
}
// Always signal even on error, since failures here
// are transparent to the front-end.
if (canSignal)
m_signal->signal(frame.frameId);
}
}

View File

@ -315,6 +315,7 @@ namespace dxvk {
std::queue<PresenterFrame> m_frameQueue;
uint64_t m_lastSignaled = 0u;
uint64_t m_lastCompleted = 0u;
alignas(CACHE_LINE_SIZE)
FpsLimiter m_fpsLimiter;