Qt: Support runtime renderer switching
This commit is contained in:
parent
c6d6b0405f
commit
e7bebb0105
|
@ -22,7 +22,8 @@ MainWindow::MainWindow(QtHostInterface* host_interface) : QMainWindow(nullptr),
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
{
|
{
|
||||||
m_host_interface->destroyDisplayWidget();
|
delete m_display_widget;
|
||||||
|
m_host_interface->displayWidgetDestroyed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onEmulationStarting()
|
void MainWindow::onEmulationStarting()
|
||||||
|
@ -74,6 +75,55 @@ void MainWindow::toggleFullscreen()
|
||||||
m_ui.actionFullscreen->setChecked(fullscreen);
|
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<QAction*>(obj);
|
||||||
|
if (action)
|
||||||
|
action->setChecked(action->text() == current_renderer_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onStartDiscActionTriggered()
|
void MainWindow::onStartDiscActionTriggered()
|
||||||
{
|
{
|
||||||
QString filename =
|
QString filename =
|
||||||
|
@ -128,10 +178,25 @@ void MainWindow::setupAdditionalUi()
|
||||||
m_game_list_widget->initialize(m_host_interface);
|
m_game_list_widget->initialize(m_host_interface);
|
||||||
m_ui.mainContainer->insertWidget(0, m_game_list_widget);
|
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->insertWidget(1, m_display_widget);
|
||||||
|
|
||||||
m_ui.mainContainer->setCurrentIndex(0);
|
m_ui.mainContainer->setCurrentIndex(0);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(GPURenderer::Count); i++)
|
||||||
|
{
|
||||||
|
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->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)
|
void MainWindow::updateEmulationActions(bool starting, bool running)
|
||||||
|
|
|
@ -25,6 +25,7 @@ private Q_SLOTS:
|
||||||
void onEmulationStopped();
|
void onEmulationStopped();
|
||||||
void onEmulationPaused(bool paused);
|
void onEmulationPaused(bool paused);
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
|
void switchRenderer();
|
||||||
|
|
||||||
void onStartDiscActionTriggered();
|
void onStartDiscActionTriggered();
|
||||||
void onChangeDiscActionTriggered();
|
void onChangeDiscActionTriggered();
|
||||||
|
|
|
@ -58,9 +58,6 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Renderer</string>
|
<string>Renderer</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionRendererD3D11"/>
|
|
||||||
<addaction name="actionRendererOpenGL"/>
|
|
||||||
<addaction name="actionRendererSoftware"/>
|
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="actionCPUExecutionMode">
|
<widget class="QMenu" name="actionCPUExecutionMode">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -247,30 +244,6 @@
|
||||||
<string>Fullscreen</string>
|
<string>Fullscreen</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionRendererD3D11">
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Hardware (D3D11)</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionRendererOpenGL">
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Hardware (OpenGL)</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionRendererSoftware">
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Software</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionCPUExecutionModeInterpreter">
|
<action name="actionCPUExecutionModeInterpreter">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#include "qthostinterface.h"
|
#include "qthostinterface.h"
|
||||||
|
#include "YBaseLib/AutoReleasePtr.h"
|
||||||
|
#include "YBaseLib/ByteStream.h"
|
||||||
#include "YBaseLib/Log.h"
|
#include "YBaseLib/Log.h"
|
||||||
#include "YBaseLib/String.h"
|
#include "YBaseLib/String.h"
|
||||||
#include "common/null_audio_stream.h"
|
#include "common/null_audio_stream.h"
|
||||||
|
@ -137,7 +139,10 @@ void QtHostInterface::refreshGameList(bool invalidate_cache /*= false*/)
|
||||||
QWidget* QtHostInterface::createDisplayWidget(QWidget* parent)
|
QWidget* QtHostInterface::createDisplayWidget(QWidget* parent)
|
||||||
{
|
{
|
||||||
#ifdef WIN32
|
#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
|
#else
|
||||||
m_display_window = new OpenGLDisplayWindow(this, nullptr);
|
m_display_window = new OpenGLDisplayWindow(this, nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
@ -148,7 +153,7 @@ QWidget* QtHostInterface::createDisplayWidget(QWidget* parent)
|
||||||
return QWidget::createWindowContainer(m_display_window, parent);
|
return QWidget::createWindowContainer(m_display_window, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtHostInterface::destroyDisplayWidget()
|
void QtHostInterface::displayWidgetDestroyed()
|
||||||
{
|
{
|
||||||
m_display.release();
|
m_display.release();
|
||||||
delete m_display_window;
|
delete m_display_window;
|
||||||
|
@ -157,6 +162,7 @@ void QtHostInterface::destroyDisplayWidget()
|
||||||
|
|
||||||
void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_state_filename)
|
void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_state_filename)
|
||||||
{
|
{
|
||||||
|
Assert(!isOnWorkerThread());
|
||||||
emit emulationStarting();
|
emit emulationStarting();
|
||||||
|
|
||||||
if (!m_display_window->createDeviceContext(m_worker_thread))
|
if (!m_display_window->createDeviceContext(m_worker_thread))
|
||||||
|
@ -327,7 +333,7 @@ void QtHostInterface::powerOffSystem()
|
||||||
{
|
{
|
||||||
if (!isOnWorkerThread())
|
if (!isOnWorkerThread())
|
||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(this, "doPowerOffSystem", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "powerOffSystem", Qt::QueuedConnection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,12 +345,22 @@ void QtHostInterface::powerOffSystem()
|
||||||
|
|
||||||
m_system.reset();
|
m_system.reset();
|
||||||
m_audio_stream->PauseOutput(true);
|
m_audio_stream->PauseOutput(true);
|
||||||
m_audio_stream->EmptyBuffers();
|
|
||||||
m_display_window->destroyDeviceContext();
|
m_display_window->destroyDeviceContext();
|
||||||
|
|
||||||
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())
|
||||||
|
@ -377,6 +393,44 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoReleasePtr<ByteStream> 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<ByteStream> 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)
|
void QtHostInterface::doBootSystem(QString initial_filename, QString initial_save_state_filename)
|
||||||
{
|
{
|
||||||
if (!m_display_window->initializeDeviceContext())
|
if (!m_display_window->initializeDeviceContext())
|
||||||
|
@ -403,8 +457,8 @@ void QtHostInterface::doBootSystem(QString initial_filename, QString initial_sav
|
||||||
void QtHostInterface::createAudioStream()
|
void QtHostInterface::createAudioStream()
|
||||||
{
|
{
|
||||||
// Qt at least on Windows seems to want a buffer size of at least 8KB.
|
// Qt at least on Windows seems to want a buffer size of at least 8KB.
|
||||||
m_audio_stream = QtAudioStream::Create();
|
// m_audio_stream = QtAudioStream::Create();
|
||||||
if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4))
|
// 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";
|
qWarning() << "Failed to configure audio stream, falling back to null output";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "core/host_interface.h"
|
#include "core/host_interface.h"
|
||||||
#include "opengldisplaywindow.h"
|
#include "opengldisplaywindow.h"
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
#include <QtCore/QSettings>
|
#include <QtCore/QSettings>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
|
@ -8,8 +9,10 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ByteStream;
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
|
||||||
|
@ -44,9 +47,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);
|
||||||
void destroyDisplayWidget();
|
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);
|
||||||
|
@ -66,12 +70,15 @@ Q_SIGNALS:
|
||||||
void emulationPaused(bool paused);
|
void emulationPaused(bool paused);
|
||||||
void gameListRefreshed();
|
void gameListRefreshed();
|
||||||
void toggleFullscreenRequested();
|
void toggleFullscreenRequested();
|
||||||
|
void switchRendererRequested();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void powerOffSystem();
|
void powerOffSystem();
|
||||||
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();
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
|
#include "YBaseLib/ByteStream.h"
|
||||||
|
#include <QtCore/QMetaObject>
|
||||||
#include <QtGui/QKeyEvent>
|
#include <QtGui/QKeyEvent>
|
||||||
#include <QtWidgets/QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
#include <QtWidgets/QMainWindow>
|
#include <QtWidgets/QMainWindow>
|
||||||
|
@ -571,4 +573,25 @@ int KeyEventToInt(const QKeyEvent* ke)
|
||||||
return static_cast<int>(ke->modifiers() & s_qt_modifier_mask) | ke->key();
|
return static_cast<int>(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<int>(stream_size));
|
||||||
|
if (stream_size > 0 && !stream->Read2(ret.data(), static_cast<uint32>(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<uint32>(arr.size()));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QtUtils
|
} // namespace QtUtils
|
|
@ -1,8 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QtCore/QString>
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
class ByteStream;
|
||||||
|
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
class QTableView;
|
class QTableView;
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
@ -31,4 +34,10 @@ std::optional<int> ParseKeyString(const QString& key_str);
|
||||||
/// Returns a key id for a key event, including any modifiers.
|
/// Returns a key id for a key event, including any modifiers.
|
||||||
int KeyEventToInt(const QKeyEvent* ke);
|
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
|
} // namespace QtUtils
|
Loading…
Reference in New Issue