summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/assert.h8
-rw-r--r--src/common/fs/fs_util.cpp4
-rw-r--r--src/common/fs/fs_util.h11
-rw-r--r--src/common/hex_util.h2
-rw-r--r--src/common/host_memory.cpp9
-rw-r--r--src/common/logging/backend.cpp350
-rw-r--r--src/common/logging/backend.h113
-rw-r--r--src/common/settings.h194
-rw-r--r--src/common/threadsafe_queue.h10
-rw-r--r--src/common/uuid.cpp56
-rw-r--r--src/common/uuid.h30
-rw-r--r--src/common/x64/xbyak_abi.h2
-rw-r--r--src/common/x64/xbyak_util.h2
13 files changed, 501 insertions, 290 deletions
diff --git a/src/common/assert.h b/src/common/assert.h
index b3ba35c0f..33060d865 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -52,8 +52,12 @@ assert_noinline_call(const Fn& fn) {
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
#else // not debug
-#define DEBUG_ASSERT(_a_)
-#define DEBUG_ASSERT_MSG(_a_, _desc_, ...)
+#define DEBUG_ASSERT(_a_) \
+ do { \
+ } while (0)
+#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
+ do { \
+ } while (0)
#endif
#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!")
diff --git a/src/common/fs/fs_util.cpp b/src/common/fs/fs_util.cpp
index 357cf5855..9f8671982 100644
--- a/src/common/fs/fs_util.cpp
+++ b/src/common/fs/fs_util.cpp
@@ -20,6 +20,10 @@ std::string ToUTF8String(std::u8string_view u8_string) {
return std::string{u8_string.begin(), u8_string.end()};
}
+std::string BufferToUTF8String(std::span<const u8> buffer) {
+ return std::string{buffer.begin(), std::ranges::find(buffer, u8{0})};
+}
+
std::string PathToUTF8String(const std::filesystem::path& path) {
return ToUTF8String(path.u8string());
}
diff --git a/src/common/fs/fs_util.h b/src/common/fs/fs_util.h
index ec9950ee7..1ec82eb35 100644
--- a/src/common/fs/fs_util.h
+++ b/src/common/fs/fs_util.h
@@ -47,6 +47,17 @@ concept IsChar = std::same_as<T, char>;
[[nodiscard]] std::string ToUTF8String(std::u8string_view u8_string);
/**
+ * Converts a buffer of bytes to a UTF8-encoded std::string.
+ * This converts from the start of the buffer until the first encountered null-terminator.
+ * If no null-terminator is found, this converts the entire buffer instead.
+ *
+ * @param buffer Buffer of bytes
+ *
+ * @returns UTF-8 encoded std::string.
+ */
+[[nodiscard]] std::string BufferToUTF8String(std::span<const u8> buffer);
+
+/**
* Converts a filesystem path to a UTF-8 encoded std::string.
*
* @param path Filesystem path
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index f5f9e4507..5e9b6ef8b 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -61,7 +61,7 @@ template <typename ContiguousContainer>
return out;
}
-[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) {
+[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[33]) {
return HexStringToArray<16>(data);
}
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index 2a5a7596c..6661244cf 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -6,7 +6,7 @@
#include <windows.h>
#include "common/dynamic_library.h"
-#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv
+#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
@@ -343,7 +343,7 @@ private:
std::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
};
-#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv
+#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
class HostMemory::Impl {
public:
@@ -357,7 +357,12 @@ public:
});
// Backing memory initialization
+#if defined(__FreeBSD__) && __FreeBSD__ < 13
+ // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
+ fd = shm_open(SHM_ANON, O_RDWR, 0600);
+#else
fd = memfd_create("HostMemory", 0);
+#endif
if (fd == -1) {
LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno));
throw std::bad_alloc{};
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 61dddab3f..13edda9c9 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -2,13 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
#include <atomic>
#include <chrono>
#include <climits>
-#include <condition_variable>
-#include <memory>
-#include <mutex>
#include <thread>
#include <vector>
@@ -16,104 +12,229 @@
#include <windows.h> // For OutputDebugStringW
#endif
-#include "common/assert.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
+#include "common/fs/fs_paths.h"
+#include "common/fs/path_util.h"
#include "common/literals.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
#include "common/settings.h"
+#ifdef _WIN32
#include "common/string_util.h"
+#endif
#include "common/threadsafe_queue.h"
namespace Common::Log {
+namespace {
+
/**
- * Static state as a singleton.
+ * Interface for logging backends.
*/
-class Impl {
+class Backend {
public:
- static Impl& Instance() {
- static Impl backend;
- return backend;
+ virtual ~Backend() = default;
+
+ virtual void Write(const Entry& entry) = 0;
+
+ virtual void EnableForStacktrace() = 0;
+
+ virtual void Flush() = 0;
+};
+
+/**
+ * Backend that writes to stderr and with color
+ */
+class ColorConsoleBackend final : public Backend {
+public:
+ explicit ColorConsoleBackend() = default;
+
+ ~ColorConsoleBackend() override = default;
+
+ void Write(const Entry& entry) override {
+ if (enabled.load(std::memory_order_relaxed)) {
+ PrintColoredMessage(entry);
+ }
}
- Impl(const Impl&) = delete;
- Impl& operator=(const Impl&) = delete;
+ void Flush() override {
+ // stderr shouldn't be buffered
+ }
- Impl(Impl&&) = delete;
- Impl& operator=(Impl&&) = delete;
+ void EnableForStacktrace() override {
+ enabled = true;
+ }
- void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
- const char* function, std::string message) {
- message_queue.Push(
- CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
+ void SetEnabled(bool enabled_) {
+ enabled = enabled_;
+ }
+
+private:
+ std::atomic_bool enabled{false};
+};
+
+/**
+ * Backend that writes to a file passed into the constructor
+ */
+class FileBackend final : public Backend {
+public:
+ explicit FileBackend(const std::filesystem::path& filename) {
+ auto old_filename = filename;
+ old_filename += ".old.txt";
+
+ // Existence checks are done within the functions themselves.
+ // We don't particularly care if these succeed or not.
+ static_cast<void>(FS::RemoveFile(old_filename));
+ static_cast<void>(FS::RenameFile(filename, old_filename));
+
+ file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write,
+ FS::FileType::TextFile);
+ }
+
+ ~FileBackend() override = default;
+
+ void Write(const Entry& entry) override {
+ if (!enabled) {
+ return;
+ }
+
+ bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
+
+ using namespace Common::Literals;
+ // Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
+ const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
+ const bool write_limit_exceeded = bytes_written > write_limit;
+ if (entry.log_level >= Level::Error || write_limit_exceeded) {
+ if (write_limit_exceeded) {
+ // Stop writing after the write limit is exceeded.
+ // Don't close the file so we can print a stacktrace if necessary
+ enabled = false;
+ }
+ file->Flush();
+ }
+ }
+
+ void Flush() override {
+ file->Flush();
+ }
+
+ void EnableForStacktrace() override {
+ enabled = true;
+ bytes_written = 0;
}
- void AddBackend(std::unique_ptr<Backend> backend) {
- std::lock_guard lock{writing_mutex};
- backends.push_back(std::move(backend));
+private:
+ std::unique_ptr<FS::IOFile> file;
+ bool enabled = true;
+ std::size_t bytes_written = 0;
+};
+
+/**
+ * Backend that writes to Visual Studio's output window
+ */
+class DebuggerBackend final : public Backend {
+public:
+ explicit DebuggerBackend() = default;
+
+ ~DebuggerBackend() override = default;
+
+ void Write(const Entry& entry) override {
+#ifdef _WIN32
+ ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
+#endif
}
- void RemoveBackend(std::string_view backend_name) {
- std::lock_guard lock{writing_mutex};
+ void Flush() override {}
+
+ void EnableForStacktrace() override {}
+};
+
+bool initialization_in_progress_suppress_logging = false;
- std::erase_if(backends, [&backend_name](const auto& backend) {
- return backend_name == backend->GetName();
- });
+/**
+ * Static state as a singleton.
+ */
+class Impl {
+public:
+ static Impl& Instance() {
+ if (!instance) {
+ abort();
+ }
+ return *instance;
}
- const Filter& GetGlobalFilter() const {
- return filter;
+ static void Initialize() {
+ if (instance) {
+ abort();
+ }
+ using namespace Common::FS;
+ initialization_in_progress_suppress_logging = true;
+ const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
+ void(CreateDir(log_dir));
+ Filter filter;
+ filter.ParseFilterString(Settings::values.log_filter.GetValue());
+ instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
+ Deleter);
+ initialization_in_progress_suppress_logging = false;
}
+ Impl(const Impl&) = delete;
+ Impl& operator=(const Impl&) = delete;
+
+ Impl(Impl&&) = delete;
+ Impl& operator=(Impl&&) = delete;
+
void SetGlobalFilter(const Filter& f) {
filter = f;
}
- Backend* GetBackend(std::string_view backend_name) {
- const auto it =
- std::find_if(backends.begin(), backends.end(),
- [&backend_name](const auto& i) { return backend_name == i->GetName(); });
- if (it == backends.end())
- return nullptr;
- return it->get();
+ void SetColorConsoleBackendEnabled(bool enabled) {
+ color_console_backend.SetEnabled(enabled);
+ }
+
+ void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
+ const char* function, std::string message) {
+ if (!filter.CheckMessage(log_class, log_level))
+ return;
+ const Entry& entry =
+ CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
+ message_queue.Push(entry);
}
private:
- Impl() {
- backend_thread = std::thread([&] {
- Entry entry;
- auto write_logs = [&](Entry& e) {
- std::lock_guard lock{writing_mutex};
- for (const auto& backend : backends) {
- backend->Write(e);
- }
- };
- while (true) {
- entry = message_queue.PopWait();
- if (entry.final_entry) {
- break;
- }
- write_logs(entry);
- }
+ Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
+ : filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] {
+ Common::SetCurrentThreadName("yuzu:Log");
+ Entry entry;
+ const auto write_logs = [this, &entry]() {
+ ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
+ };
+ while (true) {
+ entry = message_queue.PopWait();
+ if (entry.final_entry) {
+ break;
+ }
+ write_logs();
+ }
+ // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
+ // case where a system is repeatedly spamming logs even on close.
+ int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
+ while (max_logs_to_write-- && message_queue.Pop(entry)) {
+ write_logs();
+ }
+ })} {}
- // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
- // case where a system is repeatedly spamming logs even on close.
- const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
- int logs_written = 0;
- while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
- write_logs(entry);
- }
- });
+ ~Impl() {
+ StopBackendThread();
}
- ~Impl() {
- Entry entry;
- entry.final_entry = true;
- message_queue.Push(entry);
+ void StopBackendThread() {
+ Entry stop_entry{};
+ stop_entry.final_entry = true;
+ message_queue.Push(stop_entry);
backend_thread.join();
}
@@ -135,100 +256,51 @@ private:
};
}
- std::mutex writing_mutex;
- std::thread backend_thread;
- std::vector<std::unique_ptr<Backend>> backends;
- MPSCQueue<Entry> message_queue;
- Filter filter;
- std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
-};
-
-ConsoleBackend::~ConsoleBackend() = default;
-
-void ConsoleBackend::Write(const Entry& entry) {
- PrintMessage(entry);
-}
-
-ColorConsoleBackend::~ColorConsoleBackend() = default;
-
-void ColorConsoleBackend::Write(const Entry& entry) {
- PrintColoredMessage(entry);
-}
-
-FileBackend::FileBackend(const std::filesystem::path& filename) {
- auto old_filename = filename;
- old_filename += ".old.txt";
-
- // Existence checks are done within the functions themselves.
- // We don't particularly care if these succeed or not.
- FS::RemoveFile(old_filename);
- void(FS::RenameFile(filename, old_filename));
-
- file =
- std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
-}
-
-FileBackend::~FileBackend() = default;
+ void ForEachBackend(auto lambda) {
+ lambda(static_cast<Backend&>(debugger_backend));
+ lambda(static_cast<Backend&>(color_console_backend));
+ lambda(static_cast<Backend&>(file_backend));
+ }
-void FileBackend::Write(const Entry& entry) {
- if (!file->IsOpen()) {
- return;
+ static void Deleter(Impl* ptr) {
+ delete ptr;
}
- using namespace Common::Literals;
- // Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
- constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB;
- constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB;
+ static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
- const bool write_limit_exceeded =
- bytes_written > MAX_BYTES_WRITTEN_EXTENDED ||
- (bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging);
+ Filter filter;
+ DebuggerBackend debugger_backend{};
+ ColorConsoleBackend color_console_backend{};
+ FileBackend file_backend;
- // Close the file after the write limit is exceeded.
- if (write_limit_exceeded) {
- file->Close();
- return;
- }
+ std::thread backend_thread;
+ MPSCQueue<Entry> message_queue{};
+ std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
+};
+} // namespace
- bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
- if (entry.log_level >= Level::Error) {
- file->Flush();
- }
+void Initialize() {
+ Impl::Initialize();
}
-DebuggerBackend::~DebuggerBackend() = default;
-
-void DebuggerBackend::Write(const Entry& entry) {
-#ifdef _WIN32
- ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
-#endif
+void DisableLoggingInTests() {
+ initialization_in_progress_suppress_logging = true;
}
void SetGlobalFilter(const Filter& filter) {
Impl::Instance().SetGlobalFilter(filter);
}
-void AddBackend(std::unique_ptr<Backend> backend) {
- Impl::Instance().AddBackend(std::move(backend));
-}
-
-void RemoveBackend(std::string_view backend_name) {
- Impl::Instance().RemoveBackend(backend_name);
-}
-
-Backend* GetBackend(std::string_view backend_name) {
- return Impl::Instance().GetBackend(backend_name);
+void SetColorConsoleBackendEnabled(bool enabled) {
+ Impl::Instance().SetColorConsoleBackendEnabled(enabled);
}
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args) {
- auto& instance = Impl::Instance();
- const auto& filter = instance.GetGlobalFilter();
- if (!filter.CheckMessage(log_class, log_level))
- return;
-
- instance.PushEntry(log_class, log_level, filename, line_num, function,
- fmt::vformat(format, args));
+ if (!initialization_in_progress_suppress_logging) {
+ Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
+ fmt::vformat(format, args));
+ }
}
} // namespace Common::Log
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index 4b9a910c1..cb7839ee9 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -5,120 +5,21 @@
#pragma once
#include <filesystem>
-#include <memory>
-#include <string>
-#include <string_view>
#include "common/logging/filter.h"
-#include "common/logging/log.h"
-
-namespace Common::FS {
-class IOFile;
-}
namespace Common::Log {
class Filter;
-/**
- * Interface for logging backends. As loggers can be created and removed at runtime, this can be
- * used by a frontend for adding a custom logging backend as needed
- */
-class Backend {
-public:
- virtual ~Backend() = default;
-
- virtual void SetFilter(const Filter& new_filter) {
- filter = new_filter;
- }
- virtual const char* GetName() const = 0;
- virtual void Write(const Entry& entry) = 0;
-
-private:
- Filter filter;
-};
-
-/**
- * Backend that writes to stderr without any color commands
- */
-class ConsoleBackend : public Backend {
-public:
- ~ConsoleBackend() override;
-
- static const char* Name() {
- return "console";
- }
- const char* GetName() const override {
- return Name();
- }
- void Write(const Entry& entry) override;
-};
-
-/**
- * Backend that writes to stderr and with color
- */
-class ColorConsoleBackend : public Backend {
-public:
- ~ColorConsoleBackend() override;
-
- static const char* Name() {
- return "color_console";
- }
-
- const char* GetName() const override {
- return Name();
- }
- void Write(const Entry& entry) override;
-};
+/// Initializes the logging system. This should be the first thing called in main.
+void Initialize();
-/**
- * Backend that writes to a file passed into the constructor
- */
-class FileBackend : public Backend {
-public:
- explicit FileBackend(const std::filesystem::path& filename);
- ~FileBackend() override;
-
- static const char* Name() {
- return "file";
- }
-
- const char* GetName() const override {
- return Name();
- }
-
- void Write(const Entry& entry) override;
-
-private:
- std::unique_ptr<FS::IOFile> file;
- std::size_t bytes_written = 0;
-};
-
-/**
- * Backend that writes to Visual Studio's output window
- */
-class DebuggerBackend : public Backend {
-public:
- ~DebuggerBackend() override;
-
- static const char* Name() {
- return "debugger";
- }
- const char* GetName() const override {
- return Name();
- }
- void Write(const Entry& entry) override;
-};
-
-void AddBackend(std::unique_ptr<Backend> backend);
-
-void RemoveBackend(std::string_view backend_name);
-
-Backend* GetBackend(std::string_view backend_name);
+void DisableLoggingInTests();
/**
- * The global filter will prevent any messages from even being processed if they are filtered. Each
- * backend can have a filter, but if the level is lower than the global filter, the backend will
- * never get the message
+ * The global filter will prevent any messages from even being processed if they are filtered.
*/
void SetGlobalFilter(const Filter& filter);
-} // namespace Common::Log \ No newline at end of file
+
+void SetColorConsoleBackendEnabled(bool enabled);
+} // namespace Common::Log
diff --git a/src/common/settings.h b/src/common/settings.h
index d8730f515..20769d310 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -4,6 +4,7 @@
#pragma once
+#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
@@ -42,6 +43,11 @@ enum class CPUAccuracy : u32 {
Unsafe = 2,
};
+enum class FullscreenMode : u32 {
+ Borderless = 0,
+ Exclusive = 1,
+};
+
/** The BasicSetting class is a simple resource manager. It defines a label and default value
* alongside the actual value of the setting for simpler and less-error prone use with frontend
* configurations. Setting a default value and label is required, though subclasses may deviate from
@@ -69,14 +75,14 @@ public:
*/
explicit BasicSetting(const Type& default_val, const std::string& name)
: default_value{default_val}, global{default_val}, label{name} {}
- ~BasicSetting() = default;
+ virtual ~BasicSetting() = default;
/**
* Returns a reference to the setting's value.
*
* @returns A reference to the setting
*/
- [[nodiscard]] const Type& GetValue() const {
+ [[nodiscard]] virtual const Type& GetValue() const {
return global;
}
@@ -85,7 +91,7 @@ public:
*
* @param value The desired value
*/
- void SetValue(const Type& value) {
+ virtual void SetValue(const Type& value) {
Type temp{value};
std::swap(global, temp);
}
@@ -115,7 +121,7 @@ public:
*
* @returns A reference to the setting
*/
- const Type& operator=(const Type& value) {
+ virtual const Type& operator=(const Type& value) {
Type temp{value};
std::swap(global, temp);
return global;
@@ -126,7 +132,7 @@ public:
*
* @returns A reference to the setting
*/
- explicit operator const Type&() const {
+ explicit virtual operator const Type&() const {
return global;
}
@@ -137,6 +143,51 @@ protected:
};
/**
+ * BasicRangedSetting class is intended for use with quantifiable settings that need a more
+ * restrictive range than implicitly defined by its type. Implements a minimum and maximum that is
+ * simply used to sanitize SetValue and the assignment overload.
+ */
+template <typename Type>
+class BasicRangedSetting : virtual public BasicSetting<Type> {
+public:
+ /**
+ * Sets a default value, minimum value, maximum value, and label.
+ *
+ * @param default_val Intial value of the setting, and default value of the setting
+ * @param min_val Sets the minimum allowed value of the setting
+ * @param max_val Sets the maximum allowed value of the setting
+ * @param name Label for the setting
+ */
+ explicit BasicRangedSetting(const Type& default_val, const Type& min_val, const Type& max_val,
+ const std::string& name)
+ : BasicSetting<Type>{default_val, name}, minimum{min_val}, maximum{max_val} {}
+ virtual ~BasicRangedSetting() = default;
+
+ /**
+ * Like BasicSetting's SetValue, except value is clamped to the range of the setting.
+ *
+ * @param value The desired value
+ */
+ void SetValue(const Type& value) override {
+ this->global = std::clamp(value, minimum, maximum);
+ }
+
+ /**
+ * Like BasicSetting's assignment overload, except value is clamped to the range of the setting.
+ *
+ * @param value The desired value
+ * @returns A reference to the setting's value
+ */
+ const Type& operator=(const Type& value) override {
+ this->global = std::clamp(value, minimum, maximum);
+ return this->global;
+ }
+
+ const Type minimum; ///< Minimum allowed value of the setting
+ const Type maximum; ///< Maximum allowed value of the setting
+};
+
+/**
* The Setting class is a slightly more complex version of the BasicSetting class. This adds a
* custom setting to switch to when a guest application specifically requires it. The effect is that
* other components of the emulator can access the setting's intended value without any need for the
@@ -147,7 +198,7 @@ protected:
* Like the BasicSetting, this requires setting a default value and label to use.
*/
template <typename Type>
-class Setting final : public BasicSetting<Type> {
+class Setting : virtual public BasicSetting<Type> {
public:
/**
* Sets a default value, label, and setting value.
@@ -157,7 +208,7 @@ public:
*/
explicit Setting(const Type& default_val, const std::string& name)
: BasicSetting<Type>(default_val, name) {}
- ~Setting() = default;
+ virtual ~Setting() = default;
/**
* Tells this setting to represent either the global or custom setting when other member
@@ -186,7 +237,13 @@ public:
*
* @returns The required value of the setting
*/
- [[nodiscard]] const Type& GetValue(bool need_global = false) const {
+ [[nodiscard]] virtual const Type& GetValue() const override {
+ if (use_global) {
+ return this->global;
+ }
+ return custom;
+ }
+ [[nodiscard]] virtual const Type& GetValue(bool need_global) const {
if (use_global || need_global) {
return this->global;
}
@@ -198,7 +255,7 @@ public:
*
* @param value The new value
*/
- void SetValue(const Type& value) {
+ void SetValue(const Type& value) override {
Type temp{value};
if (use_global) {
std::swap(this->global, temp);
@@ -214,7 +271,7 @@ public:
*
* @returns A reference to the current setting value
*/
- const Type& operator=(const Type& value) {
+ const Type& operator=(const Type& value) override {
Type temp{value};
if (use_global) {
std::swap(this->global, temp);
@@ -229,19 +286,88 @@ public:
*
* @returns A reference to the current setting value
*/
- explicit operator const Type&() const {
+ virtual explicit operator const Type&() const override {
if (use_global) {
return this->global;
}
return custom;
}
-private:
+protected:
bool use_global{true}; ///< The setting's global state
Type custom{}; ///< The custom value of the setting
};
/**
+ * RangedSetting is a Setting that implements a maximum and minimum value for its setting. Intended
+ * for use with quantifiable settings.
+ */
+template <typename Type>
+class RangedSetting final : public BasicRangedSetting<Type>, public Setting<Type> {
+public:
+ /**
+ * Sets a default value, minimum value, maximum value, and label.
+ *
+ * @param default_val Intial value of the setting, and default value of the setting
+ * @param min_val Sets the minimum allowed value of the setting
+ * @param max_val Sets the maximum allowed value of the setting
+ * @param name Label for the setting
+ */
+ explicit RangedSetting(const Type& default_val, const Type& min_val, const Type& max_val,
+ const std::string& name)
+ : BasicSetting<Type>{default_val, name},
+ BasicRangedSetting<Type>{default_val, min_val, max_val, name}, Setting<Type>{default_val,
+ name} {}
+ virtual ~RangedSetting() = default;
+
+ // The following are needed to avoid a MSVC bug
+ // (source: https://stackoverflow.com/questions/469508)
+ [[nodiscard]] const Type& GetValue() const override {
+ return Setting<Type>::GetValue();
+ }
+ [[nodiscard]] const Type& GetValue(bool need_global) const override {
+ return Setting<Type>::GetValue(need_global);
+ }
+ explicit operator const Type&() const override {
+ if (this->use_global) {
+ return this->global;
+ }
+ return this->custom;
+ }
+
+ /**
+ * Like BasicSetting's SetValue, except value is clamped to the range of the setting. Sets the
+ * appropriate value depending on the global state.
+ *
+ * @param value The desired value
+ */
+ void SetValue(const Type& value) override {
+ const Type temp = std::clamp(value, this->minimum, this->maximum);
+ if (this->use_global) {
+ this->global = temp;
+ }
+ this->custom = temp;
+ }
+
+ /**
+ * Like BasicSetting's assignment overload, except value is clamped to the range of the setting.
+ * Uses the appropriate value depending on the global state.
+ *
+ * @param value The desired value
+ * @returns A reference to the setting's value
+ */
+ const Type& operator=(const Type& value) override {
+ const Type temp = std::clamp(value, this->minimum, this->maximum);
+ if (this->use_global) {
+ this->global = temp;
+ return this->global;
+ }
+ this->custom = temp;
+ return this->custom;
+ }
+};
+
+/**
* The InputSetting class allows for getting a reference to either the global or custom members.
* This is required as we cannot easily modify the values of user-defined types within containers
* using the SetValue() member function found in the Setting class. The primary purpose of this
@@ -284,13 +410,14 @@ struct Values {
BasicSetting<std::string> sink_id{"auto", "output_engine"};
BasicSetting<bool> audio_muted{false, "audio_muted"};
Setting<bool> enable_audio_stretching{true, "enable_audio_stretching"};
- Setting<u8> volume{100, "volume"};
+ RangedSetting<u8> volume{100, 0, 100, "volume"};
// Core
Setting<bool> use_multi_core{true, "use_multi_core"};
// Cpu
- Setting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, "cpu_accuracy"};
+ RangedSetting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
+ CPUAccuracy::Unsafe, "cpu_accuracy"};
// TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021
BasicSetting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"};
BasicSetting<bool> cpu_debug_mode{false, "cpu_debug_mode"};
@@ -312,8 +439,10 @@ struct Values {
Setting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"};
// Renderer
- Setting<RendererBackend> renderer_backend{RendererBackend::OpenGL, "backend"};
+ RangedSetting<RendererBackend> renderer_backend{
+ RendererBackend::OpenGL, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"};
BasicSetting<bool> renderer_debug{false, "debug"};
+ BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"};
BasicSetting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
BasicSetting<bool> disable_shader_loop_safety_checks{false,
"disable_shader_loop_safety_checks"};
@@ -322,26 +451,28 @@ struct Values {
Setting<u16> resolution_factor{1, "resolution_factor"};
// *nix platforms may have issues with the borderless windowed fullscreen mode.
// Default to exclusive fullscreen on these platforms for now.
- Setting<int> fullscreen_mode{
+ RangedSetting<FullscreenMode> fullscreen_mode{
#ifdef _WIN32
- 0,
+ FullscreenMode::Borderless,
#else
- 1,
+ FullscreenMode::Exclusive,
#endif
- "fullscreen_mode"};
- Setting<int> aspect_ratio{0, "aspect_ratio"};
- Setting<int> max_anisotropy{0, "max_anisotropy"};
+ FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"};
+ RangedSetting<int> aspect_ratio{0, 0, 3, "aspect_ratio"};
+ RangedSetting<int> max_anisotropy{0, 0, 4, "max_anisotropy"};
Setting<bool> use_speed_limit{true, "use_speed_limit"};
- Setting<u16> speed_limit{100, "speed_limit"};
+ RangedSetting<u16> speed_limit{100, 0, 9999, "speed_limit"};
Setting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
- Setting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, "gpu_accuracy"};
+ RangedSetting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal,
+ GPUAccuracy::Extreme, "gpu_accuracy"};
Setting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
Setting<bool> use_nvdec_emulation{true, "use_nvdec_emulation"};
Setting<bool> accelerate_astc{true, "accelerate_astc"};
Setting<bool> use_vsync{true, "use_vsync"};
- BasicSetting<u16> fps_cap{1000, "fps_cap"};
+ BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
- Setting<ShaderBackend> shader_backend{ShaderBackend::GLASM, "shader_backend"};
+ RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
+ ShaderBackend::SPIRV, "shader_backend"};
Setting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
Setting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
Setting<bool> use_caches_gc{false, "use_caches_gc"};
@@ -358,10 +489,10 @@ struct Values {
std::chrono::seconds custom_rtc_differential;
BasicSetting<s32> current_user{0, "current_user"};
- Setting<s32> language_index{1, "language_index"};
- Setting<s32> region_index{1, "region_index"};
- Setting<s32> time_zone_index{0, "time_zone_index"};
- Setting<s32> sound_index{1, "sound_index"};
+ RangedSetting<s32> language_index{1, 0, 17, "language_index"};
+ RangedSetting<s32> region_index{1, 0, 6, "region_index"};
+ RangedSetting<s32> time_zone_index{0, 0, 45, "time_zone_index"};
+ RangedSetting<s32> sound_index{1, 0, 2, "sound_index"};
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
@@ -378,7 +509,7 @@ struct Values {
"udp_input_servers"};
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
- BasicSetting<u8> mouse_panning_sensitivity{10, "mouse_panning_sensitivity"};
+ BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
std::string mouse_device;
MouseButtonsRaw mouse_buttons;
@@ -427,9 +558,10 @@ struct Values {
BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
BasicSetting<bool> use_dev_keys{false, "use_dev_keys"};
- // Services
+ // Network
BasicSetting<std::string> bcat_backend{"none", "bcat_backend"};
BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"};
+ BasicSetting<std::string> network_interface{std::string(), "network_interface"};
// WebService
BasicSetting<bool> enable_telemetry{true, "enable_telemetry"};
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index ad04df8ca..8430b9778 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -46,15 +46,13 @@ public:
ElementPtr* new_ptr = new ElementPtr();
write_ptr->next.store(new_ptr, std::memory_order_release);
write_ptr = new_ptr;
+ ++size;
- const size_t previous_size{size++};
-
- // Acquire the mutex and then immediately release it as a fence.
+ // cv_mutex must be held or else there will be a missed wakeup if the other thread is in the
+ // line before cv.wait
// TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
// See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
- if (previous_size == 0) {
- std::lock_guard lock{cv_mutex};
- }
+ std::lock_guard lock{cv_mutex};
cv.notify_one();
}
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
index 26db03fba..d7435a6e9 100644
--- a/src/common/uuid.cpp
+++ b/src/common/uuid.cpp
@@ -6,10 +6,64 @@
#include <fmt/format.h>
+#include "common/assert.h"
#include "common/uuid.h"
namespace Common {
+namespace {
+
+bool IsHexDigit(char c) {
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
+
+u8 HexCharToByte(char c) {
+ if (c >= '0' && c <= '9') {
+ return static_cast<u8>(c - '0');
+ }
+ if (c >= 'a' && c <= 'f') {
+ return static_cast<u8>(c - 'a' + 10);
+ }
+ if (c >= 'A' && c <= 'F') {
+ return static_cast<u8>(c - 'A' + 10);
+ }
+ ASSERT_MSG(false, "{} is not a hexadecimal digit!", c);
+ return u8{0};
+}
+
+} // Anonymous namespace
+
+u128 HexStringToU128(std::string_view hex_string) {
+ const size_t length = hex_string.length();
+
+ // Detect "0x" prefix.
+ const bool has_0x_prefix = length > 2 && hex_string[0] == '0' && hex_string[1] == 'x';
+ const size_t offset = has_0x_prefix ? 2 : 0;
+
+ // Check length.
+ if (length > 32 + offset) {
+ ASSERT_MSG(false, "hex_string has more than 32 hexadecimal characters!");
+ return INVALID_UUID;
+ }
+
+ u64 lo = 0;
+ u64 hi = 0;
+ for (size_t i = 0; i < length - offset; ++i) {
+ const char c = hex_string[length - 1 - i];
+ if (!IsHexDigit(c)) {
+ ASSERT_MSG(false, "{} is not a hexadecimal digit!", c);
+ return INVALID_UUID;
+ }
+ if (i < 16) {
+ lo |= u64{HexCharToByte(c)} << (i * 4);
+ }
+ if (i >= 16) {
+ hi |= u64{HexCharToByte(c)} << ((i - 16) * 4);
+ }
+ }
+ return u128{lo, hi};
+}
+
UUID UUID::Generate() {
std::random_device device;
std::mt19937 gen(device());
@@ -18,7 +72,7 @@ UUID UUID::Generate() {
}
std::string UUID::Format() const {
- return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
+ return fmt::format("{:016x}{:016x}", uuid[1], uuid[0]);
}
std::string UUID::FormatSwitch() const {
diff --git a/src/common/uuid.h b/src/common/uuid.h
index 0ffa37e7c..2353179d8 100644
--- a/src/common/uuid.h
+++ b/src/common/uuid.h
@@ -5,6 +5,7 @@
#pragma once
#include <string>
+#include <string_view>
#include "common/common_types.h"
@@ -12,12 +13,30 @@ namespace Common {
constexpr u128 INVALID_UUID{{0, 0}};
+/**
+ * Converts a hex string to a 128-bit unsigned integer.
+ *
+ * The hex string can be formatted in lowercase or uppercase, with or without the "0x" prefix.
+ *
+ * This function will assert and return INVALID_UUID under the following conditions:
+ * - If the hex string is more than 32 characters long
+ * - If the hex string contains non-hexadecimal characters
+ *
+ * @param hex_string Hexadecimal string
+ *
+ * @returns A 128-bit unsigned integer if successfully converted, INVALID_UUID otherwise.
+ */
+[[nodiscard]] u128 HexStringToU128(std::string_view hex_string);
+
struct UUID {
// UUIDs which are 0 are considered invalid!
u128 uuid;
UUID() = default;
constexpr explicit UUID(const u128& id) : uuid{id} {}
constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
+ explicit UUID(std::string_view hex_string) {
+ uuid = HexStringToU128(hex_string);
+ }
[[nodiscard]] constexpr explicit operator bool() const {
return uuid != INVALID_UUID;
@@ -50,3 +69,14 @@ struct UUID {
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
} // namespace Common
+
+namespace std {
+
+template <>
+struct hash<Common::UUID> {
+ size_t operator()(const Common::UUID& uuid) const noexcept {
+ return uuid.uuid[1] ^ uuid.uuid[0];
+ }
+};
+
+} // namespace std
diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h
index c2c9b6134..0ddf9b83e 100644
--- a/src/common/x64/xbyak_abi.h
+++ b/src/common/x64/xbyak_abi.h
@@ -6,7 +6,7 @@
#include <bitset>
#include <initializer_list>
-#include <xbyak.h>
+#include <xbyak/xbyak.h>
#include "common/assert.h"
namespace Common::X64 {
diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h
index df17f8cbe..44d2558f1 100644
--- a/src/common/x64/xbyak_util.h
+++ b/src/common/x64/xbyak_util.h
@@ -5,7 +5,7 @@
#pragma once
#include <type_traits>
-#include <xbyak.h>
+#include <xbyak/xbyak.h>
#include "common/x64/xbyak_abi.h"
namespace Common::X64 {