diff options
Diffstat (limited to 'src/citra_qt')
| -rw-r--r-- | src/citra_qt/bootmanager.cpp | 103 | ||||
| -rw-r--r-- | src/citra_qt/bootmanager.h | 68 | ||||
| -rw-r--r-- | src/citra_qt/debugger/disassembler.cpp | 81 | ||||
| -rw-r--r-- | src/citra_qt/debugger/disassembler.h | 7 | ||||
| -rw-r--r-- | src/citra_qt/debugger/graphics_breakpoints_p.h | 2 | ||||
| -rw-r--r-- | src/citra_qt/debugger/profiler.cpp | 4 | ||||
| -rw-r--r-- | src/citra_qt/debugger/profiler.h | 2 | ||||
| -rw-r--r-- | src/citra_qt/debugger/registers.cpp | 33 | ||||
| -rw-r--r-- | src/citra_qt/debugger/registers.h | 4 | ||||
| -rw-r--r-- | src/citra_qt/main.cpp | 125 | ||||
| -rw-r--r-- | src/citra_qt/main.h | 23 | ||||
| -rw-r--r-- | src/citra_qt/main.ui | 3 | ||||
| -rw-r--r-- | src/citra_qt/util/spinbox.cpp | 3 |
13 files changed, 241 insertions, 217 deletions
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index b81bd6167..a7f949411 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | 10 | ||
| 11 | #include "common/common.h" | 11 | #include "common/common.h" |
| 12 | #include "bootmanager.h" | 12 | #include "bootmanager.h" |
| 13 | #include "main.h" | ||
| 13 | 14 | ||
| 14 | #include "core/core.h" | 15 | #include "core/core.h" |
| 15 | #include "core/settings.h" | 16 | #include "core/settings.h" |
| @@ -27,43 +28,33 @@ | |||
| 27 | #define COPYRIGHT "Copyright (C) 2013-2014 Citra Team" | 28 | #define COPYRIGHT "Copyright (C) 2013-2014 Citra Team" |
| 28 | 29 | ||
| 29 | EmuThread::EmuThread(GRenderWindow* render_window) : | 30 | EmuThread::EmuThread(GRenderWindow* render_window) : |
| 30 | filename(""), exec_cpu_step(false), cpu_running(false), | 31 | exec_step(false), running(false), stop_run(false), render_window(render_window) { |
| 31 | stop_run(false), render_window(render_window) | ||
| 32 | { | ||
| 33 | } | ||
| 34 | 32 | ||
| 35 | void EmuThread::SetFilename(std::string filename) | 33 | connect(this, SIGNAL(started()), render_window, SLOT(moveContext())); |
| 36 | { | ||
| 37 | this->filename = filename; | ||
| 38 | } | 34 | } |
| 39 | 35 | ||
| 40 | void EmuThread::run() | 36 | void EmuThread::run() { |
| 41 | { | ||
| 42 | stop_run = false; | 37 | stop_run = false; |
| 43 | 38 | ||
| 44 | // holds whether the cpu was running during the last iteration, | 39 | // holds whether the cpu was running during the last iteration, |
| 45 | // so that the DebugModeLeft signal can be emitted before the | 40 | // so that the DebugModeLeft signal can be emitted before the |
| 46 | // next execution step | 41 | // next execution step |
| 47 | bool was_active = false; | 42 | bool was_active = false; |
| 48 | while (!stop_run) | 43 | while (!stop_run) { |
| 49 | { | 44 | if (running) { |
| 50 | if (cpu_running) | ||
| 51 | { | ||
| 52 | if (!was_active) | 45 | if (!was_active) |
| 53 | emit DebugModeLeft(); | 46 | emit DebugModeLeft(); |
| 54 | 47 | ||
| 55 | Core::RunLoop(); | 48 | Core::RunLoop(); |
| 56 | 49 | ||
| 57 | was_active = cpu_running || exec_cpu_step; | 50 | was_active = running || exec_step; |
| 58 | if (!was_active) | 51 | if (!was_active && !stop_run) |
| 59 | emit DebugModeEntered(); | 52 | emit DebugModeEntered(); |
| 60 | } | 53 | } else if (exec_step) { |
| 61 | else if (exec_cpu_step) | ||
| 62 | { | ||
| 63 | if (!was_active) | 54 | if (!was_active) |
| 64 | emit DebugModeLeft(); | 55 | emit DebugModeLeft(); |
| 65 | 56 | ||
| 66 | exec_cpu_step = false; | 57 | exec_step = false; |
| 67 | Core::SingleStep(); | 58 | Core::SingleStep(); |
| 68 | emit DebugModeEntered(); | 59 | emit DebugModeEntered(); |
| 69 | yieldCurrentThread(); | 60 | yieldCurrentThread(); |
| @@ -71,47 +62,10 @@ void EmuThread::run() | |||
| 71 | was_active = false; | 62 | was_active = false; |
| 72 | } | 63 | } |
| 73 | } | 64 | } |
| 74 | render_window->moveContext(); | ||
| 75 | |||
| 76 | Core::Stop(); | ||
| 77 | } | ||
| 78 | |||
| 79 | void EmuThread::Stop() | ||
| 80 | { | ||
| 81 | if (!isRunning()) | ||
| 82 | { | ||
| 83 | LOG_WARNING(Frontend, "EmuThread::Stop called while emu thread wasn't running, returning..."); | ||
| 84 | return; | ||
| 85 | } | ||
| 86 | stop_run = true; | ||
| 87 | 65 | ||
| 88 | // Release emu threads from any breakpoints, so that this doesn't hang forever. | 66 | render_window->moveContext(); |
| 89 | Pica::g_debug_context->ClearBreakpoints(); | ||
| 90 | |||
| 91 | //core::g_state = core::SYS_DIE; | ||
| 92 | |||
| 93 | // TODO: Waiting here is just a bad workaround for retarded shutdown logic. | ||
| 94 | wait(1000); | ||
| 95 | if (isRunning()) | ||
| 96 | { | ||
| 97 | LOG_WARNING(Frontend, "EmuThread still running, terminating..."); | ||
| 98 | quit(); | ||
| 99 | |||
| 100 | // TODO: Waiting 50 seconds can be necessary if the logging subsystem has a lot of spam | ||
| 101 | // queued... This should be fixed. | ||
| 102 | wait(50000); | ||
| 103 | if (isRunning()) | ||
| 104 | { | ||
| 105 | LOG_CRITICAL(Frontend, "EmuThread STILL running, something is wrong here..."); | ||
| 106 | terminate(); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | LOG_INFO(Frontend, "EmuThread stopped"); | ||
| 110 | |||
| 111 | System::Shutdown(); | ||
| 112 | } | 67 | } |
| 113 | 68 | ||
| 114 | |||
| 115 | // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL context. | 69 | // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL context. |
| 116 | // The corresponding functionality is handled in EmuThread instead | 70 | // The corresponding functionality is handled in EmuThread instead |
| 117 | class GGLWidgetInternal : public QGLWidget | 71 | class GGLWidgetInternal : public QGLWidget |
| @@ -133,13 +87,9 @@ private: | |||
| 133 | GRenderWindow* parent; | 87 | GRenderWindow* parent; |
| 134 | }; | 88 | }; |
| 135 | 89 | ||
| 136 | EmuThread& GRenderWindow::GetEmuThread() | 90 | GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : |
| 137 | { | 91 | QWidget(parent), emu_thread(emu_thread), keyboard_id(0) { |
| 138 | return emu_thread; | ||
| 139 | } | ||
| 140 | 92 | ||
| 141 | GRenderWindow::GRenderWindow(QWidget* parent) : QWidget(parent), emu_thread(this), keyboard_id(0) | ||
| 142 | { | ||
| 143 | std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); | 93 | std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); |
| 144 | setWindowTitle(QString::fromStdString(window_title)); | 94 | setWindowTitle(QString::fromStdString(window_title)); |
| 145 | 95 | ||
| @@ -160,7 +110,6 @@ GRenderWindow::GRenderWindow(QWidget* parent) : QWidget(parent), emu_thread(this | |||
| 160 | layout->addWidget(child); | 110 | layout->addWidget(child); |
| 161 | layout->setMargin(0); | 111 | layout->setMargin(0); |
| 162 | setLayout(layout); | 112 | setLayout(layout); |
| 163 | connect(&emu_thread, SIGNAL(started()), this, SLOT(moveContext())); | ||
| 164 | 113 | ||
| 165 | OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); | 114 | OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); |
| 166 | 115 | ||
| @@ -180,29 +129,17 @@ void GRenderWindow::moveContext() | |||
| 180 | // We need to move GL context to the swapping thread in Qt5 | 129 | // We need to move GL context to the swapping thread in Qt5 |
| 181 | #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) | 130 | #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) |
| 182 | // If the thread started running, move the GL Context to the new thread. Otherwise, move it back. | 131 | // If the thread started running, move the GL Context to the new thread. Otherwise, move it back. |
| 183 | child->context()->moveToThread((QThread::currentThread() == qApp->thread()) ? &emu_thread : qApp->thread()); | 132 | auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) ? emu_thread : qApp->thread(); |
| 133 | child->context()->moveToThread(thread); | ||
| 184 | #endif | 134 | #endif |
| 185 | } | 135 | } |
| 186 | 136 | ||
| 187 | GRenderWindow::~GRenderWindow() | ||
| 188 | { | ||
| 189 | if (emu_thread.isRunning()) | ||
| 190 | emu_thread.Stop(); | ||
| 191 | } | ||
| 192 | |||
| 193 | void GRenderWindow::SwapBuffers() | 137 | void GRenderWindow::SwapBuffers() |
| 194 | { | 138 | { |
| 195 | // MakeCurrent is already called in renderer_opengl | 139 | // MakeCurrent is already called in renderer_opengl |
| 196 | child->swapBuffers(); | 140 | child->swapBuffers(); |
| 197 | } | 141 | } |
| 198 | 142 | ||
| 199 | void GRenderWindow::closeEvent(QCloseEvent* event) | ||
| 200 | { | ||
| 201 | if (emu_thread.isRunning()) | ||
| 202 | emu_thread.Stop(); | ||
| 203 | QWidget::closeEvent(event); | ||
| 204 | } | ||
| 205 | |||
| 206 | void GRenderWindow::MakeCurrent() | 143 | void GRenderWindow::MakeCurrent() |
| 207 | { | 144 | { |
| 208 | child->makeCurrent(); | 145 | child->makeCurrent(); |
| @@ -288,7 +225,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent *event) | |||
| 288 | void GRenderWindow::mouseMoveEvent(QMouseEvent *event) | 225 | void GRenderWindow::mouseMoveEvent(QMouseEvent *event) |
| 289 | { | 226 | { |
| 290 | auto pos = event->pos(); | 227 | auto pos = event->pos(); |
| 291 | this->TouchMoved(static_cast<unsigned>(pos.x()), static_cast<unsigned>(pos.y())); | 228 | this->TouchMoved(static_cast<unsigned>(std::max(pos.x(), 0)), static_cast<unsigned>(std::max(pos.y(), 0))); |
| 292 | } | 229 | } |
| 293 | 230 | ||
| 294 | void GRenderWindow::mouseReleaseEvent(QMouseEvent *event) | 231 | void GRenderWindow::mouseReleaseEvent(QMouseEvent *event) |
| @@ -335,3 +272,11 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) | |||
| 335 | void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) { | 272 | void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) { |
| 336 | setMinimumSize(minimal_size.first, minimal_size.second); | 273 | setMinimumSize(minimal_size.first, minimal_size.second); |
| 337 | } | 274 | } |
| 275 | |||
| 276 | void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { | ||
| 277 | this->emu_thread = emu_thread; | ||
| 278 | } | ||
| 279 | |||
| 280 | void GRenderWindow::OnEmulationStopping() { | ||
| 281 | emu_thread = nullptr; | ||
| 282 | } | ||
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 288da45a1..715faf2d7 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h | |||
| @@ -9,72 +9,58 @@ | |||
| 9 | 9 | ||
| 10 | #include "common/common.h" | 10 | #include "common/common.h" |
| 11 | #include "common/emu_window.h" | 11 | #include "common/emu_window.h" |
| 12 | #include "common/thread.h" | ||
| 12 | 13 | ||
| 13 | class QScreen; | 14 | class QScreen; |
| 14 | class QKeyEvent; | 15 | class QKeyEvent; |
| 15 | 16 | ||
| 16 | class GRenderWindow; | 17 | class GRenderWindow; |
| 18 | class GMainWindow; | ||
| 17 | 19 | ||
| 18 | class EmuThread : public QThread | 20 | class EmuThread : public QThread |
| 19 | { | 21 | { |
| 20 | Q_OBJECT | 22 | Q_OBJECT |
| 21 | 23 | ||
| 22 | public: | 24 | public: |
| 23 | /** | 25 | EmuThread(GRenderWindow* render_window); |
| 24 | * Set image filename | ||
| 25 | * | ||
| 26 | * @param filename | ||
| 27 | * @warning Only call when not running! | ||
| 28 | */ | ||
| 29 | void SetFilename(std::string filename); | ||
| 30 | 26 | ||
| 31 | /** | 27 | /** |
| 32 | * Start emulation (on new thread) | 28 | * Start emulation (on new thread) |
| 33 | * | ||
| 34 | * @warning Only call when not running! | 29 | * @warning Only call when not running! |
| 35 | */ | 30 | */ |
| 36 | void run() override; | 31 | void run() override; |
| 37 | 32 | ||
| 38 | /** | 33 | /** |
| 39 | * Allow the CPU to process a single instruction (if cpu is not running) | 34 | * Steps the emulation thread by a single CPU instruction (if the CPU is not already running) |
| 40 | * | ||
| 41 | * @note This function is thread-safe | 35 | * @note This function is thread-safe |
| 42 | */ | 36 | */ |
| 43 | void ExecStep() { exec_cpu_step = true; } | 37 | void ExecStep() { exec_step = true; } |
| 44 | 38 | ||
| 45 | /** | 39 | /** |
| 46 | * Allow the CPU to continue processing instructions without interruption | 40 | * Sets whether the emulation thread is running or not |
| 47 | * | 41 | * @param running Boolean value, set the emulation thread to running if true |
| 48 | * @note This function is thread-safe | 42 | * @note This function is thread-safe |
| 49 | */ | 43 | */ |
| 50 | void SetCpuRunning(bool running) { cpu_running = running; } | 44 | void SetRunning(bool running) { this->running = running; } |
| 51 | 45 | ||
| 52 | /** | 46 | /** |
| 53 | * Allow the CPU to continue processing instructions without interruption | 47 | * Check if the emulation thread is running or not |
| 54 | * | 48 | * @return True if the emulation thread is running, otherwise false |
| 55 | * @note This function is thread-safe | 49 | * @note This function is thread-safe |
| 56 | */ | 50 | */ |
| 57 | bool IsCpuRunning() { return cpu_running; } | 51 | bool IsRunning() { return running; } |
| 58 | |||
| 59 | 52 | ||
| 60 | public slots: | ||
| 61 | /** | 53 | /** |
| 62 | * Stop emulation and wait for the thread to finish. | 54 | * Requests for the emulation thread to stop running |
| 63 | * | ||
| 64 | * @details: This function will wait a second for the thread to finish; if it hasn't finished until then, we'll terminate() it and wait another second, hoping that it will be terminated by then. | ||
| 65 | * @note: This function is thread-safe. | ||
| 66 | */ | 55 | */ |
| 67 | void Stop(); | 56 | void RequestStop() { |
| 57 | stop_run = true; | ||
| 58 | running = false; | ||
| 59 | }; | ||
| 68 | 60 | ||
| 69 | private: | 61 | private: |
| 70 | friend class GRenderWindow; | 62 | bool exec_step; |
| 71 | 63 | bool running; | |
| 72 | EmuThread(GRenderWindow* render_window); | ||
| 73 | |||
| 74 | std::string filename; | ||
| 75 | |||
| 76 | bool exec_cpu_step; | ||
| 77 | bool cpu_running; | ||
| 78 | std::atomic<bool> stop_run; | 64 | std::atomic<bool> stop_run; |
| 79 | 65 | ||
| 80 | GRenderWindow* render_window; | 66 | GRenderWindow* render_window; |
| @@ -100,10 +86,7 @@ class GRenderWindow : public QWidget, public EmuWindow | |||
| 100 | Q_OBJECT | 86 | Q_OBJECT |
| 101 | 87 | ||
| 102 | public: | 88 | public: |
| 103 | GRenderWindow(QWidget* parent = NULL); | 89 | GRenderWindow(QWidget* parent, EmuThread* emu_thread); |
| 104 | ~GRenderWindow(); | ||
| 105 | |||
| 106 | void closeEvent(QCloseEvent*) override; | ||
| 107 | 90 | ||
| 108 | // EmuWindow implementation | 91 | // EmuWindow implementation |
| 109 | void SwapBuffers() override; | 92 | void SwapBuffers() override; |
| @@ -116,8 +99,6 @@ public: | |||
| 116 | void restoreGeometry(const QByteArray& geometry); // overridden | 99 | void restoreGeometry(const QByteArray& geometry); // overridden |
| 117 | QByteArray saveGeometry(); // overridden | 100 | QByteArray saveGeometry(); // overridden |
| 118 | 101 | ||
| 119 | EmuThread& GetEmuThread(); | ||
| 120 | |||
| 121 | void keyPressEvent(QKeyEvent* event) override; | 102 | void keyPressEvent(QKeyEvent* event) override; |
| 122 | void keyReleaseEvent(QKeyEvent* event) override; | 103 | void keyReleaseEvent(QKeyEvent* event) override; |
| 123 | 104 | ||
| @@ -134,15 +115,18 @@ public: | |||
| 134 | public slots: | 115 | public slots: |
| 135 | void moveContext(); // overridden | 116 | void moveContext(); // overridden |
| 136 | 117 | ||
| 118 | void OnEmulationStarting(EmuThread* emu_thread); | ||
| 119 | void OnEmulationStopping(); | ||
| 120 | |||
| 137 | private: | 121 | private: |
| 138 | void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) override; | 122 | void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) override; |
| 139 | 123 | ||
| 140 | QGLWidget* child; | 124 | QGLWidget* child; |
| 141 | 125 | ||
| 142 | EmuThread emu_thread; | ||
| 143 | |||
| 144 | QByteArray geometry; | 126 | QByteArray geometry; |
| 145 | 127 | ||
| 146 | /// Device id of keyboard for use with KeyMap | 128 | /// Device id of keyboard for use with KeyMap |
| 147 | int keyboard_id; | 129 | int keyboard_id; |
| 130 | |||
| 131 | EmuThread* emu_thread; | ||
| 148 | }; | 132 | }; |
diff --git a/src/citra_qt/debugger/disassembler.cpp b/src/citra_qt/debugger/disassembler.cpp index f620687ae..08c6b49bd 100644 --- a/src/citra_qt/debugger/disassembler.cpp +++ b/src/citra_qt/debugger/disassembler.cpp | |||
| @@ -18,8 +18,8 @@ | |||
| 18 | #include "core/arm/disassembler/arm_disasm.h" | 18 | #include "core/arm/disassembler/arm_disasm.h" |
| 19 | 19 | ||
| 20 | 20 | ||
| 21 | DisassemblerModel::DisassemblerModel(QObject* parent) : QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0), selection(QModelIndex()) { | 21 | DisassemblerModel::DisassemblerModel(QObject* parent) : |
| 22 | 22 | QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0), selection(QModelIndex()) { | |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | int DisassemblerModel::columnCount(const QModelIndex& parent) const { | 25 | int DisassemblerModel::columnCount(const QModelIndex& parent) const { |
| @@ -158,34 +158,28 @@ void DisassemblerModel::SetNextInstruction(unsigned int address) { | |||
| 158 | emit dataChanged(prev_index, prev_index); | 158 | emit dataChanged(prev_index, prev_index); |
| 159 | } | 159 | } |
| 160 | 160 | ||
| 161 | DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread& emu_thread) : QDockWidget(parent), base_addr(0), emu_thread(emu_thread) | 161 | DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread* emu_thread) : |
| 162 | { | 162 | QDockWidget(parent), emu_thread(emu_thread), base_addr(0) { |
| 163 | disasm_ui.setupUi(this); | ||
| 164 | 163 | ||
| 165 | model = new DisassemblerModel(this); | 164 | disasm_ui.setupUi(this); |
| 166 | disasm_ui.treeView->setModel(model); | ||
| 167 | 165 | ||
| 168 | RegisterHotkey("Disassembler", "Start/Stop", QKeySequence(Qt::Key_F5), Qt::ApplicationShortcut); | 166 | RegisterHotkey("Disassembler", "Start/Stop", QKeySequence(Qt::Key_F5), Qt::ApplicationShortcut); |
| 169 | RegisterHotkey("Disassembler", "Step", QKeySequence(Qt::Key_F10), Qt::ApplicationShortcut); | 167 | RegisterHotkey("Disassembler", "Step", QKeySequence(Qt::Key_F10), Qt::ApplicationShortcut); |
| 170 | RegisterHotkey("Disassembler", "Step into", QKeySequence(Qt::Key_F11), Qt::ApplicationShortcut); | 168 | RegisterHotkey("Disassembler", "Step into", QKeySequence(Qt::Key_F11), Qt::ApplicationShortcut); |
| 171 | RegisterHotkey("Disassembler", "Set Breakpoint", QKeySequence(Qt::Key_F9), Qt::ApplicationShortcut); | 169 | RegisterHotkey("Disassembler", "Set Breakpoint", QKeySequence(Qt::Key_F9), Qt::ApplicationShortcut); |
| 172 | 170 | ||
| 173 | connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint())); | ||
| 174 | connect(disasm_ui.button_step, SIGNAL(clicked()), this, SLOT(OnStep())); | 171 | connect(disasm_ui.button_step, SIGNAL(clicked()), this, SLOT(OnStep())); |
| 175 | connect(disasm_ui.button_pause, SIGNAL(clicked()), this, SLOT(OnPause())); | 172 | connect(disasm_ui.button_pause, SIGNAL(clicked()), this, SLOT(OnPause())); |
| 176 | connect(disasm_ui.button_continue, SIGNAL(clicked()), this, SLOT(OnContinue())); | 173 | connect(disasm_ui.button_continue, SIGNAL(clicked()), this, SLOT(OnContinue())); |
| 177 | 174 | ||
| 178 | connect(disasm_ui.treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), | ||
| 179 | model, SLOT(OnSelectionChanged(const QModelIndex&))); | ||
| 180 | |||
| 181 | connect(GetHotkey("Disassembler", "Start/Stop", this), SIGNAL(activated()), this, SLOT(OnToggleStartStop())); | 175 | connect(GetHotkey("Disassembler", "Start/Stop", this), SIGNAL(activated()), this, SLOT(OnToggleStartStop())); |
| 182 | connect(GetHotkey("Disassembler", "Step", this), SIGNAL(activated()), this, SLOT(OnStep())); | 176 | connect(GetHotkey("Disassembler", "Step", this), SIGNAL(activated()), this, SLOT(OnStep())); |
| 183 | connect(GetHotkey("Disassembler", "Step into", this), SIGNAL(activated()), this, SLOT(OnStepInto())); | 177 | connect(GetHotkey("Disassembler", "Step into", this), SIGNAL(activated()), this, SLOT(OnStepInto())); |
| 184 | connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model, SLOT(OnSetOrUnsetBreakpoint())); | 178 | |
| 179 | setEnabled(false); | ||
| 185 | } | 180 | } |
| 186 | 181 | ||
| 187 | void DisassemblerWidget::Init() | 182 | void DisassemblerWidget::Init() { |
| 188 | { | ||
| 189 | model->ParseFromAddress(Core::g_app_core->GetPC()); | 183 | model->ParseFromAddress(Core::g_app_core->GetPC()); |
| 190 | 184 | ||
| 191 | disasm_ui.treeView->resizeColumnToContents(0); | 185 | disasm_ui.treeView->resizeColumnToContents(0); |
| @@ -197,25 +191,21 @@ void DisassemblerWidget::Init() | |||
| 197 | disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); | 191 | disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); |
| 198 | } | 192 | } |
| 199 | 193 | ||
| 200 | void DisassemblerWidget::OnContinue() | 194 | void DisassemblerWidget::OnContinue() { |
| 201 | { | 195 | emu_thread->SetRunning(true); |
| 202 | emu_thread.SetCpuRunning(true); | ||
| 203 | } | 196 | } |
| 204 | 197 | ||
| 205 | void DisassemblerWidget::OnStep() | 198 | void DisassemblerWidget::OnStep() { |
| 206 | { | ||
| 207 | OnStepInto(); // change later | 199 | OnStepInto(); // change later |
| 208 | } | 200 | } |
| 209 | 201 | ||
| 210 | void DisassemblerWidget::OnStepInto() | 202 | void DisassemblerWidget::OnStepInto() { |
| 211 | { | 203 | emu_thread->SetRunning(false); |
| 212 | emu_thread.SetCpuRunning(false); | 204 | emu_thread->ExecStep(); |
| 213 | emu_thread.ExecStep(); | ||
| 214 | } | 205 | } |
| 215 | 206 | ||
| 216 | void DisassemblerWidget::OnPause() | 207 | void DisassemblerWidget::OnPause() { |
| 217 | { | 208 | emu_thread->SetRunning(false); |
| 218 | emu_thread.SetCpuRunning(false); | ||
| 219 | 209 | ||
| 220 | // TODO: By now, the CPU might not have actually stopped... | 210 | // TODO: By now, the CPU might not have actually stopped... |
| 221 | if (Core::g_app_core) { | 211 | if (Core::g_app_core) { |
| @@ -223,17 +213,15 @@ void DisassemblerWidget::OnPause() | |||
| 223 | } | 213 | } |
| 224 | } | 214 | } |
| 225 | 215 | ||
| 226 | void DisassemblerWidget::OnToggleStartStop() | 216 | void DisassemblerWidget::OnToggleStartStop() { |
| 227 | { | 217 | emu_thread->SetRunning(!emu_thread->IsRunning()); |
| 228 | emu_thread.SetCpuRunning(!emu_thread.IsCpuRunning()); | ||
| 229 | } | 218 | } |
| 230 | 219 | ||
| 231 | void DisassemblerWidget::OnDebugModeEntered() | 220 | void DisassemblerWidget::OnDebugModeEntered() { |
| 232 | { | ||
| 233 | ARMword next_instr = Core::g_app_core->GetPC(); | 221 | ARMword next_instr = Core::g_app_core->GetPC(); |
| 234 | 222 | ||
| 235 | if (model->GetBreakPoints().IsAddressBreakPoint(next_instr)) | 223 | if (model->GetBreakPoints().IsAddressBreakPoint(next_instr)) |
| 236 | emu_thread.SetCpuRunning(false); | 224 | emu_thread->SetRunning(false); |
| 237 | 225 | ||
| 238 | model->SetNextInstruction(next_instr); | 226 | model->SetNextInstruction(next_instr); |
| 239 | 227 | ||
| @@ -242,16 +230,35 @@ void DisassemblerWidget::OnDebugModeEntered() | |||
| 242 | disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); | 230 | disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); |
| 243 | } | 231 | } |
| 244 | 232 | ||
| 245 | void DisassemblerWidget::OnDebugModeLeft() | 233 | void DisassemblerWidget::OnDebugModeLeft() { |
| 246 | { | ||
| 247 | |||
| 248 | } | 234 | } |
| 249 | 235 | ||
| 250 | int DisassemblerWidget::SelectedRow() | 236 | int DisassemblerWidget::SelectedRow() { |
| 251 | { | ||
| 252 | QModelIndex index = disasm_ui.treeView->selectionModel()->currentIndex(); | 237 | QModelIndex index = disasm_ui.treeView->selectionModel()->currentIndex(); |
| 253 | if (!index.isValid()) | 238 | if (!index.isValid()) |
| 254 | return -1; | 239 | return -1; |
| 255 | 240 | ||
| 256 | return disasm_ui.treeView->selectionModel()->currentIndex().row(); | 241 | return disasm_ui.treeView->selectionModel()->currentIndex().row(); |
| 257 | } | 242 | } |
| 243 | |||
| 244 | void DisassemblerWidget::OnEmulationStarting(EmuThread* emu_thread) { | ||
| 245 | this->emu_thread = emu_thread; | ||
| 246 | |||
| 247 | model = new DisassemblerModel(this); | ||
| 248 | disasm_ui.treeView->setModel(model); | ||
| 249 | |||
| 250 | connect(disasm_ui.treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), | ||
| 251 | model, SLOT(OnSelectionChanged(const QModelIndex&))); | ||
| 252 | connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint())); | ||
| 253 | connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model, SLOT(OnSetOrUnsetBreakpoint())); | ||
| 254 | |||
| 255 | Init(); | ||
| 256 | setEnabled(true); | ||
| 257 | } | ||
| 258 | |||
| 259 | void DisassemblerWidget::OnEmulationStopping() { | ||
| 260 | disasm_ui.treeView->setModel(nullptr); | ||
| 261 | delete model; | ||
| 262 | emu_thread = nullptr; | ||
| 263 | setEnabled(false); | ||
| 264 | } | ||
diff --git a/src/citra_qt/debugger/disassembler.h b/src/citra_qt/debugger/disassembler.h index 5e19d7c51..45b0a7e08 100644 --- a/src/citra_qt/debugger/disassembler.h +++ b/src/citra_qt/debugger/disassembler.h | |||
| @@ -51,7 +51,7 @@ class DisassemblerWidget : public QDockWidget | |||
| 51 | Q_OBJECT | 51 | Q_OBJECT |
| 52 | 52 | ||
| 53 | public: | 53 | public: |
| 54 | DisassemblerWidget(QWidget* parent, EmuThread& emu_thread); | 54 | DisassemblerWidget(QWidget* parent, EmuThread* emu_thread); |
| 55 | 55 | ||
| 56 | void Init(); | 56 | void Init(); |
| 57 | 57 | ||
| @@ -65,6 +65,9 @@ public slots: | |||
| 65 | void OnDebugModeEntered(); | 65 | void OnDebugModeEntered(); |
| 66 | void OnDebugModeLeft(); | 66 | void OnDebugModeLeft(); |
| 67 | 67 | ||
| 68 | void OnEmulationStarting(EmuThread* emu_thread); | ||
| 69 | void OnEmulationStopping(); | ||
| 70 | |||
| 68 | private: | 71 | private: |
| 69 | // returns -1 if no row is selected | 72 | // returns -1 if no row is selected |
| 70 | int SelectedRow(); | 73 | int SelectedRow(); |
| @@ -75,5 +78,5 @@ private: | |||
| 75 | 78 | ||
| 76 | u32 base_addr; | 79 | u32 base_addr; |
| 77 | 80 | ||
| 78 | EmuThread& emu_thread; | 81 | EmuThread* emu_thread; |
| 79 | }; | 82 | }; |
diff --git a/src/citra_qt/debugger/graphics_breakpoints_p.h b/src/citra_qt/debugger/graphics_breakpoints_p.h index 232bfc863..34e72e859 100644 --- a/src/citra_qt/debugger/graphics_breakpoints_p.h +++ b/src/citra_qt/debugger/graphics_breakpoints_p.h | |||
| @@ -25,7 +25,7 @@ public: | |||
| 25 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; | 25 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; |
| 26 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; | 26 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; |
| 27 | 27 | ||
| 28 | bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); | 28 | bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; |
| 29 | 29 | ||
| 30 | public slots: | 30 | public slots: |
| 31 | void OnBreakPointHit(Pica::DebugContext::Event event); | 31 | void OnBreakPointHit(Pica::DebugContext::Event event); |
diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index ae0568b6a..2ac1748b7 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp | |||
| @@ -26,7 +26,7 @@ static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) | |||
| 26 | static const TimingCategoryInfo* GetCategoryInfo(int id) | 26 | static const TimingCategoryInfo* GetCategoryInfo(int id) |
| 27 | { | 27 | { |
| 28 | const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); | 28 | const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); |
| 29 | if (id >= categories.size()) { | 29 | if ((size_t)id >= categories.size()) { |
| 30 | return nullptr; | 30 | return nullptr; |
| 31 | } else { | 31 | } else { |
| 32 | return &categories[id]; | 32 | return &categories[id]; |
| @@ -98,7 +98,7 @@ QVariant ProfilerModel::data(const QModelIndex& index, int role) const | |||
| 98 | const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2); | 98 | const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2); |
| 99 | return info != nullptr ? QString(info->name) : QVariant(); | 99 | return info != nullptr ? QString(info->name) : QVariant(); |
| 100 | } else { | 100 | } else { |
| 101 | if (index.row() - 2 < results.time_per_category.size()) { | 101 | if (index.row() - 2 < (int)results.time_per_category.size()) { |
| 102 | return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]); | 102 | return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]); |
| 103 | } else { | 103 | } else { |
| 104 | return QVariant(); | 104 | return QVariant(); |
diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index a6d87aa0f..fabf279b8 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h | |||
| @@ -18,7 +18,7 @@ class ProfilerModel : public QAbstractItemModel | |||
| 18 | public: | 18 | public: |
| 19 | ProfilerModel(QObject* parent); | 19 | ProfilerModel(QObject* parent); |
| 20 | 20 | ||
| 21 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; | 21 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; |
| 22 | QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; | 22 | QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; |
| 23 | QModelIndex parent(const QModelIndex& child) const override; | 23 | QModelIndex parent(const QModelIndex& child) const override; |
| 24 | int columnCount(const QModelIndex& parent = QModelIndex()) const override; | 24 | int columnCount(const QModelIndex& parent = QModelIndex()) const override; |
diff --git a/src/citra_qt/debugger/registers.cpp b/src/citra_qt/debugger/registers.cpp index ab3666156..5527a2afd 100644 --- a/src/citra_qt/debugger/registers.cpp +++ b/src/citra_qt/debugger/registers.cpp | |||
| @@ -7,8 +7,7 @@ | |||
| 7 | #include "core/core.h" | 7 | #include "core/core.h" |
| 8 | #include "core/arm/arm_interface.h" | 8 | #include "core/arm/arm_interface.h" |
| 9 | 9 | ||
| 10 | RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) | 10 | RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) { |
| 11 | { | ||
| 12 | cpu_regs_ui.setupUi(this); | 11 | cpu_regs_ui.setupUi(this); |
| 13 | 12 | ||
| 14 | tree = cpu_regs_ui.treeWidget; | 13 | tree = cpu_regs_ui.treeWidget; |
| @@ -18,8 +17,7 @@ RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) | |||
| 18 | registers->setExpanded(true); | 17 | registers->setExpanded(true); |
| 19 | CSPR->setExpanded(true); | 18 | CSPR->setExpanded(true); |
| 20 | 19 | ||
| 21 | for (int i = 0; i < 16; ++i) | 20 | for (int i = 0; i < 16; ++i) { |
| 22 | { | ||
| 23 | QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("R[%1]").arg(i, 2, 10, QLatin1Char('0')))); | 21 | QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("R[%1]").arg(i, 2, 10, QLatin1Char('0')))); |
| 24 | registers->addChild(child); | 22 | registers->addChild(child); |
| 25 | } | 23 | } |
| @@ -39,12 +37,16 @@ RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) | |||
| 39 | CSPR->addChild(new QTreeWidgetItem(QStringList("C"))); | 37 | CSPR->addChild(new QTreeWidgetItem(QStringList("C"))); |
| 40 | CSPR->addChild(new QTreeWidgetItem(QStringList("Z"))); | 38 | CSPR->addChild(new QTreeWidgetItem(QStringList("Z"))); |
| 41 | CSPR->addChild(new QTreeWidgetItem(QStringList("N"))); | 39 | CSPR->addChild(new QTreeWidgetItem(QStringList("N"))); |
| 40 | |||
| 41 | setEnabled(false); | ||
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | void RegistersWidget::OnDebugModeEntered() | 44 | void RegistersWidget::OnDebugModeEntered() { |
| 45 | { | ||
| 46 | ARM_Interface* app_core = Core::g_app_core; | 45 | ARM_Interface* app_core = Core::g_app_core; |
| 47 | 46 | ||
| 47 | if (app_core == nullptr) | ||
| 48 | return; | ||
| 49 | |||
| 48 | for (int i = 0; i < 16; ++i) | 50 | for (int i = 0; i < 16; ++i) |
| 49 | registers->child(i)->setText(1, QString("0x%1").arg(app_core->GetReg(i), 8, 16, QLatin1Char('0'))); | 51 | registers->child(i)->setText(1, QString("0x%1").arg(app_core->GetReg(i), 8, 16, QLatin1Char('0'))); |
| 50 | 52 | ||
| @@ -66,7 +68,22 @@ void RegistersWidget::OnDebugModeEntered() | |||
| 66 | CSPR->child(14)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 31) & 0x1)); // N - Negative/Less than | 68 | CSPR->child(14)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 31) & 0x1)); // N - Negative/Less than |
| 67 | } | 69 | } |
| 68 | 70 | ||
| 69 | void RegistersWidget::OnDebugModeLeft() | 71 | void RegistersWidget::OnDebugModeLeft() { |
| 70 | { | 72 | } |
| 73 | |||
| 74 | void RegistersWidget::OnEmulationStarting(EmuThread* emu_thread) { | ||
| 75 | setEnabled(true); | ||
| 76 | } | ||
| 77 | |||
| 78 | void RegistersWidget::OnEmulationStopping() { | ||
| 79 | // Reset widget text | ||
| 80 | for (int i = 0; i < 16; ++i) | ||
| 81 | registers->child(i)->setText(1, QString("")); | ||
| 82 | |||
| 83 | for (int i = 0; i < 15; ++i) | ||
| 84 | CSPR->child(i)->setText(1, QString("")); | ||
| 85 | |||
| 86 | CSPR->setText(1, QString("")); | ||
| 71 | 87 | ||
| 88 | setEnabled(false); | ||
| 72 | } | 89 | } |
diff --git a/src/citra_qt/debugger/registers.h b/src/citra_qt/debugger/registers.h index bf8955625..68e3fb908 100644 --- a/src/citra_qt/debugger/registers.h +++ b/src/citra_qt/debugger/registers.h | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <QTreeWidgetItem> | 8 | #include <QTreeWidgetItem> |
| 9 | 9 | ||
| 10 | class QTreeWidget; | 10 | class QTreeWidget; |
| 11 | class EmuThread; | ||
| 11 | 12 | ||
| 12 | class RegistersWidget : public QDockWidget | 13 | class RegistersWidget : public QDockWidget |
| 13 | { | 14 | { |
| @@ -20,6 +21,9 @@ public slots: | |||
| 20 | void OnDebugModeEntered(); | 21 | void OnDebugModeEntered(); |
| 21 | void OnDebugModeLeft(); | 22 | void OnDebugModeLeft(); |
| 22 | 23 | ||
| 24 | void OnEmulationStarting(EmuThread* emu_thread); | ||
| 25 | void OnEmulationStopping(); | ||
| 26 | |||
| 23 | private: | 27 | private: |
| 24 | Ui::ARMRegisters cpu_regs_ui; | 28 | Ui::ARMRegisters cpu_regs_ui; |
| 25 | 29 | ||
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index e5ca04124..dd0e4de8f 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp | |||
| @@ -15,6 +15,7 @@ | |||
| 15 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 16 | #include "common/logging/backend.h" | 16 | #include "common/logging/backend.h" |
| 17 | #include "common/logging/filter.h" | 17 | #include "common/logging/filter.h" |
| 18 | #include "common/make_unique.h" | ||
| 18 | #include "common/platform.h" | 19 | #include "common/platform.h" |
| 19 | #include "common/scope_exit.h" | 20 | #include "common/scope_exit.h" |
| 20 | 21 | ||
| @@ -46,7 +47,7 @@ | |||
| 46 | 47 | ||
| 47 | #include "version.h" | 48 | #include "version.h" |
| 48 | 49 | ||
| 49 | GMainWindow::GMainWindow() | 50 | GMainWindow::GMainWindow() : emu_thread(nullptr) |
| 50 | { | 51 | { |
| 51 | Pica::g_debug_context = Pica::DebugContext::Construct(); | 52 | Pica::g_debug_context = Pica::DebugContext::Construct(); |
| 52 | 53 | ||
| @@ -55,14 +56,14 @@ GMainWindow::GMainWindow() | |||
| 55 | ui.setupUi(this); | 56 | ui.setupUi(this); |
| 56 | statusBar()->hide(); | 57 | statusBar()->hide(); |
| 57 | 58 | ||
| 58 | render_window = new GRenderWindow; | 59 | render_window = new GRenderWindow(this, emu_thread.get()); |
| 59 | render_window->hide(); | 60 | render_window->hide(); |
| 60 | 61 | ||
| 61 | profilerWidget = new ProfilerWidget(this); | 62 | profilerWidget = new ProfilerWidget(this); |
| 62 | addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); | 63 | addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); |
| 63 | profilerWidget->hide(); | 64 | profilerWidget->hide(); |
| 64 | 65 | ||
| 65 | disasmWidget = new DisassemblerWidget(this, render_window->GetEmuThread()); | 66 | disasmWidget = new DisassemblerWidget(this, emu_thread.get()); |
| 66 | addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); | 67 | addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); |
| 67 | disasmWidget->hide(); | 68 | disasmWidget->hide(); |
| 68 | 69 | ||
| @@ -138,14 +139,12 @@ GMainWindow::GMainWindow() | |||
| 138 | connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); | 139 | connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); |
| 139 | connect(ui.action_Hotkeys, SIGNAL(triggered()), this, SLOT(OnOpenHotkeysDialog())); | 140 | connect(ui.action_Hotkeys, SIGNAL(triggered()), this, SLOT(OnOpenHotkeysDialog())); |
| 140 | 141 | ||
| 141 | // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views before the CPU continues | 142 | connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget, SLOT(OnEmulationStarting(EmuThread*))); |
| 142 | connect(&render_window->GetEmuThread(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); | 143 | connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping())); |
| 143 | connect(&render_window->GetEmuThread(), SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); | 144 | connect(this, SIGNAL(EmulationStarting(EmuThread*)), registersWidget, SLOT(OnEmulationStarting(EmuThread*))); |
| 144 | connect(&render_window->GetEmuThread(), SIGNAL(DebugModeEntered()), callstackWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); | 145 | connect(this, SIGNAL(EmulationStopping()), registersWidget, SLOT(OnEmulationStopping())); |
| 145 | 146 | connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, SLOT(OnEmulationStarting(EmuThread*))); | |
| 146 | connect(&render_window->GetEmuThread(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); | 147 | connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); |
| 147 | connect(&render_window->GetEmuThread(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); | ||
| 148 | connect(&render_window->GetEmuThread(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); | ||
| 149 | 148 | ||
| 150 | // Setup hotkeys | 149 | // Setup hotkeys |
| 151 | RegisterHotkey("Main Window", "Load File", QKeySequence::Open); | 150 | RegisterHotkey("Main Window", "Load File", QKeySequence::Open); |
| @@ -196,32 +195,76 @@ void GMainWindow::OnDisplayTitleBars(bool show) | |||
| 196 | } | 195 | } |
| 197 | } | 196 | } |
| 198 | 197 | ||
| 199 | void GMainWindow::BootGame(std::string filename) | 198 | void GMainWindow::BootGame(std::string filename) { |
| 200 | { | ||
| 201 | LOG_INFO(Frontend, "Citra starting...\n"); | 199 | LOG_INFO(Frontend, "Citra starting...\n"); |
| 200 | |||
| 201 | // Initialize the core emulation | ||
| 202 | System::Init(render_window); | 202 | System::Init(render_window); |
| 203 | 203 | ||
| 204 | // Load a game or die... | 204 | // Load the game |
| 205 | if (Loader::ResultStatus::Success != Loader::LoadFile(filename)) { | 205 | if (Loader::ResultStatus::Success != Loader::LoadFile(filename)) { |
| 206 | LOG_CRITICAL(Frontend, "Failed to load ROM!"); | 206 | LOG_CRITICAL(Frontend, "Failed to load ROM!"); |
| 207 | System::Shutdown(); | ||
| 208 | return; | ||
| 207 | } | 209 | } |
| 208 | 210 | ||
| 209 | disasmWidget->Init(); | 211 | // Create and start the emulation thread |
| 212 | emu_thread = Common::make_unique<EmuThread>(render_window); | ||
| 213 | emit EmulationStarting(emu_thread.get()); | ||
| 214 | emu_thread->start(); | ||
| 215 | |||
| 216 | // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views before the CPU continues | ||
| 217 | connect(emu_thread.get(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); | ||
| 218 | connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); | ||
| 219 | connect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); | ||
| 220 | connect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); | ||
| 221 | connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); | ||
| 222 | connect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); | ||
| 223 | |||
| 224 | // Update the GUI | ||
| 210 | registersWidget->OnDebugModeEntered(); | 225 | registersWidget->OnDebugModeEntered(); |
| 211 | callstackWidget->OnDebugModeEntered(); | 226 | callstackWidget->OnDebugModeEntered(); |
| 212 | |||
| 213 | render_window->GetEmuThread().SetFilename(filename); | ||
| 214 | render_window->GetEmuThread().start(); | ||
| 215 | |||
| 216 | render_window->show(); | 227 | render_window->show(); |
| 228 | |||
| 217 | OnStartGame(); | 229 | OnStartGame(); |
| 218 | } | 230 | } |
| 219 | 231 | ||
| 232 | void GMainWindow::ShutdownGame() { | ||
| 233 | emu_thread->RequestStop(); | ||
| 234 | |||
| 235 | // Release emu threads from any breakpoints | ||
| 236 | // This belongs after RequestStop() and before wait() because if emulation stops on a GPU | ||
| 237 | // breakpoint after (or before) RequestStop() is called, the emulation would never be able | ||
| 238 | // to continue out to the main loop and terminate. Thus wait() would hang forever. | ||
| 239 | // TODO(bunnei): This function is not thread safe, but it's being used as if it were | ||
| 240 | Pica::g_debug_context->ClearBreakpoints(); | ||
| 241 | |||
| 242 | emit EmulationStopping(); | ||
| 243 | |||
| 244 | // Wait for emulation thread to complete and delete it | ||
| 245 | emu_thread->wait(); | ||
| 246 | emu_thread = nullptr; | ||
| 247 | |||
| 248 | // Shutdown the core emulation | ||
| 249 | System::Shutdown(); | ||
| 250 | |||
| 251 | // Update the GUI | ||
| 252 | ui.action_Start->setEnabled(false); | ||
| 253 | ui.action_Pause->setEnabled(false); | ||
| 254 | ui.action_Stop->setEnabled(false); | ||
| 255 | render_window->hide(); | ||
| 256 | } | ||
| 257 | |||
| 220 | void GMainWindow::OnMenuLoadFile() | 258 | void GMainWindow::OnMenuLoadFile() |
| 221 | { | 259 | { |
| 222 | QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), QString(), tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.bin *.cci *.cxi)")); | 260 | QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), QString(), tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.bin *.cci *.cxi)")); |
| 223 | if (filename.size()) | 261 | if (filename.size()) { |
| 224 | BootGame(filename.toLatin1().data()); | 262 | // Shutdown previous session if the emu thread is still active... |
| 263 | if (emu_thread != nullptr) | ||
| 264 | ShutdownGame(); | ||
| 265 | |||
| 266 | BootGame(filename.toLatin1().data()); | ||
| 267 | } | ||
| 225 | } | 268 | } |
| 226 | 269 | ||
| 227 | void GMainWindow::OnMenuLoadSymbolMap() { | 270 | void GMainWindow::OnMenuLoadSymbolMap() { |
| @@ -232,7 +275,7 @@ void GMainWindow::OnMenuLoadSymbolMap() { | |||
| 232 | 275 | ||
| 233 | void GMainWindow::OnStartGame() | 276 | void GMainWindow::OnStartGame() |
| 234 | { | 277 | { |
| 235 | render_window->GetEmuThread().SetCpuRunning(true); | 278 | emu_thread->SetRunning(true); |
| 236 | 279 | ||
| 237 | ui.action_Start->setEnabled(false); | 280 | ui.action_Start->setEnabled(false); |
| 238 | ui.action_Pause->setEnabled(true); | 281 | ui.action_Pause->setEnabled(true); |
| @@ -241,21 +284,15 @@ void GMainWindow::OnStartGame() | |||
| 241 | 284 | ||
| 242 | void GMainWindow::OnPauseGame() | 285 | void GMainWindow::OnPauseGame() |
| 243 | { | 286 | { |
| 244 | render_window->GetEmuThread().SetCpuRunning(false); | 287 | emu_thread->SetRunning(false); |
| 245 | 288 | ||
| 246 | ui.action_Start->setEnabled(true); | 289 | ui.action_Start->setEnabled(true); |
| 247 | ui.action_Pause->setEnabled(false); | 290 | ui.action_Pause->setEnabled(false); |
| 248 | ui.action_Stop->setEnabled(true); | 291 | ui.action_Stop->setEnabled(true); |
| 249 | } | 292 | } |
| 250 | 293 | ||
| 251 | void GMainWindow::OnStopGame() | 294 | void GMainWindow::OnStopGame() { |
| 252 | { | 295 | ShutdownGame(); |
| 253 | render_window->GetEmuThread().SetCpuRunning(false); | ||
| 254 | // TODO: Shutdown core | ||
| 255 | |||
| 256 | ui.action_Start->setEnabled(true); | ||
| 257 | ui.action_Pause->setEnabled(false); | ||
| 258 | ui.action_Stop->setEnabled(false); | ||
| 259 | } | 296 | } |
| 260 | 297 | ||
| 261 | void GMainWindow::OnOpenHotkeysDialog() | 298 | void GMainWindow::OnOpenHotkeysDialog() |
| @@ -265,24 +302,22 @@ void GMainWindow::OnOpenHotkeysDialog() | |||
| 265 | } | 302 | } |
| 266 | 303 | ||
| 267 | 304 | ||
| 268 | void GMainWindow::ToggleWindowMode() | 305 | void GMainWindow::ToggleWindowMode() { |
| 269 | { | 306 | if (ui.action_Single_Window_Mode->isChecked()) { |
| 270 | bool enable = ui.action_Single_Window_Mode->isChecked(); | 307 | // Render in the main window... |
| 271 | if (!enable && render_window->parent() != nullptr) | ||
| 272 | { | ||
| 273 | ui.horizontalLayout->removeWidget(render_window); | ||
| 274 | render_window->setParent(nullptr); | ||
| 275 | render_window->setVisible(true); | ||
| 276 | render_window->RestoreGeometry(); | ||
| 277 | render_window->setFocusPolicy(Qt::NoFocus); | ||
| 278 | } | ||
| 279 | else if (enable && render_window->parent() == nullptr) | ||
| 280 | { | ||
| 281 | render_window->BackupGeometry(); | 308 | render_window->BackupGeometry(); |
| 282 | ui.horizontalLayout->addWidget(render_window); | 309 | ui.horizontalLayout->addWidget(render_window); |
| 283 | render_window->setVisible(true); | 310 | render_window->setVisible(true); |
| 284 | render_window->setFocusPolicy(Qt::ClickFocus); | 311 | render_window->setFocusPolicy(Qt::ClickFocus); |
| 285 | render_window->setFocus(); | 312 | render_window->setFocus(); |
| 313 | |||
| 314 | } else { | ||
| 315 | // Render in a separate window... | ||
| 316 | ui.horizontalLayout->removeWidget(render_window); | ||
| 317 | render_window->setParent(nullptr); | ||
| 318 | render_window->setVisible(true); | ||
| 319 | render_window->RestoreGeometry(); | ||
| 320 | render_window->setFocusPolicy(Qt::NoFocus); | ||
| 286 | } | 321 | } |
| 287 | } | 322 | } |
| 288 | 323 | ||
| @@ -303,6 +338,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) | |||
| 303 | settings.setValue("firstStart", false); | 338 | settings.setValue("firstStart", false); |
| 304 | SaveHotkeys(settings); | 339 | SaveHotkeys(settings); |
| 305 | 340 | ||
| 341 | ShutdownGame(); | ||
| 342 | |||
| 306 | render_window->close(); | 343 | render_window->close(); |
| 307 | 344 | ||
| 308 | QWidget::closeEvent(event); | 345 | QWidget::closeEvent(event); |
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 9b57c5772..3e29534fb 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h | |||
| @@ -5,12 +5,14 @@ | |||
| 5 | #ifndef _CITRA_QT_MAIN_HXX_ | 5 | #ifndef _CITRA_QT_MAIN_HXX_ |
| 6 | #define _CITRA_QT_MAIN_HXX_ | 6 | #define _CITRA_QT_MAIN_HXX_ |
| 7 | 7 | ||
| 8 | #include <memory> | ||
| 8 | #include <QMainWindow> | 9 | #include <QMainWindow> |
| 9 | 10 | ||
| 10 | #include "ui_main.h" | 11 | #include "ui_main.h" |
| 11 | 12 | ||
| 12 | class GImageInfo; | 13 | class GImageInfo; |
| 13 | class GRenderWindow; | 14 | class GRenderWindow; |
| 15 | class EmuThread; | ||
| 14 | class ProfilerWidget; | 16 | class ProfilerWidget; |
| 15 | class DisassemblerWidget; | 17 | class DisassemblerWidget; |
| 16 | class RegistersWidget; | 18 | class RegistersWidget; |
| @@ -34,8 +36,27 @@ public: | |||
| 34 | GMainWindow(); | 36 | GMainWindow(); |
| 35 | ~GMainWindow(); | 37 | ~GMainWindow(); |
| 36 | 38 | ||
| 39 | signals: | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Signal that is emitted when a new EmuThread has been created and an emulation session is | ||
| 43 | * about to start. At this time, the core system emulation has been initialized, and all | ||
| 44 | * emulation handles and memory should be valid. | ||
| 45 | * | ||
| 46 | * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to | ||
| 47 | * access/change emulation state). | ||
| 48 | */ | ||
| 49 | void EmulationStarting(EmuThread* emu_thread); | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Signal that is emitted when emulation is about to stop. At this time, the EmuThread and core | ||
| 53 | * system emulation handles and memory are still valid, but are about become invalid. | ||
| 54 | */ | ||
| 55 | void EmulationStopping(); | ||
| 56 | |||
| 37 | private: | 57 | private: |
| 38 | void BootGame(std::string filename); | 58 | void BootGame(std::string filename); |
| 59 | void ShutdownGame(); | ||
| 39 | 60 | ||
| 40 | void closeEvent(QCloseEvent* event) override; | 61 | void closeEvent(QCloseEvent* event) override; |
| 41 | 62 | ||
| @@ -55,6 +76,8 @@ private: | |||
| 55 | 76 | ||
| 56 | GRenderWindow* render_window; | 77 | GRenderWindow* render_window; |
| 57 | 78 | ||
| 79 | std::unique_ptr<EmuThread> emu_thread; | ||
| 80 | |||
| 58 | ProfilerWidget* profilerWidget; | 81 | ProfilerWidget* profilerWidget; |
| 59 | DisassemblerWidget* disasmWidget; | 82 | DisassemblerWidget* disasmWidget; |
| 60 | RegistersWidget* registersWidget; | 83 | RegistersWidget* registersWidget; |
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index a3752ac1e..689806465 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui | |||
| @@ -90,6 +90,9 @@ | |||
| 90 | </property> | 90 | </property> |
| 91 | </action> | 91 | </action> |
| 92 | <action name="action_Start"> | 92 | <action name="action_Start"> |
| 93 | <property name="enabled"> | ||
| 94 | <bool>false</bool> | ||
| 95 | </property> | ||
| 93 | <property name="text"> | 96 | <property name="text"> |
| 94 | <string>&Start</string> | 97 | <string>&Start</string> |
| 95 | </property> | 98 | </property> |
diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp index 2e2076a27..de4060116 100644 --- a/src/citra_qt/util/spinbox.cpp +++ b/src/citra_qt/util/spinbox.cpp | |||
| @@ -29,6 +29,7 @@ | |||
| 29 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 29 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 30 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 | 31 | ||
| 32 | #include <cstdlib> | ||
| 32 | #include <QLineEdit> | 33 | #include <QLineEdit> |
| 33 | #include <QRegExpValidator> | 34 | #include <QRegExpValidator> |
| 34 | 35 | ||
| @@ -206,7 +207,7 @@ QString CSpinBox::TextFromValue() | |||
| 206 | { | 207 | { |
| 207 | return prefix | 208 | return prefix |
| 208 | + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") | 209 | + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") |
| 209 | + QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper() | 210 | + QString("%1").arg(std::abs(value), num_digits, base, QLatin1Char('0')).toUpper() |
| 210 | + suffix; | 211 | + suffix; |
| 211 | } | 212 | } |
| 212 | 213 | ||