From 2d1fb52b2fdf52f968d49776cdf8f7980c560555 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 25 Nov 2019 17:37:16 +0100 Subject: [PATCH] [util] Reimplement Signal The new implementation is more akin to D3D12 fences or timeline semaphores, and should make implementing similar concepts easier. --- src/d3d11/d3d11_swapchain.cpp | 14 +++--- src/d3d11/d3d11_swapchain.h | 8 ++-- src/dxvk/dxvk_cmdlist.h | 6 ++- src/dxvk/dxvk_context.cpp | 4 +- src/dxvk/dxvk_context.h | 6 ++- src/dxvk/dxvk_signal.cpp | 8 ++-- src/dxvk/dxvk_signal.h | 6 ++- src/util/sync/sync_signal.h | 86 +++++++++++++++++++++++------------ 8 files changed, 85 insertions(+), 53 deletions(-) diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index a52feaf68..ec9951c94 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -26,7 +26,7 @@ namespace dxvk { m_device (pDevice->GetDXVKDevice()), m_context (m_device->createContext()), m_frameLatencyCap(pDevice->GetOptions()->maxFrameLatency) { - CreateFrameLatencySignals(); + CreateFrameLatencySignal(); if (!pDevice->GetOptions()->deferSurfaceCreation) CreatePresenter(); @@ -209,9 +209,8 @@ namespace dxvk { immediateContext->Flush(); // Wait for the sync event so that we respect the maximum frame latency - uint32_t frameId = m_frameId++ % GetActualFrameLatency(); - auto syncEvent = m_frameLatencySignals[frameId]; - syncEvent->wait(); + uint64_t frameId = ++m_frameId; + m_frameLatencySignal->wait(frameId - GetActualFrameLatency()); if (m_hud != nullptr) m_hud->update(); @@ -312,7 +311,7 @@ namespace dxvk { m_hud->render(m_context, info.imageExtent); if (i + 1 >= SyncInterval) - m_context->queueSignal(syncEvent); + m_context->signal(m_frameLatencySignal, frameId); SubmitPresent(immediateContext, sync); } @@ -370,9 +369,8 @@ namespace dxvk { } - void D3D11SwapChain::CreateFrameLatencySignals() { - for (uint32_t i = 0; i < m_frameLatencySignals.size(); i++) - m_frameLatencySignals[i] = new sync::Signal(true); + void D3D11SwapChain::CreateFrameLatencySignal() { + m_frameLatencySignal = new sync::Fence(m_frameId); } diff --git a/src/d3d11/d3d11_swapchain.h b/src/d3d11/d3d11_swapchain.h index 34d4d4f82..78a97baf6 100644 --- a/src/d3d11/d3d11_swapchain.h +++ b/src/d3d11/d3d11_swapchain.h @@ -117,9 +117,9 @@ namespace dxvk { std::vector> m_imageViews; - uint32_t m_frameId = 0; - std::array, DXGI_MAX_SWAP_CHAIN_BUFFERS> m_frameLatencySignals; - uint32_t m_frameLatencyCap = 0; + uint64_t m_frameId = DXGI_MAX_SWAP_CHAIN_BUFFERS; + uint32_t m_frameLatencyCap = 0; + Rc m_frameLatencySignal; bool m_dirty = true; bool m_vsync = true; @@ -135,7 +135,7 @@ namespace dxvk { void RecreateSwapChain( BOOL Vsync); - void CreateFrameLatencySignals(); + void CreateFrameLatencySignal(); void CreatePresenter(); diff --git a/src/dxvk/dxvk_cmdlist.h b/src/dxvk/dxvk_cmdlist.h index fb0900b47..a1665a87c 100644 --- a/src/dxvk/dxvk_cmdlist.h +++ b/src/dxvk/dxvk_cmdlist.h @@ -186,9 +186,11 @@ namespace dxvk { * * The signal will be notified once the command * buffer has finished executing on the GPU. + * \param [in] signal The signal + * \param [in] value Signal value */ - void queueSignal(const Rc& signal) { - m_signalTracker.add(signal); + void queueSignal(const Rc& signal, uint64_t value) { + m_signalTracker.add(signal, value); } /** diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index afe30c550..3e12afc61 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -2375,8 +2375,8 @@ namespace dxvk { } - void DxvkContext::queueSignal(const Rc& signal) { - m_cmd->queueSignal(signal); + void DxvkContext::signal(const Rc& signal, uint64_t value) { + m_cmd->queueSignal(signal, value); } diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h index 70c730ca9..fd8b3a3e7 100644 --- a/src/dxvk/dxvk_context.h +++ b/src/dxvk/dxvk_context.h @@ -992,9 +992,11 @@ namespace dxvk { * previously submitted commands have * finished execution on the GPU. * \param [in] signal The signal + * \param [in] value Signal value */ - void queueSignal( - const Rc& signal); + void signal( + const Rc& signal, + uint64_t value); /** * \brief Trims staging buffers diff --git a/src/dxvk/dxvk_signal.cpp b/src/dxvk/dxvk_signal.cpp index fc9420fae..3bb87452d 100644 --- a/src/dxvk/dxvk_signal.cpp +++ b/src/dxvk/dxvk_signal.cpp @@ -12,14 +12,14 @@ namespace dxvk { } - void DxvkSignalTracker::add(const Rc& signal) { - m_signals.push_back(signal); + void DxvkSignalTracker::add(const Rc& signal, uint64_t value) { + m_signals.push_back({ signal, value }); } void DxvkSignalTracker::notify() { - for (const auto& sig : m_signals) - sig->notify(); + for (const auto& pair : m_signals) + pair.first->signal(pair.second); } diff --git a/src/dxvk/dxvk_signal.h b/src/dxvk/dxvk_signal.h index 1be38e194..fa99f7810 100644 --- a/src/dxvk/dxvk_signal.h +++ b/src/dxvk/dxvk_signal.h @@ -16,9 +16,11 @@ namespace dxvk { /** * \brief Adds a signal to track + * * \param [in] signal The signal + * \param [in] value Target value */ - void add(const Rc& signal); + void add(const Rc& signal, uint64_t value); /** * \brief Notifies tracked signals @@ -32,7 +34,7 @@ namespace dxvk { private: - std::vector> m_signals; + std::vector, uint64_t>> m_signals; }; diff --git a/src/util/sync/sync_signal.h b/src/util/sync/sync_signal.h index 4303d01cf..2b8eeb6e2 100644 --- a/src/util/sync/sync_signal.h +++ b/src/util/sync/sync_signal.h @@ -9,55 +9,83 @@ namespace dxvk::sync { /** * \brief Signal * - * Acts as a simple CPU fence which can be signaled by one - * thread and waited upon by one more thread. Waiting on - * more than one thread is not supported. + * Interface for a CPU-side fence. Can be signaled + * to a given value, and any thread waiting for a + * lower value will be woken up. */ class Signal : public RcObject { public: - Signal() - : m_signaled(false) { } - Signal(bool signaled) - : m_signaled(signaled) { } - ~Signal() { } - - Signal (const Signal&) = delete; - Signal& operator = (const Signal&) = delete; + virtual ~Signal() { } + + /** + * \brief Last signaled value + * \returns Last signaled value + */ + virtual uint64_t value() const = 0; /** * \brief Notifies signal - * Wakes any waiting thread. + * + * Wakes up all threads currently waiting for + * a value lower than \c value. Note that + * \c value must monotonically increase. + * \param [in] value Value to signal to */ - void notify() { - std::lock_guard lock(m_mutex); - m_signaled.store(true); - m_cond.notify_one(); - } + virtual void signal(uint64_t value) = 0; /** * \brief Waits for signal * * Blocks the calling thread until another - * thread wakes it up, then resets it to - * the non-signaled state. + * thread signals it with a value equal to + * or greater than \c value. + * \param [in] value The value to wait for */ - void wait() { - if (!m_signaled.exchange(false)) { - std::unique_lock lock(m_mutex); - m_cond.wait(lock, [this] { - return m_signaled.exchange(false); - }); - } + virtual void wait(uint64_t value) = 0; + + }; + + + /** + * \brief Fence + * + * Simple CPU-side fence. + */ + class Fence : public Signal { + + public: + + Fence() + : m_value(0ull) { } + + explicit Fence(uint64_t value) + : m_value(value) { } + + uint64_t value() const { + return m_value.load(std::memory_order_acquire); + } + + void signal(uint64_t value) { + std::unique_lock lock(m_mutex); + m_value.store(value, std::memory_order_release); + m_cond.notify_all(); } + void wait(uint64_t value) { + std::unique_lock lock(m_mutex); + m_cond.wait(lock, [this, value] { + return value <= m_value.load(std::memory_order_acquire); + }); + } + private: - - std::atomic m_signaled; + + std::atomic m_value; std::mutex m_mutex; std::condition_variable m_cond; - + }; }