diff --git a/externals/boost b/externals/boost index 32f5cd8eb..700ae2eff 160000 --- a/externals/boost +++ b/externals/boost @@ -1 +1 @@ -Subproject commit 32f5cd8ebb35b29ccb3860861bd285f80804bc85 +Subproject commit 700ae2eff3134792f09cea2b051666688b1d5b97 diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 8c3273279..7efdeefcd 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -7,8 +7,8 @@ #include #include #include - #include "common/file_util.h" +#include "common/logging/backend.h" #include "common/logging/log.h" #include "common/param_package.h" #include "common/settings.h" @@ -260,6 +260,12 @@ void Config::ReadValues() { // Miscellaneous ReadSetting("Miscellaneous", Settings::values.log_filter); + // Apply the log_filter setting as the logger has already been initialized + // and doesn't pick up the filter on its own. + Common::Log::Filter filter; + filter.ParseFilterString(Settings::values.log_filter.GetValue()); + Common::Log::SetGlobalFilter(filter); + // Debugging Settings::values.record_frame_times = sdl2_config->GetBoolean("Debugging", "record_frame_times", false); diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index c2aa048a8..e737d19ee 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -180,12 +180,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) return JNI_ERR; - // Initialize Logger - Log::Filter log_filter; - log_filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Log::SetGlobalFilter(log_filter); - Log::AddBackend(std::make_unique()); - // Initialize misc classes s_savestate_info_class = reinterpret_cast( env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo"))); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 228db6d86..fac00bc2b 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -141,7 +141,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { app_loader->ReadProgramId(program_id); GameSettings::LoadOverrides(program_id); } - Settings::Apply(); + system.ApplySettings(); Settings::LogSettings(); Camera::RegisterFactory("image", std::make_unique()); @@ -438,10 +438,8 @@ void Java_org_citra_citra_1emu_NativeLibrary_CreateConfigFile(JNIEnv* env, void Java_org_citra_citra_1emu_NativeLibrary_CreateLogFile(JNIEnv* env, [[maybe_unused]] jclass clazz) { - Log::RemoveBackend(Log::FileBackend::Name()); - FileUtil::CreateFullPath(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); - Log::AddBackend(std::make_unique( - FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + LOG_FILE)); + Common::Log::Initialize(); + Common::Log::Start(); LOG_INFO(Frontend, "Logging backend initialised"); } @@ -474,7 +472,7 @@ void Java_org_citra_citra_1emu_NativeLibrary_ReloadSettings(JNIEnv* env, GameSettings::LoadOverrides(program_id); } - Settings::Apply(); + system.ApplySettings(); } jstring Java_org_citra_citra_1emu_NativeLibrary_GetUserSetting(JNIEnv* env, diff --git a/src/audio_core/hle/wmf_decoder_utils.h b/src/audio_core/hle/wmf_decoder_utils.h index 9925b83ab..77a12bef5 100644 --- a/src/audio_core/hle/wmf_decoder_utils.h +++ b/src/audio_core/hle/wmf_decoder_utils.h @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #pragma once +#include #include #include #include diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index b8ebb1ff1..8397992a2 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -174,23 +174,11 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) { std::cout << std::endl << "* " << message << std::endl << std::endl; } -static void InitializeLogging() { - Log::Filter log_filter(Log::Level::Debug); - log_filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Log::SetGlobalFilter(log_filter); - - Log::AddBackend(std::make_unique()); - - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); -#ifdef _WIN32 - Log::AddBackend(std::make_unique()); -#endif -} - /// Application entry point int main(int argc, char** argv) { + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); + Common::Log::Start(); Common::DetachedTasks detached_tasks; Config config; int option_index = 0; @@ -201,8 +189,6 @@ int main(int argc, char** argv) { std::string movie_play; std::string dump_video; - InitializeLogging(); - char* endarg; #ifdef _WIN32 int argc_w; @@ -357,7 +343,7 @@ int main(int argc, char** argv) { // Apply the command line arguments Settings::values.gdbstub_port = gdb_port; Settings::values.use_gdbstub = use_gdbstub; - Settings::Apply(); + system.ApplySettings(); // Register frontend applets Frontend::RegisterDefaultApplets(); diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 09fda6ca5..5edaf42da 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -11,8 +11,8 @@ #include "citra/config.h" #include "citra/default_ini.h" #include "common/file_util.h" +#include "common/logging/backend.h" #include "common/logging/log.h" -#include "common/param_package.h" #include "common/settings.h" #include "core/hle/service/service.h" #include "input_common/main.h" @@ -299,6 +299,12 @@ void Config::ReadValues() { // Miscellaneous ReadSetting("Miscellaneous", Settings::values.log_filter); + // Apply the log_filter setting as the logger has already been initialized + // and doesn't pick up the filter on its own. + Common::Log::Filter filter; + filter.ParseFilterString(Settings::values.log_filter.GetValue()); + Common::Log::SetGlobalFilter(filter); + // Debugging Settings::values.record_frame_times = sdl2_config->GetBoolean("Debugging", "record_frame_times", false); diff --git a/src/citra_qt/configuration/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index a75592fe0..d0b244133 100644 --- a/src/citra_qt/configuration/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -9,7 +9,7 @@ #include "citra_qt/debugger/console.h" #include "citra_qt/uisettings.h" #include "common/file_util.h" -#include "common/logging/log.h" +#include "common/logging/backend.h" #include "common/settings.h" #include "core/core.h" #include "ui_configure_debug.h" @@ -89,9 +89,9 @@ void ConfigureDebug::ApplyConfiguration() { UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); Debugger::ToggleConsole(); - Log::Filter filter; + Common::Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Log::SetGlobalFilter(filter); + Common::Log::SetGlobalFilter(filter); Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked(); Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked(); diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 1b00ddbe3..254a63641 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -8,10 +8,13 @@ #include "citra_qt/configuration/configure_dialog.h" #include "citra_qt/hotkeys.h" #include "common/settings.h" +#include "core/core.h" #include "ui_configure.h" -ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, bool enable_web_config) - : QDialog(parent), ui(std::make_unique()), registry(registry) { +ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Core::System& system_, + bool enable_web_config) + : QDialog(parent), ui{std::make_unique()}, registry{registry_}, + system{system_} { Settings::SetConfiguringGlobal(true); ui->setupUi(this); @@ -68,7 +71,7 @@ void ConfigureDialog::ApplyConfiguration() { ui->webTab->ApplyConfiguration(); ui->uiTab->ApplyConfiguration(); ui->storageTab->ApplyConfiguration(); - Settings::Apply(); + system.ApplySettings(); Settings::LogSettings(); } diff --git a/src/citra_qt/configuration/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h index 3631ab20a..23b32a197 100644 --- a/src/citra_qt/configuration/configure_dialog.h +++ b/src/citra_qt/configuration/configure_dialog.h @@ -13,11 +13,15 @@ namespace Ui { class ConfigureDialog; } +namespace Core { +class System; +} + class ConfigureDialog : public QDialog { Q_OBJECT public: - explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, + explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, Core::System& system, bool enable_web_config = true); ~ConfigureDialog() override; @@ -37,4 +41,5 @@ private: std::unique_ptr ui; HotkeyRegistry& registry; + Core::System& system; }; diff --git a/src/citra_qt/configuration/configure_per_game.cpp b/src/citra_qt/configuration/configure_per_game.cpp index fa11e3240..a0163dfa7 100644 --- a/src/citra_qt/configuration/configure_per_game.cpp +++ b/src/citra_qt/configuration/configure_per_game.cpp @@ -102,7 +102,7 @@ void ConfigurePerGame::ApplyConfiguration() { audio_tab->ApplyConfiguration(); debug_tab->ApplyConfiguration(); - Settings::Apply(); + system.ApplySettings(); Settings::LogSettings(); game_config->Save(); diff --git a/src/citra_qt/debugger/console.cpp b/src/citra_qt/debugger/console.cpp index eecf86048..8187b0b1d 100644 --- a/src/citra_qt/debugger/console.cpp +++ b/src/citra_qt/debugger/console.cpp @@ -21,6 +21,7 @@ void ToggleConsole() { console_shown = UISettings::values.show_console.GetValue(); } + using namespace Common::Log; #ifdef _WIN32 FILE* temp; if (UISettings::values.show_console) { @@ -29,24 +30,20 @@ void ToggleConsole() { freopen_s(&temp, "CONIN$", "r", stdin); freopen_s(&temp, "CONOUT$", "w", stdout); freopen_s(&temp, "CONOUT$", "w", stderr); - Log::AddBackend(std::make_unique()); + SetColorConsoleBackendEnabled(true); } } else { if (FreeConsole()) { // In order to close the console, we have to also detach the streams on it. // Just redirect them to NUL if there is no console window - Log::RemoveBackend(Log::ColorConsoleBackend::Name()); + SetColorConsoleBackendEnabled(false); freopen_s(&temp, "NUL", "r", stdin); freopen_s(&temp, "NUL", "w", stdout); freopen_s(&temp, "NUL", "w", stderr); } } #else - if (UISettings::values.show_console) { - Log::AddBackend(std::make_unique()); - } else { - Log::RemoveBackend(Log::ColorConsoleBackend::Name()); - } + SetColorConsoleBackendEnabled(UISettings::values.show_console.GetValue()); #endif } } // namespace Debugger diff --git a/src/citra_qt/dumping/dumping_dialog.cpp b/src/citra_qt/dumping/dumping_dialog.cpp index bbe69f245..cd05d87d9 100644 --- a/src/citra_qt/dumping/dumping_dialog.cpp +++ b/src/citra_qt/dumping/dumping_dialog.cpp @@ -8,10 +8,11 @@ #include "citra_qt/dumping/options_dialog.h" #include "citra_qt/uisettings.h" #include "common/settings.h" +#include "core/core.h" #include "ui_dumping_dialog.h" -DumpingDialog::DumpingDialog(QWidget* parent) - : QDialog(parent), ui(std::make_unique()) { +DumpingDialog::DumpingDialog(QWidget* parent, Core::System& system_) + : QDialog(parent), ui{std::make_unique()}, system{system_} { ui->setupUi(this); @@ -216,5 +217,5 @@ void DumpingDialog::ApplyConfiguration() { Settings::values.audio_encoder_options = ui->audioEncoderOptionsLineEdit->text().toStdString(); Settings::values.audio_bitrate = ui->audioBitrateSpinBox->value(); UISettings::values.video_dumping_path = last_path; - Settings::Apply(); + system.ApplySettings(); } diff --git a/src/citra_qt/dumping/dumping_dialog.h b/src/citra_qt/dumping/dumping_dialog.h index 284f215c3..f0153f179 100644 --- a/src/citra_qt/dumping/dumping_dialog.h +++ b/src/citra_qt/dumping/dumping_dialog.h @@ -10,13 +10,17 @@ namespace Ui { class DumpingDialog; } +namespace Core { +class System; +} + class QLineEdit; class DumpingDialog : public QDialog { Q_OBJECT public: - explicit DumpingDialog(QWidget* parent); + explicit DumpingDialog(QWidget* parent, Core::System& system); ~DumpingDialog() override; QString GetFilePath() const; @@ -32,6 +36,7 @@ private: QLineEdit* line_edit); std::unique_ptr ui; + Core::System& system; QString last_path; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index f1f8c7bb6..b764459eb 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -144,25 +144,12 @@ void GMainWindow::ShowTelemetryCallout() { "

