diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 2740752bf..45fa4c637 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -54,44 +54,87 @@ HostInterface::HostInterface() m_game_list->SetDatabaseFilename(GetGameListDatabaseFileName()); } -HostInterface::~HostInterface() = default; - -bool HostInterface::CreateSystem() +HostInterface::~HostInterface() { - m_system = System::Create(this); + // system should be shut down prior to the destructor + Assert(!m_system && !m_audio_stream && !m_display); +} - // Pull in any invalid settings which have been reset. - m_settings = m_system->GetSettings(); - m_paused = true; +bool HostInterface::BootSystemFromFile(const char* filename) +{ + if (!AcquireHostDisplay()) + { + ReportFormattedError("Failed to acquire host display"); + return false; + } + + // set host display settings + m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); + + // create the audio stream. this will never fail, since we'll just fall back to null + m_audio_stream = CreateAudioStream(m_settings.audio_backend); + if (!m_audio_stream || !m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4)) + { + ReportFormattedError("Failed to create or configure audio stream, falling back to null output."); + m_audio_stream.reset(); + m_audio_stream = AudioStream::CreateNullAudioStream(); + m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4); + } + + m_system = System::Create(this); + if (!m_system->Boot(filename)) + { + ReportFormattedError("System failed to boot. The log may contain more information."); + DestroySystem(); + return false; + } + + OnSystemCreated(); + + m_paused = m_settings.start_paused; + m_audio_stream->PauseOutput(m_paused); UpdateSpeedLimiterState(); + + if (m_paused) + OnSystemPaused(true); + return true; } -bool HostInterface::BootSystem(const char* filename, const char* state_filename) +bool HostInterface::BootSystemFromBIOS() { - if (!m_system->Boot(filename)) - return false; + return BootSystemFromFile(nullptr); +} - m_paused = m_settings.start_paused; +void HostInterface::PauseSystem(bool paused) +{ + if (paused == m_paused) + return; + + m_paused = paused; + m_audio_stream->PauseOutput(m_paused); + OnSystemPaused(paused); UpdateSpeedLimiterState(); - - if (state_filename && !LoadState(state_filename)) - return false; - - return true; } void HostInterface::ResetSystem() { m_system->Reset(); + m_system->ResetPerformanceCounters(); AddOSDMessage("System reset."); } void HostInterface::DestroySystem() { + if (!m_system) + return; + m_system.reset(); m_paused = false; - UpdateSpeedLimiterState(); + m_audio_stream.reset(); + ReleaseHostDisplay(); + OnSystemDestroyed(); + OnRunningGameChanged(); } void HostInterface::ReportError(const char* message) @@ -283,11 +326,6 @@ void HostInterface::DrawDebugWindows() m_system->GetMDEC()->DrawDebugStateWindow(); } -void HostInterface::ClearImGuiFocus() -{ - ImGui::SetWindowFocus(nullptr); -} - std::optional<std::vector<u8>> HostInterface::GetBIOSImage(ConsoleRegion region) { // Try the other default filenames in the directory of the configured BIOS. @@ -349,28 +387,48 @@ bool HostInterface::LoadState(const char* filename) if (!stream) return false; - AddFormattedOSDMessage(2.0f, "Loading state from %s...", filename); + AddFormattedOSDMessage(2.0f, "Loading state from '%s'...", filename); - const bool result = m_system->LoadState(stream.get()); - if (!result) + if (m_system) { - ReportFormattedError("Loading state from %s failed. Resetting.", filename); - m_system->Reset(); + if (!m_system->LoadState(stream.get())) + { + ReportFormattedError("Loading state from '%s' failed. Resetting.", filename); + m_system->Reset(); + return false; + } + + m_system->ResetPerformanceCounters(); + } + else + { + if (!BootSystemFromFile(nullptr)) + { + ReportFormattedError("Failed to boot system to load state from '%s'.", filename); + return false; + } + + if (!m_system->LoadState(stream.get())) + { + ReportFormattedError("Failed to load state. The log may contain more information. Shutting down system."); + DestroySystem(); + return false; + } } - return result; + return true; } bool HostInterface::LoadState(bool global, u32 slot) { - const std::string& code = m_system->GetRunningCode(); - if (!global && code.empty()) + if (!global && (!m_system || m_system->GetRunningCode().empty())) { ReportFormattedError("Can't save per-game state without a running game code."); return false; } - std::string save_path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(code.c_str(), slot); + std::string save_path = + global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(m_system->GetRunningCode().c_str(), slot); return LoadState(save_path.c_str()); } @@ -385,12 +443,12 @@ bool HostInterface::SaveState(const char* filename) const bool result = m_system->SaveState(stream.get()); if (!result) { - ReportFormattedError("Saving state to %s failed.", filename); + ReportFormattedError("Saving state to '%s' failed.", filename); stream->Discard(); } else { - AddFormattedOSDMessage(2.0f, "State saved to %s.", filename); + AddFormattedOSDMessage(2.0f, "State saved to '%s'.", filename); stream->Commit(); } @@ -431,7 +489,17 @@ void HostInterface::UpdateSpeedLimiterState() m_system->ResetPerformanceCounters(); } -void HostInterface::SwitchGPURenderer() {} +void HostInterface::OnSystemCreated() {} + +void HostInterface::OnSystemPaused(bool paused) +{ + ReportFormattedMessage("System %s.", paused ? "paused" : "resumed"); +} + +void HostInterface::OnSystemDestroyed() +{ + ReportFormattedMessage("System shut down."); +} void HostInterface::OnSystemPerformanceCountersUpdated() {} @@ -644,16 +712,16 @@ void HostInterface::UpdateSettings(const std::function<void()>& apply_callback) apply_callback(); if (m_settings.gpu_renderer != old_gpu_renderer) - SwitchGPURenderer(); - - if (m_settings.video_sync_enabled != old_vsync_enabled || m_settings.audio_sync_enabled != old_audio_sync_enabled || - m_settings.speed_limiter_enabled != old_speed_limiter_enabled) - { - UpdateSpeedLimiterState(); - } + RecreateSystem(); if (m_system) { + if (m_settings.video_sync_enabled != old_vsync_enabled || m_settings.audio_sync_enabled != old_audio_sync_enabled || + m_settings.speed_limiter_enabled != old_speed_limiter_enabled) + { + UpdateSpeedLimiterState(); + } + if (m_settings.emulation_speed != old_emulation_speed) { m_system->UpdateThrottlePeriod(); @@ -672,7 +740,7 @@ void HostInterface::UpdateSettings(const std::function<void()>& apply_callback) } } - if (m_settings.display_linear_filtering != old_display_linear_filtering) + if (m_display && m_settings.display_linear_filtering != old_display_linear_filtering) m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); } @@ -704,3 +772,30 @@ void HostInterface::ModifyResolutionScale(s32 increment) GPU::VRAM_WIDTH * m_settings.gpu_resolution_scale, GPU::VRAM_HEIGHT * m_settings.gpu_resolution_scale); } + +void HostInterface::RecreateSystem() +{ + std::unique_ptr<ByteStream> stream = ByteStream_CreateGrowableMemoryStream(nullptr, 8 * 1024); + if (!m_system->SaveState(stream.get()) || !stream->SeekAbsolute(0)) + { + ReportError("Failed to save state before system recreation. Shutting down."); + DestroySystem(); + return; + } + + DestroySystem(); + if (!BootSystemFromFile(nullptr)) + { + ReportError("Failed to boot system after recreation."); + return; + } + + if (!m_system->LoadState(stream.get())) + { + ReportError("Failed to load state after system recreation. Shutting down."); + DestroySystem(); + return; + } + + m_system->ResetPerformanceCounters(); +} diff --git a/src/core/host_interface.h b/src/core/host_interface.h index c3d551111..0892bcf93 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -12,6 +12,7 @@ #include <vector> class AudioStream; +class ByteStream; class CDImage; class HostDisplay; class GameList; @@ -27,22 +28,31 @@ public: virtual ~HostInterface(); /// Access to host display. - ALWAYS_INLINE HostDisplay* GetDisplay() const { return m_display.get(); } + ALWAYS_INLINE HostDisplay* GetDisplay() const { return m_display; } /// Access to host audio stream. - AudioStream* GetAudioStream() const { return m_audio_stream.get(); } + ALWAYS_INLINE AudioStream* GetAudioStream() const { return m_audio_stream.get(); } /// Returns a settings object which can be modified. - Settings& GetSettings() { return m_settings; } + ALWAYS_INLINE Settings& GetSettings() { return m_settings; } /// Returns the game list. - const GameList* GetGameList() const { return m_game_list.get(); } + ALWAYS_INLINE const GameList* GetGameList() const { return m_game_list.get(); } - bool CreateSystem(); - bool BootSystem(const char* filename, const char* state_filename); + bool BootSystemFromFile(const char* filename); + bool BootSystemFromBIOS(); + void PauseSystem(bool paused); void ResetSystem(); void DestroySystem(); + /// Loads the current emulation state from file. Specifying a slot of -1 loads the "resume" game state. + bool LoadState(bool global, u32 slot); + bool LoadState(const char* filename); + + /// Saves the current emulation state to a file. Specifying a slot of -1 saves the "resume" save state. + bool SaveState(bool global, u32 slot); + bool SaveState(const char* filename); + virtual void ReportError(const char* message); virtual void ReportMessage(const char* message); @@ -53,12 +63,8 @@ public: void AddOSDMessage(const char* message, float duration = 2.0f); void AddFormattedOSDMessage(float duration, const char* format, ...); - /// Loads the BIOS image for the specified region. - virtual std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region); - - /// Returns the base user directory path. - const std::string& GetUserDirectory() const { return m_user_directory; } + ALWAYS_INLINE const std::string& GetUserDirectory() const { return m_user_directory; } /// Returns a path relative to the user directory. std::string GetUserDirectoryRelativePath(const char* format, ...) const; @@ -89,7 +95,13 @@ protected: bool global; }; - virtual void SwitchGPURenderer(); + virtual bool AcquireHostDisplay() = 0; + virtual void ReleaseHostDisplay() = 0; + virtual std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) = 0; + + virtual void OnSystemCreated(); + virtual void OnSystemPaused(bool paused); + virtual void OnSystemDestroyed(); virtual void OnSystemPerformanceCountersUpdated(); virtual void OnRunningGameChanged(); @@ -122,13 +134,8 @@ protected: /// Returns a list of save states for the specified game code. std::vector<SaveStateInfo> GetAvailableSaveStates(const char* game_code) const; - /// Loads the current emulation state from file. Specifying a slot of -1 loads the "resume" game state. - bool LoadState(bool global, u32 slot); - bool LoadState(const char* filename); - - /// Saves the current emulation state to a file. Specifying a slot of -1 saves the "resume" save state. - bool SaveState(bool global, u32 slot); - bool SaveState(const char* filename); + /// Loads the BIOS image for the specified region. + std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region); /// Restores all settings to defaults. void SetDefaultSettings(); @@ -143,14 +150,16 @@ protected: /// Adjusts the internal (render) resolution of the hardware backends. void ModifyResolutionScale(s32 increment); + /// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed). + void RecreateSystem(); + void UpdateSpeedLimiterState(); void DrawFPSWindow(); void DrawOSDMessages(); void DrawDebugWindows(); - void ClearImGuiFocus(); - std::unique_ptr<HostDisplay> m_display; + HostDisplay* m_display = nullptr; std::unique_ptr<AudioStream> m_audio_stream; std::unique_ptr<System> m_system; std::unique_ptr<GameList> m_game_list; diff --git a/src/duckstation-qt/d3d11displaywindow.cpp b/src/duckstation-qt/d3d11displaywindow.cpp index b7a8ad2f8..910ea7124 100644 --- a/src/duckstation-qt/d3d11displaywindow.cpp +++ b/src/duckstation-qt/d3d11displaywindow.cpp @@ -169,6 +169,11 @@ void D3D11DisplayWindow::onWindowResized(int width, int height) Panic("Failed to recreate swap chain RTV after resize"); } +bool D3D11DisplayWindow::hasDeviceContext() const +{ + return static_cast<bool>(m_device); +} + bool D3D11DisplayWindow::createDeviceContext(QThread* worker_thread, bool debug_device) { ComPtr<IDXGIFactory> dxgi_factory; diff --git a/src/duckstation-qt/d3d11displaywindow.h b/src/duckstation-qt/d3d11displaywindow.h index c3a529cca..9eab0fc25 100644 --- a/src/duckstation-qt/d3d11displaywindow.h +++ b/src/duckstation-qt/d3d11displaywindow.h @@ -21,6 +21,7 @@ public: HostDisplay* getHostDisplayInterface() override; + bool hasDeviceContext() const override; bool createDeviceContext(QThread* worker_thread, bool debug_device) override; bool initializeDeviceContext(bool debug_device) override; void destroyDeviceContext() override; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 6bd691226..868184ec8 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -1,8 +1,10 @@ #include "mainwindow.h" +#include "common/assert.h" #include "core/game_list.h" #include "core/settings.h" #include "gamelistsettingswidget.h" #include "gamelistwidget.h" +#include "qtdisplaywindow.h" #include "qthostinterface.h" #include "qtsettingsinterface.h" #include "settingsdialog.h" @@ -28,8 +30,7 @@ MainWindow::MainWindow(QtHostInterface* host_interface) : QMainWindow(nullptr), MainWindow::~MainWindow() { - delete m_display_widget; - m_host_interface->displayWidgetDestroyed(); + Assert(!m_display_widget); } void MainWindow::reportError(QString message) @@ -42,31 +43,40 @@ void MainWindow::reportMessage(QString message) m_ui.statusBar->showMessage(message, 2000); } -void MainWindow::onEmulationStarting() +void MainWindow::createDisplayWindow(QThread* worker_thread, bool use_debug_device) { + DebugAssert(!m_display_widget); + + QtDisplayWindow* display_window = m_host_interface->createDisplayWindow(); + DebugAssert(display_window); + + m_display_widget = QWidget::createWindowContainer(display_window, m_ui.mainContainer); + DebugAssert(m_display_widget); + + m_display_widget->setFocusPolicy(Qt::StrongFocus); + m_ui.mainContainer->insertWidget(1, m_display_widget); + + // we need the surface visible.. this might be able to be replaced with something else switchToEmulationView(); - updateEmulationActions(true, false); - - // we need the surface visible.. QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + display_window->createDeviceContext(worker_thread, use_debug_device); } -void MainWindow::onEmulationStarted() +void MainWindow::destroyDisplayWindow() { - m_emulation_running = true; - updateEmulationActions(false, true); -} + DebugAssert(m_display_widget); + + const bool was_fullscreen = m_display_widget->isFullScreen(); + if (was_fullscreen) + toggleFullscreen(); -void MainWindow::onEmulationStopped() -{ - m_emulation_running = false; - updateEmulationActions(false, false); switchToGameListView(); -} -void MainWindow::onEmulationPaused(bool paused) -{ - m_ui.actionPause->setChecked(paused); + // recreate the display widget using the potentially-new renderer + m_ui.mainContainer->removeWidget(m_display_widget); + delete m_display_widget; + m_display_widget = nullptr; } void MainWindow::toggleFullscreen() @@ -91,36 +101,22 @@ void MainWindow::toggleFullscreen() m_ui.actionFullscreen->setChecked(fullscreen); } -void MainWindow::recreateDisplayWidget(bool create_device_context) +void MainWindow::onEmulationStarted() { - const bool was_fullscreen = m_display_widget->isFullScreen(); - if (was_fullscreen) - toggleFullscreen(); + m_emulation_running = true; + updateEmulationActions(false, true); +} +void MainWindow::onEmulationStopped() +{ + m_emulation_running = false; + updateEmulationActions(false, false); switchToGameListView(); +} - // recreate the display widget using the potentially-new renderer - m_ui.mainContainer->removeWidget(m_display_widget); - m_host_interface->displayWidgetDestroyed(); - delete m_display_widget; - 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 (create_device_context && !m_host_interface->createDisplayDeviceContext()) - { - QMessageBox::critical(this, tr("DuckStation Error"), - tr("Failed to create new device context on renderer switch. Cannot continue.")); - QCoreApplication::exit(); - return; - } - - updateDebugMenuGPURenderer(); +void MainWindow::onEmulationPaused(bool paused) +{ + m_ui.actionPause->setChecked(paused); } void MainWindow::onSystemPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time, @@ -149,7 +145,7 @@ void MainWindow::onStartDiscActionTriggered() if (filename.isEmpty()) return; - m_host_interface->bootSystem(std::move(filename), QString()); + m_host_interface->bootSystemFromFile(std::move(filename)); } void MainWindow::onChangeDiscFromFileActionTriggered() @@ -168,11 +164,6 @@ void MainWindow::onChangeDiscFromGameListActionTriggered() switchToGameListView(); } -void MainWindow::onStartBiosActionTriggered() -{ - m_host_interface->bootSystem(QString(), QString()); -} - static void OpenURL(QWidget* parent, const char* url) { const QUrl qurl(QUrl::fromEncoded(QByteArray(url, static_cast<int>(std::strlen(url))))); @@ -200,10 +191,6 @@ void MainWindow::setupAdditionalUi() m_game_list_widget = new GameListWidget(m_ui.mainContainer); m_game_list_widget->initialize(m_host_interface); m_ui.mainContainer->insertWidget(0, m_game_list_widget); - - m_display_widget = m_host_interface->createDisplayWidget(m_ui.mainContainer); - m_ui.mainContainer->insertWidget(1, m_display_widget); - m_ui.mainContainer->setCurrentIndex(0); m_status_speed_widget = new QLabel(m_ui.statusBar); @@ -304,14 +291,14 @@ void MainWindow::connectSignals() onEmulationPaused(false); connect(m_ui.actionStartDisc, &QAction::triggered, this, &MainWindow::onStartDiscActionTriggered); - connect(m_ui.actionStartBios, &QAction::triggered, this, &MainWindow::onStartBiosActionTriggered); + connect(m_ui.actionStartBios, &QAction::triggered, m_host_interface, &QtHostInterface::bootSystemFromBIOS); connect(m_ui.actionChangeDisc, &QAction::triggered, [this] { m_ui.menuChangeDisc->exec(QCursor::pos()); }); connect(m_ui.actionChangeDiscFromFile, &QAction::triggered, this, &MainWindow::onChangeDiscFromFileActionTriggered); connect(m_ui.actionChangeDiscFromGameList, &QAction::triggered, this, &MainWindow::onChangeDiscFromGameListActionTriggered); connect(m_ui.actionAddGameDirectory, &QAction::triggered, [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); }); - connect(m_ui.actionPowerOff, &QAction::triggered, [this]() { m_host_interface->powerOffSystem(true, false); }); + connect(m_ui.actionPowerOff, &QAction::triggered, [this]() { m_host_interface->destroySystem(true, false); }); connect(m_ui.actionReset, &QAction::triggered, m_host_interface, &QtHostInterface::resetSystem); connect(m_ui.actionPause, &QAction::toggled, m_host_interface, &QtHostInterface::pauseSystem); connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); }); @@ -336,14 +323,14 @@ void MainWindow::connectSignals() connect(m_host_interface, &QtHostInterface::errorReported, this, &MainWindow::reportError, Qt::BlockingQueuedConnection); + connect(m_host_interface, &QtHostInterface::createDisplayWindowRequested, this, &MainWindow::createDisplayWindow, + Qt::BlockingQueuedConnection); + connect(m_host_interface, &QtHostInterface::destroyDisplayWindowRequested, this, &MainWindow::destroyDisplayWindow); + connect(m_host_interface, &QtHostInterface::toggleFullscreenRequested, this, &MainWindow::toggleFullscreen); connect(m_host_interface, &QtHostInterface::messageReported, this, &MainWindow::reportMessage); - connect(m_host_interface, &QtHostInterface::emulationStarting, this, &MainWindow::onEmulationStarting); connect(m_host_interface, &QtHostInterface::emulationStarted, this, &MainWindow::onEmulationStarted); connect(m_host_interface, &QtHostInterface::emulationStopped, this, &MainWindow::onEmulationStopped); connect(m_host_interface, &QtHostInterface::emulationPaused, this, &MainWindow::onEmulationPaused); - connect(m_host_interface, &QtHostInterface::toggleFullscreenRequested, this, &MainWindow::toggleFullscreen); - connect(m_host_interface, &QtHostInterface::recreateDisplayWidgetRequested, this, &MainWindow::recreateDisplayWidget, - Qt::BlockingQueuedConnection); connect(m_host_interface, &QtHostInterface::systemPerformanceCountersUpdated, this, &MainWindow::onSystemPerformanceCountersUpdated); connect(m_host_interface, &QtHostInterface::runningGameChanged, this, &MainWindow::onRunningGameChanged); @@ -353,7 +340,7 @@ void MainWindow::connectSignals() QString path = QString::fromStdString(entry->path); if (!m_emulation_running) { - m_host_interface->bootSystem(path, QString()); + m_host_interface->bootSystemFromFile(path); } else { @@ -446,6 +433,6 @@ void MainWindow::updateDebugMenuGPURenderer() void MainWindow::closeEvent(QCloseEvent* event) { - m_host_interface->powerOffSystem(true, true); + m_host_interface->destroySystem(true, true); QMainWindow::closeEvent(event); } diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index a6f1f92e3..a5edd98fc 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -8,6 +8,7 @@ #include "ui_mainwindow.h" class QLabel; +class QThread; class GameListWidget; class QtHostInterface; @@ -23,12 +24,12 @@ public: private Q_SLOTS: void reportError(QString message); void reportMessage(QString message); - void onEmulationStarting(); + void createDisplayWindow(QThread* worker_thread, bool use_debug_device); + void destroyDisplayWindow(); + void toggleFullscreen(); void onEmulationStarted(); void onEmulationStopped(); void onEmulationPaused(bool paused); - void toggleFullscreen(); - void recreateDisplayWidget(bool create_device_context); void onSystemPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time, float worst_frame_time); void onRunningGameChanged(QString filename, QString game_code, QString game_title); @@ -36,7 +37,6 @@ private Q_SLOTS: void onStartDiscActionTriggered(); void onChangeDiscFromFileActionTriggered(); void onChangeDiscFromGameListActionTriggered(); - void onStartBiosActionTriggered(); void onGitHubRepositoryActionTriggered(); void onIssueTrackerActionTriggered(); void onAboutActionTriggered(); diff --git a/src/duckstation-qt/opengldisplaywindow.cpp b/src/duckstation-qt/opengldisplaywindow.cpp index d93f4f75e..37ac63253 100644 --- a/src/duckstation-qt/opengldisplaywindow.cpp +++ b/src/duckstation-qt/opengldisplaywindow.cpp @@ -223,6 +223,11 @@ static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLen } } +bool OpenGLDisplayWindow::hasDeviceContext() const +{ + return static_cast<bool>(m_gl_context); +} + bool OpenGLDisplayWindow::createDeviceContext(QThread* worker_thread, bool debug_device) { m_gl_context = std::make_unique<QOpenGLContext>(); diff --git a/src/duckstation-qt/opengldisplaywindow.h b/src/duckstation-qt/opengldisplaywindow.h index a4d35e205..4c3ca04fb 100644 --- a/src/duckstation-qt/opengldisplaywindow.h +++ b/src/duckstation-qt/opengldisplaywindow.h @@ -27,6 +27,7 @@ public: HostDisplay* getHostDisplayInterface() override; + bool hasDeviceContext() const override; bool createDeviceContext(QThread* worker_thread, bool debug_device) override; bool initializeDeviceContext(bool debug_device) override; void destroyDeviceContext() override; diff --git a/src/duckstation-qt/qtdisplaywindow.cpp b/src/duckstation-qt/qtdisplaywindow.cpp index 0605db321..14e3c53c9 100644 --- a/src/duckstation-qt/qtdisplaywindow.cpp +++ b/src/duckstation-qt/qtdisplaywindow.cpp @@ -18,6 +18,11 @@ HostDisplay* QtDisplayWindow::getHostDisplayInterface() return nullptr; } +bool QtDisplayWindow::hasDeviceContext() const +{ + return true; +} + bool QtDisplayWindow::createDeviceContext(QThread* worker_thread, bool debug_device) { return true; diff --git a/src/duckstation-qt/qtdisplaywindow.h b/src/duckstation-qt/qtdisplaywindow.h index abbd6c19c..01ad8b243 100644 --- a/src/duckstation-qt/qtdisplaywindow.h +++ b/src/duckstation-qt/qtdisplaywindow.h @@ -18,6 +18,7 @@ public: virtual HostDisplay* getHostDisplayInterface(); + virtual bool hasDeviceContext() const; virtual bool createDeviceContext(QThread* worker_thread, bool debug_device); virtual bool initializeDeviceContext(bool debug_device); virtual void destroyDeviceContext(); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index cef694278..7b787d06b 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -151,8 +151,10 @@ void QtHostInterface::refreshGameList(bool invalidate_cache /* = false */, bool emit gameListRefreshed(); } -QWidget* QtHostInterface::createDisplayWidget(QWidget* parent) +QtDisplayWindow* QtHostInterface::createDisplayWindow() { + Assert(!m_display_window); + #ifdef WIN32 if (m_settings.gpu_renderer == GPURenderer::HardwareOpenGL) m_display_window = new OpenGLDisplayWindow(this, nullptr); @@ -162,40 +164,29 @@ QWidget* QtHostInterface::createDisplayWidget(QWidget* parent) m_display_window = new OpenGLDisplayWindow(this, nullptr); #endif connect(m_display_window, &QtDisplayWindow::windowResizedEvent, this, &QtHostInterface::onDisplayWindowResized); - - m_display.release(); - m_display = std::unique_ptr<HostDisplay>(m_display_window->getHostDisplayInterface()); - m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); - - QWidget* widget = QWidget::createWindowContainer(m_display_window, parent); - widget->setFocusPolicy(Qt::StrongFocus); - return widget; + return m_display_window; } -bool QtHostInterface::createDisplayDeviceContext() +void QtHostInterface::bootSystemFromFile(QString filename) { - return m_display_window->createDeviceContext(m_worker_thread, m_settings.gpu_use_debug_device); -} - -void QtHostInterface::displayWidgetDestroyed() -{ - m_display.release(); - m_display_window = nullptr; -} - -void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_state_filename) -{ - Assert(!isOnWorkerThread()); - emit emulationStarting(); - - if (!createDisplayDeviceContext()) + if (!isOnWorkerThread()) { - emit emulationStopped(); + QMetaObject::invokeMethod(this, "bootSystemFromFile", Qt::QueuedConnection, Q_ARG(QString, filename)); return; } - QMetaObject::invokeMethod(this, "doBootSystem", Qt::QueuedConnection, Q_ARG(QString, initial_filename), - Q_ARG(QString, initial_save_state_filename)); + HostInterface::BootSystemFromFile(filename.toStdString().c_str()); +} + +void QtHostInterface::bootSystemFromBIOS() +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "bootSystemFromBIOS", Qt::QueuedConnection); + return; + } + + HostInterface::BootSystemFromBIOS(); } void QtHostInterface::handleKeyEvent(int key, bool pressed) @@ -223,51 +214,82 @@ void QtHostInterface::onDisplayWindowResized(int width, int height) m_display_window->onWindowResized(width, height); } -void QtHostInterface::SwitchGPURenderer() +bool QtHostInterface::AcquireHostDisplay() { - // Due to the GPU class owning textures, we have to shut the system down. - std::unique_ptr<ByteStream> 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"); + DebugAssert(!m_display_window); - DestroySystem(); - m_audio_stream->PauseOutput(true); + emit createDisplayWindowRequested(m_worker_thread, m_settings.gpu_use_debug_device); + if (!m_display_window->hasDeviceContext()) + { + m_display_window = nullptr; + emit destroyDisplayWindowRequested(); + return false; + } + + if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device)) + { m_display_window->destroyDeviceContext(); + m_display_window = nullptr; + emit destroyDisplayWindowRequested(); + return false; } - const bool restore_state = static_cast<bool>(stream); - emit recreateDisplayWidgetRequested(restore_state); - Assert(m_display_window != nullptr); + m_display = m_display_window->getHostDisplayInterface(); + return true; +} - if (restore_state) +void QtHostInterface::ReleaseHostDisplay() +{ + DebugAssert(m_display_window && m_display == m_display_window->getHostDisplayInterface()); + m_display = nullptr; + m_display_window->disconnect(this); + m_display_window->destroyDeviceContext(); + m_display_window = nullptr; + emit destroyDisplayWindowRequested(); +} + +std::unique_ptr<AudioStream> QtHostInterface::CreateAudioStream(AudioBackend backend) +{ + switch (backend) { - if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device)) - { - emit runningGameChanged(QString(), QString(), QString()); - emit emulationStopped(); - return; - } + case AudioBackend::Default: + case AudioBackend::Cubeb: + return AudioStream::CreateCubebAudioStream(); - CreateSystem(); - if (!BootSystem(nullptr, nullptr) || !m_system->LoadState(stream.get())) - { - ReportError("Failed to load state after GPU renderer switch, resetting"); - m_system->Reset(); - } + case AudioBackend::Null: + return AudioStream::CreateNullAudioStream(); - if (!m_paused) - { - m_audio_stream->PauseOutput(false); - UpdateSpeedLimiterState(); - } - - m_system->ResetPerformanceCounters(); + default: + return nullptr; } } +void QtHostInterface::OnSystemCreated() +{ + HostInterface::OnSystemCreated(); + + wakeThread(); + + emit emulationStarted(); +} + +void QtHostInterface::OnSystemPaused(bool paused) +{ + HostInterface::OnSystemPaused(paused); + + if (!paused) + wakeThread(); + + emit emulationPaused(paused); +} + +void QtHostInterface::OnSystemDestroyed() +{ + HostInterface::OnSystemDestroyed(); + + emit emulationStopped(); +} + void QtHostInterface::OnSystemPerformanceCountersUpdated() { HostInterface::OnSystemPerformanceCountersUpdated(); @@ -457,11 +479,11 @@ void QtHostInterface::addButtonToInputMap(const QString& binding, InputButtonHan } } -void QtHostInterface::powerOffSystem(bool save_resume_state /* = false */, bool block_until_done /* = false */) +void QtHostInterface::destroySystem(bool save_resume_state /* = false */, bool block_until_done /* = false */) { if (!isOnWorkerThread()) { - QMetaObject::invokeMethod(this, "powerOffSystem", + QMetaObject::invokeMethod(this, "destroySystem", block_until_done ? Qt::BlockingQueuedConnection : Qt::QueuedConnection, Q_ARG(bool, save_resume_state), Q_ARG(bool, block_until_done)); return; @@ -470,15 +492,7 @@ void QtHostInterface::powerOffSystem(bool save_resume_state /* = false */, bool if (!m_system) return; - if (save_resume_state) - Log_InfoPrintf("TODO: Save resume state"); - DestroySystem(); - m_audio_stream->PauseOutput(true); - m_display_window->destroyDeviceContext(); - - emit runningGameChanged(QString(), QString(), QString()); - emit emulationStopped(); } void QtHostInterface::resetSystem() @@ -515,59 +529,6 @@ void QtHostInterface::pauseSystem(bool paused) void QtHostInterface::changeDisc(QString new_disc_filename) {} -void QtHostInterface::doBootSystem(QString initial_filename, QString initial_save_state_filename) -{ - if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device)) - { - emit emulationStopped(); - return; - } - - std::string initial_filename_str = initial_filename.toStdString(); - std::string initial_save_state_filename_str = initial_save_state_filename.toStdString(); - std::lock_guard<std::mutex> lock(m_qsettings_mutex); - if (!CreateSystem() || - !BootSystem(initial_filename_str.empty() ? nullptr : initial_filename_str.c_str(), - initial_save_state_filename_str.empty() ? nullptr : initial_save_state_filename_str.c_str())) - { - DestroySystem(); - m_display_window->destroyDeviceContext(); - emit emulationStopped(); - return; - } - - wakeThread(); - m_audio_stream->PauseOutput(false); - UpdateSpeedLimiterState(); - emit emulationStarted(); -} - -void QtHostInterface::createAudioStream() -{ - switch (m_settings.audio_backend) - { - case AudioBackend::Default: - case AudioBackend::Cubeb: - m_audio_stream = AudioStream::CreateCubebAudioStream(); - break; - - case AudioBackend::Null: - default: - m_audio_stream = AudioStream::CreateNullAudioStream(); - break; - } - - if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4)) - { - qWarning() << "Failed to configure audio stream, falling back to null output"; - - // fall back to null output - m_audio_stream.reset(); - m_audio_stream = AudioStream::CreateNullAudioStream(); - m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4); - } -} - void QtHostInterface::populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu) { const std::vector<SaveStateInfo> available_states(GetAvailableSaveStates(game_code)); @@ -626,10 +587,7 @@ void QtHostInterface::loadState(QString filename) return; } - if (m_system) - LoadState(filename.toStdString().c_str()); - else - doBootSystem(QString(), filename); + LoadState(filename.toStdString().c_str()); } void QtHostInterface::loadState(bool global, qint32 slot) @@ -640,19 +598,7 @@ void QtHostInterface::loadState(bool global, qint32 slot) return; } - if (m_system) - { - LoadState(slot, global); - return; - } - - if (!global) - { - // can't load a non-global system without a game code - return; - } - - loadState(QString::fromStdString(GetGlobalSaveStateFileName(slot))); + LoadState(global, slot); } void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done /* = false */) @@ -694,8 +640,6 @@ void QtHostInterface::threadEntryPoint() { m_worker_thread_event_loop = new QEventLoop(); - createAudioStream(); - // TODO: Event which flags the thread as ready while (!m_shutdown_flag.load()) { diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 63d3aa698..e0ff40d65 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -45,11 +45,7 @@ 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); + QtDisplayWindow* createDisplayWindow(); void updateInputMap(); void handleKeyEvent(int key, bool pressed); @@ -67,20 +63,22 @@ public: Q_SIGNALS: void errorReported(QString message); void messageReported(QString message); - void emulationStarting(); void emulationStarted(); void emulationStopped(); void emulationPaused(bool paused); void gameListRefreshed(); + void createDisplayWindowRequested(QThread* worker_thread, bool use_debug_device); + void destroyDisplayWindowRequested(); void toggleFullscreenRequested(); - void recreateDisplayWidgetRequested(bool create_device_context); void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time); void runningGameChanged(QString filename, QString game_code, QString game_title); public Q_SLOTS: void applySettings(); - void powerOffSystem(bool save_resume_state = false, bool block_until_done = false); + void bootSystemFromFile(QString filename); + void bootSystemFromBIOS(); + void destroySystem(bool save_resume_state = false, bool block_until_done = false); void resetSystem(); void pauseSystem(bool paused); void changeDisc(QString new_disc_filename); @@ -90,13 +88,18 @@ public Q_SLOTS: private Q_SLOTS: void doStopThread(); - void doBootSystem(QString initial_filename, QString initial_save_state_filename); void doUpdateInputMap(); void doHandleKeyEvent(int key, bool pressed); void onDisplayWindowResized(int width, int height); protected: - void SwitchGPURenderer() override; + bool AcquireHostDisplay() override; + void ReleaseHostDisplay() override; + std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override; + + void OnSystemCreated() override; + void OnSystemPaused(bool paused) override; + void OnSystemDestroyed() override; void OnSystemPerformanceCountersUpdated() override; void OnRunningGameChanged() override; @@ -122,7 +125,6 @@ private: void updateControllerInputMap(); void updateHotkeyInputMap(); void addButtonToInputMap(const QString& binding, InputButtonHandler handler); - void createAudioStream(); void createThread(); void stopThread(); void threadEntryPoint(); diff --git a/src/duckstation-sdl/d3d11_host_display.h b/src/duckstation-sdl/d3d11_host_display.h index 9260c7582..285558fe5 100644 --- a/src/duckstation-sdl/d3d11_host_display.h +++ b/src/duckstation-sdl/d3d11_host_display.h @@ -69,5 +69,5 @@ private: D3D11::StreamBuffer m_display_uniform_buffer; bool m_allow_tearing_supported = false; - bool m_vsync = false; + bool m_vsync = true; }; diff --git a/src/duckstation-sdl/main.cpp b/src/duckstation-sdl/main.cpp index f670da553..9e831aa2d 100644 --- a/src/duckstation-sdl/main.cpp +++ b/src/duckstation-sdl/main.cpp @@ -15,28 +15,26 @@ static int Run(int argc, char* argv[]) } // parameters - const char* filename = nullptr; - const char* exp1_filename = nullptr; - std::string state_filename; + std::optional<s32> state_index; + const char* boot_filename = nullptr; for (int i = 1; i < argc; i++) { #define CHECK_ARG(str) !std::strcmp(argv[i], str) #define CHECK_ARG_PARAM(str) (!std::strcmp(argv[i], str) && ((i + 1) < argc)) if (CHECK_ARG_PARAM("-state")) - state_filename = SDLHostInterface::GetSaveStateFilename(std::strtoul(argv[++i], nullptr, 10)); - else if (CHECK_ARG_PARAM("-exp1")) - exp1_filename = argv[++i]; + state_index = std::atoi(argv[++i]); + if (CHECK_ARG_PARAM("-resume")) + state_index = -1; else - filename = argv[i]; + boot_filename = argv[i]; #undef CHECK_ARG #undef CHECK_ARG_PARAM } // create display and host interface - std::unique_ptr<SDLHostInterface> host_interface = - SDLHostInterface::Create(filename, exp1_filename, state_filename.empty() ? nullptr : state_filename.c_str()); + std::unique_ptr<SDLHostInterface> host_interface = SDLHostInterface::Create(); if (!host_interface) { Panic("Failed to create host interface"); @@ -44,6 +42,17 @@ static int Run(int argc, char* argv[]) return -1; } + // boot/load state + if (boot_filename) + { + if (host_interface->BootSystemFromFile(boot_filename) && state_index.has_value()) + host_interface->LoadState(false, state_index.value()); + } + else if (state_index.has_value()) + { + host_interface->LoadState(true, state_index.value()); + } + // run host_interface->Run(); diff --git a/src/duckstation-sdl/opengl_host_display.cpp b/src/duckstation-sdl/opengl_host_display.cpp index 5948b9857..50b2c0fa6 100644 --- a/src/duckstation-sdl/opengl_host_display.cpp +++ b/src/duckstation-sdl/opengl_host_display.cpp @@ -262,6 +262,8 @@ bool OpenGLHostDisplay::CreateGLContext(bool debug_device) glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } + // start with vsync on + SDL_GL_SetSwapInterval(1); return true; } diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 009edcb41..e0467353e 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -33,17 +33,20 @@ SDLHostInterface::SDLHostInterface() timeBeginPeriod(1); #endif - m_switch_gpu_renderer_event_id = SDL_RegisterEvents(1); + m_update_settings_event_id = SDL_RegisterEvents(1); } SDLHostInterface::~SDLHostInterface() { CloseGameControllers(); - m_display.reset(); - ImGui::DestroyContext(); + if (m_display) + { + DestroyDisplay(); + ImGui::DestroyContext(); + } if (m_window) - SDL_DestroyWindow(m_window); + DestroySDLWindow(); #ifdef WIN32 timeEndPeriod(1); @@ -87,30 +90,31 @@ void SDLHostInterface::DestroySDLWindow() bool SDLHostInterface::CreateDisplay() { const bool debug_device = m_settings.gpu_use_debug_device; + std::unique_ptr<HostDisplay> display; #ifdef WIN32 - m_display = UseOpenGLRenderer() ? OpenGLHostDisplay::Create(m_window, debug_device) : - D3D11HostDisplay::Create(m_window, debug_device); + display = UseOpenGLRenderer() ? OpenGLHostDisplay::Create(m_window, debug_device) : + D3D11HostDisplay::Create(m_window, debug_device); #else - m_display = OpenGLHostDisplay::Create(m_window, debug_device); + display = OpenGLHostDisplay::Create(m_window, debug_device); #endif - if (!m_display) + if (!display) return false; - m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); - m_app_icon_texture = - m_display->CreateTexture(APP_ICON_WIDTH, APP_ICON_HEIGHT, APP_ICON_DATA, APP_ICON_WIDTH * sizeof(u32)); - if (!m_app_icon_texture) + display->CreateTexture(APP_ICON_WIDTH, APP_ICON_HEIGHT, APP_ICON_DATA, APP_ICON_WIDTH * sizeof(u32)); + if (!display) return false; + m_display = display.release(); return true; } void SDLHostInterface::DestroyDisplay() { m_app_icon_texture.reset(); - m_display.reset(); + delete m_display; + m_display = nullptr; } void SDLHostInterface::CreateImGuiContext() @@ -123,101 +127,93 @@ void SDLHostInterface::CreateImGuiContext() ImGui::AddRobotoRegularFont(); } -void SDLHostInterface::CreateAudioStream() +bool SDLHostInterface::AcquireHostDisplay() +{ + // Handle renderer switch if required on Windows. +#ifdef WIN32 + const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI(); + const bool render_api_is_gl = + render_api == HostDisplay::RenderAPI::OpenGL || render_api == HostDisplay::RenderAPI::OpenGLES; + const bool render_api_wants_gl = UseOpenGLRenderer(); + if (render_api_is_gl != render_api_wants_gl) + { + ImGui::EndFrame(); + DestroyDisplay(); + DestroySDLWindow(); + + if (!CreateSDLWindow()) + Panic("Failed to recreate SDL window on GPU renderer switch"); + + if (!CreateDisplay()) + Panic("Failed to recreate display on GPU renderer switch"); + + ImGui::NewFrame(); + } +#endif + + return true; +} + +void SDLHostInterface::ReleaseHostDisplay() +{ + // restore vsync, since we don't want to burn cycles at the menu + m_display->SetVSync(true); +} + +std::unique_ptr<AudioStream> SDLHostInterface::CreateAudioStream(AudioBackend backend) { switch (m_settings.audio_backend) { case AudioBackend::Null: - m_audio_stream = AudioStream::CreateNullAudioStream(); - break; + return AudioStream::CreateNullAudioStream(); case AudioBackend::Cubeb: - m_audio_stream = AudioStream::CreateCubebAudioStream(); - break; + return AudioStream::CreateCubebAudioStream(); case AudioBackend::Default: - default: - m_audio_stream = std::make_unique<SDLAudioStream>(); - break; - } + return std::make_unique<SDLAudioStream>(); - if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS)) - { - ReportError("Failed to recreate audio stream, falling back to null"); - m_audio_stream.reset(); - m_audio_stream = AudioStream::CreateNullAudioStream(); - if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS)) - Panic("Failed to reconfigure null audio stream"); + default: + return nullptr; } } +void SDLHostInterface::OnSystemCreated() +{ + HostInterface::OnSystemCreated(); + + UpdateKeyboardControllerMapping(); + UpdateControllerControllerMapping(); + ClearImGuiFocus(); +} + +void SDLHostInterface::OnSystemPaused(bool paused) +{ + HostInterface::OnSystemPaused(paused); + + if (!paused) + ClearImGuiFocus(); +} + +void SDLHostInterface::OnSystemDestroyed() +{ + HostInterface::OnSystemDestroyed(); +} + void SDLHostInterface::SaveSettings() { SDLSettingsInterface si(GetSettingsFileName().c_str()); m_settings.Save(si); } -void SDLHostInterface::QueueSwitchGPURenderer() +void SDLHostInterface::QueueUpdateSettings() { SDL_Event ev = {}; ev.type = SDL_USEREVENT; - ev.user.code = m_switch_gpu_renderer_event_id; + ev.user.code = m_update_settings_event_id; SDL_PushEvent(&ev); } -void SDLHostInterface::SwitchGPURenderer() -{ - // Due to the GPU class owning textures, we have to shut the system down. - std::unique_ptr<ByteStream> 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(); - } - - ImGui::EndFrame(); - DestroyDisplay(); - DestroySDLWindow(); - - if (!CreateSDLWindow()) - Panic("Failed to recreate SDL window on GPU renderer switch"); - - if (!CreateDisplay()) - Panic("Failed to recreate display on GPU renderer switch"); - - ImGui::NewFrame(); - - if (stream) - { - CreateSystem(); - if (!BootSystem(nullptr, nullptr) || !m_system->LoadState(stream.get())) - { - ReportError("Failed to load state after GPU renderer switch, resetting"); - m_system->Reset(); - } - } - - UpdateFullscreen(); - if (m_system) - m_system->ResetPerformanceCounters(); - ClearImGuiFocus(); -} - -void SDLHostInterface::SwitchAudioBackend() -{ - m_audio_stream.reset(); - CreateAudioStream(); - - if (m_system) - { - m_audio_stream->PauseOutput(false); - UpdateSpeedLimiterState(); - } -} - void SDLHostInterface::UpdateFullscreen() { SDL_SetWindowFullscreen(m_window, m_settings.display_fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); @@ -227,15 +223,7 @@ void SDLHostInterface::UpdateFullscreen() m_settings.display_fullscreen ? 0 : static_cast<int>(20.0f * ImGui::GetIO().DisplayFramebufferScale.x)); } -void SDLHostInterface::UpdateControllerMapping() -{ - UpdateKeyboardControllerMapping(); - UpdateControllerControllerMapping(); -} - -std::unique_ptr<SDLHostInterface> SDLHostInterface::Create(const char* filename /* = nullptr */, - const char* exp1_filename /* = nullptr */, - const char* save_state_filename /* = nullptr */) +std::unique_ptr<SDLHostInterface> SDLHostInterface::Create() { std::unique_ptr<SDLHostInterface> intf = std::make_unique<SDLHostInterface>(); @@ -256,34 +244,13 @@ std::unique_ptr<SDLHostInterface> SDLHostInterface::Create(const char* filename return nullptr; } - intf->CreateAudioStream(); - ImGui::NewFrame(); - intf->UpdateSpeedLimiterState(); - - const bool boot = (filename != nullptr || exp1_filename != nullptr || save_state_filename != nullptr); - if (boot) - { - if (!intf->CreateSystem() || !intf->BootSystem(filename, exp1_filename)) - return nullptr; - - if (save_state_filename) - intf->LoadState(save_state_filename); - - intf->UpdateControllerMapping(); - } - intf->UpdateFullscreen(); return intf; } -std::string SDLHostInterface::GetSaveStateFilename(u32 index) -{ - return StringUtil::StdStringFromFormat("savestate_%u.bin", index); -} - void SDLHostInterface::ReportError(const char* message) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DuckStation Error", message, m_window); @@ -291,7 +258,7 @@ void SDLHostInterface::ReportError(const char* message) void SDLHostInterface::ReportMessage(const char* message) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "DuckStation Information", message, m_window); + AddOSDMessage(message, 2.0f); } void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) @@ -352,8 +319,13 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) case SDL_USEREVENT: { - if (static_cast<u32>(event->user.code) == m_switch_gpu_renderer_event_id) - SwitchGPURenderer(); + if (static_cast<u32>(event->user.code) == m_update_settings_event_id) + { + UpdateSettings([this]() { + SDLSettingsInterface si(GetSettingsFileName().c_str()); + m_settings.Load(si); + }); + } } break; } @@ -381,9 +353,9 @@ void SDLHostInterface::HandleSDLKeyEvent(const SDL_Event* event) { const u32 index = event->key.keysym.scancode - SDL_SCANCODE_F1 + 1; if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) - DoSaveState(index); + SaveState(true, index); else - DoLoadState(index); + LoadState(true, index); } } break; @@ -408,7 +380,7 @@ void SDLHostInterface::HandleSDLKeyEvent(const SDL_Event* event) case SDL_SCANCODE_PAUSE: { if (pressed) - DoTogglePause(); + PauseSystem(!m_paused); } break; @@ -792,51 +764,78 @@ void SDLHostInterface::DrawMainMenuBar() if (ImGui::BeginMenu("System")) { if (ImGui::MenuItem("Start Disc", nullptr, false, !system_enabled)) + { DoStartDisc(); + ClearImGuiFocus(); + } if (ImGui::MenuItem("Start BIOS", nullptr, false, !system_enabled)) - DoStartBIOS(); + { + BootSystemFromBIOS(); + ClearImGuiFocus(); + } ImGui::Separator(); if (ImGui::MenuItem("Power Off", nullptr, false, system_enabled)) - DoPowerOff(); + { + DestroySystem(); + ClearImGuiFocus(); + } if (ImGui::MenuItem("Reset", nullptr, false, system_enabled)) + { ResetSystem(); + ClearImGuiFocus(); + } if (ImGui::MenuItem("Pause", nullptr, m_paused, system_enabled)) - DoTogglePause(); + { + PauseSystem(!m_paused); + ClearImGuiFocus(); + } ImGui::Separator(); if (ImGui::MenuItem("Change Disc", nullptr, false, system_enabled)) + { DoChangeDisc(); + ClearImGuiFocus(); + } if (ImGui::MenuItem("Frame Step", nullptr, false, system_enabled)) + { DoFrameStep(); + ClearImGuiFocus(); + } ImGui::Separator(); if (ImGui::BeginMenu("Load State")) { - for (u32 i = 1; i <= NUM_QUICK_SAVE_STATES; i++) + for (u32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++) { char buf[16]; std::snprintf(buf, sizeof(buf), "State %u", i); if (ImGui::MenuItem(buf)) - DoLoadState(i); + { + LoadState(true, i); + ClearImGuiFocus(); + } } ImGui::EndMenu(); } if (ImGui::BeginMenu("Save State", system_enabled)) { - for (u32 i = 1; i <= NUM_QUICK_SAVE_STATES; i++) + for (u32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++) { char buf[16]; std::snprintf(buf, sizeof(buf), "State %u", i); if (ImGui::MenuItem(buf)) - DoSaveState(i); + { + SaveState(true, i); + ClearImGuiFocus(); + } } ImGui::EndMenu(); } @@ -916,12 +915,7 @@ void SDLHostInterface::DrawMainMenuBar() void SDLHostInterface::DrawQuickSettingsMenu() { bool settings_changed = false; - bool gpu_settings_changed = false; - if (ImGui::MenuItem("Enable Speed Limiter", nullptr, &m_settings.speed_limiter_enabled)) - { - settings_changed = true; - UpdateSpeedLimiterState(); - } + settings_changed |= ImGui::MenuItem("Enable Speed Limiter", nullptr, &m_settings.speed_limiter_enabled); ImGui::Separator(); @@ -935,8 +929,6 @@ void SDLHostInterface::DrawQuickSettingsMenu() { m_settings.cpu_execution_mode = static_cast<CPUExecutionMode>(i); settings_changed = true; - if (m_system) - m_system->SetCPUExecutionMode(m_settings.cpu_execution_mode); } } @@ -955,7 +947,6 @@ void SDLHostInterface::DrawQuickSettingsMenu() { m_settings.gpu_renderer = static_cast<GPURenderer>(i); settings_changed = true; - QueueSwitchGPURenderer(); } } @@ -968,11 +959,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() UpdateFullscreen(); } - if (ImGui::MenuItem("VSync", nullptr, &m_settings.video_sync_enabled)) - { - settings_changed = true; - UpdateSpeedLimiterState(); - } + settings_changed |= ImGui::MenuItem("VSync", nullptr, &m_settings.video_sync_enabled); ImGui::Separator(); @@ -987,26 +974,22 @@ void SDLHostInterface::DrawQuickSettingsMenu() if (ImGui::MenuItem(buf, nullptr, current_internal_resolution == scale)) { m_settings.gpu_resolution_scale = scale; - gpu_settings_changed = true; + settings_changed = true; } } ImGui::EndMenu(); } - gpu_settings_changed |= ImGui::MenuItem("True (24-Bit) Color", nullptr, &m_settings.gpu_true_color); - gpu_settings_changed |= ImGui::MenuItem("Texture Filtering", nullptr, &m_settings.gpu_texture_filtering); - if (ImGui::MenuItem("Display Linear Filtering", nullptr, &m_settings.display_linear_filtering)) + settings_changed |= ImGui::MenuItem("True (24-Bit) Color", nullptr, &m_settings.gpu_true_color); + settings_changed |= ImGui::MenuItem("Texture Filtering", nullptr, &m_settings.gpu_texture_filtering); + settings_changed |= ImGui::MenuItem("Display Linear Filtering", nullptr, &m_settings.display_linear_filtering); + + if (settings_changed) { - m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); - settings_changed = true; - } - - if (settings_changed || gpu_settings_changed) SaveSettings(); - - if (gpu_settings_changed && m_system) - m_system->GetGPU()->UpdateSettings(); + QueueUpdateSettings(); + } } void SDLHostInterface::DrawDebugMenu() @@ -1068,18 +1051,23 @@ void SDLHostInterface::DrawPoweredOffWindow() ImGui::PushStyleColor(ImGuiCol_ButtonHovered, 0xFF575757); ImGui::SetCursorPosX(button_left); - if (ImGui::Button("Resume", button_size)) - DoResume(); + ImGui::Button("Resume", button_size); ImGui::NewLine(); ImGui::SetCursorPosX(button_left); if (ImGui::Button("Start Disc", button_size)) + { DoStartDisc(); + ClearImGuiFocus(); + } ImGui::NewLine(); ImGui::SetCursorPosX(button_left); if (ImGui::Button("Start BIOS", button_size)) - DoStartBIOS(); + { + BootSystemFromFile(nullptr); + ClearImGuiFocus(); + } ImGui::NewLine(); ImGui::SetCursorPosX(button_left); @@ -1087,12 +1075,15 @@ void SDLHostInterface::DrawPoweredOffWindow() ImGui::OpenPopup("PowerOffWindow_LoadStateMenu"); if (ImGui::BeginPopup("PowerOffWindow_LoadStateMenu")) { - for (u32 i = 1; i <= NUM_QUICK_SAVE_STATES; i++) + for (u32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++) { char buf[16]; std::snprintf(buf, sizeof(buf), "State %u", i); if (ImGui::MenuItem(buf)) - DoLoadState(i); + { + LoadState(true, i); + ClearImGuiFocus(); + } } ImGui::EndPopup(); } @@ -1133,7 +1124,6 @@ void SDLHostInterface::DrawSettingsWindow() } bool settings_changed = false; - bool gpu_settings_changed = false; if (ImGui::BeginTabBar("SettingsTabBar", 0)) { @@ -1173,19 +1163,8 @@ void SDLHostInterface::DrawSettingsWindow() ImGui::Text("Emulation Speed:"); ImGui::SameLine(indent); - if (ImGui::SliderFloat("##speed", &m_settings.emulation_speed, 0.25f, 5.0f)) - { - settings_changed = true; - if (m_system) - m_system->UpdateThrottlePeriod(); - } - - if (ImGui::Checkbox("Enable Speed Limiter", &m_settings.speed_limiter_enabled)) - { - settings_changed = true; - UpdateSpeedLimiterState(); - } - + settings_changed |= ImGui::SliderFloat("##speed", &m_settings.emulation_speed, 0.25f, 5.0f); + settings_changed |= ImGui::Checkbox("Enable Speed Limiter", &m_settings.speed_limiter_enabled); settings_changed |= ImGui::Checkbox("Pause On Start", &m_settings.start_paused); } @@ -1206,14 +1185,9 @@ void SDLHostInterface::DrawSettingsWindow() { m_settings.audio_backend = static_cast<AudioBackend>(backend); settings_changed = true; - SwitchAudioBackend(); } - if (ImGui::Checkbox("Output Sync", &m_settings.audio_sync_enabled)) - { - settings_changed = true; - UpdateSpeedLimiterState(); - } + settings_changed |= ImGui::Checkbox("Output Sync", &m_settings.audio_sync_enabled); } ImGui::EndTabItem(); @@ -1242,11 +1216,6 @@ void SDLHostInterface::DrawSettingsWindow() { m_settings.controller_types[i] = static_cast<ControllerType>(controller_type); settings_changed = true; - if (m_system) - { - m_system->UpdateControllers(); - UpdateControllerControllerMapping(); - } } } @@ -1255,19 +1224,12 @@ void SDLHostInterface::DrawSettingsWindow() std::string* path_ptr = &m_settings.memory_card_paths[i]; std::snprintf(buf, sizeof(buf), "##memcard_%c_path", 'a' + i); - if (DrawFileChooser(buf, path_ptr)) - { - settings_changed = true; - if (m_system) - m_system->UpdateMemoryCards(); - } + settings_changed |= DrawFileChooser(buf, path_ptr); if (ImGui::Button("Eject Memory Card")) { path_ptr->clear(); settings_changed = true; - if (m_system) - m_system->UpdateMemoryCards(); } ImGui::NewLine(); @@ -1292,8 +1254,6 @@ void SDLHostInterface::DrawSettingsWindow() { m_settings.cpu_execution_mode = static_cast<CPUExecutionMode>(execution_mode); settings_changed = true; - if (m_system) - m_system->SetCPUExecutionMode(m_settings.cpu_execution_mode); } ImGui::EndTabItem(); @@ -1317,7 +1277,6 @@ void SDLHostInterface::DrawSettingsWindow() { m_settings.gpu_renderer = static_cast<GPURenderer>(gpu_renderer); settings_changed = true; - QueueSwitchGPURenderer(); } } @@ -1331,17 +1290,8 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed = true; } - if (ImGui::Checkbox("Linear Filtering", &m_settings.display_linear_filtering)) - { - m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); - settings_changed = true; - } - - if (ImGui::Checkbox("VSync", &m_settings.video_sync_enabled)) - { - settings_changed = true; - UpdateSpeedLimiterState(); - } + settings_changed |= ImGui::Checkbox("Linear Filtering", &m_settings.display_linear_filtering); + settings_changed |= ImGui::Checkbox("VSync", &m_settings.video_sync_enabled); } ImGui::NewLine(); @@ -1375,12 +1325,12 @@ void SDLHostInterface::DrawSettingsWindow() static_cast<int>(resolutions.size()))) { m_settings.gpu_resolution_scale = static_cast<u32>(current_resolution_index + 1); - gpu_settings_changed = true; + settings_changed = true; } - gpu_settings_changed |= ImGui::Checkbox("True 24-bit Color (disables dithering)", &m_settings.gpu_true_color); - gpu_settings_changed |= ImGui::Checkbox("Texture Filtering", &m_settings.gpu_texture_filtering); - gpu_settings_changed |= ImGui::Checkbox("Force Progressive Scan", &m_settings.gpu_force_progressive_scan); + settings_changed |= ImGui::Checkbox("True 24-bit Color (disables dithering)", &m_settings.gpu_true_color); + settings_changed |= ImGui::Checkbox("Texture Filtering", &m_settings.gpu_texture_filtering); + settings_changed |= ImGui::Checkbox("Force Progressive Scan", &m_settings.gpu_force_progressive_scan); } ImGui::EndTabItem(); @@ -1397,11 +1347,11 @@ void SDLHostInterface::DrawSettingsWindow() ImGui::End(); - if (settings_changed || gpu_settings_changed) + if (settings_changed) + { SaveSettings(); - - if (gpu_settings_changed && m_system) - m_system->GetGPU()->UpdateSettings(); + QueueUpdateSettings(); + } } void SDLHostInterface::DrawAboutWindow() @@ -1454,26 +1404,9 @@ bool SDLHostInterface::DrawFileChooser(const char* label, std::string* path, con return result; } -void SDLHostInterface::DoPowerOff() +void SDLHostInterface::ClearImGuiFocus() { - Assert(m_system); - DestroySystem(); - AddOSDMessage("System powered off."); -} - -void SDLHostInterface::DoResume() -{ - Assert(!m_system); - if (!CreateSystem() || !BootSystem(nullptr, RESUME_SAVESTATE_FILENAME)) - { - DestroySystem(); - return; - } - - UpdateControllerMapping(); - if (m_system) - m_system->ResetPerformanceCounters(); - ClearImGuiFocus(); + ImGui::SetWindowFocus(nullptr); } void SDLHostInterface::DoStartDisc() @@ -1485,33 +1418,7 @@ void SDLHostInterface::DoStartDisc() return; AddFormattedOSDMessage(2.0f, "Starting disc from '%s'...", path); - if (!CreateSystem() || !BootSystem(path, nullptr)) - { - DestroySystem(); - return; - } - - UpdateControllerMapping(); - if (m_system) - m_system->ResetPerformanceCounters(); - ClearImGuiFocus(); -} - -void SDLHostInterface::DoStartBIOS() -{ - Assert(!m_system); - - AddOSDMessage("Starting BIOS..."); - if (!CreateSystem() || !BootSystem(nullptr, nullptr)) - { - DestroySystem(); - return; - } - - UpdateControllerMapping(); - if (m_system) - m_system->ResetPerformanceCounters(); - ClearImGuiFocus(); + BootSystemFromFile(path); } void SDLHostInterface::DoChangeDisc() @@ -1527,47 +1434,7 @@ void SDLHostInterface::DoChangeDisc() else AddOSDMessage("Failed to switch CD. The log may contain further information."); - if (m_system) - m_system->ResetPerformanceCounters(); - ClearImGuiFocus(); -} - -void SDLHostInterface::DoLoadState(u32 index) -{ - if (HasSystem()) - { - LoadState(GetSaveStateFilename(index).c_str()); - } - else - { - if (!CreateSystem() || !BootSystem(nullptr, GetSaveStateFilename(index).c_str())) - { - DestroySystem(); - return; - } - } - - UpdateControllerMapping(); - if (m_system) - m_system->ResetPerformanceCounters(); - ClearImGuiFocus(); -} - -void SDLHostInterface::DoSaveState(u32 index) -{ - Assert(m_system); - SaveState(GetSaveStateFilename(index).c_str()); - ClearImGuiFocus(); -} - -void SDLHostInterface::DoTogglePause() -{ - if (!m_system) - return; - - m_paused = !m_paused; - if (!m_paused) - m_system->ResetPerformanceCounters(); + m_system->ResetPerformanceCounters(); } void SDLHostInterface::DoFrameStep() @@ -1587,8 +1454,6 @@ void SDLHostInterface::DoToggleFullscreen() void SDLHostInterface::Run() { - m_audio_stream->PauseOutput(false); - while (!m_quit_request) { for (;;) @@ -1636,10 +1501,5 @@ void SDLHostInterface::Run() // Save state on exit so it can be resumed if (m_system) - { - if (!SaveState(RESUME_SAVESTATE_FILENAME)) - ReportError("Saving state failed, you will not be able to resume this session."); - DestroySystem(); - } } diff --git a/src/duckstation-sdl/sdl_host_interface.h b/src/duckstation-sdl/sdl_host_interface.h index d7933438e..14f55c114 100644 --- a/src/duckstation-sdl/sdl_host_interface.h +++ b/src/duckstation-sdl/sdl_host_interface.h @@ -22,16 +22,22 @@ public: SDLHostInterface(); ~SDLHostInterface(); - static std::unique_ptr<SDLHostInterface> Create(const char* filename = nullptr, const char* exp1_filename = nullptr, - const char* save_state_filename = nullptr); - - static std::string GetSaveStateFilename(u32 index); + static std::unique_ptr<SDLHostInterface> Create(); void ReportError(const char* message) override; void ReportMessage(const char* message) override; void Run(); +protected: + bool AcquireHostDisplay() override; + void ReleaseHostDisplay() override; + std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override; + + void OnSystemCreated() override; + void OnSystemPaused(bool paused) override; + void OnSystemDestroyed(); + private: enum class KeyboardControllerAction { @@ -62,9 +68,6 @@ private: float last_rumble_strength; }; - static constexpr u32 NUM_QUICK_SAVE_STATES = 10; - static constexpr char RESUME_SAVESTATE_FILENAME[] = "savestate_resume.bin"; - bool HasSystem() const { return static_cast<bool>(m_system); } #ifdef WIN32 @@ -78,26 +81,16 @@ private: bool CreateDisplay(); void DestroyDisplay(); void CreateImGuiContext(); - void CreateAudioStream(); void SaveSettings(); + void QueueUpdateSettings(); - void QueueSwitchGPURenderer(); - void SwitchGPURenderer(); - void SwitchAudioBackend(); void UpdateFullscreen(); - void UpdateControllerMapping(); // We only pass mouse input through if it's grabbed void DrawImGui(); - void DoPowerOff(); - void DoResume(); void DoStartDisc(); - void DoStartBIOS(); void DoChangeDisc(); - void DoLoadState(u32 index); - void DoSaveState(u32 index); - void DoTogglePause(); void DoFrameStep(); void DoToggleFullscreen(); @@ -122,6 +115,7 @@ private: void DrawSettingsWindow(); void DrawAboutWindow(); bool DrawFileChooser(const char* label, std::string* path, const char* filter = nullptr); + void ClearImGuiFocus(); SDL_Window* m_window = nullptr; std::unique_ptr<HostDisplayTexture> m_app_icon_texture; @@ -132,7 +126,7 @@ private: std::array<s32, SDL_CONTROLLER_AXIS_MAX> m_controller_axis_mapping{}; std::array<s32, SDL_CONTROLLER_BUTTON_MAX> m_controller_button_mapping{}; - u32 m_switch_gpu_renderer_event_id = 0; + u32 m_update_settings_event_id = 0; bool m_quit_request = false; bool m_frame_step_request = false;