dxvk/src/util/sync/sync_signal.h

169 lines
3.5 KiB
C++

#pragma once
#include <atomic>
#include <functional>
#include <list>
#include "../rc/util_rc.h"
#include "../thread.h"
namespace dxvk::sync {
/**
* \brief Signal
*
* 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:
virtual ~Signal() { }
/**
* \brief Last signaled value
* \returns Last signaled value
*/
virtual uint64_t value() const = 0;
/**
* \brief Notifies signal
*
* 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
*/
virtual void signal(uint64_t value) = 0;
/**
* \brief Waits for signal
*
* Blocks the calling thread until another
* thread signals it with a value equal to
* or greater than \c value.
* \param [in] value The value to wait for
*/
virtual void wait(uint64_t value) = 0;
};
/**
* \brief Fence
*
* Simple CPU-side fence.
*/
class Fence final : 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<dxvk::mutex> lock(m_mutex);
m_value.store(value, std::memory_order_release);
m_cond.notify_all();
}
void wait(uint64_t value) {
std::unique_lock<dxvk::mutex> lock(m_mutex);
m_cond.wait(lock, [this, value] {
return value <= m_value.load(std::memory_order_acquire);
});
}
private:
std::atomic<uint64_t> m_value;
dxvk::mutex m_mutex;
dxvk::condition_variable m_cond;
};
/**
* \brief Callback signal
*
* CPU-side fence with the ability to call a
* function when signaled to a given value.
*/
class CallbackFence final : public Signal {
public:
CallbackFence()
: m_value(0ull) { }
explicit CallbackFence(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<dxvk::mutex> lock(m_mutex);
m_value.store(value, std::memory_order_release);
m_cond.notify_all();
for (auto i = m_callbacks.begin(); i != m_callbacks.end(); ) {
if (value >= i->first) {
i->second();
i = m_callbacks.erase(i);
} else {
i++;
}
}
}
void wait(uint64_t value) {
std::unique_lock<dxvk::mutex> lock(m_mutex);
m_cond.wait(lock, [this, value] {
return value <= m_value.load(std::memory_order_acquire);
});
}
template<typename Fn>
void setCallback(uint64_t value, Fn&& proc) {
if (value <= this->value()) {
proc();
return;
}
std::unique_lock<dxvk::mutex> lock(m_mutex);
// Verify value is still in the future upon lock.
if (value > this->value())
m_callbacks.emplace_back(std::piecewise_construct,
std::make_tuple(value),
std::make_tuple(proc));
else {
lock.unlock();
proc();
}
}
private:
std::atomic<uint64_t> m_value;
dxvk::mutex m_mutex;
dxvk::condition_variable m_cond;
std::list<std::pair<uint64_t, std::function<void ()>>> m_callbacks;
};
}