dxvk/src/util/thread.h

372 lines
8.0 KiB
C++

#pragma once
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
#include "util_error.h"
#include "./com/com_include.h"
#include "./rc/util_rc.h"
#include "./rc/util_rc_ptr.h"
namespace dxvk {
/**
* \brief Thread priority
*/
enum class ThreadPriority : int32_t {
Normal,
Lowest,
};
#ifdef _WIN32
/**
* \brief Thread helper class
*
* This is needed mostly for winelib builds. Wine needs to setup each thread that
* calls Windows APIs. It means that in winelib builds, we can't let standard C++
* library create threads and need to use Wine for that instead. We use a thin wrapper
* around Windows thread functions so that the rest of code just has to use
* dxvk::thread class instead of std::thread.
*/
class ThreadFn : public RcObject {
using Proc = std::function<void()>;
public:
ThreadFn(Proc&& proc)
: m_proc(std::move(proc)) {
// Reference for the thread function
this->incRef();
m_handle = ::CreateThread(nullptr, 0x100000,
ThreadFn::threadProc, this, STACK_SIZE_PARAM_IS_A_RESERVATION,
nullptr);
if (m_handle == nullptr)
throw DxvkError("Failed to create thread");
}
~ThreadFn() {
if (this->joinable())
std::terminate();
}
void detach() {
::CloseHandle(m_handle);
m_handle = nullptr;
}
void join() {
if(::WaitForSingleObjectEx(m_handle, INFINITE, FALSE) == WAIT_FAILED)
throw DxvkError("Failed to join thread");
this->detach();
}
bool joinable() const {
return m_handle != nullptr;
}
void set_priority(ThreadPriority priority) {
int32_t value;
switch (priority) {
default:
case ThreadPriority::Normal: value = THREAD_PRIORITY_NORMAL; break;
case ThreadPriority::Lowest: value = THREAD_PRIORITY_LOWEST; break;
}
::SetThreadPriority(m_handle, int32_t(value));
}
private:
Proc m_proc;
HANDLE m_handle;
static DWORD WINAPI threadProc(void *arg) {
auto thread = reinterpret_cast<ThreadFn*>(arg);
thread->m_proc();
thread->decRef();
return 0;
}
};
/**
* \brief RAII thread wrapper
*
* Wrapper for \c ThreadFn that can be used
* as a drop-in replacement for \c std::thread.
*/
class thread {
public:
thread() { }
explicit thread(std::function<void()>&& func)
: m_thread(new ThreadFn(std::move(func))) { }
thread(thread&& other)
: m_thread(std::move(other.m_thread)) { }
thread& operator = (thread&& other) {
m_thread = std::move(other.m_thread);
return *this;
}
void detach() {
m_thread->detach();
}
void join() {
m_thread->join();
}
bool joinable() const {
return m_thread != nullptr
&& m_thread->joinable();
}
void set_priority(ThreadPriority priority) {
m_thread->set_priority(priority);
}
static uint32_t hardware_concurrency() {
SYSTEM_INFO info = { };
::GetSystemInfo(&info);
return info.dwNumberOfProcessors;
}
private:
Rc<ThreadFn> m_thread;
};
namespace this_thread {
inline void yield() {
SwitchToThread();
}
inline uint32_t get_id() {
return uint32_t(GetCurrentThreadId());
}
bool isInModuleDetachment();
}
/**
* \brief SRW-based mutex implementation
*
* Drop-in replacement for \c std::mutex that uses Win32
* SRW locks, which are implemented with \c futex in wine.
*/
class mutex {
public:
using native_handle_type = PSRWLOCK;
mutex() { }
mutex(const mutex&) = delete;
mutex& operator = (const mutex&) = delete;
void lock() {
AcquireSRWLockExclusive(&m_lock);
}
void unlock() {
ReleaseSRWLockExclusive(&m_lock);
}
bool try_lock() {
return TryAcquireSRWLockExclusive(&m_lock);
}
native_handle_type native_handle() {
return &m_lock;
}
private:
SRWLOCK m_lock = SRWLOCK_INIT;
};
/**
* \brief Recursive mutex implementation
*
* Drop-in replacement for \c std::recursive_mutex that
* uses Win32 critical sections.
*/
class recursive_mutex {
public:
using native_handle_type = PCRITICAL_SECTION;
recursive_mutex() {
InitializeCriticalSection(&m_lock);
}
~recursive_mutex() {
DeleteCriticalSection(&m_lock);
}
recursive_mutex(const recursive_mutex&) = delete;
recursive_mutex& operator = (const recursive_mutex&) = delete;
void lock() {
EnterCriticalSection(&m_lock);
}
void unlock() {
LeaveCriticalSection(&m_lock);
}
bool try_lock() {
return TryEnterCriticalSection(&m_lock);
}
native_handle_type native_handle() {
return &m_lock;
}
private:
CRITICAL_SECTION m_lock;
};
/**
* \brief SRW-based condition variable implementation
*
* Drop-in replacement for \c std::condition_variable that
* uses Win32 condition variables on SRW locks.
*/
class condition_variable {
public:
using native_handle_type = PCONDITION_VARIABLE;
condition_variable() {
InitializeConditionVariable(&m_cond);
}
condition_variable(condition_variable&) = delete;
condition_variable& operator = (condition_variable&) = delete;
void notify_one() {
WakeConditionVariable(&m_cond);
}
void notify_all() {
WakeAllConditionVariable(&m_cond);
}
void wait(std::unique_lock<dxvk::mutex>& lock) {
auto srw = lock.mutex()->native_handle();
SleepConditionVariableSRW(&m_cond, srw, INFINITE, 0);
}
template<typename Predicate>
void wait(std::unique_lock<dxvk::mutex>& lock, Predicate pred) {
while (!pred())
wait(lock);
}
template<typename Clock, typename Duration>
std::cv_status wait_until(std::unique_lock<dxvk::mutex>& lock, const std::chrono::time_point<Clock, Duration>& time) {
auto now = Clock::now();
return (now < time)
? wait_for(lock, now - time)
: std::cv_status::timeout;
}
template<typename Clock, typename Duration, typename Predicate>
bool wait_until(std::unique_lock<dxvk::mutex>& lock, const std::chrono::time_point<Clock, Duration>& time, Predicate pred) {
if (pred())
return true;
auto now = Clock::now();
return now < time && wait_for(lock, now - time, pred);
}
template<typename Rep, typename Period>
std::cv_status wait_for(std::unique_lock<dxvk::mutex>& lock, const std::chrono::duration<Rep, Period>& timeout) {
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
auto srw = lock.mutex()->native_handle();
return SleepConditionVariableSRW(&m_cond, srw, ms.count(), 0)
? std::cv_status::no_timeout
: std::cv_status::timeout;
}
template<typename Rep, typename Period, typename Predicate>
bool wait_for(std::unique_lock<dxvk::mutex>& lock, const std::chrono::duration<Rep, Period>& timeout, Predicate pred) {
bool result = pred();
if (!result && wait_for(lock, timeout) == std::cv_status::no_timeout)
result = pred();
return result;
}
native_handle_type native_handle() {
return &m_cond;
}
private:
CONDITION_VARIABLE m_cond;
};
#else
class thread : public std::thread {
public:
using std::thread::thread;
void set_priority(ThreadPriority priority) {
::sched_param param = {};
int32_t policy;
switch (priority) {
default:
case ThreadPriority::Normal: policy = SCHED_OTHER; break;
case ThreadPriority::Lowest: policy = SCHED_IDLE; break;
}
::pthread_setschedparam(this->native_handle(), policy, &param);
}
};
using mutex = std::mutex;
using recursive_mutex = std::recursive_mutex;
using condition_variable = std::condition_variable;
namespace this_thread {
inline void yield() {
std::this_thread::yield();
}
uint32_t get_id();
inline bool isInModuleDetachment() {
return false;
}
}
#endif
}