From 87878d3cba43c0489f0d7efbd8c6c3a767117fbb Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 1 Dec 2024 19:12:02 +0200 Subject: [PATCH] Qt configs: Make thread-safe, properly update audio enable & renderdoc settings --- include/emulator.hpp | 3 + include/panda_qt/config_window.hpp | 17 +++- include/panda_qt/main_window.hpp | 1 + include/renderdoc.hpp | 4 + src/emulator.cpp | 8 ++ src/panda_qt/config_window.cpp | 137 ++++++++++++++--------------- src/panda_qt/main_window.cpp | 14 ++- src/renderdoc.cpp | 6 ++ 8 files changed, 116 insertions(+), 74 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index abb74089..cf231328 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -118,6 +118,9 @@ class Emulator { void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); } void deinitGraphicsContext() { gpu.deinitGraphicsContext(); } + // Reloads some settings that require special handling, such as audio enable + void reloadSettings(); + EmulatorConfig& getConfig() { return config; } Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } diff --git a/include/panda_qt/config_window.hpp b/include/panda_qt/config_window.hpp index 347a043b..7049eb8a 100644 --- a/include/panda_qt/config_window.hpp +++ b/include/panda_qt/config_window.hpp @@ -19,7 +19,9 @@ class ConfigWindow : public QDialog { Q_OBJECT - private: + private: + using ConfigCallback = std::function; + enum class Theme : int { System = 0, Light = 1, @@ -36,13 +38,20 @@ class ConfigWindow : public QDialog { static constexpr size_t settingWidgetCount = 6; std::array helpTexts; + // The config class holds a copy of the emulator config which it edits and sends + // over to the emulator in a thread-safe manner + EmulatorConfig config; + ConfigCallback updateConfig; + void addWidget(QWidget* widget, QString title, QString icon, QString helpText); void setTheme(Theme theme); - public: - ConfigWindow(Emulator* emu, QWidget* parent = nullptr); + public: + ConfigWindow(ConfigCallback callback, const EmulatorConfig& config, QWidget* parent = nullptr); ~ConfigWindow(); - private: + EmulatorConfig& getConfig() { return config; } + + private: Emulator* emu; }; diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index eb6b30e0..eb1cfb16 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -51,6 +51,7 @@ class MainWindow : public QMainWindow { ReleaseTouchscreen, ReloadUbershader, SetScreenSize, + UpdateConfig, }; // Tagged union representing our message queue messages diff --git a/include/renderdoc.hpp b/include/renderdoc.hpp index 02f2ade4..9c7de1e3 100644 --- a/include/renderdoc.hpp +++ b/include/renderdoc.hpp @@ -23,6 +23,9 @@ namespace Renderdoc { // Sets output directory for captures void setOutputDir(const std::string& path, const std::string& prefix); + // Returns whether Renderdoc has been loaded + bool isLoaded(); + // Returns whether we've compiled with Renderdoc support static constexpr bool isSupported() { return true; } } // namespace Renderdoc @@ -34,6 +37,7 @@ namespace Renderdoc { static void triggerCapture() { Helpers::panic("Tried to trigger a Renderdoc capture while support for renderdoc is disabled"); } static void setOutputDir(const std::string& path, const std::string& prefix) {} static constexpr bool isSupported() { return false; } + static constexpr bool isLoaded() { return false; } } // namespace Renderdoc #endif diff --git a/src/emulator.cpp b/src/emulator.cpp index fc25eacb..6b79b634 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -444,3 +444,11 @@ void Emulator::loadRenderdoc() { Renderdoc::loadRenderdoc(); Renderdoc::setOutputDir(capturePath, ""); } + +void Emulator::reloadSettings() { + setAudioEnabled(config.audioEnabled); + + if (Renderdoc::isSupported() && config.enableRenderdoc && !Renderdoc::isLoaded()) { + loadRenderdoc(); + } +} \ No newline at end of file diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp index 806445f7..8d5c4a56 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -1,9 +1,8 @@ #include "panda_qt/config_window.hpp" -ConfigWindow::ConfigWindow(Emulator* emu, QWidget* parent) : QDialog(parent), emu(emu) { +ConfigWindow::ConfigWindow(ConfigCallback callback, const EmulatorConfig& emuConfig, QWidget* parent) : QDialog(parent), config(emuConfig) { setWindowTitle(tr("Configuration")); - - EmulatorConfig& config = emu->getConfig(); + updateConfig = std::move(callback); // Set up theme selection setTheme(Theme::Dark); @@ -44,12 +43,12 @@ ConfigWindow::ConfigWindow(Emulator* emu, QWidget* parent) : QDialog(parent), em hLayout->addWidget(widgetContainer); // Interface settings - QGroupBox *guiGroupBox = new QGroupBox(tr("Interface Settings"), this); - QFormLayout *guiLayout = new QFormLayout(guiGroupBox); + QGroupBox* guiGroupBox = new QGroupBox(tr("Interface Settings"), this); + QFormLayout* guiLayout = new QFormLayout(guiGroupBox); guiLayout->setHorizontalSpacing(20); guiLayout->setVerticalSpacing(10); - QComboBox *themeSelect = new QComboBox; + QComboBox* themeSelect = new QComboBox; themeSelect->addItem(tr("System")); themeSelect->addItem(tr("Light")); themeSelect->addItem(tr("Dark")); @@ -61,35 +60,35 @@ ConfigWindow::ConfigWindow(Emulator* emu, QWidget* parent) : QDialog(parent), em }); guiLayout->addRow(tr("Color theme"), themeSelect); - QCheckBox *showAppVersion = new QCheckBox(tr("Show version on window title")); + QCheckBox* showAppVersion = new QCheckBox(tr("Show version on window title")); showAppVersion->setChecked(config.windowSettings.showAppVersion); connect(showAppVersion, &QCheckBox::toggled, this, [&](bool checked) { config.windowSettings.showAppVersion = checked; - config.save(); + updateConfig(); }); guiLayout->addRow(showAppVersion); - QCheckBox *rememberPosition = new QCheckBox(tr("Remember window position")); + QCheckBox* rememberPosition = new QCheckBox(tr("Remember window position")); rememberPosition->setChecked(config.windowSettings.rememberPosition); connect(rememberPosition, &QCheckBox::toggled, this, [&](bool checked) { config.windowSettings.rememberPosition = checked; - config.save(); + updateConfig(); }); guiLayout->addRow(rememberPosition); // General settings - QGroupBox *genGroupBox = new QGroupBox(tr("General Settings"), this); - QFormLayout *genLayout = new QFormLayout(genGroupBox); + QGroupBox* genGroupBox = new QGroupBox(tr("General Settings"), this); + QFormLayout* genLayout = new QFormLayout(genGroupBox); genLayout->setHorizontalSpacing(20); genLayout->setVerticalSpacing(10); - QLineEdit *defaultRomPath = new QLineEdit; + QLineEdit* defaultRomPath = new QLineEdit; defaultRomPath->setText(QString::fromStdU16String(config.defaultRomPath.u16string())); connect(defaultRomPath, &QLineEdit::textChanged, this, [&](const QString &text) { config.defaultRomPath = text.toStdString(); - config.save(); + updateConfig(); }); - QPushButton *browseRomPath = new QPushButton(tr("Browse...")); + QPushButton* browseRomPath = new QPushButton(tr("Browse...")); browseRomPath->setAutoDefault(false); connect(browseRomPath, &QPushButton::pressed, this, [&, defaultRomPath]() { QString newPath = QFileDialog::getExistingDirectory( @@ -100,217 +99,217 @@ ConfigWindow::ConfigWindow(Emulator* emu, QWidget* parent) : QDialog(parent), em defaultRomPath->setText(newPath); } }); - QHBoxLayout *romLayout = new QHBoxLayout; + QHBoxLayout* romLayout = new QHBoxLayout; romLayout->setSpacing(4); romLayout->addWidget(defaultRomPath); romLayout->addWidget(browseRomPath); genLayout->addRow(tr("Default ROMs path"), romLayout); - QCheckBox *discordRpcEnabled = new QCheckBox(tr("Enable Discord RPC")); + QCheckBox* discordRpcEnabled = new QCheckBox(tr("Enable Discord RPC")); discordRpcEnabled->setChecked(config.discordRpcEnabled); connect(discordRpcEnabled, &QCheckBox::toggled, this, [&](bool checked) { config.discordRpcEnabled = checked; - config.save(); + updateConfig(); }); genLayout->addRow(discordRpcEnabled); - QCheckBox *usePortableBuild = new QCheckBox(tr("Use portable build")); + QCheckBox* usePortableBuild = new QCheckBox(tr("Use portable build")); usePortableBuild->setChecked(config.usePortableBuild); connect(usePortableBuild, &QCheckBox::toggled, this, [&](bool checked) { config.usePortableBuild = checked; - config.save(); + updateConfig(); }); genLayout->addRow(usePortableBuild); - QCheckBox *printAppVersion = new QCheckBox(tr("Print version in console output")); + QCheckBox* printAppVersion = new QCheckBox(tr("Print version in console output")); printAppVersion->setChecked(config.printAppVersion); connect(printAppVersion, &QCheckBox::toggled, this, [&](bool checked) { config.printAppVersion = checked; - config.save(); + updateConfig(); }); genLayout->addRow(printAppVersion); // Graphics settings - QGroupBox *gpuGroupBox = new QGroupBox(tr("Graphics Settings"), this); - QFormLayout *gpuLayout = new QFormLayout(gpuGroupBox); + QGroupBox* gpuGroupBox = new QGroupBox(tr("Graphics Settings"), this); + QFormLayout* gpuLayout = new QFormLayout(gpuGroupBox); gpuLayout->setHorizontalSpacing(20); gpuLayout->setVerticalSpacing(10); - QComboBox *rendererType = new QComboBox; + QComboBox* rendererType = new QComboBox; rendererType->addItem(tr("Null")); rendererType->addItem(tr("OpenGL")); rendererType->addItem(tr("Vulkan")); rendererType->setCurrentIndex(static_cast(config.rendererType)); connect(rendererType, &QComboBox::currentIndexChanged, this, [&](int index) { config.rendererType = static_cast(index); - config.save(); + updateConfig(); }); gpuLayout->addRow(tr("GPU renderer"), rendererType); - QCheckBox *enableRenderdoc = new QCheckBox(tr("Enable Renderdoc")); + QCheckBox* enableRenderdoc = new QCheckBox(tr("Enable Renderdoc")); enableRenderdoc->setChecked(config.enableRenderdoc); connect(enableRenderdoc, &QCheckBox::toggled, this, [&](bool checked) { config.enableRenderdoc = checked; - config.save(); + updateConfig(); }); gpuLayout->addRow(enableRenderdoc); - QCheckBox *shaderJitEnabled = new QCheckBox(tr("Enable shader JIT")); + QCheckBox* shaderJitEnabled = new QCheckBox(tr("Enable shader JIT")); shaderJitEnabled->setChecked(config.shaderJitEnabled); connect(shaderJitEnabled, &QCheckBox::toggled, this, [&](bool checked) { config.shaderJitEnabled = checked; - config.save(); + updateConfig(); }); gpuLayout->addRow(shaderJitEnabled); - QCheckBox *vsyncEnabled = new QCheckBox(tr("Enable VSync")); + QCheckBox* vsyncEnabled = new QCheckBox(tr("Enable VSync")); vsyncEnabled->setChecked(config.vsyncEnabled); connect(vsyncEnabled, &QCheckBox::toggled, this, [&](bool checked) { config.vsyncEnabled = checked; - config.save(); + updateConfig(); }); gpuLayout->addRow(vsyncEnabled); - QCheckBox *useUbershaders = new QCheckBox(tr("Use ubershaders (No stutter, maybe slower)")); + QCheckBox* useUbershaders = new QCheckBox(tr("Use ubershaders (No stutter, maybe slower)")); useUbershaders->setChecked(config.useUbershaders); connect(useUbershaders, &QCheckBox::toggled, this, [&](bool checked) { config.useUbershaders = checked; - config.save(); + updateConfig(); }); gpuLayout->addRow(useUbershaders); - QCheckBox *accurateShaderMul = new QCheckBox(tr("Accurate shader multiplication")); + QCheckBox* accurateShaderMul = new QCheckBox(tr("Accurate shader multiplication")); accurateShaderMul->setChecked(config.accurateShaderMul); connect(accurateShaderMul, &QCheckBox::toggled, this, [&](bool checked) { config.accurateShaderMul = checked; - config.save(); + updateConfig(); }); gpuLayout->addRow(accurateShaderMul); - QCheckBox *accelerateShaders = new QCheckBox(tr("Accelerate shaders")); + QCheckBox* accelerateShaders = new QCheckBox(tr("Accelerate shaders")); accelerateShaders->setChecked(config.accelerateShaders); connect(accelerateShaders, &QCheckBox::toggled, this, [&](bool checked) { config.accelerateShaders = checked; - config.save(); + updateConfig(); }); gpuLayout->addRow(accelerateShaders); - QCheckBox *forceShadergenForLights = new QCheckBox(tr("Force shadergen when rendering lights")); + QCheckBox* forceShadergenForLights = new QCheckBox(tr("Force shadergen when rendering lights")); connect(forceShadergenForLights, &QCheckBox::toggled, this, [&](bool checked) { config.forceShadergenForLights = checked; - config.save(); + updateConfig(); }); gpuLayout->addRow(forceShadergenForLights); - QSpinBox *lightShadergenThreshold = new QSpinBox; + QSpinBox* lightShadergenThreshold = new QSpinBox; lightShadergenThreshold->setRange(1, 8); lightShadergenThreshold->setValue(config.lightShadergenThreshold); connect(lightShadergenThreshold, &QSpinBox::valueChanged, this, [&](int value) { config.lightShadergenThreshold = static_cast(value); - config.save(); + updateConfig(); }); gpuLayout->addRow(tr("Light threshold for forcing shadergen"), lightShadergenThreshold); // Audio settings - QGroupBox *spuGroupBox = new QGroupBox(tr("Audio Settings"), this); - QFormLayout *spuLayout = new QFormLayout(spuGroupBox); + QGroupBox* spuGroupBox = new QGroupBox(tr("Audio Settings"), this); + QFormLayout* spuLayout = new QFormLayout(spuGroupBox); spuLayout->setHorizontalSpacing(20); spuLayout->setVerticalSpacing(10); - QComboBox *dspType = new QComboBox; + QComboBox* dspType = new QComboBox; dspType->addItem(tr("Null")); dspType->addItem(tr("LLE")); dspType->addItem(tr("HLE")); dspType->setCurrentIndex(static_cast(config.dspType)); connect(dspType, &QComboBox::currentIndexChanged, this, [&](int index) { config.dspType = static_cast(index); - config.save(); + updateConfig(); }); spuLayout->addRow(tr("DSP emulation"), dspType); - QCheckBox *audioEnabled = new QCheckBox(tr("Enable audio")); + QCheckBox* audioEnabled = new QCheckBox(tr("Enable audio")); audioEnabled->setChecked(config.audioEnabled); connect(audioEnabled, &QCheckBox::toggled, this, [&](bool checked) { config.audioEnabled = checked; - config.save(); + updateConfig(); }); spuLayout->addRow(audioEnabled); - QCheckBox *aacEnabled = new QCheckBox(tr("Enable AAC audio")); + QCheckBox* aacEnabled = new QCheckBox(tr("Enable AAC audio")); aacEnabled->setChecked(config.aacEnabled); connect(aacEnabled, &QCheckBox::toggled, this, [&](bool checked) { config.aacEnabled = checked; - config.save(); + updateConfig(); }); spuLayout->addRow(aacEnabled); - QCheckBox *printDSPFirmware = new QCheckBox(tr("Print DSP firmware")); + QCheckBox* printDSPFirmware = new QCheckBox(tr("Print DSP firmware")); printDSPFirmware->setChecked(config.printDSPFirmware); connect(printDSPFirmware, &QCheckBox::toggled, this, [&](bool checked) { config.printDSPFirmware = checked; - config.save(); + updateConfig(); }); spuLayout->addRow(printDSPFirmware); - QCheckBox *muteAudio = new QCheckBox(tr("Mute audio device")); + QCheckBox* muteAudio = new QCheckBox(tr("Mute audio device")); muteAudio->setChecked(config.audioDeviceConfig.muteAudio); connect(muteAudio, &QCheckBox::toggled, this, [&](bool checked) { config.audioDeviceConfig.muteAudio = checked; - config.save(); + updateConfig(); }); spuLayout->addRow(muteAudio); - QSpinBox *volumeRaw = new QSpinBox; + QSpinBox* volumeRaw = new QSpinBox; volumeRaw->setRange(0, 200); - volumeRaw->setValue(config.audioDeviceConfig.volumeRaw * 100); + volumeRaw->setValue(config.audioDeviceConfig.volumeRaw* 100); connect(volumeRaw, &QSpinBox::valueChanged, this, [&](int value) { config.audioDeviceConfig.volumeRaw = static_cast(value) / 100.0f; - config.save(); + updateConfig(); }); spuLayout->addRow(tr("Audio device volume"), volumeRaw); // Battery settings - QGroupBox *batGroupBox = new QGroupBox(tr("Battery Settings"), this); - QFormLayout *batLayout = new QFormLayout(batGroupBox); + QGroupBox* batGroupBox = new QGroupBox(tr("Battery Settings"), this); + QFormLayout* batLayout = new QFormLayout(batGroupBox); batLayout->setHorizontalSpacing(20); batLayout->setVerticalSpacing(10); - QSpinBox *batteryPercentage = new QSpinBox; + QSpinBox* batteryPercentage = new QSpinBox; batteryPercentage->setRange(1, 100); batteryPercentage->setValue(config.batteryPercentage); connect(batteryPercentage, &QSpinBox::valueChanged, this, [&](int value) { config.batteryPercentage = static_cast(value); - config.save(); + updateConfig(); }); batLayout->addRow(tr("Battery percentage"), batteryPercentage); - QCheckBox *chargerPlugged = new QCheckBox(tr("Charger plugged")); + QCheckBox* chargerPlugged = new QCheckBox(tr("Charger plugged")); chargerPlugged->setChecked(config.chargerPlugged); connect(chargerPlugged, &QCheckBox::toggled, this, [&](bool checked) { config.chargerPlugged = checked; - config.save(); + updateConfig(); }); batLayout->addRow(chargerPlugged); // SD Card settings - QGroupBox *sdcGroupBox = new QGroupBox(tr("SD Card Settings"), this); - QFormLayout *sdcLayout = new QFormLayout(sdcGroupBox); + QGroupBox* sdcGroupBox = new QGroupBox(tr("SD Card Settings"), this); + QFormLayout* sdcLayout = new QFormLayout(sdcGroupBox); sdcLayout->setHorizontalSpacing(20); sdcLayout->setVerticalSpacing(10); - QCheckBox *sdCardInserted = new QCheckBox(tr("Enable virtual SD card")); + QCheckBox* sdCardInserted = new QCheckBox(tr("Enable virtual SD card")); sdCardInserted->setChecked(config.sdCardInserted); connect(sdCardInserted, &QCheckBox::toggled, this, [&](bool checked) { config.sdCardInserted = checked; - config.save(); + updateConfig(); }); sdcLayout->addRow(sdCardInserted); - QCheckBox *sdWriteProtected = new QCheckBox(tr("Write protect virtual SD card")); + QCheckBox* sdWriteProtected = new QCheckBox(tr("Write protect virtual SD card")); sdWriteProtected->setChecked(config.sdWriteProtected); connect(sdWriteProtected, &QCheckBox::toggled, this, [&](bool checked) { config.sdWriteProtected = checked; - config.save(); + updateConfig(); }); sdcLayout->addRow(sdWriteProtected); diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 5eead686..a2d585a4 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -81,7 +81,6 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) // Set up misc objects aboutWindow = new AboutWindow(nullptr); - configWindow = new ConfigWindow(emu, this); cheatsEditor = new CheatsWindow(emu, {}, this); patchWindow = new PatchWindow(this); luaEditor = new TextEditorWindow(this, "script.lua", ""); @@ -92,6 +91,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) shaderEditor->setText(emu->getRenderer()->getUbershader()); } + configWindow = new ConfigWindow( + [&]() { + EmulatorMessage message{.type = MessageType::UpdateConfig}; + sendMessage(message); + }, + emu->getConfig(), this + ); + auto args = QCoreApplication::arguments(); if (args.size() > 1) { auto romPath = std::filesystem::current_path() / args.at(1).toStdU16String(); @@ -410,6 +417,11 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { screen->resizeSurface(width, height); break; } + + case MessageType::UpdateConfig: + emu->getConfig() = configWindow->getConfig(); + emu->reloadSettings(); + break; } } diff --git a/src/renderdoc.cpp b/src/renderdoc.cpp index 1de9c451..43627b66 100644 --- a/src/renderdoc.cpp +++ b/src/renderdoc.cpp @@ -23,6 +23,8 @@ namespace Renderdoc { }; static CaptureState captureState{CaptureState::Idle}; + static bool renderdocLoaded{false}; + RENDERDOC_API_1_6_0* rdocAPI{}; void loadRenderdoc() { @@ -73,6 +75,8 @@ namespace Renderdoc { } #endif if (rdocAPI) { + renderdocLoaded = true; + // Disable default capture keys as they suppose to trigger present-to-present capturing // and it is not what we want rdocAPI->SetCaptureKeys(nullptr, 0); @@ -115,5 +119,7 @@ namespace Renderdoc { rdocAPI->SetCaptureFilePathTemplate((path + '\\' + prefix).c_str()); } } + + bool isLoaded() { return renderdocLoaded; } } // namespace Renderdoc #endif \ No newline at end of file