diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 6f6c6d15e..ed3d776e4 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -22,7 +22,8 @@ MainWindow::MainWindow(QtHostInterface* host_interface) : QMainWindow(nullptr), MainWindow::~MainWindow() { - m_host_interface->destroyDisplayWidget(); + delete m_display_widget; + m_host_interface->displayWidgetDestroyed(); } void MainWindow::onEmulationStarting() @@ -74,6 +75,55 @@ void MainWindow::toggleFullscreen() m_ui.actionFullscreen->setChecked(fullscreen); } +void MainWindow::switchRenderer() +{ + 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(); + } + + // 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); + + // we need the surface visible.. + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + if (!state.isEmpty()) + { + // restart the system with the new state + m_host_interface->bootSystem(QString(), QString()); + m_host_interface->loadStateFromMemory(std::move(state)); + } + + // 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); + } +} + void MainWindow::onStartDiscActionTriggered() { QString filename = @@ -128,10 +178,25 @@ void MainWindow::setupAdditionalUi() m_game_list_widget->initialize(m_host_interface); m_ui.mainContainer->insertWidget(0, m_game_list_widget); - m_display_widget = m_host_interface->createDisplayWidget(nullptr); + m_display_widget = m_host_interface->createDisplayWidget(m_ui.mainContainer); m_ui.mainContainer->insertWidget(1, m_display_widget); m_ui.mainContainer->setCurrentIndex(0); + + for (u32 i = 0; i < static_cast(GPURenderer::Count); i++) + { + 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->getQSettings().setValue(QStringLiteral("GPU/Renderer"), + QString(Settings::GetRendererName(renderer))); + m_host_interface->GetCoreSettings().gpu_renderer = renderer; + action->setChecked(true); + switchRenderer(); + }); + } } void MainWindow::updateEmulationActions(bool starting, bool running) diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index cd343e4ba..9287031c1 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -25,6 +25,7 @@ private Q_SLOTS: void onEmulationStopped(); void onEmulationPaused(bool paused); void toggleFullscreen(); + void switchRenderer(); void onStartDiscActionTriggered(); void onChangeDiscActionTriggered(); diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index aa89c0c21..1629b1349 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -58,9 +58,6 @@ Renderer - - - @@ -247,30 +244,6 @@ Fullscreen - - - true - - - Hardware (D3D11) - - - - - true - - - Hardware (OpenGL) - - - - - true - - - Software - - true diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 55015c8f1..af50bd039 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -1,4 +1,6 @@ #include "qthostinterface.h" +#include "YBaseLib/AutoReleasePtr.h" +#include "YBaseLib/ByteStream.h" #include "YBaseLib/Log.h" #include "YBaseLib/String.h" #include "common/null_audio_stream.h" @@ -137,7 +139,10 @@ void QtHostInterface::refreshGameList(bool invalidate_cache /*= false*/) QWidget* QtHostInterface::createDisplayWidget(QWidget* parent) { #ifdef WIN32 - m_display_window = new D3D11DisplayWindow(this, nullptr); + if (m_settings.gpu_renderer == GPURenderer::HardwareOpenGL) + m_display_window = new OpenGLDisplayWindow(this, nullptr); + else + m_display_window = new D3D11DisplayWindow(this, nullptr); #else m_display_window = new OpenGLDisplayWindow(this, nullptr); #endif @@ -148,7 +153,7 @@ QWidget* QtHostInterface::createDisplayWidget(QWidget* parent) return QWidget::createWindowContainer(m_display_window, parent); } -void QtHostInterface::destroyDisplayWidget() +void QtHostInterface::displayWidgetDestroyed() { m_display.release(); delete m_display_window; @@ -157,6 +162,7 @@ void QtHostInterface::destroyDisplayWidget() void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_state_filename) { + Assert(!isOnWorkerThread()); emit emulationStarting(); if (!m_display_window->createDeviceContext(m_worker_thread)) @@ -327,7 +333,7 @@ void QtHostInterface::powerOffSystem() { if (!isOnWorkerThread()) { - QMetaObject::invokeMethod(this, "doPowerOffSystem", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "powerOffSystem", Qt::QueuedConnection); return; } @@ -339,12 +345,22 @@ void QtHostInterface::powerOffSystem() m_system.reset(); m_audio_stream->PauseOutput(true); - m_audio_stream->EmptyBuffers(); m_display_window->destroyDeviceContext(); emit emulationStopped(); } +void QtHostInterface::blockingPowerOffSystem() +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "powerOffSystem", Qt::BlockingQueuedConnection); + return; + } + + powerOffSystem(); +} + void QtHostInterface::resetSystem() { if (!isOnWorkerThread()) @@ -377,6 +393,44 @@ 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; + } + + AutoReleasePtr stream = ByteStream_CreateGrowableMemoryStream(); + if (!m_system || !QtUtils::WriteQByteArrayToStream(arr, stream) || !stream->SeekAbsolute(0) || + !m_system->LoadState(stream)) + { + 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 {}; + + AutoReleasePtr stream = ByteStream_CreateGrowableMemoryStream(); + if (m_system->SaveState(stream)) + return QtUtils::ReadStreamToQByteArray(stream, true); + else + return {}; +} + void QtHostInterface::doBootSystem(QString initial_filename, QString initial_save_state_filename) { if (!m_display_window->initializeDeviceContext()) @@ -403,8 +457,8 @@ void QtHostInterface::doBootSystem(QString initial_filename, QString initial_sav void QtHostInterface::createAudioStream() { // Qt at least on Windows seems to want a buffer size of at least 8KB. - m_audio_stream = QtAudioStream::Create(); - if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4)) + // m_audio_stream = QtAudioStream::Create(); + // 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"; diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 1f661db8f..21e60d2b5 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -1,6 +1,7 @@ #pragma once #include "core/host_interface.h" #include "opengldisplaywindow.h" +#include #include #include #include @@ -8,8 +9,10 @@ #include #include #include -#include #include +#include + +class ByteStream; class QWidget; @@ -44,9 +47,10 @@ public: bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; } QWidget* createDisplayWidget(QWidget* parent); - void destroyDisplayWidget(); + void displayWidgetDestroyed(); void bootSystem(QString initial_filename, QString initial_save_state_filename); + void blockingPowerOffSystem(); void updateInputMap(); void handleKeyEvent(int key, bool pressed); @@ -66,12 +70,15 @@ Q_SIGNALS: void emulationPaused(bool paused); void gameListRefreshed(); void toggleFullscreenRequested(); + void switchRendererRequested(); public Q_SLOTS: void powerOffSystem(); void resetSystem(); void pauseSystem(bool paused); void changeDisc(QString new_disc_filename); + void loadStateFromMemory(QByteArray arr); + QByteArray saveStateToMemory(); private Q_SLOTS: void doStopThread(); diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index cec08b512..baa527d79 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -1,4 +1,6 @@ #include "qtutils.h" +#include "YBaseLib/ByteStream.h" +#include #include #include #include @@ -571,4 +573,25 @@ int KeyEventToInt(const QKeyEvent* ke) return static_cast(ke->modifiers() & s_qt_modifier_mask) | ke->key(); } +QByteArray ReadStreamToQByteArray(ByteStream* stream, bool rewind /*= false*/) +{ + QByteArray ret; + const uint64 old_pos = stream->GetPosition(); + if (rewind && !stream->SeekAbsolute(0)) + return {}; + + const uint64 stream_size = stream->GetSize() - stream->GetPosition(); + ret.resize(static_cast(stream_size)); + if (stream_size > 0 && !stream->Read2(ret.data(), static_cast(stream_size), nullptr)) + return {}; + + stream->SeekAbsolute(old_pos); + return ret; +} + +bool WriteQByteArrayToStream(QByteArray& arr, ByteStream* stream) +{ + return arr.isEmpty() || stream->Write2(arr.data(), static_cast(arr.size())); +} + } // namespace QtUtils \ No newline at end of file diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index 645fde844..4279ef976 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -1,8 +1,11 @@ #pragma once #include +#include #include #include +class ByteStream; + class QKeyEvent; class QTableView; class QWidget; @@ -31,4 +34,10 @@ std::optional ParseKeyString(const QString& key_str); /// Returns a key id for a key event, including any modifiers. int KeyEventToInt(const QKeyEvent* ke); +/// Reads a whole stream to a Qt byte array. +QByteArray ReadStreamToQByteArray(ByteStream* stream, bool rewind = false); + +/// Creates a stream from a Qt byte array. +bool WriteQByteArrayToStream(QByteArray& arr, ByteStream* stream); + } // namespace QtUtils \ No newline at end of file