diff options
Diffstat (limited to 'src/citra_qt')
-rw-r--r-- | src/citra_qt/CMakeLists.txt | 7 | ||||
-rw-r--r-- | src/citra_qt/bootmanager.cpp | 7 | ||||
-rw-r--r-- | src/citra_qt/config.cpp | 8 | ||||
-rw-r--r-- | src/citra_qt/configure.ui | 11 | ||||
-rw-r--r-- | src/citra_qt/configure_dialog.cpp | 9 | ||||
-rw-r--r-- | src/citra_qt/configure_dialog.h | 3 | ||||
-rw-r--r-- | src/citra_qt/configure_system.cpp | 136 | ||||
-rw-r--r-- | src/citra_qt/configure_system.h | 38 | ||||
-rw-r--r-- | src/citra_qt/configure_system.ui | 252 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_cmdlists.cpp | 125 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_cmdlists.h | 22 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.cpp | 356 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.h | 76 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_surface.cpp | 736 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_surface.h | 120 | ||||
-rw-r--r-- | src/citra_qt/debugger/profiler.cpp | 13 | ||||
-rw-r--r-- | src/citra_qt/game_list.cpp | 8 | ||||
-rw-r--r-- | src/citra_qt/main.cpp | 21 | ||||
-rw-r--r-- | src/citra_qt/main.h | 1 |
19 files changed, 1346 insertions, 603 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 585686627..017b43871 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -9,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 @@ -22,6 +22,7 @@ set(SRCS configure_debug.cpp configure_dialog.cpp configure_general.cpp + configure_system.cpp game_list.cpp hotkeys.cpp main.cpp @@ -39,7 +40,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 @@ -52,6 +53,7 @@ set(HEADERS configure_debug.h configure_dialog.h configure_general.h + configure_system.h game_list.h game_list_p.h hotkeys.h @@ -69,6 +71,7 @@ set(UIS configure_audio.ui configure_debug.ui configure_general.ui + configure_system.ui hotkeys.ui main.ui ) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 01b81c11c..414b2f8af 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -235,12 +235,12 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { void GRenderWindow::keyPressEvent(QKeyEvent* event) { - this->KeyPressed({event->key(), keyboard_id}); + KeyMap::PressKey(*this, { event->key(), keyboard_id }); } void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { - this->KeyReleased({event->key(), keyboard_id}); + KeyMap::ReleaseKey(*this, { event->key(), keyboard_id }); } void GRenderWindow::mousePressEvent(QMouseEvent *event) @@ -270,8 +270,9 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent *event) void GRenderWindow::ReloadSetKeymaps() { + KeyMap::ClearKeyMapping(keyboard_id); for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping({Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, Service::HID::pad_mapping[i]); + KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]); } } diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 6e4ba3907..ba7edaff9 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -22,12 +22,16 @@ Config::Config() { } static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults = { + // directly mapped keys Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, + Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, + + // indirectly mapped keys Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, - Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L + Qt::Key_D, }; void Config::ReadValues() { @@ -36,6 +40,7 @@ void Config::ReadValues() { Settings::values.input_mappings[Settings::NativeInput::All[i]] = qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]).toInt(); } + Settings::values.pad_circle_modifier_scale = qt_config->value("pad_circle_modifier_scale", 0.5).toFloat(); qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -126,6 +131,7 @@ void Config::SaveValues() { qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), Settings::values.input_mappings[Settings::NativeInput::All[i]]); } + qt_config->setValue("pad_circle_modifier_scale", (double)Settings::values.pad_circle_modifier_scale); qt_config->endGroup(); qt_config->beginGroup("Core"); diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configure.ui index e1624bbef..4a9c52650 100644 --- a/src/citra_qt/configure.ui +++ b/src/citra_qt/configure.ui @@ -24,6 +24,11 @@ <string>General</string> </attribute> </widget> + <widget class="ConfigureSystem" name="systemTab"> + <attribute name="title"> + <string>System</string> + </attribute> + </widget> <widget class="QWidget" name="inputTab"> <attribute name="title"> <string>Input</string> @@ -58,6 +63,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureSystem</class> + <extends>QWidget</extends> + <header>configure_system.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> <header>configure_audio.h</header> diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configure_dialog.cpp index 2f0317fe0..77c266d01 100644 --- a/src/citra_qt/configure_dialog.cpp +++ b/src/citra_qt/configure_dialog.cpp @@ -9,9 +9,10 @@ #include "core/settings.h" -ConfigureDialog::ConfigureDialog(QWidget *parent) : +ConfigureDialog::ConfigureDialog(QWidget *parent, bool running) : QDialog(parent), - ui(new Ui::ConfigureDialog) + ui(new Ui::ConfigureDialog), + emulation_running(running) { ui->setupUi(this); this->setConfiguration(); @@ -21,10 +22,14 @@ ConfigureDialog::~ConfigureDialog() { } void ConfigureDialog::setConfiguration() { + // System tab needs set manually + // depending on whether emulation is running + ui->systemTab->setConfiguration(emulation_running); } void ConfigureDialog::applyConfiguration() { ui->generalTab->applyConfiguration(); + ui->systemTab->applyConfiguration(); ui->audioTab->applyConfiguration(); ui->debugTab->applyConfiguration(); } diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configure_dialog.h index 89020eeb4..305b33bdf 100644 --- a/src/citra_qt/configure_dialog.h +++ b/src/citra_qt/configure_dialog.h @@ -16,7 +16,7 @@ class ConfigureDialog : public QDialog Q_OBJECT public: - explicit ConfigureDialog(QWidget *parent = nullptr); + explicit ConfigureDialog(QWidget *parent, bool emulation_running); ~ConfigureDialog(); void applyConfiguration(); @@ -26,4 +26,5 @@ private: private: std::unique_ptr<Ui::ConfigureDialog> ui; + bool emulation_running; }; diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configure_system.cpp new file mode 100644 index 000000000..4f0d4dbfe --- /dev/null +++ b/src/citra_qt/configure_system.cpp @@ -0,0 +1,136 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "citra_qt/configure_system.h" +#include "citra_qt/ui_settings.h" +#include "ui_configure_system.h" + +#include "core/hle/service/fs/archive.h" +#include "core/hle/service/cfg/cfg.h" + +static const std::array<int, 12> days_in_month = {{ + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}}; + +ConfigureSystem::ConfigureSystem(QWidget *parent) : + QWidget(parent), + ui(new Ui::ConfigureSystem) { + ui->setupUi(this); + + connect(ui->combo_birthmonth, SIGNAL(currentIndexChanged(int)), SLOT(updateBirthdayComboBox(int))); +} + +ConfigureSystem::~ConfigureSystem() { +} + +void ConfigureSystem::setConfiguration(bool emulation_running) { + enabled = !emulation_running; + + if (!enabled) { + ReadSystemSettings(); + ui->group_system_settings->setEnabled(false); + } else { + // This tab is enabled only when game is not running (i.e. all service are not initialized). + // Temporarily register archive types and load the config savegame file to memory. + Service::FS::RegisterArchiveTypes(); + ResultCode result = Service::CFG::LoadConfigNANDSaveFile(); + Service::FS::UnregisterArchiveTypes(); + + if (result.IsError()) { + ui->label_disable_info->setText(tr("Failed to load system settings data.")); + ui->group_system_settings->setEnabled(false); + enabled = false; + return; + } + + ReadSystemSettings(); + ui->label_disable_info->hide(); + } +} + +void ConfigureSystem::ReadSystemSettings() { + // set username + username = Service::CFG::GetUsername(); + // ui->edit_username->setText(QString::fromStdU16String(username)); // TODO(wwylele): Use this when we move to Qt 5.5 + ui->edit_username->setText(QString::fromUtf16(reinterpret_cast<const ushort*>(username.data()))); + + // set birthday + std::tie(birthmonth, birthday) = Service::CFG::GetBirthday(); + ui->combo_birthmonth->setCurrentIndex(birthmonth - 1); + ui->combo_birthday->setCurrentIndex(birthday - 1); + + // set system language + language_index = Service::CFG::GetSystemLanguage(); + ui->combo_language->setCurrentIndex(language_index); + + // set sound output mode + sound_index = Service::CFG::GetSoundOutputMode(); + ui->combo_sound->setCurrentIndex(sound_index); +} + +void ConfigureSystem::applyConfiguration() { + if (!enabled) + return; + + bool modified = false; + + // apply username + // std::u16string new_username = ui->edit_username->text().toStdU16String(); // TODO(wwylele): Use this when we move to Qt 5.5 + std::u16string new_username(reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16())); + if (new_username != username) { + Service::CFG::SetUsername(new_username); + modified = true; + } + + // apply birthday + int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1; + int new_birthday = ui->combo_birthday->currentIndex() + 1; + if (birthmonth != new_birthmonth || birthday != new_birthday) { + Service::CFG::SetBirthday(new_birthmonth, new_birthday); + modified = true; + } + + // apply language + int new_language = ui->combo_language->currentIndex(); + if (language_index != new_language) { + Service::CFG::SetSystemLanguage(static_cast<Service::CFG::SystemLanguage>(new_language)); + modified = true; + } + + // apply sound + int new_sound = ui->combo_sound->currentIndex(); + if (sound_index != new_sound) { + Service::CFG::SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(new_sound)); + modified = true; + } + + // update the config savegame if any item is modified. + if (modified) + Service::CFG::UpdateConfigNANDSavegame(); +} + +void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) { + if (birthmonth_index < 0 || birthmonth_index >= 12) + return; + + // store current day selection + int birthday_index = ui->combo_birthday->currentIndex(); + + // get number of days in the new selected month + int days = days_in_month[birthmonth_index]; + + // if the selected day is out of range, + // reset it to 1st + if (birthday_index < 0 || birthday_index >= days) + birthday_index = 0; + + // update the day combo box + ui->combo_birthday->clear(); + for (int i = 1; i <= days; ++i) { + ui->combo_birthday->addItem(QString::number(i)); + } + + // restore the day selection + ui->combo_birthday->setCurrentIndex(birthday_index); +} diff --git a/src/citra_qt/configure_system.h b/src/citra_qt/configure_system.h new file mode 100644 index 000000000..1f5577070 --- /dev/null +++ b/src/citra_qt/configure_system.h @@ -0,0 +1,38 @@ +// 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 ConfigureSystem; +} + +class ConfigureSystem : public QWidget +{ + Q_OBJECT + +public: + explicit ConfigureSystem(QWidget *parent = nullptr); + ~ConfigureSystem(); + + void applyConfiguration(); + void setConfiguration(bool emulation_running); + +public slots: + void updateBirthdayComboBox(int birthmonth_index); + +private: + void ReadSystemSettings(); + + std::unique_ptr<Ui::ConfigureSystem> ui; + bool enabled; + + std::u16string username; + int birthmonth, birthday; + int language_index; + int sound_index; +}; diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configure_system.ui new file mode 100644 index 000000000..6a906b61b --- /dev/null +++ b/src/citra_qt/configure_system.ui @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureSystem</class> + <widget class="QWidget" name="ConfigureSystem"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>360</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="group_system_settings"> + <property name="title"> + <string>System Settings</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_username"> + <property name="text"> + <string>Username</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="edit_username"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maxLength"> + <number>10</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_birthday"> + <property name="text"> + <string>Birthday</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> + <item> + <widget class="QComboBox" name="combo_birthmonth"> + <item> + <property name="text"> + <string>January</string> + </property> + </item> + <item> + <property name="text"> + <string>February</string> + </property> + </item> + <item> + <property name="text"> + <string>March</string> + </property> + </item> + <item> + <property name="text"> + <string>April</string> + </property> + </item> + <item> + <property name="text"> + <string>May</string> + </property> + </item> + <item> + <property name="text"> + <string>June</string> + </property> + </item> + <item> + <property name="text"> + <string>July</string> + </property> + </item> + <item> + <property name="text"> + <string>August</string> + </property> + </item> + <item> + <property name="text"> + <string>September</string> + </property> + </item> + <item> + <property name="text"> + <string>October</string> + </property> + </item> + <item> + <property name="text"> + <string>November</string> + </property> + </item> + <item> + <property name="text"> + <string>December</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="combo_birthday"/> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_language"> + <property name="text"> + <string>Language</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="combo_language"> + <item> + <property name="text"> + <string>Japanese (日本語)</string> + </property> + </item> + <item> + <property name="text"> + <string>English</string> + </property> + </item> + <item> + <property name="text"> + <string>French (français)</string> + </property> + </item> + <item> + <property name="text"> + <string>German (Deutsch)</string> + </property> + </item> + <item> + <property name="text"> + <string>Italian (italiano)</string> + </property> + </item> + <item> + <property name="text"> + <string>Spanish (español)</string> + </property> + </item> + <item> + <property name="text"> + <string>Simplified Chinese (简体中文)</string> + </property> + </item> + <item> + <property name="text"> + <string>Korean (한국어)</string> + </property> + </item> + <item> + <property name="text"> + <string>Dutch (Nederlands)</string> + </property> + </item> + <item> + <property name="text"> + <string>Portuguese (português)</string> + </property> + </item> + <item> + <property name="text"> + <string>Russian (Русский)</string> + </property> + </item> + <item> + <property name="text"> + <string>Traditional Chinese (正體中文)</string> + </property> + </item> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_sound"> + <property name="text"> + <string>Sound output mode</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="combo_sound"> + <item> + <property name="text"> + <string>Mono</string> + </property> + </item> + <item> + <property name="text"> + <string>Stereo</string> + </property> + </item> + <item> + <property name="text"> + <string>Surround</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_disable_info"> + <property name="text"> + <string>System settings are available only when game is not running.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </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> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> 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 585ac049a..17898f54b 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -151,8 +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; + /// Scale the coordinate system appropriately when dpi != 96. + qreal x_scale = 1.0, y_scale = 1.0; }; #endif @@ -222,15 +222,14 @@ 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); + // The units used by Microprofile for drawing are based in pixels on a 96 dpi display. + x_scale = qreal(painter.device()->logicalDpiX()) / 96.0; + y_scale = qreal(painter.device()->logicalDpiY()) / 96.0; painter.scale(x_scale, y_scale); painter.setBackground(Qt::black); @@ -241,7 +240,7 @@ void MicroProfileWidget::paintEvent(QPaintEvent* ev) { painter.setFont(font); mp_painter = &painter; - MicroProfileDraw(rect().width(), rect().height()); + MicroProfileDraw(rect().width() / x_scale, rect().height() / y_scale); mp_painter = nullptr; } diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 15484fae3..1910da3ac 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -120,11 +120,9 @@ void GameList::LoadInterfaceLayout() 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, - unsigned int recursion) -> bool { - + const auto callback = [this, recursion](unsigned* num_entries_out, + const std::string& directory, + const std::string& virtual_name) -> bool { std::string physical_name = directory + DIR_SEP + virtual_name; if (stop_processing) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 6239160bc..6fe5d7a3f 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()); @@ -508,7 +508,7 @@ void GMainWindow::ToggleWindowMode() { } void GMainWindow::OnConfigure() { - ConfigureDialog configureDialog(this); + ConfigureDialog configureDialog(this, emulation_running); auto result = configureDialog.exec(); if (result == QDialog::Accepted) { @@ -517,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; |