mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-23 06:15:38 +00:00
Qt: Safer GPU renderer switching
This commit is contained in:
parent
c5282b99e1
commit
d343743768
|
@ -6,6 +6,7 @@
|
||||||
#include "qtsettingsinterface.h"
|
#include "qtsettingsinterface.h"
|
||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
#include <QtWidgets/QFileDialog>
|
#include <QtWidgets/QFileDialog>
|
||||||
|
#include <QtWidgets/QMessageBox>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
static constexpr char DISC_IMAGE_FILTER[] =
|
static constexpr char DISC_IMAGE_FILTER[] =
|
||||||
|
@ -76,26 +77,13 @@ void MainWindow::toggleFullscreen()
|
||||||
m_ui.actionFullscreen->setChecked(fullscreen);
|
m_ui.actionFullscreen->setChecked(fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::switchRenderer()
|
void MainWindow::recreateDisplayWidget(bool create_device_context)
|
||||||
{
|
{
|
||||||
const bool was_fullscreen = m_display_widget->isFullScreen();
|
const bool was_fullscreen = m_display_widget->isFullScreen();
|
||||||
if (was_fullscreen)
|
if (was_fullscreen)
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
|
|
||||||
QByteArray state;
|
switchToGameListView();
|
||||||
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
|
// recreate the display widget using the potentially-new renderer
|
||||||
m_ui.mainContainer->removeWidget(m_display_widget);
|
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_display_widget = m_host_interface->createDisplayWidget(m_ui.mainContainer);
|
||||||
m_ui.mainContainer->insertWidget(1, m_display_widget);
|
m_ui.mainContainer->insertWidget(1, m_display_widget);
|
||||||
|
|
||||||
|
if (create_device_context)
|
||||||
|
switchToEmulationView();
|
||||||
|
|
||||||
// we need the surface visible..
|
// we need the surface visible..
|
||||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
if (!state.isEmpty())
|
if (create_device_context && !m_host_interface->createDisplayDeviceContext())
|
||||||
{
|
{
|
||||||
// restart the system with the new state
|
QMessageBox::critical(this, tr("DuckStation Error"),
|
||||||
m_host_interface->bootSystem(QString(), QString());
|
tr("Failed to create new device context on renderer switch. Cannot continue."));
|
||||||
m_host_interface->loadStateFromMemory(std::move(state));
|
QCoreApplication::exit();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the menu with the selected renderer
|
updateDebugMenuGPURenderer();
|
||||||
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<QAction*>(obj);
|
|
||||||
if (action)
|
|
||||||
action->setChecked(action->text() == current_renderer_name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time,
|
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<GPURenderer>(i);
|
const GPURenderer renderer = static_cast<GPURenderer>(i);
|
||||||
QAction* action = m_ui.menuRenderer->addAction(tr(Settings::GetRendererDisplayName(renderer)));
|
QAction* action = m_ui.menuRenderer->addAction(tr(Settings::GetRendererDisplayName(renderer)));
|
||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
action->setChecked(m_host_interface->GetCoreSettings().gpu_renderer == renderer);
|
|
||||||
connect(action, &QAction::triggered, [this, action, renderer]() {
|
connect(action, &QAction::triggered, [this, action, renderer]() {
|
||||||
m_host_interface->putSettingValue(QStringLiteral("GPU/Renderer"), QString(Settings::GetRendererName(renderer)));
|
m_host_interface->putSettingValue(QStringLiteral("GPU/Renderer"), QString(Settings::GetRendererName(renderer)));
|
||||||
m_host_interface->applySettings();
|
m_host_interface->applySettings();
|
||||||
action->setChecked(true);
|
|
||||||
switchRenderer();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
updateDebugMenuGPURenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateEmulationActions(bool starting, bool running)
|
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::toggleFullscreenRequested, this, &MainWindow::toggleFullscreen);
|
||||||
connect(m_host_interface, &QtHostInterface::performanceCountersUpdated, this,
|
connect(m_host_interface, &QtHostInterface::performanceCountersUpdated, this,
|
||||||
&MainWindow::onPerformanceCountersUpdated);
|
&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) {
|
connect(m_game_list_widget, &GameListWidget::bootEntryRequested, [this](const GameList::GameListEntry* entry) {
|
||||||
// if we're not running, boot the system, otherwise swap discs
|
// 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)
|
if (category != SettingsDialog::Category::Count)
|
||||||
m_settings_dialog->setCategory(category);
|
m_settings_dialog->setCategory(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateDebugMenuGPURenderer()
|
||||||
|
{
|
||||||
|
// update the menu with the new selected renderer
|
||||||
|
std::optional<GPURenderer> 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<QAction*>(obj);
|
||||||
|
if (action)
|
||||||
|
action->setChecked(action->text() == current_renderer_display_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ private Q_SLOTS:
|
||||||
void onEmulationStopped();
|
void onEmulationStopped();
|
||||||
void onEmulationPaused(bool paused);
|
void onEmulationPaused(bool paused);
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
void switchRenderer();
|
void recreateDisplayWidget(bool create_device_context);
|
||||||
void onPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time,
|
void onPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time,
|
||||||
float worst_frame_time);
|
float worst_frame_time);
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ private:
|
||||||
void switchToGameListView();
|
void switchToGameListView();
|
||||||
void switchToEmulationView();
|
void switchToEmulationView();
|
||||||
void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
|
void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
|
||||||
|
void updateDebugMenuGPURenderer();
|
||||||
|
|
||||||
Ui::MainWindow m_ui;
|
Ui::MainWindow m_ui;
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ void QtHostInterface::applySettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should we move this to the base class?
|
// 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_vsync_enabled = m_settings.video_sync_enabled;
|
||||||
const bool old_audio_sync_enabled = m_settings.audio_sync_enabled;
|
const bool old_audio_sync_enabled = m_settings.audio_sync_enabled;
|
||||||
const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled;
|
const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled;
|
||||||
|
@ -120,6 +121,10 @@ void QtHostInterface::applySettings()
|
||||||
{
|
{
|
||||||
UpdateSpeedLimiterState();
|
UpdateSpeedLimiterState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Fast path for hardware->software switches
|
||||||
|
if (m_settings.gpu_renderer != old_gpu_renderer)
|
||||||
|
switchGPURenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtHostInterface::checkSettings()
|
void QtHostInterface::checkSettings()
|
||||||
|
@ -180,6 +185,11 @@ QWidget* QtHostInterface::createDisplayWidget(QWidget* parent)
|
||||||
return QWidget::createWindowContainer(m_display_window, 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()
|
void QtHostInterface::displayWidgetDestroyed()
|
||||||
{
|
{
|
||||||
m_display.release();
|
m_display.release();
|
||||||
|
@ -191,7 +201,7 @@ void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_
|
||||||
Assert(!isOnWorkerThread());
|
Assert(!isOnWorkerThread());
|
||||||
emit emulationStarting();
|
emit emulationStarting();
|
||||||
|
|
||||||
if (!m_display_window->createDeviceContext(m_worker_thread, m_settings.gpu_use_debug_device))
|
if (!createDisplayDeviceContext())
|
||||||
{
|
{
|
||||||
emit emulationStopped();
|
emit emulationStopped();
|
||||||
return;
|
return;
|
||||||
|
@ -383,17 +393,6 @@ void QtHostInterface::powerOffSystem()
|
||||||
emit emulationStopped();
|
emit emulationStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtHostInterface::blockingPowerOffSystem()
|
|
||||||
{
|
|
||||||
if (!isOnWorkerThread())
|
|
||||||
{
|
|
||||||
QMetaObject::invokeMethod(this, "powerOffSystem", Qt::BlockingQueuedConnection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
powerOffSystem();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QtHostInterface::resetSystem()
|
void QtHostInterface::resetSystem()
|
||||||
{
|
{
|
||||||
if (!isOnWorkerThread())
|
if (!isOnWorkerThread())
|
||||||
|
@ -428,44 +427,6 @@ void QtHostInterface::pauseSystem(bool paused)
|
||||||
|
|
||||||
void QtHostInterface::changeDisc(QString new_disc_filename) {}
|
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<ByteStream> 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<ByteStream> 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)
|
void QtHostInterface::doBootSystem(QString initial_filename, QString initial_save_state_filename)
|
||||||
{
|
{
|
||||||
if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device))
|
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<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();
|
||||||
|
m_audio_stream->PauseOutput(true);
|
||||||
|
m_display_window->destroyDeviceContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool restore_state = static_cast<bool>(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()
|
void QtHostInterface::createThread()
|
||||||
{
|
{
|
||||||
m_original_thread = QThread::currentThread();
|
m_original_thread = QThread::currentThread();
|
||||||
|
|
|
@ -49,10 +49,10 @@ public:
|
||||||
bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
|
bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
|
||||||
|
|
||||||
QWidget* createDisplayWidget(QWidget* parent);
|
QWidget* createDisplayWidget(QWidget* parent);
|
||||||
|
bool createDisplayDeviceContext();
|
||||||
void displayWidgetDestroyed();
|
void displayWidgetDestroyed();
|
||||||
|
|
||||||
void bootSystem(QString initial_filename, QString initial_save_state_filename);
|
void bootSystem(QString initial_filename, QString initial_save_state_filename);
|
||||||
void blockingPowerOffSystem();
|
|
||||||
|
|
||||||
void updateInputMap();
|
void updateInputMap();
|
||||||
void handleKeyEvent(int key, bool pressed);
|
void handleKeyEvent(int key, bool pressed);
|
||||||
|
@ -72,7 +72,7 @@ Q_SIGNALS:
|
||||||
void emulationPaused(bool paused);
|
void emulationPaused(bool paused);
|
||||||
void gameListRefreshed();
|
void gameListRefreshed();
|
||||||
void toggleFullscreenRequested();
|
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);
|
void performanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
|
@ -81,8 +81,6 @@ public Q_SLOTS:
|
||||||
void resetSystem();
|
void resetSystem();
|
||||||
void pauseSystem(bool paused);
|
void pauseSystem(bool paused);
|
||||||
void changeDisc(QString new_disc_filename);
|
void changeDisc(QString new_disc_filename);
|
||||||
void loadStateFromMemory(QByteArray arr);
|
|
||||||
QByteArray saveStateToMemory();
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void doStopThread();
|
void doStopThread();
|
||||||
|
@ -123,6 +121,7 @@ private:
|
||||||
void updateHotkeyInputMap();
|
void updateHotkeyInputMap();
|
||||||
void addButtonToInputMap(const QString& binding, InputButtonHandler handler);
|
void addButtonToInputMap(const QString& binding, InputButtonHandler handler);
|
||||||
void createAudioStream();
|
void createAudioStream();
|
||||||
|
void switchGPURenderer();
|
||||||
void createThread();
|
void createThread();
|
||||||
void stopThread();
|
void stopThread();
|
||||||
void threadEntryPoint();
|
void threadEntryPoint();
|
||||||
|
|
Loading…
Reference in a new issue