diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index ec7b519e5..bc014ab15 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -6,6 +6,7 @@ #include "qtsettingsinterface.h" #include "settingsdialog.h" #include +#include #include static constexpr char DISC_IMAGE_FILTER[] = @@ -76,26 +77,13 @@ void MainWindow::toggleFullscreen() m_ui.actionFullscreen->setChecked(fullscreen); } -void MainWindow::switchRenderer() +void MainWindow::recreateDisplayWidget(bool create_device_context) { const bool was_fullscreen = m_display_widget->isFullScreen(); if (was_fullscreen) toggleFullscreen(); - QByteArray state; - if (m_emulation_running) - { - // we need to basically restart the emulator core - state = m_host_interface->saveStateToMemory(); - if (state.isEmpty()) - { - m_host_interface->ReportError("Failed to save emulator state to memory"); - return; - } - - // stop the emulation - m_host_interface->blockingPowerOffSystem(); - } + switchToGameListView(); // recreate the display widget using the potentially-new renderer m_ui.mainContainer->removeWidget(m_display_widget); @@ -104,25 +92,21 @@ void MainWindow::switchRenderer() m_display_widget = m_host_interface->createDisplayWidget(m_ui.mainContainer); m_ui.mainContainer->insertWidget(1, m_display_widget); + if (create_device_context) + switchToEmulationView(); + // we need the surface visible.. QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - if (!state.isEmpty()) + if (create_device_context && !m_host_interface->createDisplayDeviceContext()) { - // restart the system with the new state - m_host_interface->bootSystem(QString(), QString()); - m_host_interface->loadStateFromMemory(std::move(state)); + QMessageBox::critical(this, tr("DuckStation Error"), + tr("Failed to create new device context on renderer switch. Cannot continue.")); + QCoreApplication::exit(); + return; } - // update the menu with the selected renderer - QObjectList renderer_menu_items = m_ui.menuRenderer->children(); - QString current_renderer_name(Settings::GetRendererDisplayName(m_host_interface->GetCoreSettings().gpu_renderer)); - for (QObject* obj : renderer_menu_items) - { - QAction* action = qobject_cast(obj); - if (action) - action->setChecked(action->text() == current_renderer_name); - } + updateDebugMenuGPURenderer(); } void MainWindow::onPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time, @@ -214,14 +198,12 @@ void MainWindow::setupAdditionalUi() const GPURenderer renderer = static_cast(i); QAction* action = m_ui.menuRenderer->addAction(tr(Settings::GetRendererDisplayName(renderer))); action->setCheckable(true); - action->setChecked(m_host_interface->GetCoreSettings().gpu_renderer == renderer); connect(action, &QAction::triggered, [this, action, renderer]() { m_host_interface->putSettingValue(QStringLiteral("GPU/Renderer"), QString(Settings::GetRendererName(renderer))); m_host_interface->applySettings(); - action->setChecked(true); - switchRenderer(); }); } + updateDebugMenuGPURenderer(); } void MainWindow::updateEmulationActions(bool starting, bool running) @@ -307,6 +289,8 @@ void MainWindow::connectSignals() connect(m_host_interface, &QtHostInterface::toggleFullscreenRequested, this, &MainWindow::toggleFullscreen); connect(m_host_interface, &QtHostInterface::performanceCountersUpdated, this, &MainWindow::onPerformanceCountersUpdated); + connect(m_host_interface, &QtHostInterface::recreateDisplayWidgetRequested, this, &MainWindow::recreateDisplayWidget, + Qt::BlockingQueuedConnection); connect(m_game_list_widget, &GameListWidget::bootEntryRequested, [this](const GameList::GameListEntry* entry) { // if we're not running, boot the system, otherwise swap discs @@ -347,3 +331,21 @@ void MainWindow::doSettings(SettingsDialog::Category category) if (category != SettingsDialog::Category::Count) m_settings_dialog->setCategory(category); } + +void MainWindow::updateDebugMenuGPURenderer() +{ + // update the menu with the new selected renderer + std::optional current_renderer = Settings::ParseRendererName( + m_host_interface->getSettingValue(QStringLiteral("GPU/Renderer")).toString().toStdString().c_str()); + if (current_renderer.has_value()) + { + const QString current_renderer_display_name( + QString::fromUtf8(Settings::GetRendererDisplayName(current_renderer.value()))); + for (QObject* obj : m_ui.menuRenderer->children()) + { + QAction* action = qobject_cast(obj); + if (action) + action->setChecked(action->text() == current_renderer_display_name); + } + } +} diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index b057d5c34..15d2d6070 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -27,7 +27,7 @@ private Q_SLOTS: void onEmulationStopped(); void onEmulationPaused(bool paused); void toggleFullscreen(); - void switchRenderer(); + void recreateDisplayWidget(bool create_device_context); void onPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time, float worst_frame_time); @@ -48,6 +48,7 @@ private: void switchToGameListView(); void switchToEmulationView(); void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count); + void updateDebugMenuGPURenderer(); Ui::MainWindow m_ui; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 9bd8b9301..57e35a1a7 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -105,6 +105,7 @@ void QtHostInterface::applySettings() } // TODO: Should we move this to the base class? + const GPURenderer old_gpu_renderer = m_settings.gpu_renderer; const bool old_vsync_enabled = m_settings.video_sync_enabled; const bool old_audio_sync_enabled = m_settings.audio_sync_enabled; const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled; @@ -120,6 +121,10 @@ void QtHostInterface::applySettings() { UpdateSpeedLimiterState(); } + + // TODO: Fast path for hardware->software switches + if (m_settings.gpu_renderer != old_gpu_renderer) + switchGPURenderer(); } void QtHostInterface::checkSettings() @@ -180,6 +185,11 @@ QWidget* QtHostInterface::createDisplayWidget(QWidget* parent) return QWidget::createWindowContainer(m_display_window, parent); } +bool QtHostInterface::createDisplayDeviceContext() +{ + return m_display_window->createDeviceContext(m_worker_thread, m_settings.gpu_use_debug_device); +} + void QtHostInterface::displayWidgetDestroyed() { m_display.release(); @@ -191,7 +201,7 @@ void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_ Assert(!isOnWorkerThread()); emit emulationStarting(); - if (!m_display_window->createDeviceContext(m_worker_thread, m_settings.gpu_use_debug_device)) + if (!createDisplayDeviceContext()) { emit emulationStopped(); return; @@ -383,17 +393,6 @@ void QtHostInterface::powerOffSystem() emit emulationStopped(); } -void QtHostInterface::blockingPowerOffSystem() -{ - if (!isOnWorkerThread()) - { - QMetaObject::invokeMethod(this, "powerOffSystem", Qt::BlockingQueuedConnection); - return; - } - - powerOffSystem(); -} - void QtHostInterface::resetSystem() { if (!isOnWorkerThread()) @@ -428,44 +427,6 @@ void QtHostInterface::pauseSystem(bool paused) void QtHostInterface::changeDisc(QString new_disc_filename) {} -void QtHostInterface::loadStateFromMemory(QByteArray arr) -{ - if (!isOnWorkerThread()) - { - QMetaObject::invokeMethod(this, "loadStateFromMemory", Qt::QueuedConnection, Q_ARG(QByteArray, arr)); - return; - } - - std::unique_ptr stream = ByteStream_CreateGrowableMemoryStream(); - if (!m_system || !QtUtils::WriteQByteArrayToStream(arr, stream.get()) || !stream->SeekAbsolute(0) || - !m_system->LoadState(stream.get())) - { - Log_ErrorPrintf("Failed to load memory state"); - return; - } -} - -QByteArray QtHostInterface::saveStateToMemory() -{ - if (!isOnWorkerThread()) - { - QByteArray return_value; - QMetaObject::invokeMethod(this, "saveStateToMemory", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QByteArray, return_value)); - return return_value; - } - - QByteArray ret; - if (!m_system) - return {}; - - std::unique_ptr stream = ByteStream_CreateGrowableMemoryStream(); - if (m_system->SaveState(stream.get())) - return QtUtils::ReadStreamToQByteArray(stream.get(), true); - else - return {}; -} - void QtHostInterface::doBootSystem(QString initial_filename, QString initial_save_state_filename) { if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device)) @@ -517,6 +478,50 @@ void QtHostInterface::createAudioStream() } } +void QtHostInterface::switchGPURenderer() +{ + // Due to the GPU class owning textures, we have to shut the system down. + std::unique_ptr stream; + if (m_system) + { + stream = ByteStream_CreateGrowableMemoryStream(nullptr, 8 * 1024); + if (!m_system->SaveState(stream.get()) || !stream->SeekAbsolute(0)) + ReportError("Failed to save state before GPU renderer switch"); + + DestroySystem(); + m_audio_stream->PauseOutput(true); + m_display_window->destroyDeviceContext(); + } + + const bool restore_state = static_cast(stream); + emit recreateDisplayWidgetRequested(restore_state); + Assert(m_display_window != nullptr); + + if (restore_state) + { + if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device)) + { + emit emulationStopped(); + return; + } + + CreateSystem(); + if (!BootSystem(nullptr, nullptr) || !m_system->LoadState(stream.get())) + { + ReportError("Failed to load state after GPU renderer switch, resetting"); + m_system->Reset(); + } + + if (!m_paused) + { + m_audio_stream->PauseOutput(false); + UpdateSpeedLimiterState(); + } + } + + ResetPerformanceCounters(); +} + void QtHostInterface::createThread() { m_original_thread = QThread::currentThread(); diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index cbc4bcf7b..9e7aa34e8 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -49,10 +49,10 @@ public: bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; } QWidget* createDisplayWidget(QWidget* parent); + bool createDisplayDeviceContext(); void displayWidgetDestroyed(); void bootSystem(QString initial_filename, QString initial_save_state_filename); - void blockingPowerOffSystem(); void updateInputMap(); void handleKeyEvent(int key, bool pressed); @@ -72,7 +72,7 @@ Q_SIGNALS: void emulationPaused(bool paused); void gameListRefreshed(); void toggleFullscreenRequested(); - void switchRendererRequested(); + void recreateDisplayWidgetRequested(bool create_device_context); void performanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time); public Q_SLOTS: @@ -81,8 +81,6 @@ public Q_SLOTS: void resetSystem(); void pauseSystem(bool paused); void changeDisc(QString new_disc_filename); - void loadStateFromMemory(QByteArray arr); - QByteArray saveStateToMemory(); private Q_SLOTS: void doStopThread(); @@ -123,6 +121,7 @@ private: void updateHotkeyInputMap(); void addButtonToInputMap(const QString& binding, InputButtonHandler handler); void createAudioStream(); + void switchGPURenderer(); void createThread(); void stopThread(); void threadEntryPoint();