From 93ca0e6fd1a261deca8050d004e4e6d65a722833 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 1 Oct 2018 23:39:22 +0800 Subject: [PATCH] core, citra_qt: add frame advancing to framelimiter Frame advancing is a commonly used TAS feature which basically means running the game frame by frame. TASers use this feature to press exact buttons at the exact frames. This commit added frame advancing to the framelimiter and two actions to the Movie menu. The default hotkey is `\` for advancing frames, and `Ctrl+A` for toggling frame advancing. The `Advance Frame` hotkey would automatically enable frame advancing if not already enabled. --- src/citra_qt/main.cpp | 29 +++++++++++++++++++++++++++++ src/citra_qt/main.ui | 22 ++++++++++++++++++++++ src/core/perf_stats.cpp | 23 +++++++++++++++++++++++ src/core/perf_stats.h | 16 ++++++++++++++++ 4 files changed, 90 insertions(+) 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