diff options
Diffstat (limited to 'src')
46 files changed, 1326 insertions, 669 deletions
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 3a1fbe3f7..b4501eb2e 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -20,6 +20,7 @@ #include "common/logging/log.h" #include "common/logging/backend.h" #include "common/logging/filter.h" +#include "common/scm_rev.h" #include "common/scope_exit.h" #include "core/settings.h" @@ -34,11 +35,17 @@ #include "video_core/video_core.h" -static void PrintHelp() +static void PrintHelp(const char *argv0) { - std::cout << "Usage: citra [options] <filename>" << std::endl; - std::cout << "--help, -h Display this information" << std::endl; - std::cout << "--gdbport, -g number Enable gdb stub on port number" << std::endl; + std::cout << "Usage: " << argv0 << " [options] <filename>\n" + "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" + "-h, --help Display this help and exit\n" + "-v, --version Output version information and exit\n"; +} + +static void PrintVersion() +{ + std::cout << "Citra " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl; } /// Application entry point @@ -51,18 +58,16 @@ int main(int argc, char **argv) { std::string boot_filename; static struct option long_options[] = { - { "help", no_argument, 0, 'h' }, { "gdbport", required_argument, 0, 'g' }, + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, { 0, 0, 0, 0 } }; while (optind < argc) { - char arg = getopt_long(argc, argv, ":hg:", long_options, &option_index); + char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index); if (arg != -1) { switch (arg) { - case 'h': - PrintHelp(); - return 0; case 'g': errno = 0; gdb_port = strtoul(optarg, &endarg, 0); @@ -73,6 +78,12 @@ int main(int argc, char **argv) { exit(1); } break; + case 'h': + PrintHelp(argv[0]); + return 0; + case 'v': + PrintVersion(); + return 0; } } else { boot_filename = argv[optind]; @@ -93,14 +104,13 @@ int main(int argc, char **argv) { log_filter.ParseFilterString(Settings::values.log_filter); - GDBStub::ToggleServer(use_gdbstub); - GDBStub::SetServerPort(gdb_port); + // Apply the command line arguments + Settings::values.gdbstub_port = gdb_port; + Settings::values.use_gdbstub = use_gdbstub; + Settings::Apply(); std::unique_ptr<EmuWindow_SDL2> emu_window = std::make_unique<EmuWindow_SDL2>(); - VideoCore::g_hw_renderer_enabled = Settings::values.use_hw_renderer; - VideoCore::g_shader_jit_enabled = Settings::values.use_shader_jit; - System::Init(emu_window.get()); SCOPE_EXIT({ System::Shutdown(); }); diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 9b3eb2cd6..6660d9879 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -17,12 +17,16 @@ set(SRCS debugger/profiler.cpp debugger/ramview.cpp debugger/registers.cpp - game_list.cpp util/spinbox.cpp util/util.cpp bootmanager.cpp + configure_debug.cpp + configure_dialog.cpp + configure_general.cpp + game_list.cpp hotkeys.cpp main.cpp + ui_settings.cpp citra-qt.rc Info.plist ) @@ -44,12 +48,16 @@ set(HEADERS debugger/profiler.h debugger/ramview.h debugger/registers.h - game_list.h util/spinbox.h util/util.h bootmanager.h + configure_debug.h + configure_dialog.h + configure_general.h + game_list.h hotkeys.h main.h + ui_settings.h version.h ) @@ -59,6 +67,9 @@ set(UIS debugger/disassembler.ui debugger/profiler.ui debugger/registers.ui + configure.ui + configure_debug.ui + configure_general.ui hotkeys.ui main.ui ) diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 8e247ff5c..e363be38a 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -7,12 +7,12 @@ #include <QStringList> #include "citra_qt/config.h" +#include "citra_qt/ui_settings.h" #include "common/file_util.h" #include "core/settings.h" Config::Config() { - // TODO: Don't hardcode the path; let the frontend decide where to put the config files. qt_config_loc = FileUtil::GetUserPath(D_CONFIG_IDX) + "qt-config.ini"; FileUtil::CreateFullPath(qt_config_loc); @@ -67,6 +67,51 @@ void Config::ReadValues() { Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); qt_config->endGroup(); + + qt_config->beginGroup("UI"); + + qt_config->beginGroup("UILayout"); + UISettings::values.geometry = qt_config->value("geometry").toByteArray(); + UISettings::values.state = qt_config->value("state").toByteArray(); + UISettings::values.renderwindow_geometry = qt_config->value("geometryRenderWindow").toByteArray(); + UISettings::values.gamelist_header_state = qt_config->value("gameListHeaderState").toByteArray(); + UISettings::values.microprofile_geometry = qt_config->value("microProfileDialogGeometry").toByteArray(); + UISettings::values.microprofile_visible = qt_config->value("microProfileDialogVisible", false).toBool(); + qt_config->endGroup(); + + qt_config->beginGroup("Paths"); + UISettings::values.roms_path = qt_config->value("romsPath").toString(); + UISettings::values.symbols_path = qt_config->value("symbolsPath").toString(); + UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString(); + UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool(); + UISettings::values.recent_files = qt_config->value("recentFiles").toStringList(); + qt_config->endGroup(); + + qt_config->beginGroup("Shortcuts"); + QStringList groups = qt_config->childGroups(); + for (auto group : groups) { + qt_config->beginGroup(group); + + QStringList hotkeys = qt_config->childGroups(); + for (auto hotkey : hotkeys) { + qt_config->beginGroup(hotkey); + UISettings::values.shortcuts.emplace_back( + UISettings::Shortcut(group + "/" + hotkey, + UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(), + qt_config->value("Context").toInt()))); + qt_config->endGroup(); + } + + qt_config->endGroup(); + } + qt_config->endGroup(); + + UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); + UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); + UISettings::values.confirm_before_closing = qt_config->value("confirmClose",true).toBool(); + UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); + + qt_config->endGroup(); } void Config::SaveValues() { @@ -107,10 +152,44 @@ void Config::SaveValues() { qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); qt_config->endGroup(); + + qt_config->beginGroup("UI"); + + qt_config->beginGroup("UILayout"); + qt_config->setValue("geometry", UISettings::values.geometry); + qt_config->setValue("state", UISettings::values.state); + qt_config->setValue("geometryRenderWindow", UISettings::values.renderwindow_geometry); + qt_config->setValue("gameListHeaderState", UISettings::values.gamelist_header_state); + qt_config->setValue("microProfileDialogGeometry", UISettings::values.microprofile_geometry); + qt_config->setValue("microProfileDialogVisible", UISettings::values.microprofile_visible); + qt_config->endGroup(); + + qt_config->beginGroup("Paths"); + qt_config->setValue("romsPath", UISettings::values.roms_path); + qt_config->setValue("symbolsPath", UISettings::values.symbols_path); + qt_config->setValue("gameListRootDir", UISettings::values.gamedir); + qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan); + qt_config->setValue("recentFiles", UISettings::values.recent_files); + qt_config->endGroup(); + + qt_config->beginGroup("Shortcuts"); + for (auto shortcut : UISettings::values.shortcuts ) { + qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first); + qt_config->setValue(shortcut.first + "/Context", shortcut.second.second); + } + qt_config->endGroup(); + + qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); + qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); + qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); + qt_config->setValue("firstStart", UISettings::values.first_start); + + qt_config->endGroup(); } void Config::Reload() { ReadValues(); + Settings::Apply(); } void Config::Save() { diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configure.ui new file mode 100644 index 000000000..6ae056ff9 --- /dev/null +++ b/src/citra_qt/configure.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureDialog</class> + <widget class="QDialog" name="ConfigureDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>441</width> + <height>501</height> + </rect> + </property> + <property name="windowTitle"> + <string>Citra Configuration</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="ConfigureGeneral" name="generalTab"> + <attribute name="title"> + <string>General</string> + </attribute> + </widget> + <widget class="QWidget" name="inputTab"> + <attribute name="title"> + <string>Input</string> + </attribute> + </widget> + <widget class="ConfigureDebug" name="debugTab"> + <attribute name="title"> + <string>Debug</string> + </attribute> + </widget> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigureGeneral</class> + <extends>QWidget</extends> + <header>configure_general.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureDebug</class> + <extends>QWidget</extends> + <header>configure_debug.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>220</x> + <y>380</y> + </hint> + <hint type="destinationlabel"> + <x>220</x> + <y>200</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>220</x> + <y>380</y> + </hint> + <hint type="destinationlabel"> + <x>220</x> + <y>200</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/citra_qt/configure_debug.cpp b/src/citra_qt/configure_debug.cpp new file mode 100644 index 000000000..dc3d7b906 --- /dev/null +++ b/src/citra_qt/configure_debug.cpp @@ -0,0 +1,31 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "citra_qt/configure_debug.h" +#include "ui_configure_debug.h" + +#include "core/settings.h" + +ConfigureDebug::ConfigureDebug(QWidget *parent) : + QWidget(parent), + ui(new Ui::ConfigureDebug) +{ + ui->setupUi(this); + this->setConfiguration(); +} + +ConfigureDebug::~ConfigureDebug() { +} + +void ConfigureDebug::setConfiguration() { + ui->toogle_gdbstub->setChecked(Settings::values.use_gdbstub); + ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); + ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); +} + +void ConfigureDebug::applyConfiguration() { + Settings::values.use_gdbstub = ui->toogle_gdbstub->isChecked(); + Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); + Settings::Apply(); +} diff --git a/src/citra_qt/configure_debug.h b/src/citra_qt/configure_debug.h new file mode 100644 index 000000000..ab58ebbdc --- /dev/null +++ b/src/citra_qt/configure_debug.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +namespace Ui { +class ConfigureDebug; +} + +class ConfigureDebug : public QWidget +{ + Q_OBJECT + +public: + explicit ConfigureDebug(QWidget *parent = nullptr); + ~ConfigureDebug(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureDebug> ui; +}; diff --git a/src/citra_qt/configure_debug.ui b/src/citra_qt/configure_debug.ui new file mode 100644 index 000000000..3ba7f44da --- /dev/null +++ b/src/citra_qt/configure_debug.ui @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureDebug</class> + <widget class="QWidget" name="ConfigureDebug"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>GDB</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QCheckBox" name="toogle_gdbstub"> + <property name="text"> + <string>Enable GDB Stub</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="gdbport_spinbox"> + <property name="maximum"> + <number>65536</number> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>toogle_gdbstub</sender> + <signal>toggled(bool)</signal> + <receiver>gdbport_spinbox</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>84</x> + <y>157</y> + </hint> + <hint type="destinationlabel"> + <x>342</x> + <y>158</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configure_dialog.cpp new file mode 100644 index 000000000..87c26c715 --- /dev/null +++ b/src/citra_qt/configure_dialog.cpp @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "citra_qt/config.h" +#include "citra_qt/configure_dialog.h" +#include "ui_configure.h" + + +#include "core/settings.h" + +ConfigureDialog::ConfigureDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ConfigureDialog) +{ + ui->setupUi(this); + this->setConfiguration(); +} + +ConfigureDialog::~ConfigureDialog() { +} + +void ConfigureDialog::setConfiguration() { +} + +void ConfigureDialog::applyConfiguration() { + ui->generalTab->applyConfiguration(); + ui->debugTab->applyConfiguration(); +} diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configure_dialog.h new file mode 100644 index 000000000..89020eeb4 --- /dev/null +++ b/src/citra_qt/configure_dialog.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> + +namespace Ui { +class ConfigureDialog; +} + +class ConfigureDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ConfigureDialog(QWidget *parent = nullptr); + ~ConfigureDialog(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureDialog> ui; +}; diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configure_general.cpp new file mode 100644 index 000000000..a27d0d26c --- /dev/null +++ b/src/citra_qt/configure_general.cpp @@ -0,0 +1,37 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "citra_qt/configure_general.h" +#include "citra_qt/ui_settings.h" +#include "ui_configure_general.h" + +#include "core/settings.h" + +ConfigureGeneral::ConfigureGeneral(QWidget *parent) : + QWidget(parent), + ui(new Ui::ConfigureGeneral) +{ + ui->setupUi(this); + this->setConfiguration(); +} + +ConfigureGeneral::~ConfigureGeneral() { +} + +void ConfigureGeneral::setConfiguration() { + ui->toogle_deepscan->setChecked(UISettings::values.gamedir_deepscan); + ui->toogle_check_exit->setChecked(UISettings::values.confirm_before_closing); + ui->region_combobox->setCurrentIndex(Settings::values.region_value); + ui->toogle_hw_renderer->setChecked(Settings::values.use_hw_renderer); + ui->toogle_shader_jit->setChecked(Settings::values.use_shader_jit); +} + +void ConfigureGeneral::applyConfiguration() { + UISettings::values.gamedir_deepscan = ui->toogle_deepscan->isChecked(); + UISettings::values.confirm_before_closing = ui->toogle_check_exit->isChecked(); + Settings::values.region_value = ui->region_combobox->currentIndex(); + Settings::values.use_hw_renderer = ui->toogle_hw_renderer->isChecked(); + Settings::values.use_shader_jit = ui->toogle_shader_jit->isChecked(); + Settings::Apply(); +} diff --git a/src/citra_qt/configure_general.h b/src/citra_qt/configure_general.h new file mode 100644 index 000000000..a6c68e62d --- /dev/null +++ b/src/citra_qt/configure_general.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +namespace Ui { +class ConfigureGeneral; +} + +class ConfigureGeneral : public QWidget +{ + Q_OBJECT + +public: + explicit ConfigureGeneral(QWidget *parent = nullptr); + ~ConfigureGeneral(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureGeneral> ui; +}; diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui new file mode 100644 index 000000000..47184c5c6 --- /dev/null +++ b/src/citra_qt/configure_general.ui @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureGeneral</class> + <widget class="QWidget" name="ConfigureGeneral"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>377</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>General</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="toogle_deepscan"> + <property name="text"> + <string>Recursive scan for game folder</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="toogle_check_exit"> + <property name="text"> + <string>Confirm exit while emulation is running</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>Emulation</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Region:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="region_combobox"> + <item> + <property name="text"> + <string notr="true">JPN</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">USA</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">EUR</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">AUS</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">CHN</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">KOR</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">TWN</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QCheckBox" name="toogle_hw_renderer"> + <property name="text"> + <string>Enable hardware renderer</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="toogle_shader_jit"> + <property name="text"> + <string>Enable shader JIT</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Hotkeys</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="GHotkeysDialog" name="widget" native="true"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>GHotkeysDialog</class> + <extends>QWidget</extends> + <header>hotkeys.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index ffcab1f03..d14532102 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -8,6 +8,7 @@ #include "game_list.h" #include "game_list_p.h" +#include "ui_settings.h" #include "core/loader/loader.h" @@ -100,19 +101,15 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) current_worker = std::move(worker); } -void GameList::SaveInterfaceLayout(QSettings& settings) +void GameList::SaveInterfaceLayout() { - settings.beginGroup("UILayout"); - settings.setValue("gameListHeaderState", tree_view->header()->saveState()); - settings.endGroup(); + UISettings::values.gamelist_header_state = tree_view->header()->saveState(); } -void GameList::LoadInterfaceLayout(QSettings& settings) +void GameList::LoadInterfaceLayout() { auto header = tree_view->header(); - settings.beginGroup("UILayout"); - header->restoreState(settings.value("gameListHeaderState").toByteArray()); - settings.endGroup(); + header->restoreState(UISettings::values.gamelist_header_state); item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 0950d9622..48febdc60 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -31,8 +31,8 @@ public: void PopulateAsync(const QString& dir_path, bool deep_scan); - void SaveInterfaceLayout(QSettings& settings); - void LoadInterfaceLayout(QSettings& settings); + void SaveInterfaceLayout(); + void LoadInterfaceLayout(); public slots: void AddEntry(QList<QStandardItem*> entry_items); diff --git a/src/citra_qt/hotkeys.cpp b/src/citra_qt/hotkeys.cpp index ed6b12fc4..41f95c63d 100644 --- a/src/citra_qt/hotkeys.cpp +++ b/src/citra_qt/hotkeys.cpp @@ -4,11 +4,12 @@ #include <map> +#include <QtGlobal> #include <QKeySequence> -#include <QSettings> #include <QShortcut> #include "citra_qt/hotkeys.h" +#include "citra_qt/ui_settings.h" struct Hotkey { @@ -24,54 +25,39 @@ typedef std::map<QString, HotkeyMap> HotkeyGroupMap; HotkeyGroupMap hotkey_groups; -void SaveHotkeys(QSettings& settings) +void SaveHotkeys() { - settings.beginGroup("Shortcuts"); - + UISettings::values.shortcuts.clear(); for (auto group : hotkey_groups) { - settings.beginGroup(group.first); for (auto hotkey : group.second) { - settings.beginGroup(hotkey.first); - settings.setValue(QString("KeySeq"), hotkey.second.keyseq.toString()); - settings.setValue(QString("Context"), hotkey.second.context); - settings.endGroup(); + UISettings::values.shortcuts.emplace_back( + UISettings::Shortcut(group.first + "/" + hotkey.first, + UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), + hotkey.second.context))); } - settings.endGroup(); } - settings.endGroup(); } -void LoadHotkeys(QSettings& settings) +void LoadHotkeys() { - settings.beginGroup("Shortcuts"); - // Make sure NOT to use a reference here because it would become invalid once we call beginGroup() - QStringList groups = settings.childGroups(); - for (auto group : groups) + for (auto shortcut : UISettings::values.shortcuts) { - settings.beginGroup(group); + QStringList cat = shortcut.first.split("/"); + Q_ASSERT(cat.size() >= 2); - QStringList hotkeys = settings.childGroups(); - for (auto hotkey : hotkeys) + // RegisterHotkey assigns default keybindings, so use old values as default parameters + Hotkey& hk = hotkey_groups[cat[0]][cat[1]]; + if (!shortcut.second.first.isEmpty()) { - settings.beginGroup(hotkey); - - // RegisterHotkey assigns default keybindings, so use old values as default parameters - Hotkey& hk = hotkey_groups[group][hotkey]; - hk.keyseq = QKeySequence::fromString(settings.value("KeySeq", hk.keyseq.toString()).toString()); - hk.context = (Qt::ShortcutContext)settings.value("Context", hk.context).toInt(); - if (hk.shortcut) - hk.shortcut->setKey(hk.keyseq); - - settings.endGroup(); + hk.keyseq = QKeySequence::fromString(shortcut.second.first); + hk.context = (Qt::ShortcutContext)shortcut.second.second; } - - settings.endGroup(); + if (hk.shortcut) + hk.shortcut->setKey(hk.keyseq); } - - settings.endGroup(); } void RegisterHotkey(const QString& group, const QString& action, const QKeySequence& default_keyseq, Qt::ShortcutContext default_context) @@ -94,7 +80,7 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge } -GHotkeysDialog::GHotkeysDialog(QWidget* parent): QDialog(parent) +GHotkeysDialog::GHotkeysDialog(QWidget* parent): QWidget(parent) { ui.setupUi(this); diff --git a/src/citra_qt/hotkeys.h b/src/citra_qt/hotkeys.h index 2fe635882..38aa5f012 100644 --- a/src/citra_qt/hotkeys.h +++ b/src/citra_qt/hotkeys.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include "ui_hotkeys.h" class QDialog; @@ -33,16 +35,16 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge * * @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a settings group will be created to store the key sequence and the hotkey context. */ -void SaveHotkeys(QSettings& settings); +void SaveHotkeys(); /** * Loads hotkeys from the settings file. * * @note Yet unregistered hotkeys which are present in the settings will automatically be registered. */ -void LoadHotkeys(QSettings& settings); +void LoadHotkeys(); -class GHotkeysDialog : public QDialog +class GHotkeysDialog : public QWidget { Q_OBJECT diff --git a/src/citra_qt/hotkeys.ui b/src/citra_qt/hotkeys.ui index 38a9a14d1..050fe064e 100644 --- a/src/citra_qt/hotkeys.ui +++ b/src/citra_qt/hotkeys.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>hotkeys</class> - <widget class="QDialog" name="hotkeys"> + <widget class="QWidget" name="hotkeys"> <property name="geometry"> <rect> <x>0</x> @@ -39,51 +39,8 @@ </column> </widget> </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set> - </property> - </widget> - </item> </layout> </widget> <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>hotkeys</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>248</x> - <y>254</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>hotkeys</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>316</x> - <y>260</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index ca0ae6f7b..2ca1e51f6 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -14,9 +14,11 @@ #include "citra_qt/bootmanager.h" #include "citra_qt/config.h" +#include "citra_qt/configure_dialog.h" #include "citra_qt/game_list.h" #include "citra_qt/hotkeys.h" #include "citra_qt/main.h" +#include "citra_qt/ui_settings.h" // Debugger #include "citra_qt/debugger/callstack.h" @@ -50,12 +52,10 @@ #include "video_core/video_core.h" -GMainWindow::GMainWindow() : emu_thread(nullptr) +GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { Pica::g_debug_context = Pica::DebugContext::Construct(); - Config config; - ui.setupUi(this); statusBar()->hide(); @@ -133,33 +133,18 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) setGeometry(x, y, w, h); // Restore UI state - QSettings settings; - - settings.beginGroup("UILayout"); - restoreGeometry(settings.value("geometry").toByteArray()); - restoreState(settings.value("state").toByteArray()); - render_window->restoreGeometry(settings.value("geometryRenderWindow").toByteArray()); - microProfileDialog->restoreGeometry(settings.value("microProfileDialogGeometry").toByteArray()); - microProfileDialog->setVisible(settings.value("microProfileDialogVisible").toBool()); - settings.endGroup(); - - game_list->LoadInterfaceLayout(settings); - - ui.action_Use_Gdbstub->setChecked(Settings::values.use_gdbstub); - SetGdbstubEnabled(ui.action_Use_Gdbstub->isChecked()); - - GDBStub::SetServerPort(static_cast<u32>(Settings::values.gdbstub_port)); - - ui.action_Use_Hardware_Renderer->setChecked(Settings::values.use_hw_renderer); - SetHardwareRendererEnabled(ui.action_Use_Hardware_Renderer->isChecked()); + restoreGeometry(UISettings::values.geometry); + restoreState(UISettings::values.state); + render_window->restoreGeometry(UISettings::values.renderwindow_geometry); + microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry); + microProfileDialog->setVisible(UISettings::values.microprofile_visible); - ui.action_Use_Shader_JIT->setChecked(Settings::values.use_shader_jit); - SetShaderJITEnabled(ui.action_Use_Shader_JIT->isChecked()); + game_list->LoadInterfaceLayout(); - ui.action_Single_Window_Mode->setChecked(settings.value("singleWindowMode", true).toBool()); + ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode); ToggleWindowMode(); - ui.actionDisplay_widget_title_bars->setChecked(settings.value("displayTitleBars", true).toBool()); + ui.actionDisplay_widget_title_bars->setChecked(UISettings::values.display_titlebar); OnDisplayTitleBars(ui.actionDisplay_widget_title_bars->isChecked()); // Prepare actions for recent files @@ -172,21 +157,16 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) } UpdateRecentFiles(); - confirm_before_closing = settings.value("confirmClose", true).toBool(); - // Setup connections - connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); - connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile())); + connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), Qt::DirectConnection); + connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); + connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),Qt::DirectConnection); connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, SLOT(OnMenuSelectGameListRoot())); connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame())); connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame())); connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame())); - connect(ui.action_Use_Hardware_Renderer, SIGNAL(triggered(bool)), this, SLOT(SetHardwareRendererEnabled(bool))); - connect(ui.action_Use_Shader_JIT, SIGNAL(triggered(bool)), this, SLOT(SetShaderJITEnabled(bool))); - connect(ui.action_Use_Gdbstub, SIGNAL(triggered(bool)), this, SLOT(SetGdbstubEnabled(bool))); connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); - connect(ui.action_Hotkeys, SIGNAL(triggered()), this, SLOT(OnOpenHotkeysDialog())); connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget, SLOT(OnEmulationStarting(EmuThread*))); connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping())); @@ -201,7 +181,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) // Setup hotkeys RegisterHotkey("Main Window", "Load File", QKeySequence::Open); RegisterHotkey("Main Window", "Start Emulation"); - LoadHotkeys(settings); + LoadHotkeys(); connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this, SLOT(OnMenuLoadFile())); connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this, SLOT(OnStartGame())); @@ -211,7 +191,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) show(); - game_list->PopulateAsync(settings.value("gameListRootDir", ".").toString(), settings.value("gameListDeepScan", false).toBool()); + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); QStringList args = QApplication::arguments(); if (args.length() >= 2) { @@ -375,32 +355,24 @@ void GMainWindow::ShutdownGame() { emulation_running = false; } -void GMainWindow::StoreRecentFile(const std::string& filename) -{ - QSettings settings; - QStringList recent_files = settings.value("recentFiles").toStringList(); - recent_files.prepend(QString::fromStdString(filename)); - recent_files.removeDuplicates(); - while (recent_files.size() > max_recent_files_item) { - recent_files.removeLast(); +void GMainWindow::StoreRecentFile(const std::string& filename) { + UISettings::values.recent_files.prepend(QString::fromStdString(filename)); + UISettings::values.recent_files.removeDuplicates(); + while (UISettings::values.recent_files.size() > max_recent_files_item) { + UISettings::values.recent_files.removeLast(); } - settings.setValue("recentFiles", recent_files); - UpdateRecentFiles(); } void GMainWindow::UpdateRecentFiles() { - QSettings settings; - QStringList recent_files = settings.value("recentFiles").toStringList(); - - unsigned int num_recent_files = std::min(recent_files.size(), static_cast<int>(max_recent_files_item)); + unsigned int num_recent_files = std::min(UISettings::values.recent_files.size(), static_cast<int>(max_recent_files_item)); for (unsigned int i = 0; i < num_recent_files; i++) { - QString text = QString("&%1. %2").arg(i + 1).arg(QFileInfo(recent_files[i]).fileName()); + QString text = QString("&%1. %2").arg(i + 1).arg(QFileInfo(UISettings::values.recent_files[i]).fileName()); actions_recent_files[i]->setText(text); - actions_recent_files[i]->setData(recent_files[i]); - actions_recent_files[i]->setToolTip(recent_files[i]); + actions_recent_files[i]->setData(UISettings::values.recent_files[i]); + actions_recent_files[i]->setToolTip(UISettings::values.recent_files[i]); actions_recent_files[i]->setVisible(true); } @@ -421,36 +393,28 @@ void GMainWindow::OnGameListLoadFile(QString game_path) { } void GMainWindow::OnMenuLoadFile() { - QSettings settings; - QString rom_path = settings.value("romsPath", QString()).toString(); - - QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), rom_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); + QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); if (!filename.isEmpty()) { - settings.setValue("romsPath", QFileInfo(filename).path()); + UISettings::values.roms_path = QFileInfo(filename).path(); BootGame(filename.toStdString()); } } void GMainWindow::OnMenuLoadSymbolMap() { - QSettings settings; - QString symbol_path = settings.value("symbolsPath", QString()).toString(); - - QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), symbol_path, tr("Symbol map (*)")); + QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol map (*)")); if (!filename.isEmpty()) { - settings.setValue("symbolsPath", QFileInfo(filename).path()); + UISettings::values.symbols_path = QFileInfo(filename).path(); LoadSymbolMap(filename.toStdString()); } } void GMainWindow::OnMenuSelectGameListRoot() { - QSettings settings; - QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); if (!dir_path.isEmpty()) { - settings.setValue("gameListRootDir", dir_path); - game_list->PopulateAsync(dir_path, settings.value("gameListDeepScan").toBool()); + UISettings::values.gamedir = dir_path; + game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan); } } @@ -466,10 +430,7 @@ void GMainWindow::OnMenuRecentFile() { // Display an error message and remove the file from the list. QMessageBox::information(this, tr("File not found"), tr("File \"%1\" not found").arg(filename)); - QSettings settings; - QStringList recent_files = settings.value("recentFiles").toStringList(); - recent_files.removeOne(filename); - settings.setValue("recentFiles", recent_files); + UISettings::values.recent_files.removeOne(filename); UpdateRecentFiles(); } } @@ -496,31 +457,6 @@ void GMainWindow::OnStopGame() { ShutdownGame(); } -void GMainWindow::OnOpenHotkeysDialog() { - GHotkeysDialog dialog(this); - dialog.exec(); -} - -void GMainWindow::SetHardwareRendererEnabled(bool enabled) { - VideoCore::g_hw_renderer_enabled = enabled; - - Config config; - Settings::values.use_hw_renderer = enabled; - config.Save(); -} - -void GMainWindow::SetGdbstubEnabled(bool enabled) { - GDBStub::ToggleServer(enabled); -} - -void GMainWindow::SetShaderJITEnabled(bool enabled) { - VideoCore::g_shader_jit_enabled = enabled; - - Config config; - Settings::values.use_shader_jit = enabled; - config.Save(); -} - void GMainWindow::ToggleWindowMode() { if (ui.action_Single_Window_Mode->isChecked()) { // Render in the main window... @@ -547,11 +483,17 @@ void GMainWindow::ToggleWindowMode() { } void GMainWindow::OnConfigure() { - //GControllerConfigDialog* dialog = new GControllerConfigDialog(controller_ports, this); + ConfigureDialog configureDialog(this); + auto result = configureDialog.exec(); + if (result == QDialog::Accepted) + { + configureDialog.applyConfiguration(); + config->Save(); + } } bool GMainWindow::ConfirmClose() { - if (emu_thread == nullptr || !confirm_before_closing) + if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; auto answer = QMessageBox::question(this, tr("Citra"), @@ -566,23 +508,18 @@ void GMainWindow::closeEvent(QCloseEvent* event) { return; } - // Save window layout - QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Citra team", "Citra"); + UISettings::values.geometry = saveGeometry(); + UISettings::values.state = saveState(); + UISettings::values.renderwindow_geometry = render_window->saveGeometry(); + UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); + UISettings::values.microprofile_visible = microProfileDialog->isVisible(); - settings.beginGroup("UILayout"); - settings.setValue("geometry", saveGeometry()); - settings.setValue("state", saveState()); - settings.setValue("geometryRenderWindow", render_window->saveGeometry()); - settings.setValue("microProfileDialogGeometry", microProfileDialog->saveGeometry()); - settings.setValue("microProfileDialogVisible", microProfileDialog->isVisible()); - settings.endGroup(); + UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); + UISettings::values.display_titlebar = ui.actionDisplay_widget_title_bars->isChecked(); + UISettings::values.first_start = false; - settings.setValue("singleWindowMode", ui.action_Single_Window_Mode->isChecked()); - settings.setValue("displayTitleBars", ui.actionDisplay_widget_title_bars->isChecked()); - settings.setValue("firstStart", false); - settings.setValue("confirmClose", confirm_before_closing); - game_list->SaveInterfaceLayout(settings); - SaveHotkeys(settings); + game_list->SaveInterfaceLayout(); + SaveHotkeys(); // Shutdown session if the emu thread is active... if (emu_thread != nullptr) @@ -607,7 +544,6 @@ int main(int argc, char* argv[]) { }); // Init settings params - QSettings::setDefaultFormat(QSettings::IniFormat); QCoreApplication::setOrganizationName("Citra team"); QCoreApplication::setApplicationName("Citra"); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 6e4e56689..477db5c5c 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -10,6 +10,7 @@ #include "ui_main.h" +class Config; class GameList; class GImageInfo; class GRenderWindow; @@ -104,12 +105,8 @@ private slots: /// Called whenever a user selects the "File->Select Game List Root" menu item void OnMenuSelectGameListRoot(); void OnMenuRecentFile(); - void OnOpenHotkeysDialog(); void OnConfigure(); void OnDisplayTitleBars(bool); - void SetHardwareRendererEnabled(bool); - void SetGdbstubEnabled(bool); - void SetShaderJITEnabled(bool); void ToggleWindowMode(); private: @@ -118,6 +115,8 @@ private: GRenderWindow* render_window; GameList* game_list; + std::unique_ptr<Config> config; + // Whether emulation is currently running in Citra. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; @@ -131,7 +130,6 @@ private: GPUCommandListWidget* graphicsCommandsWidget; QAction* actions_recent_files[max_recent_files_item]; - bool confirm_before_closing; }; #endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 1e8a07cfb..441e0b81e 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -45,7 +45,7 @@ <x>0</x> <y>0</y> <width>1081</width> - <height>22</height> + <height>19</height> </rect> </property> <widget class="QMenu" name="menu_File"> @@ -73,9 +73,6 @@ <addaction name="action_Pause"/> <addaction name="action_Stop"/> <addaction name="separator"/> - <addaction name="action_Use_Hardware_Renderer"/> - <addaction name="action_Use_Shader_JIT"/> - <addaction name="action_Use_Gdbstub"/> <addaction name="action_Configure"/> </widget> <widget class="QMenu" name="menu_View"> @@ -84,7 +81,6 @@ </property> <addaction name="action_Single_Window_Mode"/> <addaction name="actionDisplay_widget_title_bars"/> - <addaction name="action_Hotkeys"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> @@ -150,35 +146,6 @@ <string>Single Window Mode</string> </property> </action> - <action name="action_Hotkeys"> - <property name="text"> - <string>Configure &Hotkeys ...</string> - </property> - </action> - <action name="action_Use_Hardware_Renderer"> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="text"> - <string>Use Hardware Renderer</string> - </property> - </action> - <action name="action_Use_Shader_JIT"> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="text"> - <string>Use Shader JIT</string> - </property> - </action> - <action name="action_Use_Gdbstub"> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="text"> - <string>Use Gdbstub</string> - </property> - </action> <action name="action_Configure"> <property name="text"> <string>Configure ...</string> @@ -220,22 +187,6 @@ </hints> </connection> <connection> - <sender>action_Configure</sender> - <signal>triggered()</signal> - <receiver>MainWindow</receiver> - <slot>OnConfigure()</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>540</x> - <y>364</y> - </hint> - </hints> - </connection> - <connection> <sender>actionDisplay_widget_title_bars</sender> <signal>triggered(bool)</signal> <receiver>MainWindow</receiver> diff --git a/src/citra_qt/ui_settings.cpp b/src/citra_qt/ui_settings.cpp new file mode 100644 index 000000000..5f2215899 --- /dev/null +++ b/src/citra_qt/ui_settings.cpp @@ -0,0 +1,11 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_settings.h" + +namespace UISettings { + +Values values = {}; + +} diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h new file mode 100644 index 000000000..62db4a73e --- /dev/null +++ b/src/citra_qt/ui_settings.h @@ -0,0 +1,47 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QByteArray> +#include <QStringList> +#include <QString> + +#include <vector> + +namespace UISettings { + +using ContextualShortcut = std::pair<QString, int> ; +using Shortcut = std::pair<QString, ContextualShortcut>; + +struct Values { + QByteArray geometry; + QByteArray state; + + QByteArray renderwindow_geometry; + + QByteArray gamelist_header_state; + + QByteArray microprofile_geometry; + bool microprofile_visible; + + bool single_window_mode; + bool display_titlebar; + + bool confirm_before_closing; + bool first_start; + + QString roms_path; + QString symbols_path; + QString gamedir; + bool gamedir_deepscan; + QStringList recent_files; + + // Shortcut name <Shortcut, context> + std::vector<Shortcut> shortcuts; +}; + +extern Values values; + +} diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 9ada09f8a..6e2867658 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -69,9 +69,10 @@ static void StripTailDirSlashes(std::string &fname) { if (fname.length() > 1) { - size_t i = fname.length() - 1; - while (fname[i] == DIR_SEP_CHR) - fname[i--] = '\0'; + size_t i = fname.length(); + while (i > 0 && fname[i - 1] == DIR_SEP_CHR) + --i; + fname.resize(i); } return; } @@ -85,6 +86,10 @@ bool Exists(const std::string &filename) StripTailDirSlashes(copy); #ifdef _WIN32 + // Windows needs a slash to identify a driver root + if (copy.size() != 0 && copy.back() == ':') + copy += DIR_SEP_CHR; + int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); #else int result = stat64(copy.c_str(), &file_info); @@ -102,6 +107,10 @@ bool IsDirectory(const std::string &filename) StripTailDirSlashes(copy); #ifdef _WIN32 + // Windows needs a slash to identify a driver root + if (copy.size() != 0 && copy.back() == ':') + copy += DIR_SEP_CHR; + int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); #else int result = stat64(copy.c_str(), &file_info); @@ -824,13 +833,12 @@ size_t WriteStringToFile(bool text_file, const std::string &str, const char *fil size_t ReadFileToString(bool text_file, const char *filename, std::string &str) { - FileUtil::IOFile file(filename, text_file ? "r" : "rb"); - auto const f = file.GetHandle(); + IOFile file(filename, text_file ? "r" : "rb"); - if (!f) + if (!file) return false; - str.resize(static_cast<u32>(GetSize(f))); + str.resize(static_cast<u32>(file.GetSize())); return file.ReadArray(&str[0], str.size()); } @@ -877,15 +885,10 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam } IOFile::IOFile() - : m_file(nullptr), m_good(true) -{} - -IOFile::IOFile(std::FILE* file) - : m_file(file), m_good(true) -{} +{ +} IOFile::IOFile(const std::string& filename, const char openmode[]) - : m_file(nullptr), m_good(true) { Open(filename, openmode); } @@ -896,7 +899,6 @@ IOFile::~IOFile() } IOFile::IOFile(IOFile&& other) - : m_file(nullptr), m_good(true) { Swap(other); } @@ -935,26 +937,12 @@ bool IOFile::Close() return m_good; } -std::FILE* IOFile::ReleaseHandle() -{ - std::FILE* const ret = m_file; - m_file = nullptr; - return ret; -} - -void IOFile::SetHandle(std::FILE* file) -{ - Close(); - Clear(); - m_file = file; -} - -u64 IOFile::GetSize() +u64 IOFile::GetSize() const { if (IsOpen()) return FileUtil::GetSize(m_file); - else - return 0; + + return 0; } bool IOFile::Seek(s64 off, int origin) @@ -965,12 +953,12 @@ bool IOFile::Seek(s64 off, int origin) return m_good; } -u64 IOFile::Tell() +u64 IOFile::Tell() const { if (IsOpen()) return ftello(m_file); - else - return -1; + + return -1; } bool IOFile::Flush() diff --git a/src/common/file_util.h b/src/common/file_util.h index a85121aa6..b54a9fb72 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -14,6 +14,10 @@ #include "common/common_types.h" +#ifdef _MSC_VER +#include "common/string_util.h" +#endif + // User directory indices for GetUserPath enum { D_USER_IDX, @@ -172,7 +176,6 @@ class IOFile : public NonCopyable { public: IOFile(); - IOFile(std::FILE* file); IOFile(const std::string& filename, const char openmode[]); ~IOFile(); @@ -188,6 +191,9 @@ public: template <typename T> size_t ReadArray(T* data, size_t length) { + static_assert(std::is_standard_layout<T>(), "Given array does not consist of standard layout objects"); + static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects"); + if (!IsOpen()) { m_good = false; return -1; @@ -203,9 +209,8 @@ public: template <typename T> size_t WriteArray(const T* data, size_t length) { - static_assert(std::is_standard_layout<T>::value, "Given array does not consist of standard layout objects"); - // TODO: gcc 4.8 does not support is_trivially_copyable, but we really should check for it here. - //static_assert(std::is_trivially_copyable<T>::value, "Given array does not consist of trivially copyable objects"); + static_assert(std::is_standard_layout<T>(), "Given array does not consist of standard layout objects"); + static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects"); if (!IsOpen()) { m_good = false; @@ -235,32 +240,24 @@ public: return WriteArray(&object, 1); } - bool IsOpen() { return nullptr != m_file; } + bool IsOpen() const { return nullptr != m_file; } // m_good is set to false when a read, write or other function fails - bool IsGood() { return m_good; } - operator void*() { return m_good ? m_file : nullptr; } - - std::FILE* ReleaseHandle(); - - std::FILE* GetHandle() { return m_file; } - - void SetHandle(std::FILE* file); + bool IsGood() const { return m_good; } + explicit operator bool() const { return IsGood(); } bool Seek(s64 off, int origin); - u64 Tell(); - u64 GetSize(); + u64 Tell() const; + u64 GetSize() const; bool Resize(u64 size); bool Flush(); // clear error state void Clear() { m_good = true; std::clearerr(m_file); } - std::FILE* m_file; - bool m_good; private: - IOFile(IOFile&); - IOFile& operator=(IOFile& other); + std::FILE* m_file = nullptr; + bool m_good = true; }; } // namespace diff --git a/src/common/thread.h b/src/common/thread.h index 8255ee6d3..bbfa8befa 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -30,8 +30,7 @@ # endif #endif -namespace Common -{ +namespace Common { int CurrentThreadId(); @@ -43,55 +42,55 @@ public: Event() : is_set(false) {} void Set() { - std::lock_guard<std::mutex> lk(m_mutex); + std::lock_guard<std::mutex> lk(mutex); if (!is_set) { is_set = true; - m_condvar.notify_one(); + condvar.notify_one(); } } void Wait() { - std::unique_lock<std::mutex> lk(m_mutex); - m_condvar.wait(lk, [&]{ return is_set; }); + std::unique_lock<std::mutex> lk(mutex); + condvar.wait(lk, [&]{ return is_set; }); is_set = false; } void Reset() { - std::unique_lock<std::mutex> lk(m_mutex); + std::unique_lock<std::mutex> lk(mutex); // no other action required, since wait loops on the predicate and any lingering signal will get cleared on the first iteration is_set = false; } private: bool is_set; - std::condition_variable m_condvar; - std::mutex m_mutex; + std::condition_variable condvar; + std::mutex mutex; }; class Barrier { public: - Barrier(size_t count) : m_count(count), m_waiting(0) {} + explicit Barrier(size_t count_) : count(count_), waiting(0), generation(0) {} /// Blocks until all "count" threads have called Sync() void Sync() { - std::unique_lock<std::mutex> lk(m_mutex); + std::unique_lock<std::mutex> lk(mutex); + const size_t current_generation = generation; - // TODO: broken when next round of Sync()s - // is entered before all waiting threads return from the notify_all - - if (++m_waiting == m_count) { - m_waiting = 0; - m_condvar.notify_all(); + if (++waiting == count) { + generation++; + waiting = 0; + condvar.notify_all(); } else { - m_condvar.wait(lk, [&]{ return m_waiting == 0; }); + condvar.wait(lk, [this, current_generation]{ return current_generation != generation; }); } } private: - std::condition_variable m_condvar; - std::mutex m_mutex; - const size_t m_count; - size_t m_waiting; + std::condition_variable condvar; + std::mutex mutex; + const size_t count; + size_t waiting; + size_t generation; // Incremented once each time the barrier is used }; void SleepCurrentThread(int ms); @@ -100,8 +99,7 @@ void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms // Use this function during a spin-wait to make the current thread // relax while another thread is working. This may be more efficient // than using events because event functions use kernel calls. -inline void YieldCPU() -{ +inline void YieldCPU() { std::this_thread::yield(); } diff --git a/src/common/x64/emitter.cpp b/src/common/x64/emitter.cpp index 1dcf2416c..5662f7f86 100644 --- a/src/common/x64/emitter.cpp +++ b/src/common/x64/emitter.cpp @@ -455,6 +455,18 @@ void XEmitter::CALL(const void* fnptr) Write32(u32(distance)); } +FixupBranch XEmitter::CALL() +{ + FixupBranch branch; + branch.type = 1; + branch.ptr = code + 5; + + Write8(0xE8); + Write32(0); + + return branch; +} + FixupBranch XEmitter::J(bool force5bytes) { FixupBranch branch; @@ -531,6 +543,22 @@ void XEmitter::SetJumpTarget(const FixupBranch& branch) } } +void XEmitter::SetJumpTarget(const FixupBranch& branch, const u8* target) +{ + if (branch.type == 0) + { + s64 distance = (s64)(target - branch.ptr); + ASSERT_MSG(distance >= -0x80 && distance < 0x80, "Jump target too far away, needs force5Bytes = true"); + branch.ptr[-1] = (u8)(s8)distance; + } + else if (branch.type == 1) + { + s64 distance = (s64)(target - branch.ptr); + ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, "Jump target too far away, needs indirect register"); + ((s32*)branch.ptr)[-1] = (s32)distance; + } +} + //Single byte opcodes //There is no PUSHAD/POPAD in 64-bit mode. void XEmitter::INT3() {Write8(0xCC);} diff --git a/src/common/x64/emitter.h b/src/common/x64/emitter.h index 7c6548fb5..a33724146 100644 --- a/src/common/x64/emitter.h +++ b/src/common/x64/emitter.h @@ -425,12 +425,14 @@ public: #undef CALL #endif void CALL(const void* fnptr); + FixupBranch CALL(); void CALLptr(OpArg arg); FixupBranch J_CC(CCFlags conditionCode, bool force5bytes = false); void J_CC(CCFlags conditionCode, const u8* addr, bool force5Bytes = false); void SetJumpTarget(const FixupBranch& branch); + void SetJumpTarget(const FixupBranch& branch, const u8* target); void SETcc(CCFlags flag, OpArg dest); // Note: CMOV brings small if any benefit on current cpus. diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp index b1a72dc0c..ccd73cfcb 100644 --- a/src/core/hle/config_mem.cpp +++ b/src/core/hle/config_mem.cpp @@ -3,13 +3,6 @@ // Refer to the license.txt file included. #include <cstring> - -#include "common/assert.h" -#include "common/common_types.h" -#include "common/common_funcs.h" - -#include "core/core.h" -#include "core/memory.h" #include "core/hle/config_mem.h" //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp index 331b1b22a..e545de3b5 100644 --- a/src/core/hle/hle.cpp +++ b/src/core/hle/hle.cpp @@ -8,8 +8,6 @@ #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/hle.h" -#include "core/hle/config_mem.h" -#include "core/hle/shared_page.h" #include "core/hle/service/service.h" //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index ff0af8f12..d3e5d4bca 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -151,6 +151,34 @@ static int TranslateError(int error) { return error; } +/// Holds the translation from system network socket options to 3DS network socket options +/// Note: -1 = No effect/unavailable +static const std::unordered_map<int, int> sockopt_map = { { + { 0x0004, SO_REUSEADDR }, + { 0x0080, -1 }, + { 0x0100, -1 }, + { 0x1001, SO_SNDBUF }, + { 0x1002, SO_RCVBUF }, + { 0x1003, -1 }, +#ifdef _WIN32 + /// Unsupported in WinSock2 + { 0x1004, -1 }, +#else + { 0x1004, SO_RCVLOWAT }, +#endif + { 0x1008, SO_TYPE }, + { 0x1009, SO_ERROR }, +}}; + +/// Converts a socket option from 3ds-specific to platform-specific +static int TranslateSockOpt(int console_opt_name) { + auto found = sockopt_map.find(console_opt_name); + if (found != sockopt_map.end()) { + return found->second; + } + return console_opt_name; +} + /// Holds information about a particular socket struct SocketHolder { u32 socket_fd; ///< The socket descriptor @@ -568,7 +596,7 @@ static void RecvFrom(Service::Interface* self) { socklen_t src_addr_len = sizeof(src_addr); int ret = ::recvfrom(socket_handle, (char*)output_buff, len, flags, &src_addr, &src_addr_len); - if (buffer_parameters.output_src_address_buffer != 0) { + if (ret >= 0 && buffer_parameters.output_src_address_buffer != 0 && src_addr_len > 0) { CTRSockAddr* ctr_src_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(buffer_parameters.output_src_address_buffer)); *ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); } @@ -724,6 +752,72 @@ static void ShutdownSockets(Service::Interface* self) { cmd_buffer[1] = 0; } +static void GetSockOpt(Service::Interface* self) { + u32* cmd_buffer = Kernel::GetCommandBuffer(); + u32 socket_handle = cmd_buffer[1]; + u32 level = cmd_buffer[2]; + int optname = TranslateSockOpt(cmd_buffer[3]); + socklen_t optlen = (socklen_t)cmd_buffer[4]; + + int ret = -1; + int err = 0; + + if(optname < 0) { +#ifdef _WIN32 + err = WSAEINVAL; +#else + err = EINVAL; +#endif + } else { + // 0x100 = static buffer offset (bytes) + // + 0x4 = 2nd pointer (u32) position + // >> 2 = convert to u32 offset instead of byte offset (cmd_buffer = u32*) + char* optval = reinterpret_cast<char *>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); + + ret = ::getsockopt(socket_handle, level, optname, optval, &optlen); + err = 0; + if (ret == SOCKET_ERROR_VALUE) { + err = TranslateError(GET_ERRNO); + } + } + + cmd_buffer[0] = IPC::MakeHeader(0x11, 4, 2); + cmd_buffer[1] = ret; + cmd_buffer[2] = err; + cmd_buffer[3] = optlen; +} + +static void SetSockOpt(Service::Interface* self) { + u32* cmd_buffer = Kernel::GetCommandBuffer(); + u32 socket_handle = cmd_buffer[1]; + u32 level = cmd_buffer[2]; + int optname = TranslateSockOpt(cmd_buffer[3]); + + int ret = -1; + int err = 0; + + if(optname < 0) { +#ifdef _WIN32 + err = WSAEINVAL; +#else + err = EINVAL; +#endif + } else { + socklen_t optlen = static_cast<socklen_t>(cmd_buffer[4]); + const char* optval = reinterpret_cast<const char *>(Memory::GetPointer(cmd_buffer[8])); + + ret = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); + err = 0; + if (ret == SOCKET_ERROR_VALUE) { + err = TranslateError(GET_ERRNO); + } + } + + cmd_buffer[0] = IPC::MakeHeader(0x12, 4, 4); + cmd_buffer[1] = ret; + cmd_buffer[2] = err; +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010044, InitializeSockets, "InitializeSockets"}, {0x000200C2, Socket, "Socket"}, @@ -741,8 +835,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000E00C2, nullptr, "GetHostByAddr"}, {0x000F0106, nullptr, "GetAddrInfo"}, {0x00100102, nullptr, "GetNameInfo"}, - {0x00110102, nullptr, "GetSockOpt"}, - {0x00120104, nullptr, "SetSockOpt"}, + {0x00110102, GetSockOpt, "GetSockOpt"}, + {0x00120104, SetSockOpt, "SetSockOpt"}, {0x001300C2, Fcntl, "Fcntl"}, {0x00140084, Poll, "Poll"}, {0x00150042, nullptr, "SockAtMark"}, diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index 50c5bc01b..2a1caeaac 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -16,6 +16,9 @@ void Init() { std::memset(&shared_page, 0, sizeof(shared_page)); shared_page.running_hw = 0x1; // product + + // Some games wait until this value becomes 0x1, before asking running_hw + shared_page.unknown_value = 0x1; } } // namespace diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index 379bb7b63..35a07c685 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -39,12 +39,14 @@ struct SharedPageDef { DateTime date_time_0; // 20 DateTime date_time_1; // 40 u8 wifi_macaddr[6]; // 60 - u8 wifi_unknown1; // 66 + u8 wifi_link_level; // 66 u8 wifi_unknown2; // 67 INSERT_PADDING_BYTES(0x80 - 0x68); // 68 float_le sliderstate_3d; // 80 u8 ledstate_3d; // 84 - INSERT_PADDING_BYTES(0xA0 - 0x85); // 85 + INSERT_PADDING_BYTES(1); // 85 + u8 unknown_value; // 86 + INSERT_PADDING_BYTES(0xA0 - 0x87); // 87 u64_le menu_title_id; // A0 u64_le active_menu_title_id; // A8 INSERT_PADDING_BYTES(0x1000 - 0xB0); // B0 diff --git a/src/core/hw/y2r.cpp b/src/core/hw/y2r.cpp index 48c45564f..083391e83 100644 --- a/src/core/hw/y2r.cpp +++ b/src/core/hw/y2r.cpp @@ -261,7 +261,7 @@ void PerformConversion(ConversionConfiguration& cvt) { ASSERT(cvt.block_alignment != BlockAlignment::Block8x8 || cvt.input_lines % 8 == 0); // Tiles per row size_t num_tiles = cvt.input_line_width / 8; - ASSERT(num_tiles < MAX_TILES); + ASSERT(num_tiles <= MAX_TILES); // Buffer used as a CDMA source/target. std::unique_ptr<u8[]> data_buffer(new u8[cvt.input_line_width * 8 * 4]); diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 8eed6a50a..5fb3b9e2b 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -10,13 +10,9 @@ #include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" -#include "core/hle/service/fs/archive.h" -#include "core/loader/elf.h" -#include "core/loader/ncch.h" +#include "core/loader/3dsx.h" #include "core/memory.h" -#include "3dsx.h" - namespace Loader { /* diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index e63cab33f..a4b47ef8c 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -174,7 +174,7 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>& return ResultStatus::Error; LOG_DEBUG(Loader, "%d sections:", kMaxSections); - // Iterate through the ExeFs archive until we find the .code file... + // Iterate through the ExeFs archive until we find a section with the specified name... for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { const auto& section = exefs_header.section[section_number]; @@ -186,7 +186,7 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>& s64 section_offset = (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); file.Seek(section_offset, SEEK_SET); - if (is_compressed) { + if (strcmp(section.name, ".code") == 0 && is_compressed) { // Section is compressed, read compressed .code section... std::unique_ptr<u8[]> temp_buffer; try { diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 8a14f75aa..1aa26fbd2 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -4,8 +4,22 @@ #include "settings.h" +#include "core/gdbstub/gdbstub.h" + +#include "video_core/video_core.h" + namespace Settings { Values values = {}; +void Apply() { + + GDBStub::SetServerPort(static_cast<u32>(values.gdbstub_port)); + GDBStub::ToggleServer(values.use_gdbstub); + + VideoCore::g_hw_renderer_enabled = values.use_hw_renderer; + VideoCore::g_shader_jit_enabled = values.use_shader_jit; + } + +} // namespace diff --git a/src/core/settings.h b/src/core/settings.h index 97ddcdff9..4933a516d 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -67,4 +67,6 @@ struct Values { u16 gdbstub_port; } extern values; +void Apply(); + } diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 028b59348..3abe79c09 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -140,7 +140,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { immediate_attribute_id = 0; Shader::UnitState<false> shader_unit; - Shader::Setup(shader_unit); + Shader::Setup(); if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, static_cast<void*>(&immediate_input)); @@ -249,10 +249,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8); bool index_u16 = index_info.format != 0; -#if PICA_DUMP_GEOMETRY - DebugUtils::GeometryDumper geometry_dumper; - PrimitiveAssembler<DebugUtils::GeometryDumper::Vertex> dumping_primitive_assembler(regs.triangle_topology.Value()); -#endif PrimitiveAssembler<Shader::OutputVertex>& primitive_assembler = g_state.primitive_assembler; if (g_debug_context) { @@ -304,7 +300,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { vertex_cache_ids.fill(-1); Shader::UnitState<false> shader_unit; - Shader::Setup(shader_unit); + Shader::Setup(); for (unsigned int index = 0; index < regs.num_vertices; ++index) { @@ -388,17 +384,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, (void*)&input); -#if PICA_DUMP_GEOMETRY - // NOTE: When dumping geometry, we simply assume that the first input attribute - // corresponds to the position for now. - DebugUtils::GeometryDumper::Vertex dumped_vertex = { - input.attr[0][0].ToFloat32(), input.attr[0][1].ToFloat32(), input.attr[0][2].ToFloat32() - }; - using namespace std::placeholders; - dumping_primitive_assembler.SubmitVertex(dumped_vertex, - std::bind(&DebugUtils::GeometryDumper::AddTriangle, - &geometry_dumper, _1, _2, _3)); -#endif // Send to vertex shader output = Shader::Run(shader_unit, input, attribute_config.GetNumTotalAttributes()); @@ -424,10 +409,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { range.second, range.first); } -#if PICA_DUMP_GEOMETRY - geometry_dumper.Dump(); -#endif - break; } diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index bac6d69c7..c3a9c9598 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -85,35 +85,6 @@ std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global namespace DebugUtils { -void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { - vertices.push_back(v0); - vertices.push_back(v1); - vertices.push_back(v2); - - int num_vertices = (int)vertices.size(); - faces.push_back({{ num_vertices-3, num_vertices-2, num_vertices-1 }}); -} - -void GeometryDumper::Dump() { - static int index = 0; - std::string filename = std::string("geometry_dump") + std::to_string(++index) + ".obj"; - - std::ofstream file(filename); - - for (const auto& vertex : vertices) { - file << "v " << vertex.pos[0] - << " " << vertex.pos[1] - << " " << vertex.pos[2] << std::endl; - } - - for (const Face& face : faces) { - file << "f " << 1+face.index[0] - << " " << 1+face.index[1] - << " " << 1+face.index[2] << std::endl; - } -} - - void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, const Shader::ShaderSetup& setup, const Regs::VSOutputAttributes* output_attributes) { struct StuffToWrite { @@ -315,7 +286,7 @@ void StartPicaTracing() } std::lock_guard<std::mutex> lock(pica_trace_mutex); - pica_trace = std::unique_ptr<PicaTrace>(new PicaTrace); + pica_trace = std::make_unique<PicaTrace>(); is_pica_tracing = true; } @@ -615,6 +586,21 @@ TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, return info; } +#ifdef HAVE_PNG +// Adapter functions to libpng to write/flush to File::IOFile instances. +static void WriteIOFile(png_structp png_ptr, png_bytep data, png_size_t length) { + auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); + if (!fp->WriteBytes(data, length)) + png_error(png_ptr, "Failed to write to output PNG file."); +} + +static void FlushIOFile(png_structp png_ptr) { + auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); + if (!fp->Flush()) + png_error(png_ptr, "Failed to flush to output PNG file."); +} +#endif + void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { #ifndef HAVE_PNG return; @@ -658,7 +644,7 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { goto finalise; } - png_init_io(png_ptr, fp.GetHandle()); + png_set_write_fn(png_ptr, static_cast<void*>(&fp), WriteIOFile, FlushIOFile); // Write header (8 bit color depth) png_set_IHDR(png_ptr, info_ptr, texture_config.width, texture_config.height, diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 795160a32..7df941619 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -158,30 +158,9 @@ extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this g namespace DebugUtils { -#define PICA_DUMP_GEOMETRY 0 #define PICA_DUMP_TEXTURES 0 #define PICA_LOG_TEV 0 -// Simple utility class for dumping geometry data to an OBJ file -class GeometryDumper { -public: - struct Vertex { - std::array<float,3> pos; - }; - - void AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2); - - void Dump(); - -private: - struct Face { - int index[3]; - }; - - std::vector<Vertex> vertices; - std::vector<Face> faces; -}; - void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, const Shader::ShaderSetup& setup, const Regs::VSOutputAttributes* output_attributes); diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index 0061690f1..ff3e2b862 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -68,7 +68,5 @@ void PrimitiveAssembler<VertexType>::Reconfigure(Regs::TriangleTopology topology // explicitly instantiate use cases template struct PrimitiveAssembler<Shader::OutputVertex>; -template -struct PrimitiveAssembler<DebugUtils::GeometryDumper::Vertex>; } // namespace diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index 5b9ed7c64..0434ad05a 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -923,92 +923,72 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, if (output_merger.alphablend_enable) { auto params = output_merger.alpha_blending; - auto LookupFactorRGB = [&](Regs::BlendFactor factor) -> Math::Vec3<u8> { + auto LookupFactor = [&](unsigned channel, Regs::BlendFactor factor) -> u8 { + DEBUG_ASSERT(channel < 4); + + const Math::Vec4<u8> blend_const = { + static_cast<u8>(output_merger.blend_const.r), + static_cast<u8>(output_merger.blend_const.g), + static_cast<u8>(output_merger.blend_const.b), + static_cast<u8>(output_merger.blend_const.a) + }; + switch (factor) { - case Regs::BlendFactor::Zero : - return Math::Vec3<u8>(0, 0, 0); + case Regs::BlendFactor::Zero: + return 0; - case Regs::BlendFactor::One : - return Math::Vec3<u8>(255, 255, 255); + case Regs::BlendFactor::One: + return 255; case Regs::BlendFactor::SourceColor: - return combiner_output.rgb(); + return combiner_output[channel]; case Regs::BlendFactor::OneMinusSourceColor: - return Math::Vec3<u8>(255 - combiner_output.r(), 255 - combiner_output.g(), 255 - combiner_output.b()); + return 255 - combiner_output[channel]; case Regs::BlendFactor::DestColor: - return dest.rgb(); + return dest[channel]; case Regs::BlendFactor::OneMinusDestColor: - return Math::Vec3<u8>(255 - dest.r(), 255 - dest.g(), 255 - dest.b()); + return 255 - dest[channel]; case Regs::BlendFactor::SourceAlpha: - return Math::Vec3<u8>(combiner_output.a(), combiner_output.a(), combiner_output.a()); + return combiner_output.a(); case Regs::BlendFactor::OneMinusSourceAlpha: - return Math::Vec3<u8>(255 - combiner_output.a(), 255 - combiner_output.a(), 255 - combiner_output.a()); + return 255 - combiner_output.a(); case Regs::BlendFactor::DestAlpha: - return Math::Vec3<u8>(dest.a(), dest.a(), dest.a()); + return dest.a(); case Regs::BlendFactor::OneMinusDestAlpha: - return Math::Vec3<u8>(255 - dest.a(), 255 - dest.a(), 255 - dest.a()); + return 255 - dest.a(); case Regs::BlendFactor::ConstantColor: - return Math::Vec3<u8>(output_merger.blend_const.r, output_merger.blend_const.g, output_merger.blend_const.b); + return blend_const[channel]; case Regs::BlendFactor::OneMinusConstantColor: - return Math::Vec3<u8>(255 - output_merger.blend_const.r, 255 - output_merger.blend_const.g, 255 - output_merger.blend_const.b); + return 255 - blend_const[channel]; case Regs::BlendFactor::ConstantAlpha: - return Math::Vec3<u8>(output_merger.blend_const.a, output_merger.blend_const.a, output_merger.blend_const.a); + return blend_const.a(); case Regs::BlendFactor::OneMinusConstantAlpha: - return Math::Vec3<u8>(255 - output_merger.blend_const.a, 255 - output_merger.blend_const.a, 255 - output_merger.blend_const.a); - - default: - LOG_CRITICAL(HW_GPU, "Unknown color blend factor %x", factor); - UNIMPLEMENTED(); - break; - } - - return {}; - }; - - auto LookupFactorA = [&](Regs::BlendFactor factor) -> u8 { - switch (factor) { - case Regs::BlendFactor::Zero: - return 0; - - case Regs::BlendFactor::One: - return 255; - - case Regs::BlendFactor::SourceAlpha: - return combiner_output.a(); - - case Regs::BlendFactor::OneMinusSourceAlpha: - return 255 - combiner_output.a(); + return 255 - blend_const.a(); - case Regs::BlendFactor::DestAlpha: - return dest.a(); - - case Regs::BlendFactor::OneMinusDestAlpha: - return 255 - dest.a(); - - case Regs::BlendFactor::ConstantAlpha: - return output_merger.blend_const.a; - - case Regs::BlendFactor::OneMinusConstantAlpha: - return 255 - output_merger.blend_const.a; + case Regs::BlendFactor::SourceAlphaSaturate: + // Returns 1.0 for the alpha channel + if (channel == 3) + return 255; + return std::min(combiner_output.a(), static_cast<u8>(255 - dest.a())); default: - LOG_CRITICAL(HW_GPU, "Unknown alpha blend factor %x", factor); + LOG_CRITICAL(HW_GPU, "Unknown blend factor %x", factor); UNIMPLEMENTED(); break; } - return {}; + return combiner_output[channel]; }; static auto EvaluateBlendEquation = [](const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, @@ -1060,10 +1040,15 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, MathUtil::Clamp(result.a(), 0, 255)); }; - auto srcfactor = Math::MakeVec(LookupFactorRGB(params.factor_source_rgb), - LookupFactorA(params.factor_source_a)); - auto dstfactor = Math::MakeVec(LookupFactorRGB(params.factor_dest_rgb), - LookupFactorA(params.factor_dest_a)); + auto srcfactor = Math::MakeVec(LookupFactor(0, params.factor_source_rgb), + LookupFactor(1, params.factor_source_rgb), + LookupFactor(2, params.factor_source_rgb), + LookupFactor(3, params.factor_source_a)); + + auto dstfactor = Math::MakeVec(LookupFactor(0, params.factor_dest_rgb), + LookupFactor(1, params.factor_dest_rgb), + LookupFactor(2, params.factor_dest_rgb), + LookupFactor(3, params.factor_dest_a)); blend_output = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_rgb); blend_output.a() = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_a).a(); diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 78d295c76..75301accd 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -28,36 +28,24 @@ namespace Pica { namespace Shader { #ifdef ARCHITECTURE_x86_64 -static std::unordered_map<u64, CompiledShader*> shader_map; -static JitCompiler jit; -static CompiledShader* jit_shader; - -static void ClearCache() { - shader_map.clear(); - jit.Clear(); - LOG_INFO(HW_GPU, "Shader JIT cache cleared"); -} +static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; +static const JitShader* jit_shader; #endif // ARCHITECTURE_x86_64 -void Setup(UnitState<false>& state) { +void Setup() { #ifdef ARCHITECTURE_x86_64 if (VideoCore::g_shader_jit_enabled) { u64 cache_key = (Common::ComputeHash64(&g_state.vs.program_code, sizeof(g_state.vs.program_code)) ^ - Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data)) ^ - g_state.regs.vs.main_offset); + Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data))); auto iter = shader_map.find(cache_key); if (iter != shader_map.end()) { - jit_shader = iter->second; + jit_shader = iter->second.get(); } else { - // Check if remaining JIT code space is enough for at least one more (massive) shader - if (jit.GetSpaceLeft() < jit_shader_size) { - // If not, clear the cache of all previously compiled shaders - ClearCache(); - } - - jit_shader = jit.Compile(); - shader_map.emplace(cache_key, jit_shader); + auto shader = std::make_unique<JitShader>(); + shader->Compile(); + jit_shader = shader.get(); + shader_map[cache_key] = std::move(shader); } } #endif // ARCHITECTURE_x86_64 @@ -65,7 +53,7 @@ void Setup(UnitState<false>& state) { void Shutdown() { #ifdef ARCHITECTURE_x86_64 - ClearCache(); + shader_map.clear(); #endif // ARCHITECTURE_x86_64 } @@ -109,7 +97,7 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr #ifdef ARCHITECTURE_x86_64 if (VideoCore::g_shader_jit_enabled) - jit_shader(&state.registers); + jit_shader->Run(&state.registers, g_state.regs.vs.main_offset); else RunInterpreter(state); #else diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 7af8f1fa1..9c5bd97bd 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -339,9 +339,8 @@ struct UnitState { /** * Performs any shader unit setup that only needs to happen once per shader (as opposed to once per * vertex, which would happen within the `Run` function). - * @param state Shader unit state, must be setup per shader and per shader unit */ -void Setup(UnitState<false>& state); +void Setup(); /// Performs any cleanup when the emulator is shutdown void Shutdown(); diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index dffe051ef..b47d3beda 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <smmintrin.h> #include "common/x64/abi.h" @@ -19,73 +20,73 @@ namespace Shader { using namespace Gen; -typedef void (JitCompiler::*JitFunction)(Instruction instr); +typedef void (JitShader::*JitFunction)(Instruction instr); const JitFunction instr_table[64] = { - &JitCompiler::Compile_ADD, // add - &JitCompiler::Compile_DP3, // dp3 - &JitCompiler::Compile_DP4, // dp4 - &JitCompiler::Compile_DPH, // dph + &JitShader::Compile_ADD, // add + &JitShader::Compile_DP3, // dp3 + &JitShader::Compile_DP4, // dp4 + &JitShader::Compile_DPH, // dph nullptr, // unknown - &JitCompiler::Compile_EX2, // ex2 - &JitCompiler::Compile_LG2, // lg2 + &JitShader::Compile_EX2, // ex2 + &JitShader::Compile_LG2, // lg2 nullptr, // unknown - &JitCompiler::Compile_MUL, // mul - &JitCompiler::Compile_SGE, // sge - &JitCompiler::Compile_SLT, // slt - &JitCompiler::Compile_FLR, // flr - &JitCompiler::Compile_MAX, // max - &JitCompiler::Compile_MIN, // min - &JitCompiler::Compile_RCP, // rcp - &JitCompiler::Compile_RSQ, // rsq + &JitShader::Compile_MUL, // mul + &JitShader::Compile_SGE, // sge + &JitShader::Compile_SLT, // slt + &JitShader::Compile_FLR, // flr + &JitShader::Compile_MAX, // max + &JitShader::Compile_MIN, // min + &JitShader::Compile_RCP, // rcp + &JitShader::Compile_RSQ, // rsq nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_MOVA, // mova - &JitCompiler::Compile_MOV, // mov + &JitShader::Compile_MOVA, // mova + &JitShader::Compile_MOV, // mov nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_DPH, // dphi + &JitShader::Compile_DPH, // dphi nullptr, // unknown - &JitCompiler::Compile_SGE, // sgei - &JitCompiler::Compile_SLT, // slti + &JitShader::Compile_SGE, // sgei + &JitShader::Compile_SLT, // slti nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_NOP, // nop - &JitCompiler::Compile_END, // end + &JitShader::Compile_NOP, // nop + &JitShader::Compile_END, // end nullptr, // break - &JitCompiler::Compile_CALL, // call - &JitCompiler::Compile_CALLC, // callc - &JitCompiler::Compile_CALLU, // callu - &JitCompiler::Compile_IF, // ifu - &JitCompiler::Compile_IF, // ifc - &JitCompiler::Compile_LOOP, // loop + &JitShader::Compile_CALL, // call + &JitShader::Compile_CALLC, // callc + &JitShader::Compile_CALLU, // callu + &JitShader::Compile_IF, // ifu + &JitShader::Compile_IF, // ifc + &JitShader::Compile_LOOP, // loop nullptr, // emit nullptr, // sete - &JitCompiler::Compile_JMP, // jmpc - &JitCompiler::Compile_JMP, // jmpu - &JitCompiler::Compile_CMP, // cmp - &JitCompiler::Compile_CMP, // cmp - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad + &JitShader::Compile_JMP, // jmpc + &JitShader::Compile_JMP, // jmpu + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad }; // The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can @@ -138,13 +139,32 @@ static const u8 NO_SRC_REG_SWIZZLE = 0x1b; static const u8 NO_DEST_REG_MASK = 0xf; /** + * Get the vertex shader instruction for a given offset in the current shader program + * @param offset Offset in the current shader program of the instruction + * @return Instruction at the specified offset + */ +static Instruction GetVertexShaderInstruction(size_t offset) { + return { g_state.vs.program_code[offset] }; +} + +static void LogCritical(const char* msg) { + LOG_CRITICAL(HW_GPU, msg); +} + +void JitShader::Compile_Assert(bool condition, const char* msg) { + if (!condition) { + ABI_CallFunctionP(reinterpret_cast<const void*>(LogCritical), const_cast<char*>(msg)); + } +} + +/** * Loads and swizzles a source register into the specified XMM register. * @param instr VS instruction, used for determining how to load the source register * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3) * @param src_reg SourceRegister object corresponding to the source register to load * @param dest Destination XMM register to store the loaded, swizzled source register */ -void JitCompiler::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, X64Reg dest) { +void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, X64Reg dest) { X64Reg src_ptr; size_t src_offset; @@ -216,7 +236,7 @@ void JitCompiler::Compile_SwizzleSrc(Instruction instr, unsigned src_num, Source } } -void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) { +void JitShader::Compile_DestEnable(Instruction instr,X64Reg src) { DestRegister dest; unsigned operand_desc_id; if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || @@ -263,7 +283,7 @@ void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) { } } -void JitCompiler::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { +void JitShader::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { MOVAPS(scratch, R(src1)); CMPPS(scratch, R(src2), CMP_ORD); @@ -276,7 +296,7 @@ void JitCompiler::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen:: ANDPS(src1, R(scratch)); } -void JitCompiler::Compile_EvaluateCondition(Instruction instr) { +void JitShader::Compile_EvaluateCondition(Instruction instr) { // Note: NXOR is used below to check for equality switch (instr.flow_control.op) { case Instruction::FlowControlType::Or: @@ -307,23 +327,23 @@ void JitCompiler::Compile_EvaluateCondition(Instruction instr) { } } -void JitCompiler::Compile_UniformCondition(Instruction instr) { +void JitShader::Compile_UniformCondition(Instruction instr) { int offset = offsetof(decltype(g_state.vs.uniforms), b) + (instr.flow_control.bool_uniform_id * sizeof(bool)); CMP(sizeof(bool) * 8, MDisp(UNIFORMS, offset), Imm8(0)); } -BitSet32 JitCompiler::PersistentCallerSavedRegs() { +BitSet32 JitShader::PersistentCallerSavedRegs() { return persistent_regs & ABI_ALL_CALLER_SAVED; } -void JitCompiler::Compile_ADD(Instruction instr) { +void JitShader::Compile_ADD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); ADDPS(SRC1, R(SRC2)); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DP3(Instruction instr) { +void JitShader::Compile_DP3(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); @@ -342,7 +362,7 @@ void JitCompiler::Compile_DP3(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DP4(Instruction instr) { +void JitShader::Compile_DP4(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); @@ -359,7 +379,7 @@ void JitCompiler::Compile_DP4(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DPH(Instruction instr) { +void JitShader::Compile_DPH(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::DPHI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -391,7 +411,7 @@ void JitCompiler::Compile_DPH(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_EX2(Instruction instr) { +void JitShader::Compile_EX2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); MOVSS(XMM0, R(SRC1)); @@ -404,7 +424,7 @@ void JitCompiler::Compile_EX2(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_LG2(Instruction instr) { +void JitShader::Compile_LG2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); MOVSS(XMM0, R(SRC1)); @@ -417,14 +437,14 @@ void JitCompiler::Compile_LG2(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MUL(Instruction instr) { +void JitShader::Compile_MUL(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); Compile_SanitizedMul(SRC1, SRC2, SCRATCH); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_SGE(Instruction instr) { +void JitShader::Compile_SGE(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SGEI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -439,7 +459,7 @@ void JitCompiler::Compile_SGE(Instruction instr) { Compile_DestEnable(instr, SRC2); } -void JitCompiler::Compile_SLT(Instruction instr) { +void JitShader::Compile_SLT(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SLTI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -454,7 +474,7 @@ void JitCompiler::Compile_SLT(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_FLR(Instruction instr) { +void JitShader::Compile_FLR(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); if (Common::GetCPUCaps().sse4_1) { @@ -467,7 +487,7 @@ void JitCompiler::Compile_FLR(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MAX(Instruction instr) { +void JitShader::Compile_MAX(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. @@ -475,7 +495,7 @@ void JitCompiler::Compile_MAX(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MIN(Instruction instr) { +void JitShader::Compile_MIN(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. @@ -483,7 +503,7 @@ void JitCompiler::Compile_MIN(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MOVA(Instruction instr) { +void JitShader::Compile_MOVA(Instruction instr) { SwizzlePattern swiz = { g_state.vs.swizzle_data[instr.common.operand_desc_id] }; if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) { @@ -528,12 +548,12 @@ void JitCompiler::Compile_MOVA(Instruction instr) { } } -void JitCompiler::Compile_MOV(Instruction instr) { +void JitShader::Compile_MOV(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_RCP(Instruction instr) { +void JitShader::Compile_RCP(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica @@ -544,7 +564,7 @@ void JitCompiler::Compile_RCP(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_RSQ(Instruction instr) { +void JitShader::Compile_RSQ(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica @@ -555,36 +575,41 @@ void JitCompiler::Compile_RSQ(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_NOP(Instruction instr) { +void JitShader::Compile_NOP(Instruction instr) { } -void JitCompiler::Compile_END(Instruction instr) { +void JitShader::Compile_END(Instruction instr) { ABI_PopRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); RET(); } -void JitCompiler::Compile_CALL(Instruction instr) { - unsigned offset = instr.flow_control.dest_offset; - while (offset < (instr.flow_control.dest_offset + instr.flow_control.num_instructions)) { - Compile_NextInstr(&offset); - } +void JitShader::Compile_CALL(Instruction instr) { + // Push offset of the return + PUSH(64, Imm32(instr.flow_control.dest_offset + instr.flow_control.num_instructions)); + + // Call the subroutine + FixupBranch b = CALL(); + fixup_branches.push_back({ b, instr.flow_control.dest_offset }); + + // Skip over the return offset that's on the stack + ADD(64, R(RSP), Imm32(8)); } -void JitCompiler::Compile_CALLC(Instruction instr) { +void JitShader::Compile_CALLC(Instruction instr) { Compile_EvaluateCondition(instr); FixupBranch b = J_CC(CC_Z, true); Compile_CALL(instr); SetJumpTarget(b); } -void JitCompiler::Compile_CALLU(Instruction instr) { +void JitShader::Compile_CALLU(Instruction instr) { Compile_UniformCondition(instr); FixupBranch b = J_CC(CC_Z, true); Compile_CALL(instr); SetJumpTarget(b); } -void JitCompiler::Compile_CMP(Instruction instr) { +void JitShader::Compile_CMP(Instruction instr) { using Op = Instruction::Common::CompareOpType::Op; Op op_x = instr.common.compare_op.x; Op op_y = instr.common.compare_op.y; @@ -627,7 +652,7 @@ void JitCompiler::Compile_CMP(Instruction instr) { SHR(64, R(COND1), Imm8(63)); } -void JitCompiler::Compile_MAD(Instruction instr) { +void JitShader::Compile_MAD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1); if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { @@ -644,9 +669,8 @@ void JitCompiler::Compile_MAD(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_IF(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards if-statements (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); +void JitShader::Compile_IF(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards if-statements not supported"); // Evaluate the "IF" condition if (instr.opcode.Value() == OpCode::Id::IFU) { @@ -676,10 +700,9 @@ void JitCompiler::Compile_IF(Instruction instr) { SetJumpTarget(b2); } -void JitCompiler::Compile_LOOP(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards loops (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); - ASSERT_MSG(!looping, "Nested loops not supported"); +void JitShader::Compile_LOOP(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards loops not supported"); + Compile_Assert(!looping, "Nested loops not supported"); looping = true; @@ -705,10 +728,7 @@ void JitCompiler::Compile_LOOP(Instruction instr) { looping = false; } -void JitCompiler::Compile_JMP(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards jumps (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); - +void JitShader::Compile_JMP(Instruction instr) { if (instr.opcode.Value() == OpCode::Id::JMPC) Compile_EvaluateCondition(instr); else if (instr.opcode.Value() == OpCode::Id::JMPU) @@ -718,30 +738,38 @@ void JitCompiler::Compile_JMP(Instruction instr) { bool inverted_condition = (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1); + FixupBranch b = J_CC(inverted_condition ? CC_Z : CC_NZ, true); + fixup_branches.push_back({ b, instr.flow_control.dest_offset }); +} - Compile_Block(instr.flow_control.dest_offset); +void JitShader::Compile_Block(unsigned end) { + while (program_counter < end) { + Compile_NextInstr(); + } +} + +void JitShader::Compile_Return() { + // Peek return offset on the stack and check if we're at that offset + MOV(64, R(RAX), MDisp(RSP, 8)); + CMP(32, R(RAX), Imm32(program_counter)); + // If so, jump back to before CALL + FixupBranch b = J_CC(CC_NZ, true); + RET(); SetJumpTarget(b); } -void JitCompiler::Compile_Block(unsigned end) { - // Save current offset pointer - unsigned* prev_offset_ptr = offset_ptr; - unsigned offset = *prev_offset_ptr; +void JitShader::Compile_NextInstr() { + if (std::binary_search(return_offsets.begin(), return_offsets.end(), program_counter)) { + Compile_Return(); + } - while (offset < end) - Compile_NextInstr(&offset); + ASSERT_MSG(code_ptr[program_counter] == nullptr, "Tried to compile already compiled shader location!"); + code_ptr[program_counter] = GetCodePtr(); - // Restore current offset pointer - offset_ptr = prev_offset_ptr; - *offset_ptr = offset; -} + Instruction instr = GetVertexShaderInstruction(program_counter++); -void JitCompiler::Compile_NextInstr(unsigned* offset) { - offset_ptr = offset; - - Instruction instr = *(Instruction*)&g_state.vs.program_code[(*offset_ptr)++]; OpCode::Id opcode = instr.opcode.Value(); auto instr_func = instr_table[static_cast<unsigned>(opcode)]; @@ -755,9 +783,35 @@ void JitCompiler::Compile_NextInstr(unsigned* offset) { } } -CompiledShader* JitCompiler::Compile() { - const u8* start = GetCodePtr(); - unsigned offset = g_state.regs.vs.main_offset; +void JitShader::FindReturnOffsets() { + return_offsets.clear(); + + for (size_t offset = 0; offset < g_state.vs.program_code.size(); ++offset) { + Instruction instr = GetVertexShaderInstruction(offset); + + switch (instr.opcode.Value()) { + case OpCode::Id::CALL: + case OpCode::Id::CALLC: + case OpCode::Id::CALLU: + return_offsets.push_back(instr.flow_control.dest_offset + instr.flow_control.num_instructions); + break; + } + } + + // Sort for efficient binary search later + std::sort(return_offsets.begin(), return_offsets.end()); +} + +void JitShader::Compile() { + // Reset flow control state + program = (CompiledShader*)GetCodePtr(); + program_counter = 0; + looping = false; + code_ptr.fill(nullptr); + fixup_branches.clear(); + + // Find all `CALL` instructions and identify return locations + FindReturnOffsets(); // The stack pointer is 8 modulo 16 at the entry of a procedure ABI_PushRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); @@ -780,21 +834,31 @@ CompiledShader* JitCompiler::Compile() { MOV(PTRBITS, R(RAX), ImmPtr(&neg)); MOVAPS(NEGBIT, MatR(RAX)); - looping = false; + // Jump to start of the shader program + JMPptr(R(ABI_PARAM2)); + + // Compile entire program + Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size())); - while (offset < g_state.vs.program_code.size()) { - Compile_NextInstr(&offset); + // Set the target for any incomplete branches now that the entire shader program has been emitted + for (const auto& branch : fixup_branches) { + SetJumpTarget(branch.first, code_ptr[branch.second]); } - return (CompiledShader*)start; -} + // Free memory that's no longer needed + return_offsets.clear(); + return_offsets.shrink_to_fit(); + fixup_branches.clear(); + fixup_branches.shrink_to_fit(); + + uintptr_t size = reinterpret_cast<uintptr_t>(GetCodePtr()) - reinterpret_cast<uintptr_t>(program); + ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); -JitCompiler::JitCompiler() { - AllocCodeSpace(jit_cache_size); + LOG_DEBUG(HW_GPU, "Compiled shader size=%d", size); } -void JitCompiler::Clear() { - ClearCodeSpace(); +JitShader::JitShader() { + AllocCodeSpace(MAX_SHADER_SIZE); } } // namespace Shader diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h index 5357c964b..cd6280ade 100644 --- a/src/video_core/shader/shader_jit_x64.h +++ b/src/video_core/shader/shader_jit_x64.h @@ -4,6 +4,9 @@ #pragma once +#include <utility> +#include <vector> + #include <nihstro/shader_bytecode.h> #include "common/x64/emitter.h" @@ -19,24 +22,22 @@ namespace Pica { namespace Shader { -/// Memory needed to be available to compile the next shader (otherwise, clear the cache) -constexpr size_t jit_shader_size = 1024 * 512; -/// Memory allocated for the JIT code space cache -constexpr size_t jit_cache_size = 1024 * 1024 * 8; - -using CompiledShader = void(void* registers); +/// Memory allocated for each compiled shader (64Kb) +constexpr size_t MAX_SHADER_SIZE = 1024 * 64; /** * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 * code that can be executed on the host machine directly. */ -class JitCompiler : public Gen::XCodeBlock { +class JitShader : public Gen::XCodeBlock { public: - JitCompiler(); + JitShader(); - CompiledShader* Compile(); + void Run(void* registers, unsigned offset) const { + program(registers, code_ptr[offset]); + } - void Clear(); + void Compile(); void Compile_ADD(Instruction instr); void Compile_DP3(Instruction instr); @@ -66,8 +67,9 @@ public: void Compile_MAD(Instruction instr); private: + void Compile_Block(unsigned end); - void Compile_NextInstr(unsigned* offset); + void Compile_NextInstr(); void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, Gen::X64Reg dest); void Compile_DestEnable(Instruction instr, Gen::X64Reg dest); @@ -81,13 +83,39 @@ private: void Compile_EvaluateCondition(Instruction instr); void Compile_UniformCondition(Instruction instr); + /** + * Emits the code to conditionally return from a subroutine envoked by the `CALL` instruction. + */ + void Compile_Return(); + BitSet32 PersistentCallerSavedRegs(); - /// Pointer to the variable that stores the current Pica code offset. Used to handle nested code blocks. - unsigned* offset_ptr = nullptr; + /** + * Assertion evaluated at compile-time, but only triggered if executed at runtime. + * @param msg Message to be logged if the assertion fails. + */ + void Compile_Assert(bool condition, const char* msg); + + /** + * Analyzes the entire shader program for `CALL` instructions before emitting any code, + * identifying the locations where a return needs to be inserted. + */ + void FindReturnOffsets(); + + /// Mapping of Pica VS instructions to pointers in the emitted code + std::array<const u8*, 1024> code_ptr; + + /// Offsets in code where a return needs to be inserted + std::vector<unsigned> return_offsets; + + unsigned program_counter = 0; ///< Offset of the next instruction to decode + bool looping = false; ///< True if compiling a loop, used to check for nested loops + + /// Branches that need to be fixed up once the entire shader program is compiled + std::vector<std::pair<Gen::FixupBranch, unsigned>> fixup_branches; - /// Set to true if currently in a loop, used to check for the existence of nested loops - bool looping = false; + using CompiledShader = void(void* registers, const u8* start_addr); + CompiledShader* program = nullptr; }; } // Shader |