diff options
Diffstat (limited to 'src/citra_qt')
24 files changed, 1062 insertions, 1329 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 3f0099200..43a766053 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -2,8 +2,6 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(SRCS - config/controller_config.cpp - config/controller_config_util.cpp config.cpp debugger/callstack.cpp debugger/disassembler.cpp @@ -11,7 +9,7 @@ set(SRCS debugger/graphics_breakpoint_observer.cpp debugger/graphics_breakpoints.cpp debugger/graphics_cmdlists.cpp - debugger/graphics_framebuffer.cpp + debugger/graphics_surface.cpp debugger/graphics_tracing.cpp debugger/graphics_vertex_shader.cpp debugger/profiler.cpp @@ -20,6 +18,7 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp + configure_audio.cpp configure_debug.cpp configure_dialog.cpp configure_general.cpp @@ -32,8 +31,6 @@ set(SRCS ) set(HEADERS - config/controller_config.h - config/controller_config_util.h config.h debugger/callstack.h debugger/disassembler.h @@ -42,7 +39,7 @@ set(HEADERS debugger/graphics_breakpoints.h debugger/graphics_breakpoints_p.h debugger/graphics_cmdlists.h - debugger/graphics_framebuffer.h + debugger/graphics_surface.h debugger/graphics_tracing.h debugger/graphics_vertex_shader.h debugger/profiler.h @@ -51,6 +48,7 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h + configure_audio.h configure_debug.h configure_dialog.h configure_general.h @@ -63,12 +61,12 @@ set(HEADERS ) set(UIS - config/controller_config.ui debugger/callstack.ui debugger/disassembler.ui debugger/profiler.ui debugger/registers.ui configure.ui + configure_audio.ui configure_debug.ui configure_general.ui hotkeys.ui diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 539fafb6f..ba7edaff9 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -65,7 +65,8 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); - qt_config->beginGroup("System Region"); + qt_config->beginGroup("System"); + Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool(); Settings::values.region_value = qt_config->value("region_value", 1).toInt(); qt_config->endGroup(); @@ -156,7 +157,8 @@ void Config::SaveValues() { qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); - qt_config->beginGroup("System Region"); + qt_config->beginGroup("System"); + qt_config->setValue("is_new_3ds", Settings::values.is_new_3ds); qt_config->setValue("region_value", Settings::values.region_value); qt_config->endGroup(); diff --git a/src/citra_qt/config/controller_config.cpp b/src/citra_qt/config/controller_config.cpp deleted file mode 100644 index 512879f1b..000000000 --- a/src/citra_qt/config/controller_config.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QDialogButtonBox> - -#include "controller_config.h" -#include "controller_config_util.h" - -/* TODO(bunnei): ImplementMe - -using common::Config; - -GControllerConfig::GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent) : QWidget(parent) -{ - ui.setupUi(this); - ((QGridLayout*)ui.mainStickTab->layout())->addWidget(new GStickConfig(Config::ANALOG_LEFT, Config::ANALOG_RIGHT, Config::ANALOG_UP, Config::ANALOG_DOWN, this, this), 1, 1); - ((QGridLayout*)ui.cStickTab->layout())->addWidget(new GStickConfig(Config::C_LEFT, Config::C_RIGHT, Config::C_UP, Config::C_DOWN, this, this), 1, 1); - ((QGridLayout*)ui.dPadTab->layout())->addWidget(new GStickConfig(Config::DPAD_LEFT, Config::DPAD_RIGHT, Config::DPAD_UP, Config::DPAD_DOWN, this, this), 1, 1); - - // TODO: Arrange these more compactly? - QVBoxLayout* layout = (QVBoxLayout*)ui.buttonsTab->layout(); - layout->addWidget(new GButtonConfigGroup("A Button", Config::BUTTON_A, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("B Button", Config::BUTTON_B, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("X Button", Config::BUTTON_X, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("Y Button", Config::BUTTON_Y, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("Z Button", Config::BUTTON_Z, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("L Trigger", Config::TRIGGER_L, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("R Trigger", Config::TRIGGER_R, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("Start Button", Config::BUTTON_START, this, ui.buttonsTab)); - - memcpy(config, initial_config, sizeof(config)); - - emit ActivePortChanged(config[0]); -} - -void GControllerConfig::OnKeyConfigChanged(common::Config::Control id, int key, const QString& name) -{ - if (InputSourceJoypad()) - { - config[GetActiveController()].pads.key_code[id] = key; - } - else - { - config[GetActiveController()].keys.key_code[id] = key; - } - emit ActivePortChanged(config[GetActiveController()]); -} - -int GControllerConfig::GetActiveController() -{ - return ui.activeControllerCB->currentIndex(); -} - -bool GControllerConfig::InputSourceJoypad() -{ - return ui.inputSourceCB->currentIndex() == 1; -} - -GControllerConfigDialog::GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent) : QDialog(parent), config_ptr(controller_ports) -{ - setWindowTitle(tr("Input configuration")); - - QVBoxLayout* layout = new QVBoxLayout(this); - config_widget = new GControllerConfig(controller_ports, this); - layout->addWidget(config_widget); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - layout->addWidget(buttons); - - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - - connect(this, SIGNAL(accepted()), this, SLOT(EnableChanges())); - - layout->setSizeConstraint(QLayout::SetFixedSize); - setLayout(layout); - setModal(true); - show(); -} - -void GControllerConfigDialog::EnableChanges() -{ - for (unsigned int i = 0; i < 4; ++i) - { - memcpy(&config_ptr[i], &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort)); - - if (common::g_config) { - // Apply changes if running a game - memcpy(&common::g_config->controller_ports(i), &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort)); - } - } -} - -*/ diff --git a/src/citra_qt/config/controller_config.h b/src/citra_qt/config/controller_config.h deleted file mode 100644 index 451593de1..000000000 --- a/src/citra_qt/config/controller_config.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#ifndef _CONTROLLER_CONFIG_HXX_ -#define _CONTROLLER_CONFIG_HXX_ - -#include <QDialog> - -//#include "ui_controller_config.h" - -/* TODO(bunnei): ImplementMe - -#include "config.h" - -class GControllerConfig : public QWidget -{ - Q_OBJECT - -public: - GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent = NULL); - - const common::Config::ControllerPort& GetControllerConfig(int index) const { return config[index]; } - -signals: - void ActivePortChanged(const common::Config::ControllerPort&); - -public slots: - void OnKeyConfigChanged(common::Config::Control id, int key, const QString& name); - -private: - int GetActiveController(); - bool InputSourceJoypad(); - - Ui::ControllerConfig ui; - common::Config::ControllerPort config[4]; -}; - -class GControllerConfigDialog : public QDialog -{ - Q_OBJECT - -public: - GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent = NULL); - -public slots: - void EnableChanges(); - -private: - GControllerConfig* config_widget; - common::Config::ControllerPort* config_ptr; -}; - -*/ - -#endif // _CONTROLLER_CONFIG_HXX_ diff --git a/src/citra_qt/config/controller_config.ui b/src/citra_qt/config/controller_config.ui deleted file mode 100644 index 9f650047b..000000000 --- a/src/citra_qt/config/controller_config.ui +++ /dev/null @@ -1,308 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ControllerConfig</class> - <widget class="QWidget" name="ControllerConfig"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>503</width> - <height>293</height> - </rect> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="windowTitle"> - <string>Controller Configuration</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="sizeConstraint"> - <enum>QLayout::SetFixedSize</enum> - </property> - <item> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="1"> - <widget class="QComboBox" name="activeControllerCB"> - <item> - <property name="text"> - <string>Controller 1</string> - </property> - </item> - <item> - <property name="text"> - <string>Controller 2</string> - </property> - </item> - <item> - <property name="text"> - <string>Controller 3</string> - </property> - </item> - <item> - <property name="text"> - <string>Controller 4</string> - </property> - </item> - </widget> - </item> - <item row="0" column="2"> - <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 row="1" column="2"> - <widget class="QCheckBox" name="checkBox"> - <property name="text"> - <string>Enabled</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="inputSourceCB"> - <item> - <property name="text"> - <string>Keyboard</string> - </property> - </item> - <item> - <property name="text"> - <string>Joypad</string> - </property> - </item> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Active Controller:</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Input Source:</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="mainStickTab"> - <attribute name="title"> - <string>Main Stick</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="2" column="2"> - <spacer name="verticalSpacer_2"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="2"> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="0"> - <spacer name="horizontalSpacer_4"> - <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 row="1" column="4"> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="cStickTab"> - <attribute name="title"> - <string>C-Stick</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_4"> - <item row="1" column="0"> - <spacer name="horizontalSpacer_6"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>0</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="1"> - <spacer name="verticalSpacer_5"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="2" column="1"> - <spacer name="verticalSpacer_4"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="2"> - <spacer name="horizontalSpacer_5"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="dPadTab"> - <attribute name="title"> - <string>D-Pad</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_5"> - <item row="1" column="2"> - <spacer name="horizontalSpacer_7"> - <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 row="0" column="1"> - <spacer name="verticalSpacer_7"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="2" column="1"> - <spacer name="verticalSpacer_6"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="0"> - <spacer name="horizontalSpacer_8"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="buttonsTab"> - <attribute name="title"> - <string>Buttons</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_2"/> - </widget> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> - <slots> - <signal>ControlsChanged()</signal> - <signal>MainStickCleared()</signal> - <signal>CStickCleared()</signal> - <signal>DPadCleared()</signal> - <signal>ButtonsCleared()</signal> - <slot>OnControlsChanged()</slot> - <slot>OnMainStickCleared()</slot> - <slot>OnCStickCleared()</slot> - <slot>OnDPadCleared()</slot> - <slot>OnButtonsCleared()</slot> - </slots> -</ui> diff --git a/src/citra_qt/config/controller_config_util.cpp b/src/citra_qt/config/controller_config_util.cpp deleted file mode 100644 index d68b119df..000000000 --- a/src/citra_qt/config/controller_config_util.cpp +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QPushButton> -#include <QStyle> -#include <QGridLayout> -#include <QKeyEvent> -#include <QHBoxLayout> -#include <QLabel> - -#include "controller_config_util.h" - -/* TODO(bunnei): ImplementMe -GStickConfig::GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent) : QWidget(parent) -{ - left = new GKeyConfigButton(leftid, style()->standardIcon(QStyle::SP_ArrowLeft), QString(), change_receiver, this); - right = new GKeyConfigButton(rightid, style()->standardIcon(QStyle::SP_ArrowRight), QString(), change_receiver, this); - up = new GKeyConfigButton(upid, style()->standardIcon(QStyle::SP_ArrowUp), QString(), change_receiver, this); - down = new GKeyConfigButton(downid, style()->standardIcon(QStyle::SP_ArrowDown), QString(), change_receiver, this); - clear = new QPushButton(tr("Clear"), this); - - QGridLayout* layout = new QGridLayout(this); - layout->addWidget(left, 1, 0); - layout->addWidget(right, 1, 2); - layout->addWidget(up, 0, 1); - layout->addWidget(down, 2, 1); - layout->addWidget(clear, 1, 1); - - setLayout(layout); -} - -GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(icon, text, parent), id(id), inputGrabbed(false) -{ - connect(this, SIGNAL(clicked()), this, SLOT(OnClicked())); - connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&))); - connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&))); -} - -GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(text, parent), id(id), inputGrabbed(false) -{ - connect(this, SIGNAL(clicked()), this, SLOT(OnClicked())); - connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&))); - connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&))); -} - -void GKeyConfigButton::OnActivePortChanged(const common::Config::ControllerPort& config) -{ - // TODO: Doesn't use joypad struct if that's the input source... - QString text = QKeySequence(config.keys.key_code[id]).toString(); // has a nicer format - if (config.keys.key_code[id] == Qt::Key_Shift) text = tr("Shift"); - else if (config.keys.key_code[id] == Qt::Key_Control) text = tr("Control"); - else if (config.keys.key_code[id] == Qt::Key_Alt) text = tr("Alt"); - else if (config.keys.key_code[id] == Qt::Key_Meta) text = tr("Meta"); - setText(text); -} - -void GKeyConfigButton::OnClicked() -{ - grabKeyboard(); - grabMouse(); - inputGrabbed = true; - - old_text = text(); - setText(tr("Input...")); -} - -void GKeyConfigButton::keyPressEvent(QKeyEvent* event) -{ - if (inputGrabbed) - { - releaseKeyboard(); - releaseMouse(); - setText(QString()); - - // TODO: Doesn't capture "return" key - // TODO: This doesn't quite work well, yet... find a better way - QString text = QKeySequence(event->key()).toString(); // has a nicer format than event->text() - int key = event->key(); - if (event->modifiers() == Qt::ShiftModifier) { text = tr("Shift"); key = Qt::Key_Shift; } - else if (event->modifiers() == Qt::ControlModifier) { text = tr("Ctrl"); key = Qt::Key_Control; } - else if (event->modifiers() == Qt::AltModifier) { text = tr("Alt"); key = Qt::Key_Alt; } - else if (event->modifiers() == Qt::MetaModifier) { text = tr("Meta"); key = Qt::Key_Meta; } - - setText(old_text); - emit KeyAssigned(id, key, text); - - inputGrabbed = false; - - // TODO: Keys like "return" cause another keyPressEvent to be generated after this one... - } - - QPushButton::keyPressEvent(event); // TODO: Necessary? -} - -void GKeyConfigButton::mousePressEvent(QMouseEvent* event) -{ - // Abort key assignment - if (inputGrabbed) - { - releaseKeyboard(); - releaseMouse(); - setText(old_text); - inputGrabbed = false; - } - - QAbstractButton::mousePressEvent(event); -} - -GButtonConfigGroup::GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent) : QWidget(parent), id(id) -{ - QHBoxLayout* layout = new QHBoxLayout(this); - - QPushButton* clear_button = new QPushButton(tr("Clear")); - - layout->addWidget(new QLabel(name, this)); - layout->addWidget(config_button = new GKeyConfigButton(id, QString(), change_receiver, this)); - layout->addWidget(clear_button); - - // TODO: connect config_button, clear_button - - setLayout(layout); -} - -*/ diff --git a/src/citra_qt/config/controller_config_util.h b/src/citra_qt/config/controller_config_util.h deleted file mode 100644 index 15e025b57..000000000 --- a/src/citra_qt/config/controller_config_util.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#ifndef _CONTROLLER_CONFIG_UTIL_HXX_ -#define _CONTROLLER_CONFIG_UTIL_HXX_ - -#include <QWidget> -#include <QPushButton> - -/* TODO(bunnei): ImplementMe - -#include "config.h" - -class GStickConfig : public QWidget -{ - Q_OBJECT - -public: - // change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot! - GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent = NULL); - -signals: - void LeftChanged(); - void RightChanged(); - void UpChanged(); - void DownChanged(); - -private: - QPushButton* left; - QPushButton* right; - QPushButton* up; - QPushButton* down; - - QPushButton* clear; -}; - -class GKeyConfigButton : public QPushButton -{ - Q_OBJECT - -public: - // TODO: change_receiver also needs to have an ActivePortChanged(const common::Config::ControllerPort&) signal - // change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot! - GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent); - GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent); - -signals: - void KeyAssigned(common::Config::Control id, int key, const QString& text); - -private slots: - void OnActivePortChanged(const common::Config::ControllerPort& config); - - void OnClicked(); - - void keyPressEvent(QKeyEvent* event); // TODO: bGrabbed? - void mousePressEvent(QMouseEvent* event); - -private: - common::Config::Control id; - bool inputGrabbed; - - QString old_text; -}; - -class GButtonConfigGroup : public QWidget -{ - Q_OBJECT - -public: - // change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot! - GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent = NULL); - -private: - GKeyConfigButton* config_button; - - common::Config::Control id; -}; - -*/ - -#endif // _CONTROLLER_CONFIG_HXX_ diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configure.ui index 6ae056ff9..e1624bbef 100644 --- a/src/citra_qt/configure.ui +++ b/src/citra_qt/configure.ui @@ -29,6 +29,11 @@ <string>Input</string> </attribute> </widget> + <widget class="ConfigureAudio" name="audioTab"> + <attribute name="title"> + <string>Audio</string> + </attribute> + </widget> <widget class="ConfigureDebug" name="debugTab"> <attribute name="title"> <string>Debug</string> @@ -53,6 +58,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureAudio</class> + <extends>QWidget</extends> + <header>configure_audio.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureDebug</class> <extends>QWidget</extends> <header>configure_debug.h</header> diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configure_audio.cpp new file mode 100644 index 000000000..cedfa2f2a --- /dev/null +++ b/src/citra_qt/configure_audio.cpp @@ -0,0 +1,44 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/sink_details.h" + +#include "citra_qt/configure_audio.h" +#include "ui_configure_audio.h" + +#include "core/settings.h" + +ConfigureAudio::ConfigureAudio(QWidget* parent) : + QWidget(parent), + ui(std::make_unique<Ui::ConfigureAudio>()) +{ + ui->setupUi(this); + + ui->output_sink_combo_box->clear(); + ui->output_sink_combo_box->addItem("auto"); + for (const auto& sink_detail : AudioCore::g_sink_details) { + ui->output_sink_combo_box->addItem(sink_detail.id); + } + + this->setConfiguration(); +} + +ConfigureAudio::~ConfigureAudio() { +} + +void ConfigureAudio::setConfiguration() { + int new_sink_index = 0; + for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { + if (ui->output_sink_combo_box->itemText(index).toStdString() == Settings::values.sink_id) { + new_sink_index = index; + break; + } + } + ui->output_sink_combo_box->setCurrentIndex(new_sink_index); +} + +void ConfigureAudio::applyConfiguration() { + Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString(); + Settings::Apply(); +} diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configure_audio.h new file mode 100644 index 000000000..51df2e27b --- /dev/null +++ b/src/citra_qt/configure_audio.h @@ -0,0 +1,27 @@ +// 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 ConfigureAudio; +} + +class ConfigureAudio : public QWidget { + Q_OBJECT + +public: + explicit ConfigureAudio(QWidget* parent = nullptr); + ~ConfigureAudio(); + + void applyConfiguration(); + +private: + void setConfiguration(); + + std::unique_ptr<Ui::ConfigureAudio> ui; +}; diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui new file mode 100644 index 000000000..d7f6946ca --- /dev/null +++ b/src/citra_qt/configure_audio.ui @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> + +<ui version="4.0"> + <class>ConfigureAudio</class> + <widget class="QWidget" name="ConfigureAudio"> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox"> + <property name="title"> + <string>Audio</string> + </property> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel"> + <property name="text"> + <string>Output Engine:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="output_sink_combo_box"> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <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 /> +</ui> diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configure_dialog.cpp index 87c26c715..2f0317fe0 100644 --- a/src/citra_qt/configure_dialog.cpp +++ b/src/citra_qt/configure_dialog.cpp @@ -25,5 +25,6 @@ void ConfigureDialog::setConfiguration() { void ConfigureDialog::applyConfiguration() { ui->generalTab->applyConfiguration(); + ui->audioTab->applyConfiguration(); ui->debugTab->applyConfiguration(); } diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp index 793944639..1a3077495 100644 --- a/src/citra_qt/debugger/callstack.cpp +++ b/src/citra_qt/debugger/callstack.cpp @@ -37,10 +37,13 @@ void CallstackWidget::OnDebugModeEntered() int counter = 0; for (u32 addr = 0x10000000; addr >= sp; addr -= 4) { + if (!Memory::IsValidVirtualAddress(addr)) + break; + const u32 ret_addr = Memory::Read32(addr); const u32 call_addr = ret_addr - 4; //get call address??? - if (Memory::GetPointer(call_addr) == nullptr) + if (!Memory::IsValidVirtualAddress(call_addr)) break; /* TODO (mattvail) clean me, move to debugger interface */ diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 5186d2b44..3e0a0a145 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -50,123 +50,6 @@ public: } }; -TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent) - : QDockWidget(tr("Texture 0x%1").arg(info.physical_address, 8, 16, QLatin1Char('0'))), - info(info) { - - QWidget* main_widget = new QWidget; - - QLabel* image_widget = new QLabel; - - connect(this, SIGNAL(UpdatePixmap(const QPixmap&)), image_widget, SLOT(setPixmap(const QPixmap&))); - - CSpinBox* phys_address_spinbox = new CSpinBox; - phys_address_spinbox->SetBase(16); - phys_address_spinbox->SetRange(0, 0xFFFFFFFF); - phys_address_spinbox->SetPrefix("0x"); - phys_address_spinbox->SetValue(info.physical_address); - connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64))); - - QComboBox* format_choice = new QComboBox; - format_choice->addItem(tr("RGBA8")); - format_choice->addItem(tr("RGB8")); - format_choice->addItem(tr("RGB5A1")); - format_choice->addItem(tr("RGB565")); - format_choice->addItem(tr("RGBA4")); - format_choice->addItem(tr("IA8")); - format_choice->addItem(tr("RG8")); - format_choice->addItem(tr("I8")); - format_choice->addItem(tr("A8")); - format_choice->addItem(tr("IA4")); - format_choice->addItem(tr("I4")); - format_choice->addItem(tr("A4")); - format_choice->addItem(tr("ETC1")); - format_choice->addItem(tr("ETC1A4")); - format_choice->setCurrentIndex(static_cast<int>(info.format)); - connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int))); - - QSpinBox* width_spinbox = new QSpinBox; - width_spinbox->setMaximum(65535); - width_spinbox->setValue(info.width); - connect(width_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnWidthChanged(int))); - - QSpinBox* height_spinbox = new QSpinBox; - height_spinbox->setMaximum(65535); - height_spinbox->setValue(info.height); - connect(height_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnHeightChanged(int))); - - QSpinBox* stride_spinbox = new QSpinBox; - stride_spinbox->setMaximum(65535 * 4); - stride_spinbox->setValue(info.stride); - connect(stride_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnStrideChanged(int))); - - QVBoxLayout* main_layout = new QVBoxLayout; - main_layout->addWidget(image_widget); - - { - QHBoxLayout* sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Source Address:"))); - sub_layout->addWidget(phys_address_spinbox); - main_layout->addLayout(sub_layout); - } - - { - QHBoxLayout* sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Format"))); - sub_layout->addWidget(format_choice); - main_layout->addLayout(sub_layout); - } - - { - QHBoxLayout* sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Width:"))); - sub_layout->addWidget(width_spinbox); - sub_layout->addStretch(); - sub_layout->addWidget(new QLabel(tr("Height:"))); - sub_layout->addWidget(height_spinbox); - sub_layout->addStretch(); - sub_layout->addWidget(new QLabel(tr("Stride:"))); - sub_layout->addWidget(stride_spinbox); - main_layout->addLayout(sub_layout); - } - - main_widget->setLayout(main_layout); - - emit UpdatePixmap(ReloadPixmap()); - - setWidget(main_widget); -} - -void TextureInfoDockWidget::OnAddressChanged(qint64 value) { - info.physical_address = value; - emit UpdatePixmap(ReloadPixmap()); -} - -void TextureInfoDockWidget::OnFormatChanged(int value) { - info.format = static_cast<Pica::Regs::TextureFormat>(value); - emit UpdatePixmap(ReloadPixmap()); -} - -void TextureInfoDockWidget::OnWidthChanged(int value) { - info.width = value; - emit UpdatePixmap(ReloadPixmap()); -} - -void TextureInfoDockWidget::OnHeightChanged(int value) { - info.height = value; - emit UpdatePixmap(ReloadPixmap()); -} - -void TextureInfoDockWidget::OnStrideChanged(int value) { - info.stride = value; - emit UpdatePixmap(ReloadPixmap()); -} - -QPixmap TextureInfoDockWidget::ReloadPixmap() const { - u8* src = Memory::GetPhysicalPointer(info.physical_address); - return QPixmap::fromImage(LoadTexture(src, info)); -} - GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { } @@ -249,16 +132,16 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { index = 0; } else if (COMMAND_IN_RANGE(command_id, texture1)) { index = 1; - } else { + } else if (COMMAND_IN_RANGE(command_id, texture2)) { index = 2; + } else { + UNREACHABLE_MSG("Unknown texture command"); } auto config = Pica::g_state.regs.GetTextures()[index].config; auto format = Pica::g_state.regs.GetTextures()[index].format; auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); - // TODO: Instead, emit a signal here to be caught by the main window widget. - auto main_window = static_cast<QMainWindow*>(parent()); - main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window)); + // TODO: Open a surface debugger } } diff --git a/src/citra_qt/debugger/graphics_cmdlists.h b/src/citra_qt/debugger/graphics_cmdlists.h index 586cc7239..8a2a294b9 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.h +++ b/src/citra_qt/debugger/graphics_cmdlists.h @@ -61,25 +61,3 @@ private: QWidget* command_info_widget; QPushButton* toggle_tracing; }; - -class TextureInfoDockWidget : public QDockWidget { - Q_OBJECT - -public: - TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr); - -signals: - void UpdatePixmap(const QPixmap& pixmap); - -private slots: - void OnAddressChanged(qint64 value); - void OnFormatChanged(int value); - void OnWidthChanged(int value); - void OnHeightChanged(int value); - void OnStrideChanged(int value); - -private: - QPixmap ReloadPixmap() const; - - Pica::DebugUtils::TextureInfo info; -}; diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp deleted file mode 100644 index 68cff78b2..000000000 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QBoxLayout> -#include <QComboBox> -#include <QDebug> -#include <QLabel> -#include <QPushButton> -#include <QSpinBox> - -#include "citra_qt/debugger/graphics_framebuffer.h" -#include "citra_qt/util/spinbox.h" - -#include "common/color.h" - -#include "core/memory.h" -#include "core/hw/gpu.h" - -#include "video_core/pica.h" -#include "video_core/pica_state.h" -#include "video_core/utils.h" - -GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, - QWidget* parent) - : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent), - framebuffer_source(Source::PicaTarget) -{ - setObjectName("PicaFramebuffer"); - - framebuffer_source_list = new QComboBox; - framebuffer_source_list->addItem(tr("Active Render Target")); - framebuffer_source_list->addItem(tr("Active Depth Buffer")); - framebuffer_source_list->addItem(tr("Custom")); - framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source)); - - framebuffer_address_control = new CSpinBox; - framebuffer_address_control->SetBase(16); - framebuffer_address_control->SetRange(0, 0xFFFFFFFF); - framebuffer_address_control->SetPrefix("0x"); - - framebuffer_width_control = new QSpinBox; - framebuffer_width_control->setMinimum(1); - framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum - - framebuffer_height_control = new QSpinBox; - framebuffer_height_control->setMinimum(1); - framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum - - framebuffer_format_control = new QComboBox; - framebuffer_format_control->addItem(tr("RGBA8")); - framebuffer_format_control->addItem(tr("RGB8")); - framebuffer_format_control->addItem(tr("RGB5A1")); - framebuffer_format_control->addItem(tr("RGB565")); - framebuffer_format_control->addItem(tr("RGBA4")); - framebuffer_format_control->addItem(tr("D16")); - framebuffer_format_control->addItem(tr("D24")); - framebuffer_format_control->addItem(tr("D24X8")); - framebuffer_format_control->addItem(tr("X24S8")); - framebuffer_format_control->addItem(tr("(unknown)")); - - // TODO: This QLabel should shrink the image to the available space rather than just expanding... - framebuffer_picture_label = new QLabel; - - auto enlarge_button = new QPushButton(tr("Enlarge")); - - // Connections - connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); - connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int))); - connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64))); - connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int))); - connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int))); - connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int))); - - auto main_widget = new QWidget; - auto main_layout = new QVBoxLayout; - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Source:"))); - sub_layout->addWidget(framebuffer_source_list); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Virtual Address:"))); - sub_layout->addWidget(framebuffer_address_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Width:"))); - sub_layout->addWidget(framebuffer_width_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Height:"))); - sub_layout->addWidget(framebuffer_height_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Format:"))); - sub_layout->addWidget(framebuffer_format_control); - main_layout->addLayout(sub_layout); - } - main_layout->addWidget(framebuffer_picture_label); - main_layout->addWidget(enlarge_button); - main_widget->setLayout(main_layout); - setWidget(main_widget); - - // Load current data - TODO: Make sure this works when emulation is not running - if (debug_context && debug_context->at_breakpoint) - emit Update(); - widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint -} - -void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) -{ - emit Update(); - widget()->setEnabled(true); -} - -void GraphicsFramebufferWidget::OnResumed() -{ - widget()->setEnabled(false); -} - -void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value) -{ - framebuffer_source = static_cast<Source>(new_value); - emit Update(); -} - -void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value) -{ - if (framebuffer_address != new_value) { - framebuffer_address = static_cast<unsigned>(new_value); - - framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value) -{ - if (framebuffer_width != static_cast<unsigned>(new_value)) { - framebuffer_width = static_cast<unsigned>(new_value); - - framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) -{ - if (framebuffer_height != static_cast<unsigned>(new_value)) { - framebuffer_height = static_cast<unsigned>(new_value); - - framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value) -{ - if (framebuffer_format != static_cast<Format>(new_value)) { - framebuffer_format = static_cast<Format>(new_value); - - framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsFramebufferWidget::OnUpdate() -{ - QPixmap pixmap; - - switch (framebuffer_source) { - case Source::PicaTarget: - { - // TODO: Store a reference to the registers in the debug context instead of accessing them directly... - - const auto& framebuffer = Pica::g_state.regs.framebuffer; - - framebuffer_address = framebuffer.GetColorBufferPhysicalAddress(); - framebuffer_width = framebuffer.GetWidth(); - framebuffer_height = framebuffer.GetHeight(); - - switch (framebuffer.color_format) { - case Pica::Regs::ColorFormat::RGBA8: - framebuffer_format = Format::RGBA8; - break; - - case Pica::Regs::ColorFormat::RGB8: - framebuffer_format = Format::RGB8; - break; - - case Pica::Regs::ColorFormat::RGB5A1: - framebuffer_format = Format::RGB5A1; - break; - - case Pica::Regs::ColorFormat::RGB565: - framebuffer_format = Format::RGB565; - break; - - case Pica::Regs::ColorFormat::RGBA4: - framebuffer_format = Format::RGBA4; - break; - - default: - framebuffer_format = Format::Unknown; - break; - } - - break; - } - - case Source::DepthBuffer: - { - const auto& framebuffer = Pica::g_state.regs.framebuffer; - - framebuffer_address = framebuffer.GetDepthBufferPhysicalAddress(); - framebuffer_width = framebuffer.GetWidth(); - framebuffer_height = framebuffer.GetHeight(); - - switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D16: - framebuffer_format = Format::D16; - break; - - case Pica::Regs::DepthFormat::D24: - framebuffer_format = Format::D24; - break; - - case Pica::Regs::DepthFormat::D24S8: - framebuffer_format = Format::D24X8; - break; - - default: - framebuffer_format = Format::Unknown; - break; - } - - break; - } - - case Source::Custom: - { - // Keep user-specified values - break; - } - - default: - qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source); - break; - } - - // TODO: Implement a good way to visualize alpha components! - // TODO: Unify this decoding code with the texture decoder - u32 bytes_per_pixel = GraphicsFramebufferWidget::BytesPerPixel(framebuffer_format); - - QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u8* buffer = Memory::GetPhysicalPointer(framebuffer_address); - - for (unsigned int y = 0; y < framebuffer_height; ++y) { - for (unsigned int x = 0; x < framebuffer_width; ++x) { - const u32 coarse_y = y & ~7; - u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * framebuffer_width * bytes_per_pixel; - const u8* pixel = buffer + offset; - Math::Vec4<u8> color = { 0, 0, 0, 0 }; - - switch (framebuffer_format) { - case Format::RGBA8: - color = Color::DecodeRGBA8(pixel); - break; - case Format::RGB8: - color = Color::DecodeRGB8(pixel); - break; - case Format::RGB5A1: - color = Color::DecodeRGB5A1(pixel); - break; - case Format::RGB565: - color = Color::DecodeRGB565(pixel); - break; - case Format::RGBA4: - color = Color::DecodeRGBA4(pixel); - break; - case Format::D16: - { - u32 data = Color::DecodeD16(pixel); - color.r() = data & 0xFF; - color.g() = (data >> 8) & 0xFF; - break; - } - case Format::D24: - { - u32 data = Color::DecodeD24(pixel); - color.r() = data & 0xFF; - color.g() = (data >> 8) & 0xFF; - color.b() = (data >> 16) & 0xFF; - break; - } - case Format::D24X8: - { - Math::Vec2<u32> data = Color::DecodeD24S8(pixel); - color.r() = data.x & 0xFF; - color.g() = (data.x >> 8) & 0xFF; - color.b() = (data.x >> 16) & 0xFF; - break; - } - case Format::X24S8: - { - Math::Vec2<u32> data = Color::DecodeD24S8(pixel); - color.r() = color.g() = color.b() = data.y; - break; - } - default: - qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); - break; - } - - decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255)); - } - } - pixmap = QPixmap::fromImage(decoded_image); - - framebuffer_address_control->SetValue(framebuffer_address); - framebuffer_width_control->setValue(framebuffer_width); - framebuffer_height_control->setValue(framebuffer_height); - framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format)); - framebuffer_picture_label->setPixmap(pixmap); -} - -u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format format) { - switch (format) { - case Format::RGBA8: - case Format::D24X8: - case Format::X24S8: - return 4; - case Format::RGB8: - case Format::D24: - return 3; - case Format::RGB5A1: - case Format::RGB565: - case Format::RGBA4: - case Format::D16: - return 2; - default: - UNREACHABLE_MSG("GraphicsFramebufferWidget::BytesPerPixel: this " - "should not be reached as this function should " - "be given a format which is in " - "GraphicsFramebufferWidget::Format. Instead got %i", - static_cast<int>(format)); - } -} diff --git a/src/citra_qt/debugger/graphics_framebuffer.h b/src/citra_qt/debugger/graphics_framebuffer.h deleted file mode 100644 index 5cd96f2e9..000000000 --- a/src/citra_qt/debugger/graphics_framebuffer.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "citra_qt/debugger/graphics_breakpoint_observer.h" - -class QComboBox; -class QLabel; -class QSpinBox; - -class CSpinBox; - -class GraphicsFramebufferWidget : public BreakPointObserverDock { - Q_OBJECT - - using Event = Pica::DebugContext::Event; - - enum class Source { - PicaTarget = 0, - DepthBuffer = 1, - Custom = 2, - - // TODO: Add GPU framebuffer sources! - }; - - enum class Format { - RGBA8 = 0, - RGB8 = 1, - RGB5A1 = 2, - RGB565 = 3, - RGBA4 = 4, - D16 = 5, - D24 = 6, - D24X8 = 7, - X24S8 = 8, - Unknown = 9 - }; - - static u32 BytesPerPixel(Format format); - -public: - GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); - -public slots: - void OnFramebufferSourceChanged(int new_value); - void OnFramebufferAddressChanged(qint64 new_value); - void OnFramebufferWidthChanged(int new_value); - void OnFramebufferHeightChanged(int new_value); - void OnFramebufferFormatChanged(int new_value); - void OnUpdate(); - -private slots: - void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; - void OnResumed() override; - -signals: - void Update(); - -private: - - QComboBox* framebuffer_source_list; - CSpinBox* framebuffer_address_control; - QSpinBox* framebuffer_width_control; - QSpinBox* framebuffer_height_control; - QComboBox* framebuffer_format_control; - - QLabel* framebuffer_picture_label; - - Source framebuffer_source; - unsigned framebuffer_address; - unsigned framebuffer_width; - unsigned framebuffer_height; - Format framebuffer_format; -}; diff --git a/src/citra_qt/debugger/graphics_surface.cpp b/src/citra_qt/debugger/graphics_surface.cpp new file mode 100644 index 000000000..ac2d6f89b --- /dev/null +++ b/src/citra_qt/debugger/graphics_surface.cpp @@ -0,0 +1,736 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QBoxLayout> +#include <QComboBox> +#include <QDebug> +#include <QFileDialog> +#include <QLabel> +#include <QMouseEvent> +#include <QPushButton> +#include <QScrollArea> +#include <QSpinBox> + +#include "citra_qt/debugger/graphics_surface.h" +#include "citra_qt/util/spinbox.h" + +#include "common/color.h" + +#include "core/memory.h" +#include "core/hw/gpu.h" + +#include "video_core/pica.h" +#include "video_core/pica_state.h" +#include "video_core/utils.h" + +SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {} +SurfacePicture::~SurfacePicture() {} + +void SurfacePicture::mousePressEvent(QMouseEvent* event) +{ + // Only do something while the left mouse button is held down + if (!(event->buttons() & Qt::LeftButton)) + return; + + if (pixmap() == nullptr) + return; + + if (surface_widget) + surface_widget->Pick(event->x() * pixmap()->width() / width(), + event->y() * pixmap()->height() / height()); +} + +void SurfacePicture::mouseMoveEvent(QMouseEvent* event) +{ + // We also want to handle the event if the user moves the mouse while holding down the LMB + mousePressEvent(event); +} + + +GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent), + surface_source(Source::ColorBuffer) +{ + setObjectName("PicaSurface"); + + surface_source_list = new QComboBox; + surface_source_list->addItem(tr("Color Buffer")); + surface_source_list->addItem(tr("Depth Buffer")); + surface_source_list->addItem(tr("Stencil Buffer")); + surface_source_list->addItem(tr("Texture 0")); + surface_source_list->addItem(tr("Texture 1")); + surface_source_list->addItem(tr("Texture 2")); + surface_source_list->addItem(tr("Custom")); + surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); + + surface_address_control = new CSpinBox; + surface_address_control->SetBase(16); + surface_address_control->SetRange(0, 0xFFFFFFFF); + surface_address_control->SetPrefix("0x"); + + unsigned max_dimension = 16384; // TODO: Find actual maximum + + surface_width_control = new QSpinBox; + surface_width_control->setRange(0, max_dimension); + + surface_height_control = new QSpinBox; + surface_height_control->setRange(0, max_dimension); + + surface_picker_x_control = new QSpinBox; + surface_picker_x_control->setRange(0, max_dimension - 1); + + surface_picker_y_control = new QSpinBox; + surface_picker_y_control->setRange(0, max_dimension - 1); + + surface_format_control = new QComboBox; + + // Color formats sorted by Pica texture format index + surface_format_control->addItem(tr("RGBA8")); + surface_format_control->addItem(tr("RGB8")); + surface_format_control->addItem(tr("RGB5A1")); + surface_format_control->addItem(tr("RGB565")); + surface_format_control->addItem(tr("RGBA4")); + surface_format_control->addItem(tr("IA8")); + surface_format_control->addItem(tr("RG8")); + surface_format_control->addItem(tr("I8")); + surface_format_control->addItem(tr("A8")); + surface_format_control->addItem(tr("IA4")); + surface_format_control->addItem(tr("I4")); + surface_format_control->addItem(tr("A4")); + surface_format_control->addItem(tr("ETC1")); + surface_format_control->addItem(tr("ETC1A4")); + surface_format_control->addItem(tr("D16")); + surface_format_control->addItem(tr("D24")); + surface_format_control->addItem(tr("D24X8")); + surface_format_control->addItem(tr("X24S8")); + surface_format_control->addItem(tr("Unknown")); + + surface_info_label = new QLabel(); + surface_info_label->setWordWrap(true); + + surface_picture_label = new SurfacePicture(0, this); + surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); + surface_picture_label->setScaledContents(false); + + auto scroll_area = new QScrollArea(); + scroll_area->setBackgroundRole(QPalette::Dark); + scroll_area->setWidgetResizable(false); + scroll_area->setWidget(surface_picture_label); + + save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); + + // Connections + connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); + connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceSourceChanged(int))); + connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnSurfaceAddressChanged(qint64))); + connect(surface_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceWidthChanged(int))); + connect(surface_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceHeightChanged(int))); + connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceFormatChanged(int))); + connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerXChanged(int))); + connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerYChanged(int))); + connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source:"))); + sub_layout->addWidget(surface_source_list); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Physical Address:"))); + sub_layout->addWidget(surface_address_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(surface_width_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(surface_height_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format:"))); + sub_layout->addWidget(surface_format_control); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(scroll_area); + + auto info_layout = new QHBoxLayout; + { + auto xy_layout = new QVBoxLayout; + { + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("X:"))); + sub_layout->addWidget(surface_picker_x_control); + xy_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Y:"))); + sub_layout->addWidget(surface_picker_y_control); + xy_layout->addLayout(sub_layout); + } + } + info_layout->addLayout(xy_layout); + surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + info_layout->addWidget(surface_info_label); + } + main_layout->addLayout(info_layout); + + main_layout->addWidget(save_surface); + main_widget->setLayout(main_layout); + setWidget(main_widget); + + // Load current data - TODO: Make sure this works when emulation is not running + if (debug_context && debug_context->at_breakpoint) { + emit Update(); + widget()->setEnabled(debug_context->at_breakpoint); + } else { + widget()->setEnabled(false); + } +} + +void GraphicsSurfaceWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsSurfaceWidget::OnResumed() +{ + widget()->setEnabled(false); +} + +void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) +{ + surface_source = static_cast<Source>(new_value); + emit Update(); +} + +void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) +{ + if (surface_address != new_value) { + surface_address = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) +{ + if (surface_width != static_cast<unsigned>(new_value)) { + surface_width = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) +{ + if (surface_height != static_cast<unsigned>(new_value)) { + surface_height = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) +{ + if (surface_format != static_cast<Format>(new_value)) { + surface_format = static_cast<Format>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) +{ + if (surface_picker_x != new_value) { + surface_picker_x = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) +{ + if (surface_picker_y != new_value) { + surface_picker_y = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::Pick(int x, int y) +{ + surface_picker_x_control->setValue(x); + surface_picker_y_control->setValue(y); + + if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) { + surface_info_label->setText(tr("Pixel out of bounds")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + return; + } + + u8* buffer = Memory::GetPhysicalPointer(surface_address); + if (buffer == nullptr) { + surface_info_label->setText(tr("(unable to access pixel data)")); + surface_info_label->setAlignment(Qt::AlignCenter); + return; + } + + unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); + unsigned stride = nibbles_per_pixel * surface_width / 2; + + unsigned bytes_per_pixel; + bool nibble_mode = (nibbles_per_pixel == 1); + if (nibble_mode) { + // As nibbles are contained in a byte we still need to access one byte per nibble + bytes_per_pixel = 1; + } else { + bytes_per_pixel = nibbles_per_pixel / 2; + } + + const u32 coarse_y = y & ~7; + u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + const u8* pixel = buffer + (nibble_mode ? (offset / 2) : offset); + + auto GetText = [offset](Format format, const u8* pixel) { + switch (format) { + case Format::RGBA8: + { + auto value = Color::DecodeRGBA8(pixel) / 255.0f; + return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4") + .arg(QString::number(value.r(), 'f', 2)) + .arg(QString::number(value.g(), 'f', 2)) + .arg(QString::number(value.b(), 'f', 2)) + .arg(QString::number(value.a(), 'f', 2)); + } + case Format::RGB8: + { + auto value = Color::DecodeRGB8(pixel) / 255.0f; + return QString("Red: %1, Green: %2, Blue: %3") + .arg(QString::number(value.r(), 'f', 2)) + .arg(QString::number(value.g(), 'f', 2)) + .arg(QString::number(value.b(), 'f', 2)); + } + case Format::RGB5A1: + { + auto value = Color::DecodeRGB5A1(pixel) / 255.0f; + return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4") + .arg(QString::number(value.r(), 'f', 2)) + .arg(QString::number(value.g(), 'f', 2)) + .arg(QString::number(value.b(), 'f', 2)) + .arg(QString::number(value.a(), 'f', 2)); + } + case Format::RGB565: + { + auto value = Color::DecodeRGB565(pixel) / 255.0f; + return QString("Red: %1, Green: %2, Blue: %3") + .arg(QString::number(value.r(), 'f', 2)) + .arg(QString::number(value.g(), 'f', 2)) + .arg(QString::number(value.b(), 'f', 2)); + } + case Format::RGBA4: + { + auto value = Color::DecodeRGBA4(pixel) / 255.0f; + return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4") + .arg(QString::number(value.r(), 'f', 2)) + .arg(QString::number(value.g(), 'f', 2)) + .arg(QString::number(value.b(), 'f', 2)) + .arg(QString::number(value.a(), 'f', 2)); + } + case Format::IA8: + return QString("Index: %1, Alpha: %2") + .arg(pixel[0]) + .arg(pixel[1]); + case Format::RG8: { + auto value = Color::DecodeRG8(pixel) / 255.0f; + return QString("Red: %1, Green: %2") + .arg(QString::number(value.r(), 'f', 2)) + .arg(QString::number(value.g(), 'f', 2)); + } + case Format::I8: + return QString("Index: %1").arg(*pixel); + case Format::A8: + return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2)); + case Format::IA4: + return QString("Index: %1, Alpha: %2") + .arg(*pixel & 0xF) + .arg((*pixel & 0xF0) >> 4); + case Format::I4: + { + u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF; + return QString("Index: %1").arg(i); + } + case Format::A4: + { + u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF; + return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2)); + } + case Format::ETC1: + case Format::ETC1A4: + // TODO: Display block information or channel values? + return QString("Compressed data"); + case Format::D16: + { + auto value = Color::DecodeD16(pixel); + return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4)); + } + case Format::D24: + { + auto value = Color::DecodeD24(pixel); + return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4)); + } + case Format::D24X8: + case Format::X24S8: + { + auto values = Color::DecodeD24S8(pixel); + return QString("Depth: %1, Stencil: %2").arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4)).arg(values[1]); + } + case Format::Unknown: + return QString("Unknown format"); + default: + return QString("Unhandled format"); + } + return QString(""); + }; + + QString nibbles = ""; + for (unsigned i = 0; i < nibbles_per_pixel; i++) { + unsigned nibble_index = i; + if (nibble_mode) { + nibble_index += (offset % 2) ? 0 : 1; + } + u8 byte = pixel[nibble_index / 2]; + u8 nibble = (byte >> ((nibble_index % 2) ? 0 : 4)) & 0xF; + nibbles.append(QString::number(nibble, 16).toUpper()); + } + + surface_info_label->setText(QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel))); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); +} + +void GraphicsSurfaceWidget::OnUpdate() +{ + QPixmap pixmap; + + switch (surface_source) { + case Source::ColorBuffer: + { + // TODO: Store a reference to the registers in the debug context instead of accessing them directly... + + const auto& framebuffer = Pica::g_state.regs.framebuffer; + + surface_address = framebuffer.GetColorBufferPhysicalAddress(); + surface_width = framebuffer.GetWidth(); + surface_height = framebuffer.GetHeight(); + + switch (framebuffer.color_format) { + case Pica::Regs::ColorFormat::RGBA8: + surface_format = Format::RGBA8; + break; + + case Pica::Regs::ColorFormat::RGB8: + surface_format = Format::RGB8; + break; + + case Pica::Regs::ColorFormat::RGB5A1: + surface_format = Format::RGB5A1; + break; + + case Pica::Regs::ColorFormat::RGB565: + surface_format = Format::RGB565; + break; + + case Pica::Regs::ColorFormat::RGBA4: + surface_format = Format::RGBA4; + break; + + default: + surface_format = Format::Unknown; + break; + } + + break; + } + + case Source::DepthBuffer: + { + const auto& framebuffer = Pica::g_state.regs.framebuffer; + + surface_address = framebuffer.GetDepthBufferPhysicalAddress(); + surface_width = framebuffer.GetWidth(); + surface_height = framebuffer.GetHeight(); + + switch (framebuffer.depth_format) { + case Pica::Regs::DepthFormat::D16: + surface_format = Format::D16; + break; + + case Pica::Regs::DepthFormat::D24: + surface_format = Format::D24; + break; + + case Pica::Regs::DepthFormat::D24S8: + surface_format = Format::D24X8; + break; + + default: + surface_format = Format::Unknown; + break; + } + + break; + } + + case Source::StencilBuffer: + { + const auto& framebuffer = Pica::g_state.regs.framebuffer; + + surface_address = framebuffer.GetDepthBufferPhysicalAddress(); + surface_width = framebuffer.GetWidth(); + surface_height = framebuffer.GetHeight(); + + switch (framebuffer.depth_format) { + case Pica::Regs::DepthFormat::D24S8: + surface_format = Format::X24S8; + break; + + default: + surface_format = Format::Unknown; + break; + } + + break; + } + + case Source::Texture0: + case Source::Texture1: + case Source::Texture2: + { + unsigned texture_index; + if (surface_source == Source::Texture0) texture_index = 0; + else if (surface_source == Source::Texture1) texture_index = 1; + else if (surface_source == Source::Texture2) texture_index = 2; + else { + qDebug() << "Unknown texture source " << static_cast<int>(surface_source); + break; + } + + const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; + auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + + surface_address = info.physical_address; + surface_width = info.width; + surface_height = info.height; + surface_format = static_cast<Format>(info.format); + + if (surface_format > Format::MaxTextureFormat) { + qDebug() << "Unknown texture format " << static_cast<int>(info.format); + } + break; + } + + case Source::Custom: + { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown surface source " << static_cast<int>(surface_source); + break; + } + + surface_address_control->SetValue(surface_address); + surface_width_control->setValue(surface_width); + surface_height_control->setValue(surface_height); + surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); + + // TODO: Implement a good way to visualize alpha components! + + QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); + u8* buffer = Memory::GetPhysicalPointer(surface_address); + + if (buffer == nullptr) { + surface_picture_label->hide(); + surface_info_label->setText(tr("(invalid surface address)")); + surface_info_label->setAlignment(Qt::AlignCenter); + surface_picker_x_control->setEnabled(false); + surface_picker_y_control->setEnabled(false); + save_surface->setEnabled(false); + return; + } + + if (surface_format == Format::Unknown) { + surface_picture_label->hide(); + surface_info_label->setText(tr("(unknown surface format)")); + surface_info_label->setAlignment(Qt::AlignCenter); + surface_picker_x_control->setEnabled(false); + surface_picker_y_control->setEnabled(false); + save_surface->setEnabled(false); + return; + } + + surface_picture_label->show(); + + unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); + unsigned stride = nibbles_per_pixel * surface_width / 2; + + // We handle depth formats here because DebugUtils only supports TextureFormats + if (surface_format <= Format::MaxTextureFormat) { + + // Generate a virtual texture + Pica::DebugUtils::TextureInfo info; + info.physical_address = surface_address; + info.width = surface_width; + info.height = surface_height; + info.format = static_cast<Pica::Regs::TextureFormat>(surface_format); + info.stride = stride; + + for (unsigned int y = 0; y < surface_height; ++y) { + for (unsigned int x = 0; x < surface_width; ++x) { + Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true); + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + } else { + + ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel"); + unsigned bytes_per_pixel = nibbles_per_pixel / 2; + + for (unsigned int y = 0; y < surface_height; ++y) { + for (unsigned int x = 0; x < surface_width; ++x) { + const u32 coarse_y = y & ~7; + u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; + const u8* pixel = buffer + offset; + Math::Vec4<u8> color = { 0, 0, 0, 0 }; + + switch(surface_format) { + case Format::D16: + { + u32 data = Color::DecodeD16(pixel); + color.r() = data & 0xFF; + color.g() = (data >> 8) & 0xFF; + break; + } + case Format::D24: + { + u32 data = Color::DecodeD24(pixel); + color.r() = data & 0xFF; + color.g() = (data >> 8) & 0xFF; + color.b() = (data >> 16) & 0xFF; + break; + } + case Format::D24X8: + { + Math::Vec2<u32> data = Color::DecodeD24S8(pixel); + color.r() = data.x & 0xFF; + color.g() = (data.x >> 8) & 0xFF; + color.b() = (data.x >> 16) & 0xFF; + break; + } + case Format::X24S8: + { + Math::Vec2<u32> data = Color::DecodeD24S8(pixel); + color.r() = color.g() = color.b() = data.y; + break; + } + default: + qDebug() << "Unknown surface format " << static_cast<int>(surface_format); + break; + } + + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255)); + } + } + + } + + pixmap = QPixmap::fromImage(decoded_image); + surface_picture_label->setPixmap(pixmap); + surface_picture_label->resize(pixmap.size()); + + // Update the info with pixel data + surface_picker_x_control->setEnabled(true); + surface_picker_y_control->setEnabled(true); + Pick(surface_picker_x, surface_picker_y); + + // Enable saving the converted pixmap to file + save_surface->setEnabled(true); +} + +void GraphicsSurfaceWidget::SaveSurface() { + QString png_filter = tr("Portable Network Graphic (*.png)"); + QString bin_filter = tr("Binary data (*.bin)"); + + QString selectedFilter; + QString filename = QFileDialog::getSaveFileName(this, tr("Save Surface"), QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), + QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); + + if (filename.isEmpty()) { + // If the user canceled the dialog, don't save anything. + return; + } + + if (selectedFilter == png_filter) { + const QPixmap* pixmap = surface_picture_label->pixmap(); + ASSERT_MSG(pixmap != nullptr, "No pixmap set"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + if (pixmap) + pixmap->save(&file, "PNG"); + } else if (selectedFilter == bin_filter) { + const u8* buffer = Memory::GetPhysicalPointer(surface_address); + ASSERT_MSG(buffer != nullptr, "Memory not accessible"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + int size = surface_width * surface_height * NibblesPerPixel(surface_format) / 2; + QByteArray data(reinterpret_cast<const char*>(buffer), size); + file.write(data); + } else { + UNREACHABLE_MSG("Unhandled filter selected"); + } +} + +unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) { + if (format <= Format::MaxTextureFormat) { + return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format)); + } + + switch (format) { + case Format::D24X8: + case Format::X24S8: + return 4 * 2; + case Format::D24: + return 3 * 2; + case Format::D16: + return 2 * 2; + default: + UNREACHABLE_MSG("GraphicsSurfaceWidget::BytesPerPixel: this " + "should not be reached as this function should " + "be given a format which is in " + "GraphicsSurfaceWidget::Format. Instead got %i", + static_cast<int>(format)); + return 0; + } +} diff --git a/src/citra_qt/debugger/graphics_surface.h b/src/citra_qt/debugger/graphics_surface.h new file mode 100644 index 000000000..7c7f50e38 --- /dev/null +++ b/src/citra_qt/debugger/graphics_surface.h @@ -0,0 +1,120 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "citra_qt/debugger/graphics_breakpoint_observer.h" + +#include <QLabel> +#include <QPushButton> + +class QComboBox; +class QSpinBox; +class CSpinBox; + +class GraphicsSurfaceWidget; + +class SurfacePicture : public QLabel +{ + Q_OBJECT + +public: + SurfacePicture(QWidget* parent = 0, GraphicsSurfaceWidget* surface_widget = nullptr); + ~SurfacePicture(); + +protected slots: + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + +private: + GraphicsSurfaceWidget* surface_widget; + +}; + +class GraphicsSurfaceWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Pica::DebugContext::Event; + + enum class Source { + ColorBuffer = 0, + DepthBuffer = 1, + StencilBuffer = 2, + Texture0 = 3, + Texture1 = 4, + Texture2 = 5, + Custom = 6, + }; + + enum class Format { + // These must match the TextureFormat type! + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + IA8 = 5, + RG8 = 6, ///< @note Also called HILO8 in 3DBrew. + I8 = 7, + A8 = 8, + IA4 = 9, + I4 = 10, + A4 = 11, + ETC1 = 12, // compressed + ETC1A4 = 13, + MaxTextureFormat = 13, + D16 = 14, + D24 = 15, + D24X8 = 16, + X24S8 = 17, + Unknown = 18, + }; + + static unsigned int NibblesPerPixel(Format format); + +public: + GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); + void Pick(int x, int y); + +public slots: + void OnSurfaceSourceChanged(int new_value); + void OnSurfaceAddressChanged(qint64 new_value); + void OnSurfaceWidthChanged(int new_value); + void OnSurfaceHeightChanged(int new_value); + void OnSurfaceFormatChanged(int new_value); + void OnSurfacePickerXChanged(int new_value); + void OnSurfacePickerYChanged(int new_value); + void OnUpdate(); + +private slots: + void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnResumed() override; + + void SaveSurface(); + +signals: + void Update(); + +private: + + QComboBox* surface_source_list; + CSpinBox* surface_address_control; + QSpinBox* surface_width_control; + QSpinBox* surface_height_control; + QComboBox* surface_format_control; + + SurfacePicture* surface_picture_label; + QSpinBox* surface_picker_x_control; + QSpinBox* surface_picker_y_control; + QLabel* surface_info_label; + QPushButton* save_surface; + + Source surface_source; + unsigned surface_address; + unsigned surface_width; + unsigned surface_height; + Format surface_format; + int surface_picker_x = 0; + int surface_picker_y = 0; +}; diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index 7bb010f77..585ac049a 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -151,6 +151,8 @@ private: /// This timer is used to redraw the widget's contents continuously. To save resources, it only /// runs while the widget is visible. QTimer update_timer; + /// Scale the coordinate system appropriately when physical DPI != logical DPI. + qreal x_scale, y_scale; }; #endif @@ -220,11 +222,17 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { MicroProfileInitUI(); connect(&update_timer, SIGNAL(timeout()), SLOT(update())); + + QPainter painter(this); + x_scale = qreal(painter.device()->physicalDpiX()) / qreal(painter.device()->logicalDpiX()); + y_scale = qreal(painter.device()->physicalDpiY()) / qreal(painter.device()->logicalDpiY()); } void MicroProfileWidget::paintEvent(QPaintEvent* ev) { QPainter painter(this); + painter.scale(x_scale, y_scale); + painter.setBackground(Qt::black); painter.eraseRect(rect()); @@ -248,24 +256,24 @@ void MicroProfileWidget::hideEvent(QHideEvent* ev) { } void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), 0); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); ev->accept(); } void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), 0); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); ev->accept(); } void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), 0); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); ev->accept(); } void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), ev->delta() / 120); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120); ev->accept(); } diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index d4ac9c96e..15484fae3 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -118,46 +118,33 @@ void GameList::LoadInterfaceLayout() item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } -void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan) +void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [&](unsigned* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + const std::string& virtual_name, + unsigned int recursion) -> bool { std::string physical_name = directory + DIR_SEP + virtual_name; if (stop_processing) return false; // Breaks the callback loop. - if (deep_scan && FileUtil::IsDirectory(physical_name)) { - AddFstEntriesToGameList(physical_name, true); - } else { - std::string filename_filename, filename_extension; - Common::SplitPath(physical_name, nullptr, &filename_filename, &filename_extension); - - Loader::FileType guessed_filetype = Loader::GuessFromExtension(filename_extension); - if (guessed_filetype == Loader::FileType::Unknown) - return true; - Loader::FileType filetype = Loader::IdentifyFile(physical_name); - if (filetype == Loader::FileType::Unknown) { - LOG_WARNING(Frontend, "File %s is of indeterminate type and is possibly corrupted.", physical_name.c_str()); + if (!FileUtil::IsDirectory(physical_name)) { + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); + if (!loader) return true; - } - if (guessed_filetype != filetype) { - LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str()); - } std::vector<u8> smdh; - std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(FileUtil::IOFile(physical_name, "rb"), filetype, filename_filename, physical_name); - - if (loader) - loader->ReadIcon(smdh); + loader->ReadIcon(smdh); emit EntryReady({ new GameListItemPath(QString::fromStdString(physical_name), smdh), - new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))), + new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), }); + } else if (recursion > 0) { + AddFstEntriesToGameList(physical_name, recursion - 1); } return true; @@ -169,7 +156,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d void GameListWorker::run() { stop_processing = false; - AddFstEntriesToGameList(dir_path.toStdString(), deep_scan); + AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); emit Finished(); } diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 284f5da81..353b2d1e2 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -15,52 +15,21 @@ #include "common/string_util.h" #include "common/color.h" -#include "core/loader/loader.h" +#include "core/loader/smdh.h" #include "video_core/utils.h" /** - * Tests if data is a valid SMDH by its length and magic number. - * @param smdh_data data buffer to test - * @return bool test result - */ -static bool IsValidSMDH(const std::vector<u8>& smdh_data) { - if (smdh_data.size() < sizeof(Loader::SMDH)) - return false; - - u32 magic; - memcpy(&magic, smdh_data.data(), 4); - - return Loader::MakeMagic('S', 'M', 'D', 'H') == magic; -} - -/** * Gets game icon from SMDH * @param sdmh SMDH data * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @return QPixmap game icon */ -static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) { - u32 size; - const u8* icon_data; - - if (large) { - size = 48; - icon_data = smdh.large_icon.data(); - } else { - size = 24; - icon_data = smdh.small_icon.data(); - } - - QImage icon(size, size, QImage::Format::Format_RGB888); - for (u32 x = 0; x < size; ++x) { - for (u32 y = 0; y < size; ++y) { - u32 coarse_y = y & ~7; - auto v = Color::DecodeRGB565( - icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2); - icon.setPixel(x, y, qRgb(v.r(), v.g(), v.b())); - } - } +static QPixmap GetQPixmapFromSMDH(const Loader::SMDH& smdh, bool large) { + std::vector<u16> icon_data = smdh.GetIcon(large); + const uchar* data = reinterpret_cast<const uchar*>(icon_data.data()); + int size = large ? 48 : 24; + QImage icon(data, size, size, QImage::Format::Format_RGB16); return QPixmap::fromImage(icon); } @@ -82,8 +51,8 @@ static QPixmap GetDefaultIcon(bool large) { * @param language title language * @return QString short title */ -static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { - return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data()); +static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { + return QString::fromUtf16(smdh.GetShortTitle(language).data()); } class GameListItem : public QStandardItem { @@ -112,7 +81,7 @@ public: { setData(game_path, FullPathRole); - if (!IsValidSMDH(smdh_data)) { + if (!Loader::IsValidSMDH(smdh_data)) { // SMDH is not valid, set a default icon setData(GetDefaultIcon(true), Qt::DecorationRole); return; @@ -122,10 +91,10 @@ public: memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH)); // Get icon from SMDH - setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole); + setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole); // Get title form SMDH - setData(GetShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole); + setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole); } QVariant data(int role) const override { @@ -212,5 +181,5 @@ private: bool deep_scan; std::atomic_bool stop_processing; - void AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan); + void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); }; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index a85c94a4b..0ed1ffa5a 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -29,7 +29,7 @@ #include "citra_qt/debugger/graphics.h" #include "citra_qt/debugger/graphics_breakpoints.h" #include "citra_qt/debugger/graphics_cmdlists.h" -#include "citra_qt/debugger/graphics_framebuffer.h" +#include "citra_qt/debugger/graphics_surface.h" #include "citra_qt/debugger/graphics_tracing.h" #include "citra_qt/debugger/graphics_vertex_shader.h" #include "citra_qt/debugger/profiler.h" @@ -101,10 +101,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); graphicsBreakpointsWidget->hide(); - auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this); - addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget); - graphicsFramebufferWidget->hide(); - auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); graphicsVertexShaderWidget->hide(); @@ -113,7 +109,12 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); graphicsTracingWidget->hide(); + auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica surface viewer"), this); + connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, SLOT(OnCreateGraphicsSurfaceViewer())); + QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); + debug_menu->addAction(graphicsSurfaceViewerAction); + debug_menu->addSeparator(); debug_menu->addAction(profilerWidget->toggleViewAction()); #if MICROPROFILE_ENABLED debug_menu->addAction(microProfileDialog->toggleViewAction()); @@ -124,7 +125,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) debug_menu->addAction(graphicsWidget->toggleViewAction()); debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); - debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); @@ -272,7 +272,15 @@ bool GMainWindow::InitializeSystem() { } bool GMainWindow::LoadROM(const std::string& filename) { - Loader::ResultStatus result = Loader::LoadFile(filename); + std::unique_ptr<Loader::AppLoader> app_loader = Loader::GetLoader(filename); + if (!app_loader) { + LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filename.c_str()); + QMessageBox::critical(this, tr("Error while loading ROM!"), + tr("The ROM format is not supported.")); + return false; + } + + Loader::ResultStatus result = app_loader->Load(); if (Loader::ResultStatus::Success != result) { LOG_CRITICAL(Frontend, "Failed to load ROM!"); System::Shutdown(); @@ -509,6 +517,13 @@ void GMainWindow::OnConfigure() { } } +void GMainWindow::OnCreateGraphicsSurfaceViewer() { + auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget); + // TODO: Maybe graphicsSurfaceViewerWidget->setFloating(true); + graphicsSurfaceViewerWidget->show(); +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 477db5c5c..b836b13fb 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -108,6 +108,7 @@ private slots: void OnConfigure(); void OnDisplayTitleBars(bool); void ToggleWindowMode(); + void OnCreateGraphicsSurfaceViewer(); private: Ui::MainWindow ui; |