diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 40c42fc31..cdc79f4e2 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -352,6 +352,10 @@ void GMainWindow::InitializeHotkeys() { Qt::ApplicationShortcut); hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"), Qt::ApplicationShortcut); + hotkey_registry.RegisterHotkey("Main Window", "Toggle Frame Advancing", QKeySequence("CTRL+A"), + Qt::ApplicationShortcut); + hotkey_registry.RegisterHotkey("Main Window", "Advance Frame", QKeySequence(Qt::Key_Backslash), + Qt::ApplicationShortcut); hotkey_registry.LoadHotkeys(); connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, @@ -409,6 +413,10 @@ void GMainWindow::InitializeHotkeys() { UpdateStatusBar(); } }); + connect(hotkey_registry.GetHotkey("Main Window", "Toggle Frame Advancing", this), + &QShortcut::activated, ui.action_Enable_Frame_Advancing, &QAction::trigger); + connect(hotkey_registry.GetHotkey("Main Window", "Advance Frame", this), &QShortcut::activated, + ui.action_Advance_Frame, &QAction::trigger); } void GMainWindow::ShowUpdaterWidgets() { @@ -540,6 +548,20 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Play_Movie, &QAction::triggered, this, &GMainWindow::OnPlayMovie); connect(ui.action_Stop_Recording_Playback, &QAction::triggered, this, &GMainWindow::OnStopRecordingPlayback); + connect(ui.action_Enable_Frame_Advancing, &QAction::triggered, this, [this] { + if (emulation_running) { + Core::System::GetInstance().frame_limiter.SetFrameAdvancing( + ui.action_Enable_Frame_Advancing->isChecked()); + ui.action_Advance_Frame->setEnabled(ui.action_Enable_Frame_Advancing->isChecked()); + } + }); + connect(ui.action_Advance_Frame, &QAction::triggered, this, [this] { + if (emulation_running) { + ui.action_Enable_Frame_Advancing->setChecked(true); + ui.action_Advance_Frame->setEnabled(true); + Core::System::GetInstance().frame_limiter.AdvanceFrame(); + } + }); // Help connect(ui.action_FAQ, &QAction::triggered, @@ -803,6 +825,9 @@ void GMainWindow::ShutdownGame() { // TODO(bunnei): This function is not thread safe, but it's being used as if it were Pica::g_debug_context->ClearBreakpoints(); + // Frame advancing must be cancelled in order to release the emu thread from waiting + Core::System::GetInstance().frame_limiter.SetFrameAdvancing(false); + emit EmulationStopping(); // Wait for emulation thread to complete and delete it @@ -823,6 +848,9 @@ void GMainWindow::ShutdownGame() { ui.action_Stop->setEnabled(false); ui.action_Restart->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false); + ui.action_Enable_Frame_Advancing->setEnabled(false); + ui.action_Enable_Frame_Advancing->setChecked(false); + ui.action_Advance_Frame->setEnabled(false); render_window->hide(); if (game_list->isEmpty()) game_list_placeholder->show(); @@ -1110,6 +1138,7 @@ void GMainWindow::OnStartGame() { ui.action_Stop->setEnabled(true); ui.action_Restart->setEnabled(true); ui.action_Report_Compatibility->setEnabled(true); + ui.action_Enable_Frame_Advancing->setEnabled(true); discord_rpc->Update(); } diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index ef0f1aae2..c2c58300d 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -114,6 +114,9 @@ + + + @@ -276,6 +279,25 @@ Stop Recording / Playback + + + true + + + false + + + Enable Frame Advancing + + + + + false + + + Advance Frame + + true diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index ed4431df2..7650d8ed0 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -74,6 +74,13 @@ double PerfStats::GetLastFrameTimeScale() { } void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) { + if (frame_advancing_enabled) { + // Frame advancing is enabled: wait on event instead of doing framelimiting + frame_advance_event.Wait(); + frame_advance_event.Reset(); + return; + } + if (!Settings::values.use_frame_limit) { return; } @@ -104,4 +111,20 @@ void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) { previous_walltime = now; } +void FrameLimiter::SetFrameAdvancing(bool value) { + const bool was_enabled = frame_advancing_enabled.exchange(value); + if (was_enabled && !value) { + // Set the event to let emulation continue + frame_advance_event.Set(); + } +} + +void FrameLimiter::AdvanceFrame() { + if (!frame_advancing_enabled) { + // Start frame advancing + frame_advancing_enabled = true; + } + frame_advance_event.Set(); +} + } // namespace Core diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 6e4619701..888250e77 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -4,9 +4,11 @@ #pragma once +#include #include #include #include "common/common_types.h" +#include "common/thread.h" namespace Core { @@ -70,6 +72,14 @@ public: void DoFrameLimiting(std::chrono::microseconds current_system_time_us); + /** + * Sets whether frame advancing is enabled or not. + * Note: The frontend must cancel frame advancing before shutting down in order + * to resume the emu_thread. + */ + void SetFrameAdvancing(bool value); + void AdvanceFrame(); + private: /// Emulated system time (in microseconds) at the last limiter invocation std::chrono::microseconds previous_system_time_us{0}; @@ -78,6 +88,12 @@ private: /// Accumulated difference between walltime and emulated time std::chrono::microseconds frame_limiting_delta_err{0}; + + /// Whether to use frame advancing (i.e. frame by frame) + std::atomic_bool frame_advancing_enabled; + + /// Event to advance the frame when frame advancing is enabled + Common::Event frame_advance_event; }; } // namespace Core