common: Update thread library from yuzu
This commit is contained in:
@@ -71,6 +71,8 @@ add_library(common STATIC
|
|||||||
common_precompiled_headers.h
|
common_precompiled_headers.h
|
||||||
common_types.h
|
common_types.h
|
||||||
construct.h
|
construct.h
|
||||||
|
error.cpp
|
||||||
|
error.h
|
||||||
file_util.cpp
|
file_util.cpp
|
||||||
file_util.h
|
file_util.h
|
||||||
hash.h
|
hash.h
|
||||||
@@ -95,6 +97,7 @@ add_library(common STATIC
|
|||||||
misc.cpp
|
misc.cpp
|
||||||
param_package.cpp
|
param_package.cpp
|
||||||
param_package.h
|
param_package.h
|
||||||
|
polyfill_thread.h
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
quaternion.h
|
quaternion.h
|
||||||
ring_buffer.h
|
ring_buffer.h
|
||||||
@@ -119,9 +122,11 @@ add_library(common STATIC
|
|||||||
thread.cpp
|
thread.cpp
|
||||||
thread.h
|
thread.h
|
||||||
thread_queue_list.h
|
thread_queue_list.h
|
||||||
|
thread_worker.h
|
||||||
threadsafe_queue.h
|
threadsafe_queue.h
|
||||||
timer.cpp
|
timer.cpp
|
||||||
timer.h
|
timer.h
|
||||||
|
unique_function.h
|
||||||
vector_math.h
|
vector_math.h
|
||||||
web_result.h
|
web_result.h
|
||||||
zstd_compression.cpp
|
zstd_compression.cpp
|
||||||
|
56
src/common/error.cpp
Normal file
56
src/common/error.cpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/error.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
std::string NativeErrorToString(int e) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
LPSTR err_str;
|
||||||
|
|
||||||
|
DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
reinterpret_cast<LPSTR>(&err_str), 1, nullptr);
|
||||||
|
if (!res) {
|
||||||
|
return "(FormatMessageA failed to format error)";
|
||||||
|
}
|
||||||
|
std::string ret(err_str);
|
||||||
|
LocalFree(err_str);
|
||||||
|
return ret;
|
||||||
|
#else
|
||||||
|
char err_str[255];
|
||||||
|
#if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600))
|
||||||
|
// Thread safe (GNU-specific)
|
||||||
|
const char* str = strerror_r(e, err_str, sizeof(err_str));
|
||||||
|
return std::string(str);
|
||||||
|
#else
|
||||||
|
// Thread safe (XSI-compliant)
|
||||||
|
int second_err = strerror_r(e, err_str, sizeof(err_str));
|
||||||
|
if (second_err != 0) {
|
||||||
|
return "(strerror_r failed to format error)";
|
||||||
|
}
|
||||||
|
return std::string(err_str);
|
||||||
|
#endif // GLIBC etc.
|
||||||
|
#endif // _WIN32
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetLastErrorMsg() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return NativeErrorToString(GetLastError());
|
||||||
|
#else
|
||||||
|
return NativeErrorToString(errno);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
21
src/common/error.h
Normal file
21
src/common/error.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
// Generic function to get last error message.
|
||||||
|
// Call directly after the command or use the error num.
|
||||||
|
// This function might change the error code.
|
||||||
|
// Defined in error.cpp.
|
||||||
|
[[nodiscard]] std::string GetLastErrorMsg();
|
||||||
|
|
||||||
|
// Like GetLastErrorMsg(), but passing an explicit error code.
|
||||||
|
// Defined in error.cpp.
|
||||||
|
[[nodiscard]] std::string NativeErrorToString(int e);
|
||||||
|
|
||||||
|
} // namespace Common
|
323
src/common/polyfill_thread.h
Normal file
323
src/common/polyfill_thread.h
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
//
|
||||||
|
// TODO: remove this file when jthread is supported by all compilation targets
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <version>
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_jthread
|
||||||
|
|
||||||
|
#include <stop_token>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <typename Condvar, typename Lock, typename Pred>
|
||||||
|
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
|
||||||
|
cv.wait(lock, token, std::move(pred));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
namespace polyfill {
|
||||||
|
|
||||||
|
using stop_state_callbacks = list<function<void()>>;
|
||||||
|
|
||||||
|
class stop_state {
|
||||||
|
public:
|
||||||
|
stop_state() = default;
|
||||||
|
~stop_state() = default;
|
||||||
|
|
||||||
|
bool request_stop() {
|
||||||
|
stop_state_callbacks callbacks;
|
||||||
|
|
||||||
|
{
|
||||||
|
scoped_lock lk{m_lock};
|
||||||
|
|
||||||
|
if (m_stop_requested.load()) {
|
||||||
|
// Already set, nothing to do
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set as requested
|
||||||
|
m_stop_requested = true;
|
||||||
|
|
||||||
|
// Copy callback list
|
||||||
|
callbacks = m_callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto callback : callbacks) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stop_requested() const {
|
||||||
|
return m_stop_requested.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_state_callbacks::const_iterator insert_callback(function<void()> f) {
|
||||||
|
stop_state_callbacks::const_iterator ret{};
|
||||||
|
bool should_run{};
|
||||||
|
|
||||||
|
{
|
||||||
|
scoped_lock lk{m_lock};
|
||||||
|
should_run = m_stop_requested.load();
|
||||||
|
m_callbacks.push_front(f);
|
||||||
|
ret = m_callbacks.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_run) {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_callback(stop_state_callbacks::const_iterator it) {
|
||||||
|
scoped_lock lk{m_lock};
|
||||||
|
m_callbacks.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutex m_lock;
|
||||||
|
atomic<bool> m_stop_requested;
|
||||||
|
stop_state_callbacks m_callbacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace polyfill
|
||||||
|
|
||||||
|
class stop_token;
|
||||||
|
class stop_source;
|
||||||
|
struct nostopstate_t {
|
||||||
|
explicit nostopstate_t() = default;
|
||||||
|
};
|
||||||
|
inline constexpr nostopstate_t nostopstate{};
|
||||||
|
|
||||||
|
template <class Callback>
|
||||||
|
class stop_callback;
|
||||||
|
|
||||||
|
class stop_token {
|
||||||
|
public:
|
||||||
|
stop_token() noexcept = default;
|
||||||
|
|
||||||
|
stop_token(const stop_token&) noexcept = default;
|
||||||
|
stop_token(stop_token&&) noexcept = default;
|
||||||
|
stop_token& operator=(const stop_token&) noexcept = default;
|
||||||
|
stop_token& operator=(stop_token&&) noexcept = default;
|
||||||
|
~stop_token() = default;
|
||||||
|
|
||||||
|
void swap(stop_token& other) noexcept {
|
||||||
|
m_stop_state.swap(other.m_stop_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool stop_requested() const noexcept {
|
||||||
|
return m_stop_state && m_stop_state->stop_requested();
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool stop_possible() const noexcept {
|
||||||
|
return m_stop_state != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class stop_source;
|
||||||
|
template <typename Callback>
|
||||||
|
friend class stop_callback;
|
||||||
|
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
class stop_source {
|
||||||
|
public:
|
||||||
|
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
|
||||||
|
explicit stop_source(nostopstate_t) noexcept {}
|
||||||
|
|
||||||
|
stop_source(const stop_source&) noexcept = default;
|
||||||
|
stop_source(stop_source&&) noexcept = default;
|
||||||
|
stop_source& operator=(const stop_source&) noexcept = default;
|
||||||
|
stop_source& operator=(stop_source&&) noexcept = default;
|
||||||
|
~stop_source() = default;
|
||||||
|
void swap(stop_source& other) noexcept {
|
||||||
|
m_stop_state.swap(other.m_stop_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] stop_token get_token() const noexcept {
|
||||||
|
return stop_token(m_stop_state);
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool stop_possible() const noexcept {
|
||||||
|
return m_stop_state != nullptr;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool stop_requested() const noexcept {
|
||||||
|
return m_stop_state && m_stop_state->stop_requested();
|
||||||
|
}
|
||||||
|
bool request_stop() noexcept {
|
||||||
|
return m_stop_state && m_stop_state->request_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class jthread;
|
||||||
|
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
|
||||||
|
: m_stop_state(move(stop_state)) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Callback>
|
||||||
|
class stop_callback {
|
||||||
|
static_assert(is_nothrow_destructible_v<Callback>);
|
||||||
|
static_assert(is_invocable_v<Callback>);
|
||||||
|
|
||||||
|
public:
|
||||||
|
using callback_type = Callback;
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
requires constructible_from<Callback, C>
|
||||||
|
explicit stop_callback(const stop_token& st,
|
||||||
|
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
|
||||||
|
: m_stop_state(st.m_stop_state) {
|
||||||
|
if (m_stop_state) {
|
||||||
|
m_callback = m_stop_state->insert_callback(move(cb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template <typename C>
|
||||||
|
requires constructible_from<Callback, C>
|
||||||
|
explicit stop_callback(stop_token&& st,
|
||||||
|
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
|
||||||
|
: m_stop_state(move(st.m_stop_state)) {
|
||||||
|
if (m_stop_state) {
|
||||||
|
m_callback = m_stop_state->insert_callback(move(cb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~stop_callback() {
|
||||||
|
if (m_stop_state && m_callback) {
|
||||||
|
m_stop_state->remove_callback(*m_callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_callback(const stop_callback&) = delete;
|
||||||
|
stop_callback(stop_callback&&) = delete;
|
||||||
|
stop_callback& operator=(const stop_callback&) = delete;
|
||||||
|
stop_callback& operator=(stop_callback&&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||||
|
optional<polyfill::stop_state_callbacks::const_iterator> m_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Callback>
|
||||||
|
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
|
||||||
|
|
||||||
|
class jthread {
|
||||||
|
public:
|
||||||
|
using id = thread::id;
|
||||||
|
using native_handle_type = thread::native_handle_type;
|
||||||
|
|
||||||
|
jthread() noexcept = default;
|
||||||
|
|
||||||
|
template <typename F, typename... Args,
|
||||||
|
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
|
||||||
|
explicit jthread(F&& f, Args&&... args)
|
||||||
|
: m_stop_state(make_shared<polyfill::stop_state>()),
|
||||||
|
m_thread(make_thread(move(f), move(args)...)) {}
|
||||||
|
|
||||||
|
~jthread() {
|
||||||
|
if (joinable()) {
|
||||||
|
request_stop();
|
||||||
|
join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jthread(const jthread&) = delete;
|
||||||
|
jthread(jthread&&) noexcept = default;
|
||||||
|
jthread& operator=(const jthread&) = delete;
|
||||||
|
|
||||||
|
jthread& operator=(jthread&& other) noexcept {
|
||||||
|
m_thread.swap(other.m_thread);
|
||||||
|
m_stop_state.swap(other.m_stop_state);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(jthread& other) noexcept {
|
||||||
|
m_thread.swap(other.m_thread);
|
||||||
|
m_stop_state.swap(other.m_stop_state);
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool joinable() const noexcept {
|
||||||
|
return m_thread.joinable();
|
||||||
|
}
|
||||||
|
void join() {
|
||||||
|
m_thread.join();
|
||||||
|
}
|
||||||
|
void detach() {
|
||||||
|
m_thread.detach();
|
||||||
|
m_stop_state.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] id get_id() const noexcept {
|
||||||
|
return m_thread.get_id();
|
||||||
|
}
|
||||||
|
[[nodiscard]] native_handle_type native_handle() {
|
||||||
|
return m_thread.native_handle();
|
||||||
|
}
|
||||||
|
[[nodiscard]] stop_source get_stop_source() noexcept {
|
||||||
|
return stop_source(m_stop_state);
|
||||||
|
}
|
||||||
|
[[nodiscard]] stop_token get_stop_token() const noexcept {
|
||||||
|
return stop_source(m_stop_state).get_token();
|
||||||
|
}
|
||||||
|
bool request_stop() noexcept {
|
||||||
|
return get_stop_source().request_stop();
|
||||||
|
}
|
||||||
|
[[nodiscard]] static unsigned int hardware_concurrency() noexcept {
|
||||||
|
return thread::hardware_concurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename F, typename... Args>
|
||||||
|
thread make_thread(F&& f, Args&&... args) {
|
||||||
|
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
|
||||||
|
return thread(move(f), get_stop_token(), move(args)...);
|
||||||
|
} else {
|
||||||
|
return thread(move(f), move(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||||
|
thread m_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <typename Condvar, typename Lock, typename Pred>
|
||||||
|
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
|
||||||
|
if (token.stop_requested()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stop_callback callback(token, [&] { cv.notify_all(); });
|
||||||
|
cv.wait(lock, [&] { return pred() || token.stop_requested(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
||||||
|
#endif
|
@@ -1,8 +1,10 @@
|
|||||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
// Refer to the license.txt file included.
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/error.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
@@ -20,7 +22,6 @@
|
|||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#ifdef __FreeBSD__
|
#ifdef __FreeBSD__
|
||||||
#define cpu_set_t cpuset_t
|
#define cpu_set_t cpuset_t
|
||||||
@@ -28,6 +29,56 @@
|
|||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||||
|
auto handle = GetCurrentThread();
|
||||||
|
int windows_priority = 0;
|
||||||
|
switch (new_priority) {
|
||||||
|
case ThreadPriority::Low:
|
||||||
|
windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
|
||||||
|
break;
|
||||||
|
case ThreadPriority::Normal:
|
||||||
|
windows_priority = THREAD_PRIORITY_NORMAL;
|
||||||
|
break;
|
||||||
|
case ThreadPriority::High:
|
||||||
|
windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||||
|
break;
|
||||||
|
case ThreadPriority::VeryHigh:
|
||||||
|
windows_priority = THREAD_PRIORITY_HIGHEST;
|
||||||
|
break;
|
||||||
|
case ThreadPriority::Critical:
|
||||||
|
windows_priority = THREAD_PRIORITY_TIME_CRITICAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
windows_priority = THREAD_PRIORITY_NORMAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SetThreadPriority(handle, windows_priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||||
|
pthread_t this_thread = pthread_self();
|
||||||
|
|
||||||
|
const auto scheduling_type = SCHED_OTHER;
|
||||||
|
s32 max_prio = sched_get_priority_max(scheduling_type);
|
||||||
|
s32 min_prio = sched_get_priority_min(scheduling_type);
|
||||||
|
u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U);
|
||||||
|
|
||||||
|
struct sched_param params;
|
||||||
|
if (max_prio > min_prio) {
|
||||||
|
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
|
||||||
|
} else {
|
||||||
|
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
|
||||||
// Sets the debugger-visible name of the current thread.
|
// Sets the debugger-visible name of the current thread.
|
||||||
@@ -47,7 +98,7 @@ void SetCurrentThreadName(const char* name) {
|
|||||||
|
|
||||||
info.dwType = 0x1000;
|
info.dwType = 0x1000;
|
||||||
info.szName = name;
|
info.szName = name;
|
||||||
info.dwThreadID = static_cast<DWORD>(-1);
|
info.dwThreadID = std::numeric_limits<DWORD>::max();
|
||||||
info.dwFlags = 0;
|
info.dwFlags = 0;
|
||||||
|
|
||||||
__try {
|
__try {
|
||||||
@@ -81,6 +132,12 @@ void SetCurrentThreadName(const char* name) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
void SetCurrentThreadName(const char* name) {
|
||||||
|
// Do Nothing on MingW
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
// Refer to the license.txt file included.
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -10,13 +10,15 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/polyfill_thread.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
class Event {
|
class Event {
|
||||||
public:
|
public:
|
||||||
void Set() {
|
void Set() {
|
||||||
std::lock_guard lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
if (!is_set) {
|
if (!is_set) {
|
||||||
is_set = true;
|
is_set = true;
|
||||||
condvar.notify_one();
|
condvar.notify_one();
|
||||||
@@ -29,8 +31,7 @@ public:
|
|||||||
is_set = false;
|
is_set = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Duration>
|
bool WaitFor(const std::chrono::nanoseconds& time) {
|
||||||
bool WaitFor(const std::chrono::duration<Duration>& time) {
|
|
||||||
std::unique_lock lk{mutex};
|
std::unique_lock lk{mutex};
|
||||||
if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
|
if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
|
||||||
return false;
|
return false;
|
||||||
@@ -54,6 +55,10 @@ public:
|
|||||||
is_set = false;
|
is_set = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsSet() {
|
||||||
|
return is_set;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::condition_variable condvar;
|
std::condition_variable condvar;
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
@@ -65,7 +70,7 @@ public:
|
|||||||
explicit Barrier(std::size_t count_) : count(count_) {}
|
explicit Barrier(std::size_t count_) : count(count_) {}
|
||||||
|
|
||||||
/// Blocks until all "count" threads have called Sync()
|
/// Blocks until all "count" threads have called Sync()
|
||||||
void Sync() {
|
bool Sync(std::stop_token token = {}) {
|
||||||
std::unique_lock lk{mutex};
|
std::unique_lock lk{mutex};
|
||||||
const std::size_t current_generation = generation;
|
const std::size_t current_generation = generation;
|
||||||
|
|
||||||
@@ -73,25 +78,37 @@ public:
|
|||||||
generation++;
|
generation++;
|
||||||
waiting = 0;
|
waiting = 0;
|
||||||
condvar.notify_all();
|
condvar.notify_all();
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
condvar.wait(lk,
|
CondvarWait(condvar, lk, token,
|
||||||
[this, current_generation] { return current_generation != generation; });
|
[this, current_generation] { return current_generation != generation; });
|
||||||
|
return !token.stop_requested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t Generation() const {
|
std::size_t Generation() {
|
||||||
std::unique_lock lk(mutex);
|
std::unique_lock lk{mutex};
|
||||||
return generation;
|
return generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::condition_variable condvar;
|
std::condition_variable_any condvar;
|
||||||
mutable std::mutex mutex;
|
std::mutex mutex;
|
||||||
std::size_t count;
|
std::size_t count;
|
||||||
std::size_t waiting = 0;
|
std::size_t waiting = 0;
|
||||||
std::size_t generation = 0; // Incremented once each time the barrier is used
|
std::size_t generation = 0; // Incremented once each time the barrier is used
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ThreadPriority : u32 {
|
||||||
|
Low = 0,
|
||||||
|
Normal = 1,
|
||||||
|
High = 2,
|
||||||
|
VeryHigh = 3,
|
||||||
|
Critical = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||||
|
|
||||||
void SetCurrentThreadName(const char* name);
|
void SetCurrentThreadName(const char* name);
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
117
src/common/thread_worker.h
Normal file
117
src/common/thread_worker.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include "common/polyfill_thread.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "common/unique_function.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <class StateType = void>
|
||||||
|
class StatefulThreadWorker {
|
||||||
|
static constexpr bool with_state = !std::is_same_v<StateType, void>;
|
||||||
|
|
||||||
|
struct DummyCallable {
|
||||||
|
int operator()() const noexcept {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using Task =
|
||||||
|
std::conditional_t<with_state, UniqueFunction<void, StateType*>, UniqueFunction<void>>;
|
||||||
|
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
|
||||||
|
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
||||||
|
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName(thread_name.c_str());
|
||||||
|
{
|
||||||
|
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
Task task;
|
||||||
|
{
|
||||||
|
std::unique_lock lock{queue_mutex};
|
||||||
|
if (requests.empty()) {
|
||||||
|
wait_condition.notify_all();
|
||||||
|
}
|
||||||
|
Common::CondvarWait(condition, lock, stop_token,
|
||||||
|
[this] { return !requests.empty(); });
|
||||||
|
if (stop_token.stop_requested()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
task = std::move(requests.front());
|
||||||
|
requests.pop();
|
||||||
|
}
|
||||||
|
if constexpr (with_state) {
|
||||||
|
task(&state);
|
||||||
|
} else {
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
++work_done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++workers_stopped;
|
||||||
|
wait_condition.notify_all();
|
||||||
|
};
|
||||||
|
threads.reserve(num_workers);
|
||||||
|
for (size_t i = 0; i < num_workers; ++i) {
|
||||||
|
threads.emplace_back(lambda);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatefulThreadWorker& operator=(const StatefulThreadWorker&) = delete;
|
||||||
|
StatefulThreadWorker(const StatefulThreadWorker&) = delete;
|
||||||
|
|
||||||
|
StatefulThreadWorker& operator=(StatefulThreadWorker&&) = delete;
|
||||||
|
StatefulThreadWorker(StatefulThreadWorker&&) = delete;
|
||||||
|
|
||||||
|
void QueueWork(Task work) {
|
||||||
|
{
|
||||||
|
std::unique_lock lock{queue_mutex};
|
||||||
|
requests.emplace(std::move(work));
|
||||||
|
++work_scheduled;
|
||||||
|
}
|
||||||
|
condition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForRequests(std::stop_token stop_token = {}) {
|
||||||
|
std::stop_callback callback(stop_token, [this] {
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
thread.request_stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
std::unique_lock lock{queue_mutex};
|
||||||
|
wait_condition.wait(lock, [this] {
|
||||||
|
return workers_stopped >= workers_queued || work_done >= work_scheduled;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::queue<Task> requests;
|
||||||
|
std::mutex queue_mutex;
|
||||||
|
std::condition_variable_any condition;
|
||||||
|
std::condition_variable wait_condition;
|
||||||
|
std::atomic<size_t> work_scheduled{};
|
||||||
|
std::atomic<size_t> work_done{};
|
||||||
|
std::atomic<size_t> workers_stopped{};
|
||||||
|
std::atomic<size_t> workers_queued{};
|
||||||
|
std::string thread_name;
|
||||||
|
std::vector<std::jthread> threads;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ThreadWorker = StatefulThreadWorker<>;
|
||||||
|
|
||||||
|
} // namespace Common
|
61
src/common/unique_function.h
Normal file
61
src/common/unique_function.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
/// General purpose function wrapper similar to std::function.
|
||||||
|
/// Unlike std::function, the captured values don't have to be copyable.
|
||||||
|
/// This class can be moved but not copied.
|
||||||
|
template <typename ResultType, typename... Args>
|
||||||
|
class UniqueFunction {
|
||||||
|
class CallableBase {
|
||||||
|
public:
|
||||||
|
virtual ~CallableBase() = default;
|
||||||
|
virtual ResultType operator()(Args&&...) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Functor>
|
||||||
|
class Callable final : public CallableBase {
|
||||||
|
public:
|
||||||
|
Callable(Functor&& functor_) : functor{std::move(functor_)} {}
|
||||||
|
~Callable() override = default;
|
||||||
|
|
||||||
|
ResultType operator()(Args&&... args) override {
|
||||||
|
return functor(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Functor functor;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
UniqueFunction() = default;
|
||||||
|
|
||||||
|
template <typename Functor>
|
||||||
|
UniqueFunction(Functor&& functor)
|
||||||
|
: callable{std::make_unique<Callable<Functor>>(std::move(functor))} {}
|
||||||
|
|
||||||
|
UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default;
|
||||||
|
UniqueFunction(UniqueFunction&& rhs) noexcept = default;
|
||||||
|
|
||||||
|
UniqueFunction& operator=(const UniqueFunction&) = delete;
|
||||||
|
UniqueFunction(const UniqueFunction&) = delete;
|
||||||
|
|
||||||
|
ResultType operator()(Args&&... args) const {
|
||||||
|
return (*callable)(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const noexcept {
|
||||||
|
return static_cast<bool>(callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<CallableBase> callable;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
@@ -27,31 +27,16 @@ void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
|
|||||||
|
|
||||||
Scheduler::Scheduler(const Instance& instance, RenderpassCache& renderpass_cache)
|
Scheduler::Scheduler(const Instance& instance, RenderpassCache& renderpass_cache)
|
||||||
: instance{instance}, renderpass_cache{renderpass_cache}, master_semaphore{instance},
|
: instance{instance}, renderpass_cache{renderpass_cache}, master_semaphore{instance},
|
||||||
command_pool{instance, master_semaphore}, stop_requested{false},
|
command_pool{instance, master_semaphore},
|
||||||
use_worker_thread{Settings::values.async_command_recording} {
|
use_worker_thread{Settings::values.async_command_recording} {
|
||||||
AllocateWorkerCommandBuffers();
|
AllocateWorkerCommandBuffers();
|
||||||
if (use_worker_thread) {
|
if (use_worker_thread) {
|
||||||
AcquireNewChunk();
|
AcquireNewChunk();
|
||||||
worker_thread = std::thread([this]() { WorkerThread(); });
|
worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scheduler::~Scheduler() {
|
Scheduler::~Scheduler() = default;
|
||||||
if (!use_worker_thread) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stop_requested = true;
|
|
||||||
|
|
||||||
// Push a dummy chunk to unblock the thread
|
|
||||||
{
|
|
||||||
std::scoped_lock lock{work_mutex};
|
|
||||||
work_queue.push(std::move(chunk));
|
|
||||||
}
|
|
||||||
|
|
||||||
work_cv.notify_one();
|
|
||||||
worker_thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) {
|
void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) {
|
||||||
SubmitExecution(signal, wait);
|
SubmitExecution(signal, wait);
|
||||||
@@ -91,7 +76,7 @@ void Scheduler::DispatchWork() {
|
|||||||
AcquireNewChunk();
|
AcquireNewChunk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::WorkerThread() {
|
void Scheduler::WorkerThread(std::stop_token stop_token) {
|
||||||
do {
|
do {
|
||||||
std::unique_ptr<CommandChunk> work;
|
std::unique_ptr<CommandChunk> work;
|
||||||
bool has_submit{false};
|
bool has_submit{false};
|
||||||
@@ -100,8 +85,8 @@ void Scheduler::WorkerThread() {
|
|||||||
if (work_queue.empty()) {
|
if (work_queue.empty()) {
|
||||||
wait_cv.notify_all();
|
wait_cv.notify_all();
|
||||||
}
|
}
|
||||||
work_cv.wait(lock, [this] { return !work_queue.empty() || stop_requested; });
|
Common::CondvarWait(work_cv, lock, stop_token, [&] { return !work_queue.empty(); });
|
||||||
if (stop_requested) {
|
if (stop_token.stop_requested()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
work = std::move(work_queue.front());
|
work = std::move(work_queue.front());
|
||||||
@@ -115,7 +100,7 @@ void Scheduler::WorkerThread() {
|
|||||||
}
|
}
|
||||||
std::scoped_lock reserve_lock{reserve_mutex};
|
std::scoped_lock reserve_lock{reserve_mutex};
|
||||||
chunk_reserve.push_back(std::move(work));
|
chunk_reserve.push_back(std::move(work));
|
||||||
} while (!stop_requested);
|
} while (!stop_token.stop_requested());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::AllocateWorkerCommandBuffers() {
|
void Scheduler::AllocateWorkerCommandBuffers() {
|
||||||
|
@@ -6,12 +6,12 @@
|
|||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/polyfill_thread.h"
|
||||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void WorkerThread();
|
void WorkerThread(std::stop_token stop_token);
|
||||||
|
|
||||||
void AllocateWorkerCommandBuffers();
|
void AllocateWorkerCommandBuffers();
|
||||||
|
|
||||||
@@ -205,8 +205,7 @@ private:
|
|||||||
std::mutex work_mutex;
|
std::mutex work_mutex;
|
||||||
std::condition_variable_any work_cv;
|
std::condition_variable_any work_cv;
|
||||||
std::condition_variable wait_cv;
|
std::condition_variable wait_cv;
|
||||||
std::thread worker_thread;
|
std::jthread worker_thread;
|
||||||
std::atomic_bool stop_requested;
|
|
||||||
bool use_worker_thread;
|
bool use_worker_thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user