diff options
-rw-r--r-- | src/citra_qt/bootmanager.cpp | 14 | ||||
-rw-r--r-- | src/citra_qt/bootmanager.h | 21 | ||||
-rw-r--r-- | src/citra_qt/debugger/disassembler.cpp | 81 | ||||
-rw-r--r-- | src/citra_qt/debugger/disassembler.h | 9 | ||||
-rw-r--r-- | src/citra_qt/main.cpp | 68 | ||||
-rw-r--r-- | src/citra_qt/main.h | 24 |
6 files changed, 138 insertions, 79 deletions
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 1e902a8b6..4be410fe0 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -87,8 +87,8 @@ private: GRenderWindow* parent; }; -GRenderWindow::GRenderWindow(QWidget* parent, GMainWindow& main_window) : - QWidget(parent), main_window(main_window), keyboard_id(0) { +GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : + QWidget(parent), emu_thread(emu_thread), keyboard_id(0) { std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); @@ -129,7 +129,7 @@ void GRenderWindow::moveContext() // We need to move GL context to the swapping thread in Qt5 #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) // If the thread started running, move the GL Context to the new thread. Otherwise, move it back. - auto thread = QThread::currentThread() == qApp->thread() ? main_window.GetEmuThread() : qApp->thread(); + auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) ? emu_thread : qApp->thread(); child->context()->moveToThread(thread); #endif } @@ -272,3 +272,11 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) { setMinimumSize(minimal_size.first, minimal_size.second); } + +void GRenderWindow::OnEmulationStarted(EmuThread* emu_thread) { + this->emu_thread = emu_thread; +} + +void GRenderWindow::OnEmulationStopped() { + emu_thread = nullptr; +} diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index e9b3ea664..e57522187 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -22,6 +22,7 @@ class EmuThread : public QThread Q_OBJECT public: + EmuThread(GRenderWindow* render_window); /** * Start emulation (on new thread) @@ -50,15 +51,14 @@ public: bool IsRunning() { return running; } /** - * Shutdown (permanently stops) the emulation thread + * Requests for the emulation thread to stop running and shutdown emulation */ - void Shutdown() { stop_run = true; }; + void RequestShutdown() { + stop_run = true; + running = false; + }; private: - friend class GMainWindow; - - EmuThread(GRenderWindow* render_window); - bool exec_step; bool running; std::atomic<bool> stop_run; @@ -86,7 +86,7 @@ class GRenderWindow : public QWidget, public EmuWindow Q_OBJECT public: - GRenderWindow(QWidget* parent, GMainWindow& main_window); + GRenderWindow(QWidget* parent, EmuThread* emu_thread); // EmuWindow implementation void SwapBuffers() override; @@ -115,6 +115,9 @@ public: public slots: void moveContext(); // overridden + void OnEmulationStarted(EmuThread* emu_thread); + void OnEmulationStopped(); + private: void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) override; @@ -122,8 +125,8 @@ private: QByteArray geometry; - GMainWindow& main_window; - /// Device id of keyboard for use with KeyMap int keyboard_id; + + EmuThread* emu_thread; }; diff --git a/src/citra_qt/debugger/disassembler.cpp b/src/citra_qt/debugger/disassembler.cpp index f9423e1d6..6400bb856 100644 --- a/src/citra_qt/debugger/disassembler.cpp +++ b/src/citra_qt/debugger/disassembler.cpp @@ -4,7 +4,6 @@ #include "disassembler.h" -#include "../main.h" #include "../bootmanager.h" #include "../hotkeys.h" @@ -19,8 +18,8 @@ #include "core/arm/disassembler/arm_disasm.h" -DisassemblerModel::DisassemblerModel(QObject* parent) : QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0), selection(QModelIndex()) { - +DisassemblerModel::DisassemblerModel(QObject* parent) : + QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0), selection(QModelIndex()) { } int DisassemblerModel::columnCount(const QModelIndex& parent) const { @@ -159,35 +158,28 @@ void DisassemblerModel::SetNextInstruction(unsigned int address) { emit dataChanged(prev_index, prev_index); } -DisassemblerWidget::DisassemblerWidget(QWidget* parent, GMainWindow& main_window) : - QDockWidget(parent), main_window(main_window), base_addr(0) { +DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread* emu_thread) : + QDockWidget(parent), emu_thread(emu_thread), base_addr(0) { disasm_ui.setupUi(this); - model = new DisassemblerModel(this); - disasm_ui.treeView->setModel(model); - RegisterHotkey("Disassembler", "Start/Stop", QKeySequence(Qt::Key_F5), Qt::ApplicationShortcut); RegisterHotkey("Disassembler", "Step", QKeySequence(Qt::Key_F10), Qt::ApplicationShortcut); RegisterHotkey("Disassembler", "Step into", QKeySequence(Qt::Key_F11), Qt::ApplicationShortcut); RegisterHotkey("Disassembler", "Set Breakpoint", QKeySequence(Qt::Key_F9), Qt::ApplicationShortcut); - connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint())); connect(disasm_ui.button_step, SIGNAL(clicked()), this, SLOT(OnStep())); connect(disasm_ui.button_pause, SIGNAL(clicked()), this, SLOT(OnPause())); connect(disasm_ui.button_continue, SIGNAL(clicked()), this, SLOT(OnContinue())); - connect(disasm_ui.treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), - model, SLOT(OnSelectionChanged(const QModelIndex&))); - connect(GetHotkey("Disassembler", "Start/Stop", this), SIGNAL(activated()), this, SLOT(OnToggleStartStop())); connect(GetHotkey("Disassembler", "Step", this), SIGNAL(activated()), this, SLOT(OnStep())); connect(GetHotkey("Disassembler", "Step into", this), SIGNAL(activated()), this, SLOT(OnStepInto())); - connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model, SLOT(OnSetOrUnsetBreakpoint())); + + setEnabled(false); } -void DisassemblerWidget::Init() -{ +void DisassemblerWidget::Init() { model->ParseFromAddress(Core::g_app_core->GetPC()); disasm_ui.treeView->resizeColumnToContents(0); @@ -199,25 +191,21 @@ void DisassemblerWidget::Init() disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } -void DisassemblerWidget::OnContinue() -{ - main_window.GetEmuThread()->SetRunning(true); +void DisassemblerWidget::OnContinue() { + emu_thread->SetRunning(true); } -void DisassemblerWidget::OnStep() -{ +void DisassemblerWidget::OnStep() { OnStepInto(); // change later } -void DisassemblerWidget::OnStepInto() -{ - main_window.GetEmuThread()->SetRunning(false); - main_window.GetEmuThread()->ExecStep(); +void DisassemblerWidget::OnStepInto() { + emu_thread->SetRunning(false); + emu_thread->ExecStep(); } -void DisassemblerWidget::OnPause() -{ - main_window.GetEmuThread()->SetRunning(false); +void DisassemblerWidget::OnPause() { + emu_thread->SetRunning(false); // TODO: By now, the CPU might not have actually stopped... if (Core::g_app_core) { @@ -225,17 +213,15 @@ void DisassemblerWidget::OnPause() } } -void DisassemblerWidget::OnToggleStartStop() -{ - main_window.GetEmuThread()->SetRunning(!main_window.GetEmuThread()->IsRunning()); +void DisassemblerWidget::OnToggleStartStop() { + emu_thread->SetRunning(!emu_thread->IsRunning()); } -void DisassemblerWidget::OnDebugModeEntered() -{ +void DisassemblerWidget::OnDebugModeEntered() { ARMword next_instr = Core::g_app_core->GetPC(); if (model->GetBreakPoints().IsAddressBreakPoint(next_instr)) - main_window.GetEmuThread()->SetRunning(false); + emu_thread->SetRunning(false); model->SetNextInstruction(next_instr); @@ -244,16 +230,35 @@ void DisassemblerWidget::OnDebugModeEntered() disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } -void DisassemblerWidget::OnDebugModeLeft() -{ - +void DisassemblerWidget::OnDebugModeLeft() { } -int DisassemblerWidget::SelectedRow() -{ +int DisassemblerWidget::SelectedRow() { QModelIndex index = disasm_ui.treeView->selectionModel()->currentIndex(); if (!index.isValid()) return -1; return disasm_ui.treeView->selectionModel()->currentIndex().row(); } + +void DisassemblerWidget::OnEmulationStarted(EmuThread* emu_thread) { + this->emu_thread = emu_thread; + + model = new DisassemblerModel(this); + disasm_ui.treeView->setModel(model); + + connect(disasm_ui.treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + model, SLOT(OnSelectionChanged(const QModelIndex&))); + connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint())); + connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model, SLOT(OnSetOrUnsetBreakpoint())); + + Init(); + setEnabled(true); +} + +void DisassemblerWidget::OnEmulationStopped() { + disasm_ui.treeView->setModel(nullptr); + delete model; + emu_thread = nullptr; + setEnabled(false); +} diff --git a/src/citra_qt/debugger/disassembler.h b/src/citra_qt/debugger/disassembler.h index d9e32dbdf..b771e95b7 100644 --- a/src/citra_qt/debugger/disassembler.h +++ b/src/citra_qt/debugger/disassembler.h @@ -13,7 +13,7 @@ #include "common/break_points.h" class QAction; -class GMainWindow; +class EmuThread; class DisassemblerModel : public QAbstractListModel { @@ -51,7 +51,7 @@ class DisassemblerWidget : public QDockWidget Q_OBJECT public: - DisassemblerWidget(QWidget* parent, GMainWindow& main_window); + DisassemblerWidget(QWidget* parent, EmuThread* emu_thread); void Init(); @@ -65,6 +65,9 @@ public slots: void OnDebugModeEntered(); void OnDebugModeLeft(); + void OnEmulationStarted(EmuThread* emu_thread); + void OnEmulationStopped(); + private: // returns -1 if no row is selected int SelectedRow(); @@ -75,5 +78,5 @@ private: u32 base_addr; - GMainWindow& main_window; + EmuThread* emu_thread; }; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index dd180baa4..7de2bf8ba 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -15,6 +15,7 @@ #include "common/logging/log.h" #include "common/logging/backend.h" #include "common/logging/filter.h" +#include "common/make_unique.h" #include "common/platform.h" #include "common/scope_exit.h" @@ -55,14 +56,14 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) ui.setupUi(this); statusBar()->hide(); - render_window = new GRenderWindow(this, *this); + render_window = new GRenderWindow(this, emu_thread.get()); render_window->hide(); profilerWidget = new ProfilerWidget(this); addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); profilerWidget->hide(); - disasmWidget = new DisassemblerWidget(this, *this); + disasmWidget = new DisassemblerWidget(this, emu_thread.get()); addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); disasmWidget->hide(); @@ -138,14 +139,10 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); connect(ui.action_Hotkeys, SIGNAL(triggered()), this, SLOT(OnOpenHotkeysDialog())); - // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views before the CPU continues - connect(emu_thread, SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread, SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread, SIGNAL(DebugModeEntered()), callstackWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - - connect(emu_thread, SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); - connect(emu_thread, SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); - connect(emu_thread, SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(EmulationStarted(EmuThread*)), disasmWidget, SLOT(OnEmulationStarted(EmuThread*))); + connect(this, SIGNAL(EmulationStopped()), disasmWidget, SLOT(OnEmulationStopped())); + connect(this, SIGNAL(EmulationStarted(EmuThread*)), render_window, SLOT(OnEmulationStarted(EmuThread*))); + connect(this, SIGNAL(EmulationStopped()), render_window, SLOT(OnEmulationStopped())); // Setup hotkeys RegisterHotkey("Main Window", "Load File", QKeySequence::Open); @@ -199,35 +196,62 @@ void GMainWindow::OnDisplayTitleBars(bool show) void GMainWindow::BootGame(std::string filename) { LOG_INFO(Frontend, "Citra starting...\n"); + // Initialize the core emulation System::Init(render_window); - // Load a game or die... + // Load the game if (Loader::ResultStatus::Success != Loader::LoadFile(filename)) { LOG_CRITICAL(Frontend, "Failed to load ROM!"); + System::Shutdown(); + return; } - disasmWidget->Init(); - registersWidget->OnDebugModeEntered(); - callstackWidget->OnDebugModeEntered(); - - emu_thread = new EmuThread(render_window); + // Create and start the emulation thread + emu_thread = Common::make_unique<EmuThread>(render_window); + emit EmulationStarted(emu_thread.get()); emu_thread->start(); + // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views before the CPU continues + connect(emu_thread.get(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); + connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); + connect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); + connect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); + connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); + connect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); + + // Update the GUI + registersWidget->OnDebugModeEntered(); + callstackWidget->OnDebugModeEntered(); render_window->show(); + OnStartGame(); } void GMainWindow::ShutdownGame() { - // Shutdown the emulation thread and wait for it to complete - emu_thread->SetRunning(false); - emu_thread->Shutdown(); - emu_thread->wait(); - delete emu_thread; - emu_thread = nullptr; + // Shutdown the emulation thread + emu_thread->RequestShutdown(); + + // Disconnect signals that are attached to the current emulation thread + disconnect(emu_thread.get(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered())); + disconnect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered())); + disconnect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget, SLOT(OnDebugModeEntered())); + disconnect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft())); + disconnect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft())); + disconnect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft())); // Release emu threads from any breakpoints + // This belongs after RequestShutdown() and before wait() because if emulation stops on a GPU + // breakpoint after (or before) RequestShutdown() is called, the emulation would never be able + // to continue out to the main loop and terminate. Thus wait() would hang forever. + // TODO(bunnei): This function is not thread safe, but it's being used as if it were Pica::g_debug_context->ClearBreakpoints(); + emit EmulationStopped(); + + // Wait for emulation thread to complete and delete it + emu_thread->wait(); + emu_thread = nullptr; + // Shutdown the core emulation System::Shutdown(); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 1821ae35f..3e29534fb 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -5,6 +5,7 @@ #ifndef _CITRA_QT_MAIN_HXX_ #define _CITRA_QT_MAIN_HXX_ +#include <memory> #include <QMainWindow> #include "ui_main.h" @@ -35,9 +36,23 @@ public: GMainWindow(); ~GMainWindow(); - EmuThread* GetEmuThread() { - return emu_thread; - } +signals: + + /** + * Signal that is emitted when a new EmuThread has been created and an emulation session is + * about to start. At this time, the core system emulation has been initialized, and all + * emulation handles and memory should be valid. + * + * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to + * access/change emulation state). + */ + void EmulationStarting(EmuThread* emu_thread); + + /** + * Signal that is emitted when emulation is about to stop. At this time, the EmuThread and core + * system emulation handles and memory are still valid, but are about become invalid. + */ + void EmulationStopping(); private: void BootGame(std::string filename); @@ -60,7 +75,8 @@ private: Ui::MainWindow ui; GRenderWindow* render_window; - EmuThread* emu_thread; + + std::unique_ptr<EmuThread> emu_thread; ProfilerWidget* profilerWidget; DisassemblerWidget* disasmWidget; |