Qt: Safer GPU renderer switching

This commit is contained in:
Connor McLaughlin 2020-01-24 14:49:51 +10:00
parent c5282b99e1
commit d343743768
4 changed files with 93 additions and 86 deletions

View file

@ -6,6 +6,7 @@
#include "qtsettingsinterface.h"
#include "settingsdialog.h"
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <cmath>
static constexpr char DISC_IMAGE_FILTER[] =
@ -76,26 +77,13 @@ void MainWindow::toggleFullscreen()
m_ui.actionFullscreen->setChecked(fullscreen);
}
void MainWindow::switchRenderer()
void MainWindow::recreateDisplayWidget(bool create_device_context)
{
const bool was_fullscreen = m_display_widget->isFullScreen();
if (was_fullscreen)
toggleFullscreen();
QByteArray state;
if (m_emulation_running)
{
// we need to basically restart the emulator core
state = m_host_interface->saveStateToMemory();
if (state.isEmpty())
{
m_host_interface->ReportError("Failed to save emulator state to memory");
return;
}
// stop the emulation
m_host_interface->blockingPowerOffSystem();
}
switchToGameListView();
// recreate the display widget using the potentially-new renderer
m_ui.mainContainer->removeWidget(m_display_widget);
@ -104,25 +92,21 @@ void MainWindow::switchRenderer()
m_display_widget = m_host_interface->createDisplayWidget(m_ui.mainContainer);
m_ui.mainContainer->insertWidget(1, m_display_widget);
if (create_device_context)
switchToEmulationView();
// we need the surface visible..
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
if (!state.isEmpty())
if (create_device_context && !m_host_interface->createDisplayDeviceContext())
{
// restart the system with the new state
m_host_interface->bootSystem(QString(), QString());
m_host_interface->loadStateFromMemory(std::move(state));
QMessageBox::critical(this, tr("DuckStation Error"),
tr("Failed to create new device context on renderer switch. Cannot continue."));
QCoreApplication::exit();
return;
}
// update the menu with the selected renderer
QObjectList renderer_menu_items = m_ui.menuRenderer->children();
QString current_renderer_name(Settings::GetRendererDisplayName(m_host_interface->GetCoreSettings().gpu_renderer));
for (QObject* obj : renderer_menu_items)
{
QAction* action = qobject_cast<QAction*>(obj);
if (action)
action->setChecked(action->text() == current_renderer_name);
}
updateDebugMenuGPURenderer();
}
void MainWindow::onPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time,
@ -214,14 +198,12 @@ void MainWindow::setupAdditionalUi()
const GPURenderer renderer = static_cast<GPURenderer>(i);
QAction* action = m_ui.menuRenderer->addAction(tr(Settings::GetRendererDisplayName(renderer)));
action->setCheckable(true);
action->setChecked(m_host_interface->GetCoreSettings().gpu_renderer == renderer);
connect(action, &QAction::triggered, [this, action, renderer]() {
m_host_interface->putSettingValue(QStringLiteral("GPU/Renderer"), QString(Settings::GetRendererName(renderer)));
m_host_interface->applySettings();
action->setChecked(true);
switchRenderer();
});
}
updateDebugMenuGPURenderer();
}
void MainWindow::updateEmulationActions(bool starting, bool running)
@ -307,6 +289,8 @@ void MainWindow::connectSignals()
connect(m_host_interface, &QtHostInterface::toggleFullscreenRequested, this, &MainWindow::toggleFullscreen);
connect(m_host_interface, &QtHostInterface::performanceCountersUpdated, this,
&MainWindow::onPerformanceCountersUpdated);
connect(m_host_interface, &QtHostInterface::recreateDisplayWidgetRequested, this, &MainWindow::recreateDisplayWidget,
Qt::BlockingQueuedConnection);
connect(m_game_list_widget, &GameListWidget::bootEntryRequested, [this](const GameList::GameListEntry* entry) {
// if we're not running, boot the system, otherwise swap discs
@ -347,3 +331,21 @@ void MainWindow::doSettings(SettingsDialog::Category category)
if (category != SettingsDialog::Category::Count)
m_settings_dialog->setCategory(category);
}
void MainWindow::updateDebugMenuGPURenderer()
{
// update the menu with the new selected renderer
std::optional<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);
}
}
}

