Move more logic from frontend to base HostInterface
This commit is contained in:
parent
f0578bb932
commit
fd8ed08307
|
@ -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);
|
||||
if (!m_system->LoadState(stream.get()))
|
||||
{
|
||||
ReportFormattedError("Loading state from '%s' failed. Resetting.", filename);
|
||||
m_system->Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
return result;
|
||||
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 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();
|
||||
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_system)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
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;
|
||||
m_emulation_running = true;
|
||||
updateEmulationActions(false, true);
|
||||
}
|
||||
|
||||
updateDebugMenuGPURenderer();
|
||||
void MainWindow::onEmulationStopped()
|
||||
{
|
||||
m_emulation_running = false;
|
||||
updateEmulationActions(false, false);
|
||||
switchToGameListView();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
if (!isOnWorkerThread())
|
||||
{
|
||||
m_display.release();
|
||||
m_display_window = nullptr;
|
||||
}
|
||||
|
||||
void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_state_filename)
|
||||
{
|
||||
Assert(!isOnWorkerThread());
|
||||
emit emulationStarting();
|
||||
|
||||
if (!createDisplayDeviceContext())
|
||||
{
|
||||
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,49 +214,80 @@ 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);
|
||||
m_display_window->destroyDeviceContext();
|
||||
emit createDisplayWindowRequested(m_worker_thread, m_settings.gpu_use_debug_device);
|
||||
if (!m_display_window->hasDeviceContext())
|
||||
{
|
||||
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);
|
||||
|
||||
if (restore_state)
|
||||
{
|
||||
if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device))
|
||||
{
|
||||
emit runningGameChanged(QString(), QString(), QString());
|
||||
m_display_window->destroyDeviceContext();
|
||||
m_display_window = nullptr;
|
||||
emit destroyDisplayWindowRequested();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_display = m_display_window->getHostDisplayInterface();
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
case AudioBackend::Default:
|
||||
case AudioBackend::Cubeb:
|
||||
return AudioStream::CreateCubebAudioStream();
|
||||
|
||||
case AudioBackend::Null:
|
||||
return AudioStream::CreateNullAudioStream();
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
||||
m_system->ResetPerformanceCounters();
|
||||
}
|
||||
}
|
||||
|
||||
void QtHostInterface::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);
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
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) :
|
||||
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,61 +127,16 @@ void SDLHostInterface::CreateImGuiContext()
|
|||
ImGui::AddRobotoRegularFont();
|
||||
}
|
||||
|
||||
void SDLHostInterface::CreateAudioStream()
|
||||
bool SDLHostInterface::AcquireHostDisplay()
|
||||
{
|
||||
switch (m_settings.audio_backend)
|
||||
// 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)
|
||||
{
|
||||
case AudioBackend::Null:
|
||||
m_audio_stream = AudioStream::CreateNullAudioStream();
|
||||
break;
|
||||
|
||||
case AudioBackend::Cubeb:
|
||||
m_audio_stream = AudioStream::CreateCubebAudioStream();
|
||||
break;
|
||||
|
||||
case AudioBackend::Default:
|
||||
default:
|
||||
m_audio_stream = std::make_unique<SDLAudioStream>();
|
||||
break;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
void SDLHostInterface::SaveSettings()
|
||||
{
|
||||
SDLSettingsInterface si(GetSettingsFileName().c_str());
|
||||
m_settings.Save(si);
|
||||
}
|
||||
|
||||
void SDLHostInterface::QueueSwitchGPURenderer()
|
||||
{
|
||||
SDL_Event ev = {};
|
||||
ev.type = SDL_USEREVENT;
|
||||
ev.user.code = m_switch_gpu_renderer_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();
|
||||
|
@ -189,33 +148,70 @@ void SDLHostInterface::SwitchGPURenderer()
|
|||
Panic("Failed to recreate display on GPU renderer switch");
|
||||
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (stream)
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDLHostInterface::ReleaseHostDisplay()
|
||||
{
|
||||
CreateSystem();
|
||||
if (!BootSystem(nullptr, nullptr) || !m_system->LoadState(stream.get()))
|
||||
// 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)
|
||||
{
|
||||
ReportError("Failed to load state after GPU renderer switch, resetting");
|
||||
m_system->Reset();
|
||||
switch (m_settings.audio_backend)
|
||||
{
|
||||
case AudioBackend::Null:
|
||||
return AudioStream::CreateNullAudioStream();
|
||||
|
||||
case AudioBackend::Cubeb:
|
||||
return AudioStream::CreateCubebAudioStream();
|
||||
|
||||
case AudioBackend::Default:
|
||||
return std::make_unique<SDLAudioStream>();
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFullscreen();
|
||||
if (m_system)
|
||||
m_system->ResetPerformanceCounters();
|
||||
void SDLHostInterface::OnSystemCreated()
|
||||
{
|
||||
HostInterface::OnSystemCreated();
|
||||
|
||||
UpdateKeyboardControllerMapping();
|
||||
UpdateControllerControllerMapping();
|
||||
ClearImGuiFocus();
|
||||
}
|
||||
|
||||
void SDLHostInterface::SwitchAudioBackend()
|
||||
void SDLHostInterface::OnSystemPaused(bool paused)
|
||||
{
|
||||
m_audio_stream.reset();
|
||||
CreateAudioStream();
|
||||
HostInterface::OnSystemPaused(paused);
|
||||
|
||||
if (m_system)
|
||||
{
|
||||
m_audio_stream->PauseOutput(false);
|
||||
UpdateSpeedLimiterState();
|
||||
if (!paused)
|
||||
ClearImGuiFocus();
|
||||
}
|
||||
|
||||
void SDLHostInterface::OnSystemDestroyed()
|
||||
{
|
||||
HostInterface::OnSystemDestroyed();
|
||||
}
|
||||
|
||||
void SDLHostInterface::SaveSettings()
|
||||
{
|
||||
SDLSettingsInterface si(GetSettingsFileName().c_str());
|
||||
m_settings.Save(si);
|
||||
}
|
||||
|
||||
void SDLHostInterface::QueueUpdateSettings()
|
||||
{
|
||||
SDL_Event ev = {};
|
||||
ev.type = SDL_USEREVENT;
|
||||
ev.user.code = m_update_settings_event_id;
|
||||
SDL_PushEvent(&ev);
|
||||
}
|
||||
|
||||
void SDLHostInterface::UpdateFullscreen()
|
||||
|
@ -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,46 +1434,6 @@ 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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue