diff options
Diffstat (limited to 'src/citra_qt/main.cpp')
| -rw-r--r-- | src/citra_qt/main.cpp | 125 |
1 files changed, 81 insertions, 44 deletions
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); |