View file

@ -27,7 +27,7 @@ private Q_SLOTS:
void onEmulationStopped();
void onEmulationPaused(bool paused);
void toggleFullscreen();
void switchRenderer();
void recreateDisplayWidget(bool create_device_context);
void onPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time,
float worst_frame_time);
@ -48,6 +48,7 @@ private:
void switchToGameListView();
void switchToEmulationView();
void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
void updateDebugMenuGPURenderer();
Ui::MainWindow m_ui;

View file

@ -105,6 +105,7 @@ void QtHostInterface::applySettings()
}
// TODO: Should we move this to the base class?
const GPURenderer old_gpu_renderer = m_settings.gpu_renderer;
const bool old_vsync_enabled = m_settings.video_sync_enabled;
const bool old_audio_sync_enabled = m_settings.audio_sync_enabled;
const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled;
@ -120,6 +121,10 @@ void QtHostInterface::applySettings()
{
UpdateSpeedLimiterState();
}
// TODO: Fast path for hardware->software switches
if (m_settings.gpu_renderer != old_gpu_renderer)
switchGPURenderer();
}
void QtHostInterface::checkSettings()
@ -180,6 +185,11 @@ QWidget* QtHostInterface::createDisplayWidget(QWidget* parent)
return QWidget::createWindowContainer(m_display_window, parent);
}
bool QtHostInterface::createDisplayDeviceContext()
{
return m_display_window->createDeviceContext(m_worker_thread, m_settings.gpu_use_debug_device);
}
void QtHostInterface::displayWidgetDestroyed()
{
m_display.release();
@ -191,7 +201,7 @@ void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_
Assert(!isOnWorkerThread());
emit emulationStarting();
if (!m_display_window->createDeviceContext(m_worker_thread, m_settings.gpu_use_debug_device))
if (!createDisplayDeviceContext())
{
emit emulationStopped();
return;
@ -383,17 +393,6 @@ void QtHostInterface::powerOffSystem()
emit emulationStopped();
}
void QtHostInterface::blockingPowerOffSystem()
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "powerOffSystem", Qt::BlockingQueuedConnection);
return;
}
powerOffSystem();
}
void QtHostInterface::resetSystem()
{
if (!isOnWorkerThread())
@ -428,44 +427,6 @@ void QtHostInterface::pauseSystem(bool paused)
void QtHostInterface::changeDisc(QString new_disc_filename) {}
void QtHostInterface::loadStateFromMemory(QByteArray arr)
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "loadStateFromMemory", Qt::QueuedConnection, Q_ARG(QByteArray, arr));
return;
}
std::unique_ptr<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)
{
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()
{
m_original_thread = QThread::currentThread();

View file

@ -49,10 +49,10 @@ public:
bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
QWidget* createDisplayWidget(QWidget* parent);
bool createDisplayDeviceContext();
void displayWidgetDestroyed();
void bootSystem(QString initial_filename, QString initial_save_state_filename);
void blockingPowerOffSystem();
void updateInputMap();
void handleKeyEvent(int key, bool pressed);
@ -72,7 +72,7 @@ Q_SIGNALS:
void emulationPaused(bool paused);
void gameListRefreshed();
void toggleFullscreenRequested();
void switchRendererRequested();
void recreateDisplayWidgetRequested(bool create_device_context);
void performanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time);
public Q_SLOTS:
@ -81,8 +81,6 @@ public Q_SLOTS:
void resetSystem();
void pauseSystem(bool paused);
void changeDisc(QString new_disc_filename);
void loadStateFromMemory(QByteArray arr);
QByteArray saveStateToMemory();
private Q_SLOTS:
void doStopThread();
@ -123,6 +121,7 @@ private:
void updateHotkeyInputMap();
void addButtonToInputMap(const QString& binding, InputButtonHandler handler);
void createAudioStream();
void switchGPURenderer();
void createThread();
void stopThread();
void threadEntryPoint();