diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index a42ce563a0..ba9650bcbd 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -463,10 +463,10 @@ set(pcsx2RDebugHeaders # Recording sources set(pcsx2RecordingSources Recording/InputRecording.cpp + Recording/InputRecordingControls.cpp Recording/InputRecordingFile.cpp Recording/NewRecordingFrame.cpp Recording/PadData.cpp - Recording/RecordingControls.cpp Recording/RecordingInputManager.cpp Recording/VirtualPad.cpp ) @@ -474,10 +474,10 @@ set(pcsx2RecordingSources # Recording headers set(pcsx2RecordingHeaders Recording/InputRecording.h + Recording/InputRecordingControls.h Recording/InputRecordingFile.h Recording/NewRecordingFrame.h Recording/PadData.h - Recording/RecordingControls.h Recording/RecordingInputManager.h Recording/VirtualPad.h ) diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index 68b38ae4b8..356e8e612b 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -33,7 +33,7 @@ #include "Sio.h" #ifndef DISABLE_RECORDING -# include "Recording/RecordingControls.h" +# include "Recording/InputRecordingControls.h" #endif using namespace Threading; @@ -588,7 +588,7 @@ __fi void rcntUpdate_vSync() #ifndef DISABLE_RECORDING if (g_Conf->EmuOptions.EnableRecordingTools) { - g_RecordingControls.HandleFrameAdvanceAndStop(); + g_InputRecordingControls.HandleFrameAdvanceAndPausing(); } #endif VSyncStart(vsyncCounter.sCycle); diff --git a/pcsx2/Recording/InputRecording.cpp b/pcsx2/Recording/InputRecording.cpp index 7221633eae..b630006724 100644 --- a/pcsx2/Recording/InputRecording.cpp +++ b/pcsx2/Recording/InputRecording.cpp @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2019 PCSX2 Dev Team + * Copyright (C) 2002-2020 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -23,31 +23,51 @@ #include "SaveState.h" #include "InputRecording.h" -#include "Recording/RecordingControls.h" +#include "InputRecordingControls.h" #include -// Tag and save framecount along with savestate +// Save or load PCSX2's global frame counter (g_FrameCount) along with each savestate +// +// This is to prevent any inaccuracy issues caused by having a different +// internal emulation frame count than what it was at the beginning of the +// original recording void SaveStateBase::InputRecordingFreeze() { FreezeTag("InputRecording"); Freeze(g_FrameCount); #ifndef DISABLE_RECORDING - if (g_FrameCount > 0 && IsLoading()) + // Explicitly set the frame change tracking variable as to not + // detect loading a savestate as a frame being drawn + g_InputRecordingControls.SetFrameCountTracker(g_FrameCount); + // Loading a save-state is an asynchronous task, if we are playing a recording + // that starts from a savestate (not power-on) and the starting (pcsx2 internal) frame + // marker has not been set (which comes from the save-state), we initialize it. + // TODO - get rid of the -1 + if (g_InputRecording.GetStartingFrame() == -1 && g_InputRecording.GetInputRecordingData().FromSaveState()) { + g_InputRecording.SetStartingFrame(g_FrameCount); + // TODO - make a function of my own to simplify working with the logging macros + recordingConLog(wxString::Format(L"[REC]: Internal Starting Frame: %d\n", g_InputRecording.GetStartingFrame())); + } + // Otherwise the starting savestate has been loaded and if loaded a save-state while recording the movie + // it is an undo operation that needs to be tracked. + else if (g_InputRecording.RecordingActive() && IsLoading()) { - g_InputRecordingData.IncrementUndoCount(); + g_InputRecording.GetInputRecordingData().IncrementUndoCount(); + // Reloading a save-state means the internal recording frame counter may need to be adjusted + // Since we persist the g_FrameCount of the beginning of the movie, we can use it to recalculate it + g_InputRecording.SetFrameCounter(g_FrameCount - (g_InputRecording.GetStartingFrame())); } #endif } #ifndef DISABLE_RECORDING + InputRecording g_InputRecording; -// Main func for handling controller input data -// - Called by Sio.cpp::sioWriteController -void InputRecording::ControllerInterrupt(u8& data, u8& port, u16& bufCount, u8 buf[]) +void InputRecording::ControllerInterrupt(u8 &data, u8 &port, u16 &bufCount, u8 buf[]) { // TODO - Multi-Tap Support @@ -82,127 +102,195 @@ void InputRecording::ControllerInterrupt(u8& data, u8& port, u16& bufCount, u8 b } } - if (!fInterruptFrame || state == INPUT_RECORDING_MODE_NONE - // We do not want to record or save the first two - // bytes in the data returned from LilyPad - || bufCount < 3) + // We do not want to record or save the first two bytes in the data returned from the PAD plugin + if (!fInterruptFrame || state == InputRecordingMode::NoneActive || bufCount < 3) { return; } // Read or Write - const u8& nowBuf = buf[bufCount]; - if (state == INPUT_RECORDING_MODE_RECORD) + const u8 &nowBuf = buf[bufCount]; + if (state == InputRecordingMode::Recording) { - InputRecordingData.SetTotalFrames(g_FrameCount); - InputRecordingData.WriteKeyBuffer(g_FrameCount, port, bufCount - 3, nowBuf); + inputRecordingData.WriteKeyBuffer(frameCounter, port, bufCount - 3, nowBuf); } - else if (state == INPUT_RECORDING_MODE_REPLAY) + else if (state == InputRecordingMode::Replaying) { - if (InputRecordingData.GetTotalFrames() <= g_FrameCount) - { - // Pause the emulation but the movie is not closed - g_RecordingControls.Pause(); - return; - } u8 tmp = 0; - if (InputRecordingData.ReadKeyBuffer(tmp, g_FrameCount, port, bufCount - 3)) + if (inputRecordingData.ReadKeyBuffer(tmp, frameCounter, port, bufCount - 3)) { buf[bufCount] = tmp; } } } +u32 InputRecording::GetFrameCounter() +{ + return frameCounter; +} + +InputRecordingFile &InputRecording::GetInputRecordingData() +{ + return inputRecordingData; +} + +u32 InputRecording::GetStartingFrame() +{ + return startingFrame; +} + +void InputRecording::IncrementFrameCounter() +{ + frameCounter++; + if (state == InputRecordingMode::Recording) { + GetInputRecordingData().SetTotalFrames(frameCounter); + } +} + +bool InputRecording::IsInterruptFrame() +{ + return fInterruptFrame; +} + +bool InputRecording::IsRecordingReplaying() +{ + return RecordingActive() && state == InputRecordingMode::Replaying; +} + +bool InputRecording::RecordingActive() +{ + return state != InputRecordingMode::NoneActive; +} + +wxString InputRecording::RecordingModeTitleSegment() +{ + switch (state) + { + case InputRecordingMode::Recording: + return wxString("Recording"); + break; + case InputRecordingMode::Replaying: + return wxString("Replaying"); + break; + default: + return wxString("No Movie"); + break; + } +} + +void InputRecording::RecordModeToggle() +{ + if (state == InputRecordingMode::Replaying) + { + state = InputRecordingMode::Recording; + recordingConLog("[REC]: Record mode ON.\n"); + } + else if (state == InputRecordingMode::Recording) + { + state = InputRecordingMode::Replaying; + recordingConLog("[REC]: Replay mode ON.\n"); + } +} + +void InputRecording::SetFrameCounter(u32 newFrameCounter) +{ + frameCounter = newFrameCounter; + if (state == InputRecordingMode::Recording) + { + GetInputRecordingData().SetTotalFrames(frameCounter); + } +} + +void InputRecording::SetStartingFrame(u32 newStartingFrame) +{ + startingFrame = newStartingFrame; +} -// GUI Handler - Stop recording void InputRecording::Stop() { - state = INPUT_RECORDING_MODE_NONE; - if (InputRecordingData.Close()) + // Reset the frame counter when starting a new recording + frameCounter = 0; + startingFrame = -1; + state = InputRecordingMode::NoneActive; + if (inputRecordingData.Close()) { recordingConLog(L"[REC]: InputRecording Recording Stopped.\n"); } } -// GUI Handler - Start recording bool InputRecording::Create(wxString FileName, bool fromSaveState, wxString authorName) { - if (!InputRecordingData.OpenNew(FileName, fromSaveState)) + if (!inputRecordingData.OpenNew(FileName, fromSaveState)) { return false; } // Set emulator version - InputRecordingData.GetHeader().SetEmulatorVersion(); + inputRecordingData.GetHeader().SetEmulatorVersion(); // Set author name if (!authorName.IsEmpty()) { - InputRecordingData.GetHeader().SetAuthor(authorName); + inputRecordingData.GetHeader().SetAuthor(authorName); } // Set Game Name - InputRecordingData.GetHeader().SetGameName(resolveGameName()); + inputRecordingData.GetHeader().SetGameName(resolveGameName()); // Write header contents - InputRecordingData.WriteHeader(); - state = INPUT_RECORDING_MODE_RECORD; + inputRecordingData.WriteHeader(); + state = InputRecordingMode::Recording; recordingConLog(wxString::Format(L"[REC]: Started new recording - [%s]\n", FileName)); - - // In every case, we reset the g_FrameCount - g_FrameCount = 0; return true; } -// GUI Handler - Play a recording bool InputRecording::Play(wxString fileName) { - if (state != INPUT_RECORDING_MODE_NONE) + if (RecordingActive()) Stop(); - // Open the file and verify if it can be played - if (!InputRecordingData.OpenExisting(fileName)) + if (!inputRecordingData.OpenExisting(fileName)) { return false; } // Either load the savestate, or restart the game - if (InputRecordingData.FromSaveState()) + if (inputRecordingData.FromSaveState()) { if (!CoreThread.IsOpen()) { recordingConLog(L"[REC]: Game is not open, aborting playing input recording which starts on a save-state.\n"); - InputRecordingData.Close(); + inputRecordingData.Close(); return false; } - FILE* ssFileCheck = wxFopen(InputRecordingData.GetFilename() + "_SaveState.p2s", "r"); + FILE* ssFileCheck = wxFopen(inputRecordingData.GetFilename() + "_SaveState.p2s", "r"); if (ssFileCheck == NULL) { - recordingConLog(wxString::Format("[REC]: Could not locate savestate file at location - %s_SaveState.p2s\n", InputRecordingData.GetFilename())); - InputRecordingData.Close(); + recordingConLog(wxString::Format("[REC]: Could not locate savestate file at location - %s_SaveState.p2s\n", inputRecordingData.GetFilename())); + inputRecordingData.Close(); return false; } fclose(ssFileCheck); - StateCopy_LoadFromFile(InputRecordingData.GetFilename() + "_SaveState.p2s"); + StateCopy_LoadFromFile(inputRecordingData.GetFilename() + "_SaveState.p2s"); } else { - g_RecordingControls.Unpause(); + g_InputRecordingControls.Resume(); sApp.SysExecute(); } // Check if the current game matches with the one used to make the original recording if (!g_Conf->CurrentIso.IsEmpty()) { - if (resolveGameName() != InputRecordingData.GetHeader().gameName) + if (resolveGameName() != inputRecordingData.GetHeader().gameName) { recordingConLog(L"[REC]: Recording was possibly constructed for a different game.\n"); } } - state = INPUT_RECORDING_MODE_REPLAY; - recordingConLog(wxString::Format(L"[REC]: Replaying input recording - [%s]\n", InputRecordingData.GetFilename())); - recordingConLog(wxString::Format(L"[REC]: PCSX2 Version Used: %s\n", InputRecordingData.GetHeader().emu)); - recordingConLog(wxString::Format(L"[REC]: Recording File Version: %d\n", InputRecordingData.GetHeader().version)); - recordingConLog(wxString::Format(L"[REC]: Associated Game Name or ISO Filename: %s\n", InputRecordingData.GetHeader().gameName)); - recordingConLog(wxString::Format(L"[REC]: Author: %s\n", InputRecordingData.GetHeader().author)); - recordingConLog(wxString::Format(L"[REC]: Total Frames: %d\n", InputRecordingData.GetTotalFrames())); - recordingConLog(wxString::Format(L"[REC]: Undo Count: %d\n", InputRecordingData.GetUndoCount())); + state = InputRecordingMode::Replaying; + recordingConLog(wxString::Format(L"[REC]: Replaying input recording - [%s]\n", inputRecordingData.GetFilename())); + recordingConLog(wxString::Format(L"[REC]: PCSX2 Version Used: %s\n", inputRecordingData.GetHeader().emu)); + recordingConLog(wxString::Format(L"[REC]: Recording File Version: %d\n", inputRecordingData.GetHeader().version)); + recordingConLog(wxString::Format(L"[REC]: Associated Game Name or ISO Filename: %s\n", inputRecordingData.GetHeader().gameName)); + recordingConLog(wxString::Format(L"[REC]: Author: %s\n", inputRecordingData.GetHeader().author)); + recordingConLog(wxString::Format(L"[REC]: Total Frames: %d\n", inputRecordingData.GetTotalFrames())); + recordingConLog(wxString::Format(L"[REC]: Undo Count: %d\n", inputRecordingData.GetUndoCount())); return true; } @@ -226,33 +314,4 @@ wxString InputRecording::resolveGameName() return !gameName.IsEmpty() ? gameName : Path::GetFilename(g_Conf->CurrentIso); } -// Keybind Handler - Toggle between recording input and not -void InputRecording::RecordModeToggle() -{ - if (state == INPUT_RECORDING_MODE_REPLAY) - { - state = INPUT_RECORDING_MODE_RECORD; - recordingConLog("[REC]: Record mode ON.\n"); - } - else if (state == INPUT_RECORDING_MODE_RECORD) - { - state = INPUT_RECORDING_MODE_REPLAY; - recordingConLog("[REC]: Replay mode ON.\n"); - } -} - -INPUT_RECORDING_MODE InputRecording::GetModeState() -{ - return state; -} - -InputRecordingFile& InputRecording::GetInputRecordingData() -{ - return InputRecordingData; -} - -bool InputRecording::IsInterruptFrame() -{ - return fInterruptFrame; -} #endif diff --git a/pcsx2/Recording/InputRecording.h b/pcsx2/Recording/InputRecording.h index d3feb4000d..63c2ea8925 100644 --- a/pcsx2/Recording/InputRecording.h +++ b/pcsx2/Recording/InputRecording.h @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2019 PCSX2 Dev Team + * Copyright (C) 2002-2020 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -19,41 +19,69 @@ #ifndef DISABLE_RECORDING -enum INPUT_RECORDING_MODE -{ - INPUT_RECORDING_MODE_NONE, - INPUT_RECORDING_MODE_RECORD, - INPUT_RECORDING_MODE_REPLAY, -}; - class InputRecording { public: - InputRecording() {} - ~InputRecording() {} + // Main handler for ingesting input data and either saving it to the recording file (recording) + // or mutating it to the contents of the recording file (replaying) + void ControllerInterrupt(u8 &data, u8 &port, u16 &BufCount, u8 buf[]); - void ControllerInterrupt(u8& data, u8& port, u16& BufCount, u8 buf[]); + // The running frame counter for the input recording + u32 GetFrameCounter(); - void RecordModeToggle(); + InputRecordingFile &GetInputRecordingData(); - INPUT_RECORDING_MODE GetModeState(); - InputRecordingFile& GetInputRecordingData(); + // The internal PCSX2 g_FrameCount value on the first frame of the recording + u32 GetStartingFrame(); + + void IncrementFrameCounter(); + + // DEPRECATED: Slated for removal + // If the current frame contains controller / input data bool IsInterruptFrame(); - void Stop(); + // If there is currently an input recording being played back or actively being recorded + bool RecordingActive(); + bool IsRecordingReplaying(); + + // String representation of the current recording mode to be interpolated into the title + wxString RecordingModeTitleSegment(); + + // Switches between recording and replaying the active input recording file + void RecordModeToggle(); + + // Set the running frame counter for the input recording to an arbitrary value + void SetFrameCounter(u32 newFrameCounter); + // Store the starting internal PCSX2 g_FrameCount value + void SetStartingFrame(u32 newStartingFrame); + + /// Functions called from GUI + + // Create a new input recording file bool Create(wxString filename, bool fromSaveState, wxString authorName); + // Play an existing input recording from a file bool Play(wxString filename); + // Stop the active input recording + void Stop(); private: - InputRecordingFile InputRecordingData; - INPUT_RECORDING_MODE state = INPUT_RECORDING_MODE_NONE; + enum class InputRecordingMode { + NoneActive, + Recording, + Replaying, + }; + bool fInterruptFrame = false; + + InputRecordingFile inputRecordingData; + InputRecordingMode state = InputRecording::InputRecordingMode::NoneActive; + u32 frameCounter = 0; + u32 startingFrame = -1; + // Resolve the name and region of the game currently loaded using the GameDB // If the game cannot be found in the DB, the fallback is the ISO filename wxString resolveGameName(); }; extern InputRecording g_InputRecording; -static InputRecordingFile& g_InputRecordingData = g_InputRecording.GetInputRecordingData(); -static InputRecordingFileHeader& g_InputRecordingHeader = g_InputRecording.GetInputRecordingData().GetHeader(); #endif diff --git a/pcsx2/Recording/InputRecordingControls.cpp b/pcsx2/Recording/InputRecordingControls.cpp new file mode 100644 index 0000000000..da476ee2ec --- /dev/null +++ b/pcsx2/Recording/InputRecordingControls.cpp @@ -0,0 +1,140 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "App.h" +#include "Counters.h" +#include "DebugTools/Debug.h" +#include "GSFrame.h" +#include "MemoryTypes.h" + +#include "InputRecording.h" +#include "InputRecordingControls.h" + + +#ifndef DISABLE_RECORDING + +InputRecordingControls g_InputRecordingControls; + + +void InputRecordingControls::HandleFrameAdvanceAndPausing() +{ + // This function can be called multiple times per frame via Counters::rcntUpdate_vSync, + // often this is twice per frame but this may vary as Counters.cpp mentions the + // vsync timing can change. + // + // As a safeguard, use the global g_FrameCount to know when the frame counter has truly changed. + // + // NOTE - Do not mutate g_FrameCount or use it's value to set the input recording's internal frame counter + if (frameCountTracker != g_FrameCount) { + frameCountTracker = g_FrameCount; + g_InputRecording.IncrementFrameCounter(); + } else { + if (pauseEmulation) { + emulationCurrentlyPaused = true; + CoreThread.PauseSelf(); + } + return; + } + + if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) { + pauseEmulation = true; + } + + // If we havn't yet advanced atleast a single frame from when we paused, setup things to be paused + if (frameAdvancing && frameAdvanceMarker < g_InputRecording.GetFrameCounter()) + { + frameAdvancing = false; + pauseEmulation = true; + } + + // Pause emulation if we need to (either due to frame advancing, or pause has been explicitly toggled on) + if (pauseEmulation && CoreThread.IsOpen() && CoreThread.IsRunning()) { + emulationCurrentlyPaused = true; + CoreThread.PauseSelf(); + } +} + +void InputRecordingControls::ResumeCoreThreadIfStarted() +{ + if (resumeEmulation && CoreThread.IsOpen() && CoreThread.IsPaused()) { + CoreThread.Resume(); + resumeEmulation = false; + emulationCurrentlyPaused = false; + } +} + +void InputRecordingControls::FrameAdvance() +{ + if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) + { + g_InputRecording.RecordModeToggle(); + return; + } + frameAdvanceMarker = g_InputRecording.GetFrameCounter(); + frameAdvancing = true; + Resume(); +} + +bool InputRecordingControls::IsRecordingPaused() +{ + return (emulationCurrentlyPaused && CoreThread.IsOpen() && CoreThread.IsPaused()); +} + +void InputRecordingControls::Pause() +{ + pauseEmulation = true; + resumeEmulation = false; +} + +void InputRecordingControls::PauseImmediately() +{ + if (CoreThread.IsPaused()) { + return; + } + Pause(); + if (pauseEmulation && CoreThread.IsOpen() && CoreThread.IsRunning()) { + emulationCurrentlyPaused = true; + CoreThread.PauseSelf(); + } +} + +void InputRecordingControls::Resume() +{ + if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) { + g_InputRecording.RecordModeToggle(); + return; + } + pauseEmulation = false; + resumeEmulation = true; +} + +void InputRecordingControls::SetFrameCountTracker(u32 newFrame) +{ + frameCountTracker = newFrame; +} + +void InputRecordingControls::TogglePause() +{ + if (pauseEmulation && g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) { + g_InputRecording.RecordModeToggle(); + return; + } + pauseEmulation = !pauseEmulation; + resumeEmulation = !pauseEmulation; +} + +#endif diff --git a/pcsx2/Recording/InputRecordingControls.h b/pcsx2/Recording/InputRecordingControls.h new file mode 100644 index 0000000000..9d3924d7a0 --- /dev/null +++ b/pcsx2/Recording/InputRecordingControls.h @@ -0,0 +1,72 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#ifndef DISABLE_RECORDING +class InputRecordingControls +{ +public: + // Intended to be called at the end of each frame, but will no-op if the frame count has not + // truly incremented + // + // Increments the input recording's frame counter and will pause emulation if: + // - The InputRecordingControls::FrameAdvance was hit on the previous frame + // - Emulation was explicitly paused using InputRecordingControls::TogglePause + // - We are replaying an input recording and have hit the end + void HandleFrameAdvanceAndPausing(); + // Called much more frequently than HandleFrameAdvanceAndPausing, instead of being per frame + // this hooks into pcsx2's main App event handler as it has to be able to resume emulation + // when drawing frames has compltely stopped + // + // Resumes emulation if: + // - CoreThread is currently open and paused + // - We've signaled emulation to be resumed via TogglePause or FrameAdvancing + void ResumeCoreThreadIfStarted(); + + // Resume emulation (incase the emulation is currently paused) and pause after a single frame has passed + void FrameAdvance(); + // Returns true if the input recording has been paused, which can occur: + // - After a single frame has passed after InputRecordingControls::FrameAdvance + // - Explicitly paused via InputRecordingControls::TogglePause + bool IsRecordingPaused(); + // Pause emulation at the next available Vsync + void Pause(); + // Pause emulation immediately, not waiting for the next Vsync + void PauseImmediately(); + // Resume emulation when the next pcsx2 App event is handled + void Resume(); + void SetFrameCountTracker(u32 newFrame); + // Alternates emulation between a paused and unpaused state + void TogglePause(); + +private: + // Indicates if the input recording controls have explicitly paused emulation or not + bool emulationCurrentlyPaused = false; + // Indicates on the next VSync if we are frame advancing, this value + // and should be cleared once a single frame has passed + bool frameAdvancing = false; + // The input recording frame that frame advancing began on + u32 frameAdvanceMarker = 0; + // Used to detect if the internal PCSX2 g_FrameCount has changed + u32 frameCountTracker = -1; + // Indicates if we intend to call CoreThread.PauseSelf() on the current or next available vsync + bool pauseEmulation = false; + // Indicates if we intend to call CoreThread.Resume() when the next pcsx2 App event is handled + bool resumeEmulation = false; +}; + +extern InputRecordingControls g_InputRecordingControls; +#endif diff --git a/pcsx2/Recording/InputRecordingFile.cpp b/pcsx2/Recording/InputRecordingFile.cpp index 3f03f521d1..739f0a0214 100644 --- a/pcsx2/Recording/InputRecordingFile.cpp +++ b/pcsx2/Recording/InputRecordingFile.cpp @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2019 PCSX2 Dev Team + * Copyright (C) 2002-2020 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -260,19 +260,4 @@ bool InputRecordingFile::verifyRecordingFileHeader() } return true; } - -bool InputRecordingFile::writeSaveState() { - if (recordingFile == NULL) - { - return false; - } - - fseek(recordingFile, seekpointSaveStateHeader, SEEK_SET); - if (fwrite(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) - { - return false; - } - - return true; -} #endif diff --git a/pcsx2/Recording/InputRecordingFile.h b/pcsx2/Recording/InputRecordingFile.h index 4c5bcd8006..9bf1c6e7eb 100644 --- a/pcsx2/Recording/InputRecordingFile.h +++ b/pcsx2/Recording/InputRecordingFile.h @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2019 PCSX2 Dev Team + * Copyright (C) 2002-2020 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -82,6 +82,8 @@ public: // Writes the current frame's input data to the file so it can be replayed bool WriteKeyBuffer(const uint &frame, const uint port, const uint bufIndex, const u8 &buf); + unsigned long recordingFrameCounter = 0; + private: static const int controllerPortsSupported = 2; static const int controllerInputBytes = 18; @@ -99,6 +101,8 @@ private: wxString filename = ""; FILE * recordingFile = NULL; InputRecordingSavestate savestate; + + // An unsigned 32-bit frame limit is equivalent to 2.25 years of continuous 60fps footage unsigned long totalFrames = 0; unsigned long undoCount = 0; @@ -106,6 +110,5 @@ private: long getRecordingBlockSeekPoint(const long& frame); bool open(const wxString path, bool newRecording); bool verifyRecordingFileHeader(); - bool writeSaveState(); }; #endif diff --git a/pcsx2/Recording/RecordingControls.cpp b/pcsx2/Recording/RecordingControls.cpp deleted file mode 100644 index 516bb192f8..0000000000 --- a/pcsx2/Recording/RecordingControls.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2020 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" - -#include "App.h" -#include "Counters.h" -#include "Common.h" -#include "GSFrame.h" -#include "MemoryTypes.h" - -#include "RecordingControls.h" - - -#ifndef DISABLE_RECORDING -RecordingControls g_RecordingControls; - -//----------------------------------------------- -// Current recording status, returns true if: -// - Recording is Paused -// - GSFrame CoreThread is both running AND paused -//----------------------------------------------- -bool RecordingControls::IsEmulationAndRecordingPaused() -{ - return fPauseState && CoreThread.IsOpen() && CoreThread.IsPaused(); -} - -//----------------------------------------------- -// Called after inputs are recorded for that frame, places lock on input recording, effectively releasing resources and resuming CoreThread. -//----------------------------------------------- -void RecordingControls::ResumeCoreThreadIfStarted() -{ - if (fStart && CoreThread.IsOpen() && CoreThread.IsPaused()) - { - CoreThread.Resume(); - fStart = false; - fPauseState = false; - } -} - -//----------------------------------------------- -// Called at VSYNC End / VRender Begin, updates everything recording related for the next frame, -// toggles RecordingControl flags back to enable input recording for the next frame. -//----------------------------------------------- -void RecordingControls::HandleFrameAdvanceAndStop() -{ - if (fFrameAdvance) - { - if (stopFrameCount < g_FrameCount) - { - fFrameAdvance = false; - fStop = true; - stopFrameCount = g_FrameCount; - - // We force the frame counter in the title bar to change - wxString oldTitle = wxGetApp().GetGsFrame().GetTitle(); - wxString title = g_Conf->Templates.RecordingTemplate; - wxString frameCount = wxString::Format("%d", g_FrameCount); - - title.Replace(L"${frame}", frameCount); - int frameIndex = title.find(wxString::Format(L"%d", g_FrameCount)); - frameIndex += frameCount.length(); - - title.replace(frameIndex, oldTitle.length() - frameIndex, oldTitle.c_str().AsChar() + frameIndex); - - wxGetApp().GetGsFrame().SetTitle(title); - } - } - if (fStop && CoreThread.IsOpen() && CoreThread.IsRunning()) - { - fPauseState = true; - CoreThread.PauseSelf(); - } -} - -bool RecordingControls::GetStopFlag() -{ - return (fStop || fFrameAdvance); -} - -void RecordingControls::FrameAdvance() -{ - stopFrameCount = g_FrameCount; - fFrameAdvance = true; - fStop = false; - fStart = true; -} - -void RecordingControls::TogglePause() -{ - fStop = !fStop; - if (fStop == false) - { - fStart = true; - } -} - -void RecordingControls::Pause() -{ - fStop = true; -} - -void RecordingControls::Unpause() -{ - fStop = false; - fStart = true; -} -#endif diff --git a/pcsx2/Recording/RecordingControls.h b/pcsx2/Recording/RecordingControls.h deleted file mode 100644 index f9e60a633d..0000000000 --- a/pcsx2/Recording/RecordingControls.h +++ /dev/null @@ -1,50 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2020 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#ifndef DISABLE_RECORDING -class RecordingControls -{ -public: - - // Movie controls main functions - bool IsEmulationAndRecordingPaused(); - void ResumeCoreThreadIfStarted(); - void HandleFrameAdvanceAndStop(); - - // Shortcut Keys - void FrameAdvance(); - void TogglePause(); - - // Setters - void Pause(); - void Unpause(); - - // Getters - bool GetStopFlag(); - -private: - uint stopFrameCount = false; - - bool fStop = false; - bool fStart = false; - bool fFrameAdvance = false; - bool fPauseState = false; - -}; - -extern RecordingControls g_RecordingControls; -#endif diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 8e856d3c78..45eb842edd 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -32,7 +32,7 @@ #include "Debugger/DisassemblyDialog.h" #ifndef DISABLE_RECORDING -# include "Recording/RecordingControls.h" +# include "Recording/InputRecordingControls.h" # include "Recording/InputRecording.h" #endif @@ -620,7 +620,7 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& #ifndef DISABLE_RECORDING if (g_Conf->EmuOptions.EnableRecordingTools) { - if (g_RecordingControls.IsEmulationAndRecordingPaused()) + if (g_InputRecordingControls.IsRecordingPaused()) { // When the GSFrame CoreThread is paused, so is the logical VSync // Meaning that we have to grab the user-input through here to potentially @@ -633,7 +633,7 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& } } } - g_RecordingControls.ResumeCoreThreadIfStarted(); + g_InputRecordingControls.ResumeCoreThreadIfStarted(); } #endif (handler->*func)(event); @@ -1059,7 +1059,7 @@ void Pcsx2App::OnProgramLogClosed( wxWindowID id ) void Pcsx2App::OnMainFrameClosed( wxWindowID id ) { #ifndef DISABLE_RECORDING - if (g_InputRecording.GetModeState() == INPUT_RECORDING_MODE_NONE) + if (g_InputRecording.RecordingActive()) { g_InputRecording.Stop(); } diff --git a/pcsx2/gui/FrameForGS.cpp b/pcsx2/gui/FrameForGS.cpp index c2a3ad38aa..0a432d533e 100644 --- a/pcsx2/gui/FrameForGS.cpp +++ b/pcsx2/gui/FrameForGS.cpp @@ -425,7 +425,7 @@ void GSPanel::DirectKeyCommand( wxKeyEvent& evt ) void GSPanel::UpdateScreensaver() { bool prevent = g_Conf->GSWindow.DisableScreenSaver - && m_HasFocus && m_coreRunning; + && m_HasFocus && m_coreRunning; ScreensaverAllow(!prevent); } @@ -732,25 +732,15 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt ) #ifndef DISABLE_RECORDING wxString title; wxString movieMode; - switch (g_InputRecording.GetModeState()) + if (g_InputRecording.RecordingActive()) { - case INPUT_RECORDING_MODE_RECORD: - movieMode = "Recording"; - title = templates.RecordingTemplate; - break; - case INPUT_RECORDING_MODE_REPLAY: - movieMode = "Replaying"; - title = templates.RecordingTemplate; - break; - case INPUT_RECORDING_MODE_NONE: - movieMode = "No movie"; - title = templates.TitleTemplate; - break; + title = templates.RecordingTemplate; + title.Replace(L"${frame}", pxsFmt(L"%d", g_InputRecording.GetFrameCounter())); + title.Replace(L"${maxFrame}", pxsFmt(L"%d", g_InputRecording.GetInputRecordingData().GetTotalFrames())); + title.Replace(L"${mode}", g_InputRecording.RecordingModeTitleSegment()); + } else { + title = templates.TitleTemplate; } - - title.Replace(L"${frame}", pxsFmt(L"%d", g_FrameCount)); - title.Replace(L"${maxFrame}", pxsFmt(L"%d", g_InputRecording.GetInputRecordingData().GetTotalFrames())); - title.Replace(L"${mode}", movieMode); #else wxString title = templates.TitleTemplate; #endif diff --git a/pcsx2/gui/GlobalCommands.cpp b/pcsx2/gui/GlobalCommands.cpp index 75fd9e1cc8..da411e1925 100644 --- a/pcsx2/gui/GlobalCommands.cpp +++ b/pcsx2/gui/GlobalCommands.cpp @@ -23,7 +23,7 @@ #include "AppSaveStates.h" #ifndef DISABLE_RECORDING -# include "Recording/RecordingControls.h" +# include "Recording/InputRecordingControls.h" # include "Recording/InputRecording.h" #endif @@ -473,7 +473,7 @@ namespace Implementations { if (g_Conf->EmuOptions.EnableRecordingTools) { - g_RecordingControls.FrameAdvance(); + g_InputRecordingControls.FrameAdvance(); } } @@ -481,7 +481,7 @@ namespace Implementations { if (g_Conf->EmuOptions.EnableRecordingTools) { - g_RecordingControls.TogglePause(); + g_InputRecordingControls.TogglePause(); } } diff --git a/pcsx2/gui/MainMenuClicks.cpp b/pcsx2/gui/MainMenuClicks.cpp index adf90ed6c9..ae27bd6b8f 100644 --- a/pcsx2/gui/MainMenuClicks.cpp +++ b/pcsx2/gui/MainMenuClicks.cpp @@ -32,9 +32,8 @@ #ifndef DISABLE_RECORDING # include "Recording/InputRecording.h" -# include "Recording/RecordingControls.h" +# include "Recording/InputRecordingControls.h" # include "Recording/VirtualPad.h" -# include "Recording/RecordingControls.h" #endif @@ -531,7 +530,7 @@ void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent& event) else { //Properly close any currently loaded recording file before disabling - if (g_InputRecording.GetModeState() != INPUT_RECORDING_MODE_NONE) + if (g_InputRecording.RecordingActive()) Menu_Recording_Stop_Click(event); GetMenuBar()->Remove(TopLevelMenu_InputRecording); // Always turn controller logs off, but never turn it on by default @@ -544,8 +543,8 @@ void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent& event) viewport->InitDefaultAccelerators(); } } - if (g_RecordingControls.IsEmulationAndRecordingPaused()) - g_RecordingControls.Unpause(); + if (g_InputRecordingControls.IsRecordingPaused()) + g_InputRecordingControls.Resume(); } g_Conf->EmuOptions.EnableRecordingTools = checked; @@ -690,11 +689,11 @@ void MainEmuFrame::Menu_ConfigPlugin_Click(wxCommandEvent& event) // If the CoreThread is paused prior to opening the PAD plugin settings then when the settings // are closed the PAD will not re-open. To avoid this, we resume emulation prior to the plugins // configuration handler doing so. - if (g_Conf->EmuOptions.EnableRecordingTools && g_RecordingControls.IsEmulationAndRecordingPaused()) + if (g_Conf->EmuOptions.EnableRecordingTools && g_InputRecordingControls.IsRecordingPaused()) { - g_RecordingControls.Unpause(); + g_InputRecordingControls.Resume(); GetCorePlugins().Configure(pid); - g_RecordingControls.Pause(); + g_InputRecordingControls.Pause(); } else GetCorePlugins().Configure(pid); @@ -874,53 +873,53 @@ void MainEmuFrame::Menu_Capture_Screenshot_Screenshot_Click(wxCommandEvent & eve #ifndef DISABLE_RECORDING void MainEmuFrame::Menu_Recording_New_Click(wxCommandEvent &event) { - const bool initiallyPaused = g_RecordingControls.IsEmulationAndRecordingPaused(); + const bool initiallyPaused = g_InputRecordingControls.IsRecordingPaused(); if (!initiallyPaused) - g_RecordingControls.Pause(); + g_InputRecordingControls.PauseImmediately(); NewRecordingFrame* NewRecordingFrame = wxGetApp().GetNewRecordingFramePtr(); if (NewRecordingFrame) { if (NewRecordingFrame->ShowModal() == wxID_CANCEL) { if (!initiallyPaused) - g_RecordingControls.Unpause(); + g_InputRecordingControls.Resume(); return; } if (!g_InputRecording.Create(NewRecordingFrame->GetFile(), !NewRecordingFrame->GetFrom(), NewRecordingFrame->GetAuthor())) { if (!initiallyPaused) - g_RecordingControls.Unpause(); + g_InputRecordingControls.Resume(); return; } } m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false); m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true); - if (!g_InputRecordingData.FromSaveState()) - g_RecordingControls.Unpause(); + if (!g_InputRecording.GetInputRecordingData().FromSaveState()) + g_InputRecordingControls.Resume(); } void MainEmuFrame::Menu_Recording_Play_Click(wxCommandEvent &event) { - const bool initiallyPaused = g_RecordingControls.IsEmulationAndRecordingPaused(); + const bool initiallyPaused = g_InputRecordingControls.IsRecordingPaused(); if (!initiallyPaused) - g_RecordingControls.Pause(); + g_InputRecordingControls.PauseImmediately(); wxFileDialog openFileDialog(this, _("Select P2M2 record file."), L"", L"", L"p2m2 file(*.p2m2)|*.p2m2", wxFD_OPEN); if (openFileDialog.ShowModal() == wxID_CANCEL) { if (!initiallyPaused) - g_RecordingControls.Unpause(); + g_InputRecordingControls.Resume(); return; } wxString path = openFileDialog.GetPath(); - const bool recordingLoaded = g_InputRecording.GetModeState() != INPUT_RECORDING_MODE_NONE; + const bool recordingLoaded = g_InputRecording.RecordingActive(); if (!g_InputRecording.Play(path)) { if (recordingLoaded) Menu_Recording_Stop_Click(event); if (!initiallyPaused) - g_RecordingControls.Unpause(); + g_InputRecordingControls.Resume(); return; } if (!recordingLoaded) @@ -928,7 +927,7 @@ void MainEmuFrame::Menu_Recording_Play_Click(wxCommandEvent &event) m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false); m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true); } - g_RecordingControls.Unpause(); + g_InputRecordingControls.Resume(); } void MainEmuFrame::Menu_Recording_Stop_Click(wxCommandEvent &event) diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj b/pcsx2/windows/VCprojects/pcsx2.vcxproj index d385dcd0ec..23a4d988cf 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj @@ -192,12 +192,12 @@ + - @@ -430,9 +430,9 @@ + - diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters index 414cabdcdd..5ffaa322b6 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters @@ -847,9 +847,6 @@ Recording - - Recording - Recording @@ -862,6 +859,9 @@ System\Ps2\IPU + + Recording + @@ -1299,9 +1299,6 @@ Recording - - Recording - Recording @@ -1311,6 +1308,9 @@ Recording\gui + + Recording +