Would you like to share your usage data with us?"); if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) == QMessageBox::Yes) { NetSettings::values.enable_telemetry = true; - Settings::Apply(); + system.ApplySettings(); } } const int GMainWindow::max_recent_files_item; -static void InitializeLogging() { - Log::Filter log_filter; - log_filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Log::SetGlobalFilter(log_filter); - - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); -#ifdef _WIN32 - Log::AddBackend(std::make_unique()); -#endif -} - static QString PrettyProductName() { #ifdef _WIN32 // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 @@ -188,9 +175,10 @@ static QString PrettyProductName() { GMainWindow::GMainWindow(Core::System& system_) : ui{std::make_unique()}, system{system_}, movie{Core::Movie::GetInstance()}, config{std::make_unique()}, emu_thread{nullptr} { - InitializeLogging(); + Common::Log::Initialize(); + Common::Log::Start(); + Debugger::ToggleConsole(); - Settings::LogSettings(); // register types to use in slots and signals qRegisterMetaType("std::size_t"); @@ -1193,12 +1181,13 @@ void GMainWindow::BootGame(const QString& filename) { const std::string config_file_name = title_id == 0 ? name : fmt::format("{:016X}", title_id); Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); - Settings::Apply(); + system.ApplySettings(); LOG_INFO(Frontend, "Using per game config file for title id {}", config_file_name); - Settings::LogSettings(); } + Settings::LogSettings(); + // Save configurations UpdateUISettings(); game_list->SaveInterfaceLayout(); @@ -1936,7 +1925,7 @@ void GMainWindow::ChangeScreenLayout() { } Settings::values.layout_option = new_layout; - Settings::Apply(); + system.ApplySettings(); UpdateSecondaryWindowVisibility(); } @@ -1964,18 +1953,18 @@ void GMainWindow::ToggleScreenLayout() { Settings::values.layout_option = new_layout; SyncMenuUISettings(); - Settings::Apply(); + system.ApplySettings(); UpdateSecondaryWindowVisibility(); } void GMainWindow::OnSwapScreens() { Settings::values.swap_screen = ui->action_Screen_Layout_Swap_Screens->isChecked(); - Settings::Apply(); + system.ApplySettings(); } void GMainWindow::OnRotateScreens() { Settings::values.upright_screen = ui->action_Screen_Layout_Upright_Screens->isChecked(); - Settings::Apply(); + system.ApplySettings(); } void GMainWindow::TriggerSwapScreens() { @@ -2014,7 +2003,7 @@ void GMainWindow::OnLoadState() { void GMainWindow::OnConfigure() { game_list->SetDirectoryWatcherEnabled(false); Settings::SetConfiguringGlobal(true); - ConfigureDialog configureDialog(this, hotkey_registry, + ConfigureDialog configureDialog(this, hotkey_registry, system, !multiplayer_state->IsHostingPublicRoom()); connect(&configureDialog, &ConfigureDialog::LanguageChanged, this, &GMainWindow::OnLanguageChanged); @@ -2336,7 +2325,7 @@ void GMainWindow::OnOpenFFmpeg() { #endif void GMainWindow::OnStartVideoDumping() { - DumpingDialog dialog(this); + DumpingDialog dialog(this, system); if (dialog.exec() != QDialog::DialogCode::Accepted) { ui->action_Dump_Video->setChecked(false); return; @@ -2938,7 +2927,7 @@ int main(int argc, char* argv[]) { // generating shaders setlocale(LC_ALL, "C"); - Core::System& system = Core::System::GetInstance(); + auto& system{Core::System::GetInstance()}; GMainWindow main_window(system); // Register frontend applets diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5162dea9b..ddf715f2f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -57,6 +57,7 @@ add_library(citra_common STATIC detached_tasks.h bit_field.h bit_set.h + bounded_threadsafe_queue.h cityhash.cpp cityhash.h color.h @@ -85,8 +86,10 @@ add_library(citra_common STATIC logging/filter.h logging/formatter.h logging/log.h + logging/log_entry.h logging/text_formatter.cpp logging/text_formatter.h + logging/types.h math_util.h memory_detect.cpp memory_detect.h @@ -173,3 +176,6 @@ endif() if (CITRA_USE_PRECOMPILED_HEADERS) target_precompile_headers(citra_common PRIVATE precompiled_headers.h) endif() +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID STREQUAL GNU) + target_link_libraries(citra_common PRIVATE backtrace dl) +endif() diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h new file mode 100644 index 000000000..15c9c1db0 --- /dev/null +++ b/src/common/bounded_threadsafe_queue.h @@ -0,0 +1,251 @@ +// Copyright 2023 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "common/polyfill_thread.h" + +namespace Common { + +namespace detail { +constexpr size_t DefaultCapacity = 0x1000; +} // namespace detail + +template +class SPSCQueue { + static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two."); + +public: + template + bool TryEmplace(Args&&... args) { + return Emplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) { + Emplace(std::forward(args)...); + } + + bool TryPop(T& t) { + return Pop(t); + } + + void PopWait(T& t) { + Pop(t); + } + + void PopWait(T& t, std::stop_token stop_token) { + Pop(t, stop_token); + } + + T PopWait() { + T t; + Pop(t); + return t; + } + + T PopWait(std::stop_token stop_token) { + T t; + Pop(t, stop_token); + return t; + } + +private: + enum class PushMode { + Try, + Wait, + Count, + }; + + enum class PopMode { + Try, + Wait, + WaitWithStopToken, + Count, + }; + + template + bool Emplace(Args&&... args) { + const size_t write_index = m_write_index.load(std::memory_order::relaxed); + + if constexpr (Mode == PushMode::Try) { + // Check if we have free slots to write to. + if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) { + return false; + } + } else if constexpr (Mode == PushMode::Wait) { + // Wait until we have free slots to write to. + std::unique_lock lock{producer_cv_mutex}; + producer_cv.wait(lock, [this, write_index] { + return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity; + }); + } else { + static_assert(Mode < PushMode::Count, "Invalid PushMode."); + } + + // Determine the position to write to. + const size_t pos = write_index % Capacity; + + // Emplace into the queue. + new (std::addressof(m_data[pos])) T(std::forward(args)...); + + // Increment the write index. + ++m_write_index; + + // Notify the consumer that we have pushed into the queue. + std::scoped_lock lock{consumer_cv_mutex}; + consumer_cv.notify_one(); + + return true; + } + + template + bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) { + const size_t read_index = m_read_index.load(std::memory_order::relaxed); + + if constexpr (Mode == PopMode::Try) { + // Check if the queue is empty. + if (read_index == m_write_index.load(std::memory_order::acquire)) { + return false; + } + } else if constexpr (Mode == PopMode::Wait) { + // Wait until the queue is not empty. + std::unique_lock lock{consumer_cv_mutex}; + consumer_cv.wait(lock, [this, read_index] { + return read_index != m_write_index.load(std::memory_order::acquire); + }); + } else if constexpr (Mode == PopMode::WaitWithStopToken) { + // Wait until the queue is not empty. + std::unique_lock lock{consumer_cv_mutex}; + Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] { + return read_index != m_write_index.load(std::memory_order::acquire); + }); + if (stop_token.stop_requested()) { + return false; + } + } else { + static_assert(Mode < PopMode::Count, "Invalid PopMode."); + } + + // Determine the position to read from. + const size_t pos = read_index % Capacity; + + // Pop the data off the queue, moving it. + t = std::move(m_data[pos]); + + // Increment the read index. + ++m_read_index; + + // Notify the producer that we have popped off the queue. + std::scoped_lock lock{producer_cv_mutex}; + producer_cv.notify_one(); + + return true; + } + + alignas(128) std::atomic_size_t m_read_index{0}; + alignas(128) std::atomic_size_t m_write_index{0}; + + std::array m_data; + + std::condition_variable_any producer_cv; + std::mutex producer_cv_mutex; + std::condition_variable_any consumer_cv; + std::mutex consumer_cv_mutex; +}; + +template +class MPSCQueue { +public: + template + bool TryEmplace(Args&&... args) { + std::scoped_lock lock{write_mutex}; + return spsc_queue.TryEmplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) { + std::scoped_lock lock{write_mutex}; + spsc_queue.EmplaceWait(std::forward(args)...); + } + + bool TryPop(T& t) { + return spsc_queue.TryPop(t); + } + + void PopWait(T& t) { + spsc_queue.PopWait(t); + } + + void PopWait(T& t, std::stop_token stop_token) { + spsc_queue.PopWait(t, stop_token); + } + + T PopWait() { + return spsc_queue.PopWait(); + } + + T PopWait(std::stop_token stop_token) { + return spsc_queue.PopWait(stop_token); + } + +private: + SPSCQueue spsc_queue; + std::mutex write_mutex; +}; + +template +class MPMCQueue { +public: + template + bool TryEmplace(Args&&... args) { + std::scoped_lock lock{write_mutex}; + return spsc_queue.TryEmplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) { + std::scoped_lock lock{write_mutex}; + spsc_queue.EmplaceWait(std::forward(args)...); + } + + bool TryPop(T& t) { + std::scoped_lock lock{read_mutex}; + return spsc_queue.TryPop(t); + } + + void PopWait(T& t) { + std::scoped_lock lock{read_mutex}; + spsc_queue.PopWait(t); + } + + void PopWait(T& t, std::stop_token stop_token) { + std::scoped_lock lock{read_mutex}; + spsc_queue.PopWait(t, stop_token); + } + + T PopWait() { + std::scoped_lock lock{read_mutex}; + return spsc_queue.PopWait(); + } + + T PopWait(std::stop_token stop_token) { + std::scoped_lock lock{read_mutex}; + return spsc_queue.PopWait(stop_token); + } + +private: + SPSCQueue spsc_queue; + std::mutex write_mutex; + std::mutex read_mutex; +}; + +} // namespace Common diff --git a/src/common/dynamic_library/fdk-aac.cpp b/src/common/dynamic_library/fdk-aac.cpp index 20dedfd07..48f8ca2cc 100644 --- a/src/common/dynamic_library/fdk-aac.cpp +++ b/src/common/dynamic_library/fdk-aac.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include + #include "common/dynamic_library/dynamic_library.h" #include "common/dynamic_library/fdk-aac.h" #include "common/logging/log.h" diff --git a/src/common/dynamic_library/ffmpeg.cpp b/src/common/dynamic_library/ffmpeg.cpp index c9c1af33a..709f887a5 100644 --- a/src/common/dynamic_library/ffmpeg.cpp +++ b/src/common/dynamic_library/ffmpeg.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include + #include "common/dynamic_library/dynamic_library.h" #include "common/dynamic_library/ffmpeg.h" #include "common/logging/log.h" diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 3a68e7349..3fa335d35 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/common_funcs.h" #include "common/common_paths.h" diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index de5ce7b41..5c80cdff0 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -2,304 +2,444 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include #include -#include -#include -#include -#include -#include + +#include + #ifdef _WIN32 #include // For _SH_DENYWR #include // For OutputDebugStringW #else #define _SH_DENYWR 0 #endif -#include "common/assert.h" + +#if defined(__linux__) && defined(__GNUG__) && !defined(__clang__) +#define BOOST_STACKTRACE_USE_BACKTRACE +#include +#undef BOOST_STACKTRACE_USE_BACKTRACE +#include +#define CITRA_LINUX_GCC_BACKTRACE +#endif + +#include "common/bounded_threadsafe_queue.h" +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/literals.h" #include "common/logging/backend.h" #include "common/logging/log.h" +#include "common/logging/log_entry.h" #include "common/logging/text_formatter.h" +#include "common/polyfill_thread.h" +#include "common/settings.h" #include "common/string_util.h" -#include "common/threadsafe_queue.h" +#include "common/thread.h" -namespace Log { +namespace Common::Log { -Filter filter; -void SetGlobalFilter(const Filter& f) { - filter = f; +namespace { + +/** + * Interface for logging backends. + */ +class Backend { +public: + 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); + } + } + + void Flush() override { + // stderr shouldn't be buffered + } + + void EnableForStacktrace() override { + enabled = true; + } + + 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::string& 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(FileUtil::Delete(old_filename)); + static_cast(FileUtil::Rename(filename, old_filename)); + + // _SH_DENYWR allows read only access to the file for other programs. + // It is #defined to 0 on other platforms + file = std::make_unique(filename, "w", _SH_DENYWR); + } + + ~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 = 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; + } + +private: + std::unique_ptr 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 Flush() override {} + + void EnableForStacktrace() override {} +}; + +#ifdef ANDROID +/** + * Backend that writes to the Android logcat + */ +class LogcatBackend : public Backend { +public: + explicit LogcatBackend() = default; + + ~LogcatBackend() override = default; + + void Write(const Entry& entry) override { + PrintMessageToLogcat(entry); + } + + void Flush() override {} + + void EnableForStacktrace() override {} +}; +#endif + +bool initialization_in_progress_suppress_logging = true; + +#ifdef CITRA_LINUX_GCC_BACKTRACE +[[noreturn]] void SleepForever() { + while (true) { + pause(); + } } +#endif + /** * Static state as a singleton. */ class Impl { public: static Impl& Instance() { - static Impl backend; - return backend; + if (!instance) { + throw std::runtime_error("Using Logging instance before its initialization"); + } + return *instance; } - Impl(Impl const&) = delete; - const Impl& operator=(Impl const&) = delete; + static void Initialize(std::string_view log_file) { + if (instance) { + LOG_WARNING(Log, "Reinitializing logging backend"); + return; + } + initialization_in_progress_suppress_logging = true; + const auto& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + void(FileUtil::CreateDir(log_dir)); + Filter filter; + filter.ParseFilterString(Settings::values.log_filter.GetValue()); + instance = std::unique_ptr( + new Impl(fmt::format("{}{}", log_dir, log_file), filter), Deleter); + initialization_in_progress_suppress_logging = false; + } + + static void Start() { + instance->StartBackendThread(); + } + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + Impl(Impl&&) = delete; + Impl& operator=(Impl&&) = delete; + + void SetGlobalFilter(const Filter& f) { + filter = f; + } + + 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) { - message_queue.Push( + if (!filter.CheckMessage(log_class, log_level)) { + return; + } + message_queue.EmplaceWait( CreateEntry(log_class, log_level, filename, line_num, function, std::move(message))); } - void AddBackend(std::unique_ptr backend) { - std::lock_guard lock{writing_mutex}; - backends.push_back(std::move(backend)); - } - - void RemoveBackend(std::string_view backend_name) { - std::lock_guard lock{writing_mutex}; - const auto it = - std::remove_if(backends.begin(), backends.end(), - [&backend_name](const auto& i) { return backend_name == i->GetName(); }); - backends.erase(it, backends.end()); - } - - 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(); - } - 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::string& file_backend_filename, const Filter& filter_) + : filter{filter_}, file_backend{file_backend_filename} { +#ifdef CITRA_LINUX_GCC_BACKTRACE + int waker_pipefd[2]; + int done_printing_pipefd[2]; + if (pipe2(waker_pipefd, O_CLOEXEC) || pipe2(done_printing_pipefd, O_CLOEXEC)) { + abort(); + } + backtrace_thread_waker_fd = waker_pipefd[1]; + backtrace_done_printing_fd = done_printing_pipefd[0]; + std::thread([this, wait_fd = waker_pipefd[0], done_fd = done_printing_pipefd[1]] { + Common::SetCurrentThreadName("citra:Crash"); + for (u8 ignore = 0; read(wait_fd, &ignore, 1) != 1;) + ; + const int sig = received_signal; + if (sig <= 0) { + abort(); } + backend_thread.request_stop(); + backend_thread.join(); + const auto signal_entry = + CreateEntry(Class::Log, Level::Critical, "?", 0, "?", + fmt::vformat("Received signal {}", fmt::make_format_args(sig))); + ForEachBackend([&signal_entry](Backend& backend) { + backend.EnableForStacktrace(); + backend.Write(signal_entry); + }); + const auto backtrace = + boost::stacktrace::stacktrace::from_dump(backtrace_storage.data(), 4096); + for (const auto& frame : backtrace.as_vector()) { + auto line = boost::stacktrace::detail::to_string(&frame, 1); + if (line.empty()) { + abort(); + } + line.pop_back(); // Remove newline + const auto frame_entry = + CreateEntry(Class::Log, Level::Critical, "?", 0, "?", std::move(line)); + ForEachBackend([&frame_entry](Backend& backend) { backend.Write(frame_entry); }); + } + using namespace std::literals; + const auto rip_entry = CreateEntry(Class::Log, Level::Critical, "?", 0, "?", "RIP"s); + ForEachBackend([&rip_entry](Backend& backend) { + backend.Write(rip_entry); + backend.Flush(); + }); + for (const u8 anything = 0; write(done_fd, &anything, 1) != 1;) + ; + // Abort on original thread to help debugging + SleepForever(); + }).detach(); + signal(SIGSEGV, &HandleSignal); + signal(SIGABRT, &HandleSignal); +#endif + } - // 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. - constexpr int MAX_LOGS_TO_WRITE = 100; - int logs_written = 0; - while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) { - write_logs(entry); + ~Impl() { +#ifdef CITRA_LINUX_GCC_BACKTRACE + if (int zero_or_ignore = 0; + !received_signal.compare_exchange_strong(zero_or_ignore, SIGKILL)) { + SleepForever(); + } +#endif + } + + void StartBackendThread() { + backend_thread = std::jthread([this](std::stop_token stop_token) { + Common::SetCurrentThreadName("citra:Log"); + Entry entry; + const auto write_logs = [this, &entry]() { + ForEachBackend([&entry](Backend& backend) { backend.Write(entry); }); + }; + while (!stop_token.stop_requested()) { + message_queue.PopWait(entry, stop_token); + if (entry.filename != nullptr) { + 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.TryPop(entry)) { + write_logs(); } }); } - ~Impl() { - Entry entry; - entry.final_entry = true; - message_queue.Push(entry); - backend_thread.join(); - } - Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, - const char* function, std::string message) const { + const char* function, std::string&& message) const { using std::chrono::duration_cast; + using std::chrono::microseconds; using std::chrono::steady_clock; - Entry entry; - entry.timestamp = - duration_cast(steady_clock::now() - time_origin); - entry.log_class = log_class; - entry.log_level = log_level; - entry.filename = filename; - entry.line_num = line_nr; - entry.function = function; - entry.message = std::move(message); - - return entry; + return { + .timestamp = duration_cast(steady_clock::now() - time_origin), + .log_class = log_class, + .log_level = log_level, + .filename = filename, + .line_num = line_nr, + .function = function, + .message = std::move(message), + }; } - std::mutex writing_mutex; - std::thread backend_thread; - std::vector> backends; - Common::MPSCQueue message_queue; - Filter filter; - std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; -}; - -void ConsoleBackend::Write(const Entry& entry) { - PrintMessage(entry); -} - -void ColorConsoleBackend::Write(const Entry& entry) { - PrintColoredMessage(entry); -} - -void LogcatBackend::Write(const Entry& entry) { - PrintMessageToLogcat(entry); -} - -FileBackend::FileBackend(const std::string& filename) : bytes_written(0) { - if (FileUtil::Exists(filename + ".old.txt")) { - FileUtil::Delete(filename + ".old.txt"); - } - if (FileUtil::Exists(filename)) { - FileUtil::Rename(filename, filename + ".old.txt"); - } - - // _SH_DENYWR allows read only access to the file for other programs. - // It is #defined to 0 on other platforms - file = FileUtil::IOFile(filename, "w", _SH_DENYWR); -} - -void FileBackend::Write(const Entry& entry) { - // prevent logs from going over the maximum size (in case its spamming and the user doesn't - // know) - constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; - if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) { - return; - } - bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); - if (entry.log_level >= Level::Error) { - file.Flush(); - } -} - -void DebuggerBackend::Write(const Entry& entry) { -#ifdef _WIN32 - ::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); + void ForEachBackend(auto lambda) { + lambda(static_cast(debugger_backend)); + lambda(static_cast(color_console_backend)); + lambda(static_cast(file_backend)); +#ifdef ANDROID + lambda(static_cast(lc_backend)); #endif -} - -/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. -#define ALL_LOG_CLASSES() \ - CLS(Log) \ - CLS(Common) \ - SUB(Common, Filesystem) \ - SUB(Common, Memory) \ - CLS(Core) \ - SUB(Core, ARM11) \ - SUB(Core, Timing) \ - SUB(Core, Cheats) \ - CLS(Config) \ - CLS(Debug) \ - SUB(Debug, Emulated) \ - SUB(Debug, GPU) \ - SUB(Debug, Breakpoint) \ - SUB(Debug, GDBStub) \ - CLS(Kernel) \ - SUB(Kernel, SVC) \ - CLS(Applet) \ - SUB(Applet, SWKBD) \ - CLS(Service) \ - SUB(Service, SRV) \ - SUB(Service, FRD) \ - SUB(Service, FS) \ - SUB(Service, ERR) \ - SUB(Service, APT) \ - SUB(Service, BOSS) \ - SUB(Service, GSP) \ - SUB(Service, AC) \ - SUB(Service, AM) \ - SUB(Service, PTM) \ - SUB(Service, LDR) \ - SUB(Service, MIC) \ - SUB(Service, NDM) \ - SUB(Service, NFC) \ - SUB(Service, NIM) \ - SUB(Service, NS) \ - SUB(Service, NWM) \ - SUB(Service, CAM) \ - SUB(Service, CECD) \ - SUB(Service, CFG) \ - SUB(Service, CSND) \ - SUB(Service, DSP) \ - SUB(Service, DLP) \ - SUB(Service, HID) \ - SUB(Service, HTTP) \ - SUB(Service, SOC) \ - SUB(Service, IR) \ - SUB(Service, Y2R) \ - SUB(Service, PS) \ - SUB(Service, PLGLDR) \ - CLS(HW) \ - SUB(HW, Memory) \ - SUB(HW, LCD) \ - SUB(HW, GPU) \ - SUB(HW, AES) \ - CLS(Frontend) \ - CLS(Render) \ - SUB(Render, Software) \ - SUB(Render, OpenGL) \ - SUB(Render, Vulkan) \ - CLS(Audio) \ - SUB(Audio, DSP) \ - SUB(Audio, Sink) \ - CLS(Input) \ - CLS(Network) \ - CLS(Movie) \ - CLS(Loader) \ - CLS(WebService) \ - CLS(RPC_Server) - -// GetClassName is a macro defined by Windows.h, grrr... -const char* GetLogClassName(Class log_class) { - switch (log_class) { -#define CLS(x) \ - case Class::x: \ - return #x; -#define SUB(x, y) \ - case Class::x##_##y: \ - return #x "." #y; - ALL_LOG_CLASSES() -#undef CLS -#undef SUB - case Class::Count: - default: - break; } - UNREACHABLE(); -} -const char* GetLevelName(Level log_level) { -#define LVL(x) \ - case Level::x: \ - return #x - switch (log_level) { - LVL(Trace); - LVL(Debug); - LVL(Info); - LVL(Warning); - LVL(Error); - LVL(Critical); - case Level::Count: - default: - break; + static void Deleter(Impl* ptr) { + delete ptr; } -#undef LVL - UNREACHABLE(); + +#ifdef CITRA_LINUX_GCC_BACKTRACE + [[noreturn]] static void HandleSignal(int sig) { + signal(SIGABRT, SIG_DFL); + signal(SIGSEGV, SIG_DFL); + if (sig <= 0) { + abort(); + } + instance->InstanceHandleSignal(sig); + } + + [[noreturn]] void InstanceHandleSignal(int sig) { + if (int zero_or_ignore = 0; !received_signal.compare_exchange_strong(zero_or_ignore, sig)) { + if (received_signal == SIGKILL) { + abort(); + } + SleepForever(); + } + // Don't restart like boost suggests. We want to append to the log file and not lose dynamic + // symbols. This may segfault if it unwinds outside C/C++ code but we'll just have to fall + // back to core dumps. + boost::stacktrace::safe_dump_to(backtrace_storage.data(), 4096); + std::atomic_thread_fence(std::memory_order_seq_cst); + for (const int anything = 0; write(backtrace_thread_waker_fd, &anything, 1) != 1;) + ; + for (u8 ignore = 0; read(backtrace_done_printing_fd, &ignore, 1) != 1;) + ; + abort(); + } +#endif + + static inline std::unique_ptr instance{nullptr, Deleter}; + + Filter filter; + DebuggerBackend debugger_backend{}; + ColorConsoleBackend color_console_backend{}; + FileBackend file_backend; +#ifdef ANDROID + LogcatBackend lc_backend{}; +#endif + + MPSCQueue message_queue{}; + std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; + std::jthread backend_thread; + +#ifdef CITRA_LINUX_GCC_BACKTRACE + std::atomic_int received_signal{0}; + std::array backtrace_storage{}; + int backtrace_thread_waker_fd; + int backtrace_done_printing_fd; +#endif +}; +} // namespace + +void Initialize(std::string_view log_file) { + Impl::Initialize(log_file.empty() ? LOG_FILE : log_file); } -void AddBackend(std::unique_ptr backend) { - Impl::Instance().AddBackend(std::move(backend)); +void Start() { + Impl::Start(); } -void RemoveBackend(std::string_view backend_name) { - Impl::Instance().RemoveBackend(backend_name); +void DisableLoggingInTests() { + initialization_in_progress_suppress_logging = true; } -Backend* GetBackend(std::string_view backend_name) { - return Impl::Instance().GetBackend(backend_name); +void SetGlobalFilter(const Filter& filter) { + Impl::Instance().SetGlobalFilter(filter); +} + +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(); - 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 Log +} // namespace Common::Log diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 907c6a297..19a5ef0f4 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -4,149 +4,24 @@ #pragma once -#include -#include -#include #include -#include "common/file_util.h" #include "common/logging/filter.h" -#include "common/logging/log.h" -namespace Log { +namespace Common::Log { + +class Filter; + +/// Initializes the logging system. This should be the first thing called in main. +void Initialize(std::string_view log_file = ""); + +void Start(); + +void DisableLoggingInTests(); /** - * A log entry. Log entries are store in a structured format to permit more varied output - * formatting on different frontends, as well as facilitating filtering and aggregation. + * The global filter will prevent any messages from even being processed if they are filtered. */ -struct Entry { - std::chrono::microseconds timestamp; - Class log_class; - Level log_level; - const char* filename; - unsigned int line_num; - std::string function; - std::string message; - bool final_entry = false; +void SetGlobalFilter(const Filter& filter); - Entry() = default; - Entry(Entry&& o) = default; - - Entry& operator=(Entry&& o) = default; - Entry& operator=(const Entry& o) = default; -}; - -/** - * 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: - 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: - static const char* Name() { - return "color_console"; - } - - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -/** - * Backend that writes to the Android logcat - */ -class LogcatBackend : public Backend { -public: - static const char* Name() { - return "logcat"; - } - - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -/** - * Backend that writes to a file passed into the constructor - */ -class FileBackend : public Backend { -public: - explicit FileBackend(const std::string& filename); - - static const char* Name() { - return "file"; - } - - const char* GetName() const override { - return Name(); - } - - void Write(const Entry& entry) override; - -private: - FileUtil::IOFile file; - std::size_t bytes_written; -}; - -/** - * Backend that writes to Visual Studio's output window - */ -class DebuggerBackend : public Backend { -public: - 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); - -void RemoveBackend(std::string_view backend_name); - -Backend* GetBackend(std::string_view backend_name); - -/** - * Returns the name of the passed log class as a C-string. Subclasses are separated by periods - * instead of underscores as in the enumeration. - */ -const char* GetLogClassName(Class log_class); - -/** - * Returns the name of the passed log level as a C-string. - */ -const char* GetLevelName(Level log_level); - -} // namespace Log +void SetColorConsoleBackendEnabled(bool enabled); +} // namespace Common::Log diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index fa42b6047..c469993e8 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -3,11 +3,12 @@ // Refer to the license.txt file included. #include -#include "common/logging/backend.h" + +#include "common/assert.h" #include "common/logging/filter.h" #include "common/string_util.h" -namespace Log { +namespace Common::Log { namespace { template Level GetLevelByName(const It begin, const It end) { @@ -22,7 +23,7 @@ Level GetLevelByName(const It begin, const It end) { template Class GetClassByName(const It begin, const It end) { - for (ClassType i = 0; i < static_cast(Class::Count); ++i) { + for (u8 i = 0; i < static_cast(Class::Count); ++i) { const char* level_name = GetLogClassName(static_cast(i)); if (Common::ComparePartialString(begin, end, level_name)) { return static_cast(i); @@ -62,6 +63,115 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { } } // Anonymous namespace +/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. +#define ALL_LOG_CLASSES() \ + CLS(Log) \ + CLS(Common) \ + SUB(Common, Filesystem) \ + SUB(Common, Memory) \ + CLS(Core) \ + SUB(Core, ARM11) \ + SUB(Core, Timing) \ + SUB(Core, Cheats) \ + CLS(Config) \ + CLS(Debug) \ + SUB(Debug, Emulated) \ + SUB(Debug, GPU) \ + SUB(Debug, Breakpoint) \ + SUB(Debug, GDBStub) \ + CLS(Kernel) \ + SUB(Kernel, SVC) \ + CLS(Applet) \ + SUB(Applet, SWKBD) \ + CLS(Service) \ + SUB(Service, SRV) \ + SUB(Service, FRD) \ + SUB(Service, FS) \ + SUB(Service, ERR) \ + SUB(Service, APT) \ + SUB(Service, BOSS) \ + SUB(Service, GSP) \ + SUB(Service, AC) \ + SUB(Service, AM) \ + SUB(Service, PTM) \ + SUB(Service, LDR) \ + SUB(Service, MIC) \ + SUB(Service, NDM) \ + SUB(Service, NFC) \ + SUB(Service, NIM) \ + SUB(Service, NS) \ + SUB(Service, NWM) \ + SUB(Service, CAM) \ + SUB(Service, CECD) \ + SUB(Service, CFG) \ + SUB(Service, CSND) \ + SUB(Service, DSP) \ + SUB(Service, DLP) \ + SUB(Service, HID) \ + SUB(Service, HTTP) \ + SUB(Service, SOC) \ + SUB(Service, IR) \ + SUB(Service, Y2R) \ + SUB(Service, PS) \ + SUB(Service, PLGLDR) \ + CLS(HW) \ + SUB(HW, Memory) \ + SUB(HW, LCD) \ + SUB(HW, GPU) \ + SUB(HW, AES) \ + CLS(Frontend) \ + CLS(Render) \ + SUB(Render, Software) \ + SUB(Render, OpenGL) \ + SUB(Render, Vulkan) \ + CLS(Audio) \ + SUB(Audio, DSP) \ + SUB(Audio, Sink) \ + CLS(Input) \ + CLS(Network) \ + CLS(Movie) \ + CLS(Loader) \ + CLS(WebService) \ + CLS(RPC_Server) + +// GetClassName is a macro defined by Windows.h, grrr... +const char* GetLogClassName(Class log_class) { + switch (log_class) { +#define CLS(x) \ + case Class::x: \ + return #x; +#define SUB(x, y) \ + case Class::x##_##y: \ + return #x "." #y; + ALL_LOG_CLASSES() +#undef CLS +#undef SUB + case Class::Count: + default: + break; + } + UNREACHABLE(); +} + +const char* GetLevelName(Level log_level) { +#define LVL(x) \ + case Level::x: \ + return #x + switch (log_level) { + LVL(Trace); + LVL(Debug); + LVL(Info); + LVL(Warning); + LVL(Error); + LVL(Critical); + case Level::Count: + default: + break; + } +#undef LVL + UNREACHABLE(); +} + Filter::Filter(Level default_level) { ResetAll(default_level); } @@ -96,4 +206,11 @@ bool Filter::CheckMessage(Class log_class, Level level) const { return static_cast(level) >= static_cast(class_levels[static_cast(log_class)]); } -} // namespace Log + +bool Filter::IsDebug() const { + return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) { + return static_cast(l) <= static_cast(Level::Debug); + }); +} + +} // namespace Common::Log diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index 058c7b345..681e5922b 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -7,6 +7,60 @@ #include #include #include -#include "common/logging/log.h" +#include "common/logging/types.h" -namespace Log {} // namespace Log +namespace Common::Log { + +/** + * Returns the name of the passed log class as a C-string. Subclasses are separated by periods + * instead of underscores as in the enumeration. + */ +const char* GetLogClassName(Class log_class); + +/** + * Returns the name of the passed log level as a C-string. + */ +const char* GetLevelName(Level log_level); + +/** + * Implements a log message filter which allows different log classes to have different minimum + * severity levels. The filter can be changed at runtime and can be parsed from a string to allow + * editing via the interface or loading from a configuration file. + */ +class Filter { +public: + /// Initializes the filter with all classes having `default_level` as the minimum level. + explicit Filter(Level default_level = Level::Info); + + /// Resets the filter so that all classes have `level` as the minimum displayed level. + void ResetAll(Level level); + /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. + void SetClassLevel(Class log_class, Level level); + + /** + * Parses a filter string and applies it to this filter. + * + * A filter string consists of a space-separated list of filter rules, each of the format + * `:`. `` is a log class name, with subclasses separated using periods. + * `*` is allowed as a class name and will reset all filters to the specified level. `` + * a severity level name which will be set as the minimum logging level of the matched classes. + * Rules are applied left to right, with each rule overriding previous ones in the sequence. + * + * A few examples of filter rules: + * - `*:Info` -- Resets the level of all classes to Info. + * - `Service:Info` -- Sets the level of Service to Info. + * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. + */ + void ParseFilterString(std::string_view filter_view); + + /// Matches class/level combination against the filter, returning true if it passed. + bool CheckMessage(Class log_class, Level level) const; + + /// Returns true if any logging classes are set to debug + bool IsDebug() const; + +private: + std::array(Class::Count)> class_levels; +}; + +} // namespace Common::Log diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 8cd98db14..59269af3e 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -6,10 +6,12 @@ #include #include -#include "common/common_types.h" -#include "common/logging/formatter.h" +#include -namespace Log { +#include "common/logging/formatter.h" +#include "common/logging/types.h" + +namespace Common::Log { // trims up to and including the last of ../, ..\, src/, src\ in a string constexpr const char* TrimSourcePath(std::string_view source) { @@ -20,144 +22,6 @@ constexpr const char* TrimSourcePath(std::string_view source) { return source.data() + idx; } -/// Specifies the severity or level of detail of the log message. -enum class Level : u8 { - Trace, ///< Extremely detailed and repetitive debugging information that is likely to - ///< pollute logs. - Debug, ///< Less detailed debugging information. - Info, ///< Status information from important points during execution. - Warning, ///< Minor or potential problems found during execution of a task. - Error, ///< Major problems found during execution of a task that prevent it from being - ///< completed. - Critical, ///< Major problems during execution that threaten the stability of the entire - ///< application. - - Count ///< Total number of logging levels -}; - -typedef u8 ClassType; - -/** - * Specifies the sub-system that generated the log message. - * - * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in - * backend.cpp. - */ -enum class Class : ClassType { - Log, ///< Messages about the log system itself - Common, ///< Library routines - Common_Filesystem, ///< Filesystem interface library - Common_Memory, ///< Memory mapping and management functions - Core, ///< LLE emulation core - Core_ARM11, ///< ARM11 CPU core - Core_Timing, ///< CoreTiming functions - Core_Cheats, ///< Cheat functions - Config, ///< Emulator configuration (including commandline) - Debug, ///< Debugging tools - Debug_Emulated, ///< Debug messages from the emulated programs - Debug_GPU, ///< GPU debugging tools - Debug_Breakpoint, ///< Logging breakpoints and watchpoints - Debug_GDBStub, ///< GDB Stub - Kernel, ///< The HLE implementation of the CTR kernel - Kernel_SVC, ///< Kernel system calls - Applet, ///< HLE implementation of system applets. Each applet - ///< should have its own subclass. - Applet_SWKBD, ///< The Software Keyboard applet - Service, ///< HLE implementation of system services. Each major service - ///< should have its own subclass. - Service_SRV, ///< The SRV (Service Directory) implementation - Service_FRD, ///< The FRD (Friends) service - Service_FS, ///< The FS (Filesystem) service implementation - Service_ERR, ///< The ERR (Error) port implementation - Service_APT, ///< The APT (Applets) service - Service_BOSS, ///< The BOSS (SpotPass) service - Service_GSP, ///< The GSP (GPU control) service - Service_AC, ///< The AC (WiFi status) service - Service_AM, ///< The AM (Application manager) service - Service_PTM, ///< The PTM (Power status & misc.) service - Service_LDR, ///< The LDR (3ds dll loader) service - Service_MIC, ///< The MIC (Microphone) service - Service_NDM, ///< The NDM (Network daemon manager) service - Service_NFC, ///< The NFC service - Service_NIM, ///< The NIM (Network interface manager) service - Service_NS, ///< The NS (Nintendo User Interface Shell) service - Service_NWM, ///< The NWM (Network wlan manager) service - Service_CAM, ///< The CAM (Camera) service - Service_CECD, ///< The CECD (StreetPass) service - Service_CFG, ///< The CFG (Configuration) service - Service_CSND, ///< The CSND (CWAV format process) service - Service_DSP, ///< The DSP (DSP control) service - Service_DLP, ///< The DLP (Download Play) service - Service_HID, ///< The HID (Human interface device) service - Service_HTTP, ///< The HTTP service - Service_SOC, ///< The SOC (Socket) service - Service_IR, ///< The IR service - Service_Y2R, ///< The Y2R (YUV to RGB conversion) service - Service_PS, ///< The PS (Process) service - Service_PLGLDR, ///< The PLGLDR (plugin loader) service - HW, ///< Low-level hardware emulation - HW_Memory, ///< Memory-map and address translation - HW_LCD, ///< LCD register emulation - HW_GPU, ///< GPU control emulation - HW_AES, ///< AES engine emulation - Frontend, ///< Emulator UI - Render, ///< Emulator video output and hardware acceleration - Render_Software, ///< Software renderer backend - Render_OpenGL, ///< OpenGL backend - Render_Vulkan, ///< Vulkan backend - Audio, ///< Audio emulation - Audio_DSP, ///< The HLE and LLE implementations of the DSP - Audio_Sink, ///< Emulator audio output backend - Loader, ///< ROM loader - Input, ///< Input emulation - Network, ///< Network emulation - Movie, ///< Movie (Input Recording) Playback - WebService, ///< Interface to Citra Web Services - RPC_Server, ///< RPC server - Count ///< Total number of logging classes -}; - -/** - * Implements a log message filter which allows different log classes to have different minimum - * severity levels. The filter can be changed at runtime and can be parsed from a string to allow - * editing via the interface or loading from a configuration file. - */ -class Filter { -public: - /// Initializes the filter with all classes having `default_level` as the minimum level. - explicit Filter(Level default_level = Level::Info); - - /// Resets the filter so that all classes have `level` as the minimum displayed level. - void ResetAll(Level level); - /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. - void SetClassLevel(Class log_class, Level level); - - /** - * Parses a filter string and applies it to this filter. - * - * A filter string consists of a space-separated list of filter rules, each of the format - * `:`. `` is a log class name, with subclasses separated using periods. - * `*` is allowed as a class name and will reset all filters to the specified level. `` - * a severity level name which will be set as the minimum logging level of the matched classes. - * Rules are applied left to right, with each rule overriding previous ones in the sequence. - * - * A few examples of filter rules: - * - `*:Info` -- Resets the level of all classes to Info. - * - `Service:Info` -- Sets the level of Service to Info. - * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. - */ - void ParseFilterString(std::string_view filter_view); - - /// Matches class/level combination against the filter, returning true if it passed. - bool CheckMessage(Class log_class, Level level) const; - -private: - std::array(Class::Count)> class_levels; -}; -extern Filter filter; - -void SetGlobalFilter(const Filter& f); - /// Logs a message to the global logger, using fmt void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, const char* format, @@ -166,40 +30,43 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, template void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, const char* format, const Args&... args) { - if (!filter.CheckMessage(log_class, log_level)) - return; - FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, fmt::make_format_args(args...)); } -} // namespace Log +} // namespace Common::Log // Define the fmt lib macros #define LOG_GENERIC(log_class, log_level, ...) \ - ::Log::FmtLogMessage(log_class, log_level, ::Log::TrimSourcePath(__FILE__), __LINE__, \ - __func__, __VA_ARGS__) + Common::Log::FmtLogMessage(log_class, log_level, Common::Log::TrimSourcePath(__FILE__), \ + __LINE__, __func__, __VA_ARGS__) #ifdef _DEBUG #define LOG_TRACE(log_class, ...) \ - ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, \ - ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__) + Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \ + Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ + __VA_ARGS__) #else #define LOG_TRACE(log_class, fmt, ...) (void(0)) #endif #define LOG_DEBUG(log_class, ...) \ - ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, \ - ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__) + Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Debug, \ + Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ + __VA_ARGS__) #define LOG_INFO(log_class, ...) \ - ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, \ - ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__) + Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Info, \ + Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ + __VA_ARGS__) #define LOG_WARNING(log_class, ...) \ - ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, \ - ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__) + Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Warning, \ + Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ + __VA_ARGS__) #define LOG_ERROR(log_class, ...) \ - ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, \ - ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__) + Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Error, \ + Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ + __VA_ARGS__) #define LOG_CRITICAL(log_class, ...) \ - ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, \ - ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__) + Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Critical, \ + Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ + __VA_ARGS__) diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h new file mode 100644 index 000000000..290ec784a --- /dev/null +++ b/src/common/logging/log_entry.h @@ -0,0 +1,27 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/logging/types.h" + +namespace Common::Log { + +/** + * A log entry. Log entries are store in a structured format to permit more varied output + * formatting on different frontends, as well as facilitating filtering and aggregation. + */ +struct Entry { + std::chrono::microseconds timestamp; + Class log_class{}; + Level log_level{}; + const char* filename = nullptr; + u32 line_num = 0; + std::string function; + std::string message; +}; + +} // namespace Common::Log diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 7f713b148..753e5003e 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -12,13 +12,12 @@ #endif #include "common/assert.h" -#include "common/common_funcs.h" -#include "common/logging/backend.h" +#include "common/logging/filter.h" #include "common/logging/log.h" +#include "common/logging/log_entry.h" #include "common/logging/text_formatter.h" -#include "common/string_util.h" -namespace Log { +namespace Common::Log { std::string FormatLogMessage(const Entry& entry) { unsigned int time_seconds = static_cast(entry.timestamp.count() / 1000000); @@ -141,4 +140,4 @@ void PrintMessageToLogcat([[maybe_unused]] const Entry& entry) { __android_log_print(android_log_priority, "CitraNative", "%s", str.c_str()); #endif } -} // namespace Log +} // namespace Common::Log diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 13430951d..4ab9e18f9 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h @@ -4,10 +4,9 @@ #pragma once -#include #include -namespace Log { +namespace Common::Log { struct Entry; @@ -19,4 +18,4 @@ void PrintMessage(const Entry& entry); void PrintColoredMessage(const Entry& entry); /// Formats and prints a log entry to the android logcat. void PrintMessageToLogcat(const Entry& entry); -} // namespace Log +} // namespace Common::Log diff --git a/src/common/logging/types.h b/src/common/logging/types.h new file mode 100644 index 000000000..f4db2dc42 --- /dev/null +++ b/src/common/logging/types.h @@ -0,0 +1,106 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Common::Log { + +/// Specifies the severity or level of detail of the log message. +enum class Level : u8 { + Trace, ///< Extremely detailed and repetitive debugging information that is likely to + ///< pollute logs. + Debug, ///< Less detailed debugging information. + Info, ///< Status information from important points during execution. + Warning, ///< Minor or potential problems found during execution of a task. + Error, ///< Major problems found during execution of a task that prevent it from being + ///< completed. + Critical, ///< Major problems during execution that threaten the stability of the entire + ///< application. + + Count, ///< Total number of logging levels +}; + +/** + * Specifies the sub-system that generated the log message. + * + * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in + * backend.cpp. + */ +enum class Class : u8 { + Log, ///< Messages about the log system itself + Common, ///< Library routines + Common_Filesystem, ///< Filesystem interface library + Common_Memory, ///< Memory mapping and management functions + Core, ///< LLE emulation core + Core_ARM11, ///< ARM11 CPU core + Core_Timing, ///< CoreTiming functions + Core_Cheats, ///< Cheat functions + Config, ///< Emulator configuration (including commandline) + Debug, ///< Debugging tools + Debug_Emulated, ///< Debug messages from the emulated programs + Debug_GPU, ///< GPU debugging tools + Debug_Breakpoint, ///< Logging breakpoints and watchpoints + Debug_GDBStub, ///< GDB Stub + Kernel, ///< The HLE implementation of the CTR kernel + Kernel_SVC, ///< Kernel system calls + Applet, ///< HLE implementation of system applets. Each applet + ///< should have its own subclass. + Applet_SWKBD, ///< The Software Keyboard applet + Service, ///< HLE implementation of system services. Each major service + ///< should have its own subclass. + Service_SRV, ///< The SRV (Service Directory) implementation + Service_FRD, ///< The FRD (Friends) service + Service_FS, ///< The FS (Filesystem) service implementation + Service_ERR, ///< The ERR (Error) port implementation + Service_APT, ///< The APT (Applets) service + Service_BOSS, ///< The BOSS (SpotPass) service + Service_GSP, ///< The GSP (GPU control) service + Service_AC, ///< The AC (WiFi status) service + Service_AM, ///< The AM (Application manager) service + Service_PTM, ///< The PTM (Power status & misc.) service + Service_LDR, ///< The LDR (3ds dll loader) service + Service_MIC, ///< The MIC (Microphone) service + Service_NDM, ///< The NDM (Network daemon manager) service + Service_NFC, ///< The NFC service + Service_NIM, ///< The NIM (Network interface manager) service + Service_NS, ///< The NS (Nintendo User Interface Shell) service + Service_NWM, ///< The NWM (Network wlan manager) service + Service_CAM, ///< The CAM (Camera) service + Service_CECD, ///< The CECD (StreetPass) service + Service_CFG, ///< The CFG (Configuration) service + Service_CSND, ///< The CSND (CWAV format process) service + Service_DSP, ///< The DSP (DSP control) service + Service_DLP, ///< The DLP (Download Play) service + Service_HID, ///< The HID (Human interface device) service + Service_HTTP, ///< The HTTP service + Service_SOC, ///< The SOC (Socket) service + Service_IR, ///< The IR service + Service_Y2R, ///< The Y2R (YUV to RGB conversion) service + Service_PS, ///< The PS (Process) service + Service_PLGLDR, ///< The PLGLDR (plugin loader) service + HW, ///< Low-level hardware emulation + HW_Memory, ///< Memory-map and address translation + HW_LCD, ///< LCD register emulation + HW_GPU, ///< GPU control emulation + HW_AES, ///< AES engine emulation + Frontend, ///< Emulator UI + Render, ///< Emulator video output and hardware acceleration + Render_Software, ///< Software renderer backend + Render_OpenGL, ///< OpenGL backend + Render_Vulkan, ///< Vulkan backend + Audio, ///< Audio emulation + Audio_DSP, ///< The HLE and LLE implementations of the DSP + Audio_Sink, ///< Emulator audio output backend + Loader, ///< ROM loader + Input, ///< Input emulation + Network, ///< Network emulation + Movie, ///< Movie (Input Recording) Playback + WebService, ///< Interface to Citra Web Services + RPC_Server, ///< RPC server + Count, ///< Total number of logging classes +}; + +} // namespace Common::Log diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp index bbdde251f..188f45aa6 100644 --- a/src/common/param_package.cpp +++ b/src/common/param_package.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include "common/logging/log.h" diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 09b24770f..f0356b00a 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -5,18 +5,8 @@ #include #include #include "audio_core/dsp_interface.h" +#include "common/file_util.h" #include "common/settings.h" -#include "core/core.h" -#include "core/gdbstub/gdbstub.h" -#include "core/hle/kernel/shared_page.h" -#include "core/hle/service/cam/cam.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/ir/ir_rst.h" -#include "core/hle/service/ir/ir_user.h" -#include "core/hle/service/mic_u.h" -#include "core/hle/service/plgldr/plgldr.h" -#include "video_core/renderer_base.h" -#include "video_core/video_core.h" namespace Settings { @@ -72,64 +62,6 @@ std::string_view GetTextureFilterName(TextureFilter filter) { Values values = {}; static bool configuring_global = true; -void Apply() { - GDBStub::SetServerPort(values.gdbstub_port.GetValue()); - GDBStub::ToggleServer(values.use_gdbstub.GetValue()); - - VideoCore::g_shader_jit_enabled = values.use_shader_jit.GetValue(); - VideoCore::g_hw_shader_enabled = values.use_hw_shader.GetValue(); - VideoCore::g_hw_shader_accurate_mul = values.shaders_accurate_mul.GetValue(); - -#ifndef ANDROID - if (VideoCore::g_renderer) { - VideoCore::g_renderer->UpdateCurrentFramebufferLayout(); - } -#endif - - if (VideoCore::g_renderer) { - auto& settings = VideoCore::g_renderer->Settings(); - settings.bg_color_update_requested = true; - settings.sampler_update_requested = true; - settings.shader_update_requested = true; - settings.texture_filter_update_requested = true; - } - - auto& system = Core::System::GetInstance(); - if (system.IsPoweredOn()) { - system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage.GetValue()); - Core::DSP().SetSink(values.output_type.GetValue(), values.output_device.GetValue()); - Core::DSP().EnableStretching(values.enable_audio_stretching.GetValue()); - - auto hid = Service::HID::GetModule(system); - if (hid) { - hid->ReloadInputDevices(); - } - - auto apt = Service::APT::GetModule(system); - if (apt) { - apt->GetAppletManager()->ReloadInputDevices(); - } - - auto sm = system.ServiceManager(); - auto ir_user = sm.GetService("ir:USER"); - if (ir_user) - ir_user->ReloadInputDevices(); - auto ir_rst = sm.GetService("ir:rst"); - if (ir_rst) - ir_rst->ReloadInputDevices(); - - auto cam = Service::CAM::GetModule(system); - if (cam) { - cam->ReloadCameraDevices(); - } - - Service::MIC::ReloadMic(system); - } - - Service::PLGLDR::PLG_LDR::SetEnabled(values.plugin_loader_enabled.GetValue()); - Service::PLGLDR::PLG_LDR::SetAllowGameChangeState(values.allow_plugin_loader.GetValue()); -} - void LogSettings() { const auto log_setting = [](std::string_view name, const auto& value) { LOG_INFO(Config, "{}: {}", name, value); diff --git a/src/common/settings.h b/src/common/settings.h index 67c7b182c..c7bf565fe 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -525,7 +525,6 @@ void SetConfiguringGlobal(bool is_global); float Volume(); -void Apply(); void LogSettings(); // Restore the global state of all applicable settings in the Values struct diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index 5584229a6..8217e2bda 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -13,8 +13,10 @@ #include #include +#include "common/polyfill_thread.h" + namespace Common { -template +template class SPSCQueue { public: SPSCQueue() { @@ -46,15 +48,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::scoped_lock lock{cv_mutex}; cv.notify_one(); } @@ -93,6 +93,19 @@ public: return t; } + T PopWait(std::stop_token stop_token) { + if (Empty()) { + std::unique_lock lock{cv_mutex}; + CondvarWait(cv, lock, stop_token, [this] { return !Empty(); }); + } + if (stop_token.stop_requested()) { + return T{}; + } + T t; + Pop(t); + return t; + } + // not thread-safe void Clear() { size.store(0); @@ -121,13 +134,13 @@ private: ElementPtr* read_ptr; std::atomic_size_t size{0}; std::mutex cv_mutex; - std::condition_variable cv; + std::conditional_t cv; }; // a simple thread-safe, // single reader, multiple writer queue -template +template class MPSCQueue { public: [[nodiscard]] std::size_t Size() const { @@ -144,7 +157,7 @@ public: template void Push(Arg&& t) { - std::lock_guard lock{write_lock}; + std::scoped_lock lock{write_lock}; spsc_queue.Push(t); } @@ -160,13 +173,17 @@ public: return spsc_queue.PopWait(); } + T PopWait(std::stop_token stop_token) { + return spsc_queue.PopWait(stop_token); + } + // not thread-safe void Clear() { spsc_queue.Clear(); } private: - SPSCQueue spsc_queue; + SPSCQueue spsc_queue; std::mutex write_lock; }; } // namespace Common diff --git a/src/core/core.cpp b/src/core/core.cpp index a4ad8a817..4ac4d0fd0 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -12,9 +13,9 @@ #include "common/arch.h" #include "common/logging/log.h" #include "common/settings.h" -#include "common/texture.h" #include "core/arm/arm_interface.h" #include "core/arm/exclusive_monitor.h" +#include "core/hle/service/cam/cam.h" #if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64) #include "core/arm/dynarmic/arm_dynarmic.h" #endif @@ -23,19 +24,22 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/dumping/backend.h" -#include "core/dumping/ffmpeg_backend.h" #include "core/frontend/image_interface.h" #include "core/gdbstub/gdbstub.h" #include "core/global.h" -#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/apt/applet_manager.h" #include "core/hle/service/apt/apt.h" +#include "core/hle/service/cam/cam.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/gsp/gsp.h" -#include "core/hle/service/pm/pm_app.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/ir/ir_rst.h" +#include "core/hle/service/ir/ir_user.h" +#include "core/hle/service/mic_u.h" +#include "core/hle/service/plgldr/plgldr.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/hw/gpu.h" @@ -596,6 +600,64 @@ void System::Reset() { } } +void System::ApplySettings() { + GDBStub::SetServerPort(Settings::values.gdbstub_port.GetValue()); + GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue()); + + VideoCore::g_shader_jit_enabled = Settings::values.use_shader_jit.GetValue(); + VideoCore::g_hw_shader_enabled = Settings::values.use_hw_shader.GetValue(); + VideoCore::g_hw_shader_accurate_mul = Settings::values.shaders_accurate_mul.GetValue(); + +#ifndef ANDROID + if (VideoCore::g_renderer) { + VideoCore::g_renderer->UpdateCurrentFramebufferLayout(); + } +#endif + + if (VideoCore::g_renderer) { + auto& settings = VideoCore::g_renderer->Settings(); + settings.bg_color_update_requested = true; + settings.sampler_update_requested = true; + settings.shader_update_requested = true; + settings.texture_filter_update_requested = true; + } + + if (IsPoweredOn()) { + CoreTiming().UpdateClockSpeed(Settings::values.cpu_clock_percentage.GetValue()); + Core::DSP().SetSink(Settings::values.output_type.GetValue(), + Settings::values.output_device.GetValue()); + Core::DSP().EnableStretching(Settings::values.enable_audio_stretching.GetValue()); + + auto hid = Service::HID::GetModule(*this); + if (hid) { + hid->ReloadInputDevices(); + } + + auto apt = Service::APT::GetModule(*this); + if (apt) { + apt->GetAppletManager()->ReloadInputDevices(); + } + + auto ir_user = service_manager->GetService("ir:USER"); + if (ir_user) + ir_user->ReloadInputDevices(); + auto ir_rst = service_manager->GetService("ir:rst"); + if (ir_rst) + ir_rst->ReloadInputDevices(); + + auto cam = Service::CAM::GetModule(*this); + if (cam) { + cam->ReloadCameraDevices(); + } + + Service::MIC::ReloadMic(*this); + } + + Service::PLGLDR::PLG_LDR::SetEnabled(Settings::values.plugin_loader_enabled.GetValue()); + Service::PLGLDR::PLG_LDR::SetAllowGameChangeState( + Settings::values.allow_plugin_loader.GetValue()); +} + template void System::serialize(Archive& ar, const unsigned int file_version) { diff --git a/src/core/core.h b/src/core/core.h index b29fc20e2..3b48912d1 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -330,6 +330,9 @@ public: return false; } + /// Applies any changes to settings to this core instance. + void ApplySettings(); + private: /** * Initialize the emulated system. diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 9b59f9775..7fb5c6aa2 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -203,7 +203,7 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { status = ProcessStatus::Running; - vm_manager.LogLayout(Log::Level::Debug); + vm_manager.LogLayout(Common::Log::Level::Debug); Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this)); } diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index faa173f76..cf8564e38 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -529,7 +529,7 @@ ResultCode SVC::ControlMemory(u32* out_addr, u32 addr0, u32 addr1, u32 size, u32 return ERR_INVALID_COMBINATION; } - process.vm_manager.LogLayout(Log::Level::Trace); + process.vm_manager.LogLayout(Common::Log::Level::Trace); return RESULT_SUCCESS; } diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 944c9a5b1..f2c798750 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -239,10 +239,10 @@ ResultCode VMManager::ReprotectRange(VAddr target, u32 size, VMAPermission new_p return RESULT_SUCCESS; } -void VMManager::LogLayout(Log::Level log_level) const { +void VMManager::LogLayout(Common::Log::Level log_level) const { for (const auto& p : vma_map) { const VirtualMemoryArea& vma = p.second; - LOG_GENERIC(::Log::Class::Kernel, log_level, "{:08X} - {:08X} size: {:8X} {}{}{} {}", + LOG_GENERIC(Common::Log::Class::Kernel, log_level, "{:08X} - {:08X} size: {:8X} {}{}{} {}", vma.base, vma.base + vma.size, vma.size, (u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-', (u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-', diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 97c601fd6..87ecbc037 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -202,7 +202,7 @@ public: ResultCode ReprotectRange(VAddr target, u32 size, VMAPermission new_perms); /// Dumps the address space layout to the log, for debugging - void LogLayout(Log::Level log_level) const; + void LogLayout(Common::Log::Level log_level) const; /// Gets a list of backing memory blocks for the specified range ResultVal>> GetBackingBlocksForRange(VAddr address, diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 0f00ddc0f..52d35d3b2 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "common/archives.h" #include "common/file_util.h" #include "common/logging/log.h" diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp index eff81c711..4709f70cf 100644 --- a/src/core/hle/service/ir/extra_hid.cpp +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "common/alignment.h" #include "common/settings.h" #include "common/string_util.h" diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index 1900db902..81d631f94 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "common/string_util.h" #include "common/swap.h" #include "core/core.h" diff --git a/src/core/movie.cpp b/src/core/movie.cpp index c2d085d29..83264ea35 100644 --- a/src/core/movie.cpp +++ b/src/core/movie.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "common/bit_field.h" #include "common/common_types.h" #include "common/file_util.h" diff --git a/src/core/savestate.cpp b/src/core/savestate.cpp index 4c88eade4..e791a27f7 100644 --- a/src/core/savestate.cpp +++ b/src/core/savestate.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "common/archives.h" #include "common/logging/log.h" #include "common/scm_rev.h" diff --git a/src/dedicated_room/citra-room.cpp b/src/dedicated_room/citra-room.cpp index 46072de2d..dab153dd9 100644 --- a/src/dedicated_room/citra-room.cpp +++ b/src/dedicated_room/citra-room.cpp @@ -150,15 +150,8 @@ static void SaveBanList(const Network::Room::BanList& ban_list, const std::strin } static void InitializeLogging(const std::string& log_file) { - Log::AddBackend(std::make_unique()); - - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique(log_dir + log_file)); - -#ifdef _WIN32 - Log::AddBackend(std::make_unique()); -#endif + Common::Log::Initialize(log_file); + Common::Log::SetColorConsoleBackendEnabled(true); } /// Application entry point diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp index 75442cea4..b2a349d50 100644 --- a/src/tests/common/param_package.cpp +++ b/src/tests/common/param_package.cpp @@ -4,11 +4,13 @@ #include #include +#include "common/logging/backend.h" #include "common/param_package.h" namespace Common { TEST_CASE("ParamPackage", "[common]") { + Common::Log::DisableLoggingInTests(); ParamPackage original{ {"abc", "xyz"}, {"def", "42"}, diff --git a/src/video_core/renderer_opengl/gl_driver.cpp b/src/video_core/renderer_opengl/gl_driver.cpp index 9951d0ae7..c0b8c2fe4 100644 --- a/src/video_core/renderer_opengl/gl_driver.cpp +++ b/src/video_core/renderer_opengl/gl_driver.cpp @@ -56,22 +56,22 @@ inline std::string_view GetType(GLenum type) { static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* user_param) { - Log::Level level = Log::Level::Info; + auto level = Common::Log::Level::Info; switch (severity) { case GL_DEBUG_SEVERITY_HIGH: - level = Log::Level::Critical; + level = Common::Log::Level::Critical; break; case GL_DEBUG_SEVERITY_MEDIUM: - level = Log::Level::Warning; + level = Common::Log::Level::Warning; break; case GL_DEBUG_SEVERITY_NOTIFICATION: case GL_DEBUG_SEVERITY_LOW: - level = Log::Level::Debug; + level = Common::Log::Level::Debug; break; } - LOG_GENERIC(Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type), - id, message); + LOG_GENERIC(Common::Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), + GetType(type), id, message); } Driver::Driver(Core::TelemetrySession& telemetry_session_) diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 887b586c2..25beb60f9 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -38,23 +38,23 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( break; } - Log::Level level{}; + Common::Log::Level level{}; switch (severity) { case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - level = Log::Level::Error; + level = Common::Log::Level::Error; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - level = Log::Level::Info; + level = Common::Log::Level::Info; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: - level = Log::Level::Debug; + level = Common::Log::Level::Debug; break; default: - level = Log::Level::Info; + level = Common::Log::Level::Info; } - LOG_GENERIC(Log::Class::Render_Vulkan, level, "{}: {}", + LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, "{}: {}", callback_data->pMessageIdName ? callback_data->pMessageIdName : "", callback_data->pMessage ? callback_data->pMessage : ""); @@ -69,25 +69,25 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT const char* pMessage, void* pUserData) { const VkDebugReportFlagBitsEXT severity = static_cast(flags); - Log::Level level{}; + Common::Log::Level level{}; switch (severity) { case VK_DEBUG_REPORT_ERROR_BIT_EXT: - level = Log::Level::Error; + level = Common::Log::Level::Error; break; case VK_DEBUG_REPORT_INFORMATION_BIT_EXT: - level = Log::Level::Warning; + level = Common::Log::Level::Warning; break; case VK_DEBUG_REPORT_DEBUG_BIT_EXT: case VK_DEBUG_REPORT_WARNING_BIT_EXT: case VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT: - level = Log::Level::Debug; + level = Common::Log::Level::Debug; break; default: - level = Log::Level::Info; + level = Common::Log::Level::Info; } const vk::DebugReportObjectTypeEXT type = static_cast(objectType); - LOG_GENERIC(Log::Class::Render_Vulkan, level, + LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, "type = {}, object = {} | MessageCode = {:#x}, LayerPrefix = {} | {}", vk::to_string(type), object, messageCode, pLayerPrefix, pMessage);