diff --git a/Core/Console.cpp b/Core/Console.cpp index 6ed28b5..1a32f23 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -24,6 +24,7 @@ #include "SaveStateManager.h" #include "DebugStats.h" #include "CartTypes.h" +#include "RewindManager.h" #include "ConsoleLock.h" #include "../Utilities/Serializer.h" #include "../Utilities/Timer.h" @@ -91,6 +92,8 @@ void Console::Run() _cpu->Exec(); if(previousFrameCount != _ppu->GetFrameCount()) { + _rewindManager->ProcessEndOfFrame(); + WaitForLock(); frameLimiter.ProcessFrame(); @@ -134,6 +137,7 @@ void Console::Stop() debugger.reset(); _videoDecoder->StopThread(); + _rewindManager.reset(); _cpu.reset(); _ppu.reset(); @@ -175,6 +179,9 @@ void Console::LoadRom(VirtualFile romFile, VirtualFile patchFile) _cpu.reset(new Cpu(this)); _memoryManager->IncrementMasterClockValue<170>(); + _rewindManager.reset(new RewindManager(shared_from_this())); + _notificationManager->RegisterNotificationListener(_rewindManager); + //if(_debugger) { //Reset debugger if it was running before //auto lock = _debuggerLock.AcquireSafe(); @@ -263,6 +270,8 @@ void Console::Deserialize(istream &in, uint32_t fileFormatVersion) serializer.Stream(_cart.get()); serializer.Stream(_controlManager.get()); serializer.Stream(_spc.get()); + + _notificationManager->SendNotification(ConsoleNotificationType::StateLoaded); } shared_ptr Console::GetSoundMixer() @@ -295,6 +304,11 @@ shared_ptr Console::GetSaveStateManager() return _saveStateManager; } +shared_ptr Console::GetRewindManager() +{ + return _rewindManager; +} + shared_ptr Console::GetDebugHud() { return _debugHud; diff --git a/Core/Console.h b/Core/Console.h index 173dddb..dc70725 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -21,6 +21,7 @@ class VideoDecoder; class NotificationManager; class EmuSettings; class SaveStateManager; +class RewindManager; enum class MemoryOperationType; enum class SnesMemoryType; enum class EventType; @@ -46,6 +47,7 @@ private: shared_ptr _debugHud; shared_ptr _settings; shared_ptr _saveStateManager; + shared_ptr _rewindManager; thread::id _emulationThreadId; @@ -83,7 +85,7 @@ public: shared_ptr GetNotificationManager(); shared_ptr GetSettings(); shared_ptr GetSaveStateManager(); - + shared_ptr GetRewindManager(); shared_ptr GetDebugHud(); shared_ptr GetCpu(); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 240b9f2..7cba715 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -105,6 +105,8 @@ + + @@ -157,6 +159,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index c9819c5..6bad133 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -236,6 +236,12 @@ Misc + + Misc + + + Misc + @@ -378,6 +384,12 @@ Misc + + Misc + + + Misc + diff --git a/Core/EmuSettings.cpp b/Core/EmuSettings.cpp index 0e03e2d..f3d41fb 100644 --- a/Core/EmuSettings.cpp +++ b/Core/EmuSettings.cpp @@ -129,9 +129,22 @@ vector EmuSettings::GetShortcutSupersets(EmulatorShortcut shortc return _shortcutSupersets[keySetIndex][(uint32_t)shortcut]; } +uint32_t EmuSettings::GetRewindBufferSize() +{ + return _preferences.RewindBufferSize; +} + uint32_t EmuSettings::GetEmulationSpeed() { - return _emulation.EmulationSpeed; + if(CheckFlag(EmulationFlags::MaximumSpeed)) { + return 0; + } else if(CheckFlag(EmulationFlags::Turbo)) { + return _emulation.TurboSpeed; + } else if(CheckFlag(EmulationFlags::Rewind)) { + return _emulation.RewindSpeed; + } else { + return _emulation.EmulationSpeed; + } } double EmuSettings::GetAspectRatio() @@ -152,4 +165,32 @@ double EmuSettings::GetAspectRatio() case VideoAspectRatio::Custom: return _video.CustomAspectRatio; } return 0.0; +} + +void EmuSettings::SetFlag(EmulationFlags flag) +{ + if((_flags & (int)flag) == 0) { + _flags |= (int)flag; + } +} + +void EmuSettings::SetFlagState(EmulationFlags flag, bool enabled) +{ + if(enabled) { + SetFlag(flag); + } else { + ClearFlag(flag); + } +} + +void EmuSettings::ClearFlag(EmulationFlags flag) +{ + if((_flags & (int)flag) != 0) { + _flags &= ~(int)flag; + } +} + +bool EmuSettings::CheckFlag(EmulationFlags flag) +{ + return (_flags & (int)flag) != 0; } \ No newline at end of file diff --git a/Core/EmuSettings.h b/Core/EmuSettings.h index 093e2bb..1e67afa 100644 --- a/Core/EmuSettings.h +++ b/Core/EmuSettings.h @@ -10,6 +10,8 @@ private: EmulationConfig _emulation; PreferencesConfig _preferences; + atomic _flags; + string _audioDevice; string _saveFolder; string _saveStateFolder; @@ -42,6 +44,12 @@ public: KeyCombination GetShortcutKey(EmulatorShortcut shortcut, int keySetIndex); vector GetShortcutSupersets(EmulatorShortcut shortcut, int keySetIndex); + uint32_t GetRewindBufferSize(); uint32_t GetEmulationSpeed(); double GetAspectRatio(); + + void SetFlag(EmulationFlags flag); + void SetFlagState(EmulationFlags flag, bool enabled); + void ClearFlag(EmulationFlags flag); + bool CheckFlag(EmulationFlags flag); }; \ No newline at end of file diff --git a/Core/Ppu.cpp b/Core/Ppu.cpp index 3ccb3f9..e96a66f 100644 --- a/Core/Ppu.cpp +++ b/Core/Ppu.cpp @@ -11,6 +11,7 @@ #include "DmaController.h" #include "MessageManager.h" #include "EventType.h" +#include "RewindManager.h" #include "../Utilities/HexUtilities.h" #include "../Utilities/Serializer.h" @@ -1074,8 +1075,9 @@ void Ppu::SendFrame() uint16_t width = 512; uint16_t height = _overscanMode ? 478 : 448; - if(_screenInterlace) { - _console->GetVideoDecoder()->UpdateFrameSync(_currentBuffer, width, height, _frameCount); + bool isRewinding = _console->GetRewindManager()->IsRewinding(); + if(isRewinding || _screenInterlace) { + _console->GetVideoDecoder()->UpdateFrameSync(_currentBuffer, width, height, _frameCount, isRewinding); } else { _console->GetVideoDecoder()->UpdateFrame(_currentBuffer, width, height, _frameCount); _currentBuffer = _currentBuffer == _outputBuffers[0] ? _outputBuffers[1] : _outputBuffers[0]; diff --git a/Core/RewindData.cpp b/Core/RewindData.cpp new file mode 100644 index 0000000..3a1b324 --- /dev/null +++ b/Core/RewindData.cpp @@ -0,0 +1,52 @@ +#include "stdafx.h" +#include "RewindData.h" +#include "Console.h" +#include "SaveStateManager.h" +#include "../Utilities/miniz.h" + +void RewindData::GetStateData(stringstream &stateData) +{ + unsigned long length = OriginalSaveStateSize; + uint8_t* buffer = new uint8_t[length]; + uncompress(buffer, &length, SaveStateData.data(), (unsigned long)SaveStateData.size()); + stateData.write((char*)buffer, length); + delete[] buffer; +} + +void RewindData::LoadState(shared_ptr &console) +{ + if(SaveStateData.size() > 0 && OriginalSaveStateSize > 0) { + unsigned long length = OriginalSaveStateSize; + uint8_t* buffer = new uint8_t[length]; + uncompress(buffer, &length, SaveStateData.data(), (unsigned long)SaveStateData.size()); + + stringstream stream; + stream.write((char*)buffer, length); + stream.seekg(0, ios::beg); + + console->Deserialize(stream, SaveStateManager::FileFormatVersion); + delete[] buffer; + } +} + +void RewindData::CompressState(string stateData, vector& compressedState) +{ + unsigned long compressedSize = compressBound((unsigned long)stateData.size()); + uint8_t* compressedData = new uint8_t[compressedSize]; + compress(compressedData, &compressedSize, (unsigned char*)stateData.c_str(), (unsigned long)stateData.size()); + compressedState = vector(compressedData, compressedData + compressedSize); + delete[] compressedData; +} + +void RewindData::SaveState(shared_ptr &console) +{ + std::stringstream state; + console->Serialize(state); + + string stateData = state.str(); + vector compressedState; + CompressState(stateData, compressedState); + SaveStateData = compressedState; + OriginalSaveStateSize = (uint32_t)stateData.size(); + FrameCount = 0; +} diff --git a/Core/RewindData.h b/Core/RewindData.h new file mode 100644 index 0000000..f47894d --- /dev/null +++ b/Core/RewindData.h @@ -0,0 +1,25 @@ +#pragma once +#include "stdafx.h" +#include +#include "BaseControlDevice.h" + +class Console; + +class RewindData +{ +private: + vector SaveStateData; + uint32_t OriginalSaveStateSize = 0; + + void CompressState(string stateData, vector &compressedState); + +public: + std::deque InputLogs[BaseControlDevice::PortCount]; + int32_t FrameCount = 0; + bool EndOfSegment = false; + + void GetStateData(stringstream &stateData); + + void LoadState(shared_ptr &console); + void SaveState(shared_ptr &console); +}; diff --git a/Core/RewindManager.cpp b/Core/RewindManager.cpp new file mode 100644 index 0000000..93f6c94 --- /dev/null +++ b/Core/RewindManager.cpp @@ -0,0 +1,354 @@ +#include "stdafx.h" +#include "RewindManager.h" +#include "MessageManager.h" +#include "Console.h" +#include "EmuSettings.h" +#include "ControlManager.h" +#include "VideoRenderer.h" +#include "SoundMixer.h" +#include "BaseControlDevice.h" + +RewindManager::RewindManager(shared_ptr console) +{ + _console = console; + _settings = console->GetSettings(); + _rewindState = RewindState::Stopped; + _framesToFastForward = 0; + _hasHistory = false; + AddHistoryBlock(); + + _console->GetControlManager()->RegisterInputProvider(this); + _console->GetControlManager()->RegisterInputRecorder(this); +} + +RewindManager::~RewindManager() +{ + _console->GetControlManager()->UnregisterInputProvider(this); + _console->GetControlManager()->UnregisterInputRecorder(this); +} + +void RewindManager::ClearBuffer() +{ + _hasHistory = false; + _history.clear(); + _historyBackup.clear(); + _currentHistory = RewindData(); + _framesToFastForward = 0; + _videoHistory.clear(); + _videoHistoryBuilder.clear(); + _audioHistory.clear(); + _audioHistoryBuilder.clear(); + _rewindState = RewindState::Stopped; + _currentHistory = RewindData(); +} + +void RewindManager::ProcessNotification(ConsoleNotificationType type, void * parameter) +{ + if(type == ConsoleNotificationType::PpuFrameDone) { + _hasHistory = _history.size() >= 2; + if(_settings->GetRewindBufferSize() > 0) { + switch(_rewindState) { + case RewindState::Starting: + case RewindState::Started: + case RewindState::Debugging: + _currentHistory.FrameCount--; + break; + + case RewindState::Stopping: + _framesToFastForward--; + _currentHistory.FrameCount++; + if(_framesToFastForward == 0) { + for(int i = 0; i < 4; i++) { + size_t numberToRemove = _currentHistory.InputLogs[i].size(); + _currentHistory.InputLogs[i] = _historyBackup.front().InputLogs[i]; + for(size_t j = 0; j < numberToRemove; j++) { + _currentHistory.InputLogs[i].pop_back(); + } + } + _historyBackup.clear(); + _rewindState = RewindState::Stopped; + _settings->ClearFlag(EmulationFlags::Rewind); + _settings->ClearFlag(EmulationFlags::MaximumSpeed); + } + break; + + case RewindState::Stopped: + _currentHistory.FrameCount++; + break; + } + } else { + ClearBuffer(); + } + } else if(type == ConsoleNotificationType::StateLoaded) { + if(_rewindState == RewindState::Stopped) { + //A save state was loaded by the user, mark as the end of the current "segment" (for history viewer) + _currentHistory.EndOfSegment = true; + } + } +} + +void RewindManager::AddHistoryBlock() +{ + uint32_t maxHistorySize = _settings->GetRewindBufferSize() * 120; + if(maxHistorySize > 0) { + while(_history.size() > maxHistorySize) { + _history.pop_front(); + } + + if(_currentHistory.FrameCount > 0) { + _history.push_back(_currentHistory); + } + _currentHistory = RewindData(); + _currentHistory.SaveState(_console); + } +} + +void RewindManager::PopHistory() +{ + if(_history.empty() && _currentHistory.FrameCount <= 0) { + StopRewinding(); + } else { + if(_currentHistory.FrameCount <= 0) { + _currentHistory = _history.back(); + _history.pop_back(); + } + + _historyBackup.push_front(_currentHistory); + _currentHistory.LoadState(_console); + if(!_audioHistoryBuilder.empty()) { + _audioHistory.insert(_audioHistory.begin(), _audioHistoryBuilder.begin(), _audioHistoryBuilder.end()); + _audioHistoryBuilder.clear(); + } + } +} + +void RewindManager::Start(bool forDebugger) +{ + if(_rewindState == RewindState::Stopped && _settings->GetRewindBufferSize() > 0) { + auto lock = _console->AcquireLock(); + + _rewindState = forDebugger ? RewindState::Debugging : RewindState::Starting; + _videoHistoryBuilder.clear(); + _videoHistory.clear(); + _audioHistoryBuilder.clear(); + _audioHistory.clear(); + _historyBackup.clear(); + + PopHistory(); + _console->GetSoundMixer()->StopAudio(true); + _settings->SetFlag(EmulationFlags::MaximumSpeed); + _settings->SetFlag(EmulationFlags::Rewind); + } +} + +void RewindManager::ForceStop() +{ + if(_rewindState != RewindState::Stopped) { + while(_historyBackup.size() > 1) { + _history.push_back(_historyBackup.front()); + _historyBackup.pop_front(); + } + _currentHistory = _historyBackup.front(); + _historyBackup.clear(); + _rewindState = RewindState::Stopped; + _settings->ClearFlag(EmulationFlags::MaximumSpeed); + _settings->ClearFlag(EmulationFlags::Rewind); + } +} + +void RewindManager::Stop() +{ + if(_rewindState >= RewindState::Starting) { + auto lock = _console->AcquireLock(); + if(_rewindState == RewindState::Started) { + //Move back to the save state containing the frame currently shown on the screen + if(_historyBackup.size() > 1) { + _framesToFastForward = (uint32_t)_videoHistory.size() + _historyBackup.front().FrameCount; + do { + _history.push_back(_historyBackup.front()); + _framesToFastForward -= _historyBackup.front().FrameCount; + _historyBackup.pop_front(); + + _currentHistory = _historyBackup.front(); + } + while(_framesToFastForward > RewindManager::BufferSize && _historyBackup.size() > 1); + } + } else { + //We started rewinding, but didn't actually visually rewind anything yet + //Move back to the save state containing the frame currently shown on the screen + while(_historyBackup.size() > 1) { + _history.push_back(_historyBackup.front()); + _historyBackup.pop_front(); + } + _currentHistory = _historyBackup.front(); + _framesToFastForward = _historyBackup.front().FrameCount; + } + + _currentHistory.LoadState(_console); + if(_framesToFastForward > 0) { + _rewindState = RewindState::Stopping; + _currentHistory.FrameCount = 0; + _settings->SetFlag(EmulationFlags::MaximumSpeed); + } else { + _rewindState = RewindState::Stopped; + _historyBackup.clear(); + _settings->ClearFlag(EmulationFlags::MaximumSpeed); + _settings->ClearFlag(EmulationFlags::Rewind); + } + + _videoHistoryBuilder.clear(); + _videoHistory.clear(); + _audioHistoryBuilder.clear(); + _audioHistory.clear(); + } +} + +void RewindManager::ProcessEndOfFrame() +{ + if(_rewindState >= RewindState::Starting) { + if(_currentHistory.FrameCount <= 0 && _rewindState != RewindState::Debugging) { + //If we're debugging, we want to keep running the emulation to the end of the next frame (even if it's incomplete) + //Otherwise the emulation might diverge due to missing inputs. + PopHistory(); + } + } else if(_currentHistory.FrameCount >= RewindManager::BufferSize) { + AddHistoryBlock(); + } +} + +void RewindManager::ProcessFrame(void * frameBuffer, uint32_t width, uint32_t height, bool forRewind) +{ + if(_rewindState == RewindState::Starting || _rewindState == RewindState::Started) { + if(!forRewind) { + //Ignore any frames that occur between start of rewind process & first rewinded frame completed + //These are caused by the fact that VideoDecoder is asynchronous - a previous (extra) frame can end up + //in the rewind queue, which causes display glitches + return; + } + + _videoHistoryBuilder.push_back(vector((uint32_t*)frameBuffer, (uint32_t*)frameBuffer + width*height)); + + if(_videoHistoryBuilder.size() == (size_t)_historyBackup.front().FrameCount) { + for(int i = (int)_videoHistoryBuilder.size() - 1; i >= 0; i--) { + _videoHistory.push_front(_videoHistoryBuilder[i]); + } + _videoHistoryBuilder.clear(); + } + + if(_rewindState == RewindState::Started || _videoHistory.size() >= RewindManager::BufferSize) { + _rewindState = RewindState::Started; + _settings->ClearFlag(EmulationFlags::MaximumSpeed); + if(!_videoHistory.empty()) { + _console->GetVideoRenderer()->UpdateFrame(_videoHistory.back().data(), width, height); + _videoHistory.pop_back(); + } + } + } else if(_rewindState == RewindState::Stopping || _rewindState == RewindState::Debugging) { + //Display nothing while resyncing + } else { + _console->GetVideoRenderer()->UpdateFrame(frameBuffer, width, height); + } +} + +bool RewindManager::ProcessAudio(int16_t * soundBuffer, uint32_t sampleCount) +{ + if(_rewindState == RewindState::Starting || _rewindState == RewindState::Started) { + _audioHistoryBuilder.insert(_audioHistoryBuilder.end(), soundBuffer, soundBuffer + sampleCount * 2); + + if(_rewindState == RewindState::Started && _audioHistory.size() > sampleCount * 2) { + for(uint32_t i = 0; i < sampleCount * 2; i++) { + soundBuffer[i] = _audioHistory.back(); + _audioHistory.pop_back(); + } + + return true; + } else { + //Mute while we prepare to rewind + return false; + } + } else if(_rewindState == RewindState::Stopping || _rewindState == RewindState::Debugging) { + //Mute while we resync + return false; + } else { + return true; + } +} + +void RewindManager::RecordInput(vector> devices) +{ + if(_settings->GetRewindBufferSize() > 0 && _rewindState == RewindState::Stopped) { + for(shared_ptr &device : devices) { + _currentHistory.InputLogs[device->GetPort()].push_back(device->GetRawState()); + } + } +} + +bool RewindManager::SetInput(BaseControlDevice *device) +{ + uint8_t port = device->GetPort(); + if(!_currentHistory.InputLogs[port].empty() && IsRewinding()) { + ControlDeviceState state = _currentHistory.InputLogs[port].front(); + _currentHistory.InputLogs[port].pop_front(); + device->SetRawState(state); + return true; + } else { + return false; + } +} + +void RewindManager::StartRewinding(bool forDebugger) +{ + Start(forDebugger); +} + +void RewindManager::StopRewinding(bool forDebugger) +{ + if(forDebugger) { + ForceStop(); + } else { + Stop(); + } +} + +bool RewindManager::IsRewinding() +{ + return _rewindState != RewindState::Stopped; +} + +bool RewindManager::IsStepBack() +{ + return _rewindState == RewindState::Debugging; +} + +void RewindManager::RewindSeconds(uint32_t seconds) +{ + if(_rewindState == RewindState::Stopped) { + uint32_t removeCount = (seconds * 60 / RewindManager::BufferSize) + 1; + auto lock = _console->AcquireLock(); + + for(uint32_t i = 0; i < removeCount; i++) { + if(!_history.empty()) { + _currentHistory = _history.back(); + _history.pop_back(); + } else { + break; + } + } + _currentHistory.LoadState(_console); + } +} + +bool RewindManager::HasHistory() +{ + return _hasHistory; +} + +void RewindManager::SendFrame(void * frameBuffer, uint32_t width, uint32_t height, bool forRewind) +{ + ProcessFrame(frameBuffer, width, height, forRewind); +} + +bool RewindManager::SendAudio(int16_t * soundBuffer, uint32_t sampleCount) +{ + return ProcessAudio(soundBuffer, sampleCount); +} diff --git a/Core/RewindManager.h b/Core/RewindManager.h new file mode 100644 index 0000000..85fa7f8 --- /dev/null +++ b/Core/RewindManager.h @@ -0,0 +1,75 @@ +#pragma once +#include "stdafx.h" +#include +#include "INotificationListener.h" +#include "RewindData.h" +#include "IInputProvider.h" +#include "IInputRecorder.h" + +class Console; +class EmuSettings; + +enum class RewindState +{ + Stopped = 0, + Stopping = 1, + Starting = 2, + Started = 3, + Debugging = 4 +}; + +class RewindManager : public INotificationListener, public IInputProvider, public IInputRecorder +{ +private: + static constexpr int32_t BufferSize = 30; //Number of frames between each save state + + shared_ptr _console; + shared_ptr _settings; + + bool _hasHistory; + + std::deque _history; + std::deque _historyBackup; + RewindData _currentHistory; + + RewindState _rewindState; + int32_t _framesToFastForward; + + std::deque> _videoHistory; + vector> _videoHistoryBuilder; + std::deque _audioHistory; + vector _audioHistoryBuilder; + + void AddHistoryBlock(); + void PopHistory(); + + void Start(bool forDebugger); + void Stop(); + void ForceStop(); + + void ProcessFrame(void *frameBuffer, uint32_t width, uint32_t height, bool forRewind); + bool ProcessAudio(int16_t *soundBuffer, uint32_t sampleCount); + + void ClearBuffer(); + +public: + RewindManager(shared_ptr console); + virtual ~RewindManager(); + + void ProcessNotification(ConsoleNotificationType type, void* parameter) override; + void ProcessEndOfFrame(); + + void RecordInput(vector> devices) override; + bool SetInput(BaseControlDevice *device) override; + + void StartRewinding(bool forDebugger = false); + void StopRewinding(bool forDebugger = false); + bool IsRewinding(); + bool IsStepBack(); + void RewindSeconds(uint32_t seconds); + + bool HasHistory(); + + void SendFrame(void *frameBuffer, uint32_t width, uint32_t height, bool forRewind); + bool SendAudio(int16_t *soundBuffer, uint32_t sampleCount); +}; \ No newline at end of file diff --git a/Core/SNES_SPC_state.cpp b/Core/SNES_SPC_state.cpp index 26b61cd..9a6296a 100644 --- a/Core/SNES_SPC_state.cpp +++ b/Core/SNES_SPC_state.cpp @@ -27,7 +27,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define REGS (m.smp_regs [0]) #define REGS_IN (m.smp_regs [1]) -void SNES_SPC::save_regs( uint8_t out [reg_count] ) +void SNES_SPC::save_regs( uint8_t out [reg_count*2] ) { // Use current timer counter values for ( int i = 0; i < timer_count; i++ ) @@ -35,6 +35,7 @@ void SNES_SPC::save_regs( uint8_t out [reg_count] ) // Last written values memcpy( out, REGS, r_t0out ); + memcpy( out + reg_count, REGS_IN, reg_count); } void SNES_SPC::init_header( void* spc_out ) @@ -92,12 +93,15 @@ void SNES_SPC::copy_state( unsigned char** io, copy_func_t copy ) { // SMP registers uint8_t out_ports [port_count]; - uint8_t regs [reg_count]; + uint8_t regs [reg_count*2]; memcpy( out_ports, ®S [r_cpuio0], sizeof out_ports ); save_regs( regs ); copier.copy( regs, sizeof regs ); copier.copy( out_ports, sizeof out_ports ); - load_regs( regs ); + + memcpy(REGS, regs, reg_count); + memcpy(REGS_IN, regs + reg_count, reg_count); + regs_loaded(); memcpy( ®S [r_cpuio0], out_ports, sizeof out_ports ); } diff --git a/Core/SettingTypes.h b/Core/SettingTypes.h index 622566b..4c9c6c5 100644 --- a/Core/SettingTypes.h +++ b/Core/SettingTypes.h @@ -1,6 +1,13 @@ #pragma once #include "stdafx.h" +enum class EmulationFlags +{ + Turbo = 1, + Rewind = 2, + MaximumSpeed = 4 +}; + enum class ScaleFilterType { xBRZ = 0, @@ -146,15 +153,17 @@ struct EmulationConfig struct PreferencesConfig { - bool ShowFps; - bool ShowFrameCounter; - bool ShowGameTimer; - bool ShowDebugInfo; - bool DisableOsd; + bool ShowFps = false; + bool ShowFrameCounter = false; + bool ShowGameTimer = false; + bool ShowDebugInfo = false; + bool DisableOsd = false; - const char* SaveFolderOverride; - const char* SaveStateFolderOverride; - const char* ScreenshotFolderOverride; + uint32_t RewindBufferSize = 600; + + const char* SaveFolderOverride = nullptr; + const char* SaveStateFolderOverride = nullptr; + const char* ScreenshotFolderOverride = nullptr; }; struct OverscanDimensions diff --git a/Core/ShortcutKeyHandler.cpp b/Core/ShortcutKeyHandler.cpp index 5d9b1f7..cccd4e7 100644 --- a/Core/ShortcutKeyHandler.cpp +++ b/Core/ShortcutKeyHandler.cpp @@ -5,6 +5,7 @@ #include "VideoDecoder.h" #include "ControlManager.h" #include "Console.h" +#include "RewindManager.h" #include "NotificationManager.h" ShortcutKeyHandler::ShortcutKeyHandler(shared_ptr console) @@ -12,7 +13,6 @@ ShortcutKeyHandler::ShortcutKeyHandler(shared_ptr console) _console = console; _keySetIndex = 0; _isKeyUp = false; - _keyboardMode = false; _repeatStarted = false; _stopThread = false; @@ -60,11 +60,7 @@ bool ShortcutKeyHandler::IsKeyPressed(KeyCombination comb) bool ShortcutKeyHandler::IsKeyPressed(uint32_t keyCode) { - if(keyCode >= 0x200 || !_keyboardMode) { - return KeyManager::IsKeyPressed(keyCode); - } else { - return false; - } + return KeyManager::IsKeyPressed(keyCode); } bool ShortcutKeyHandler::DetectKeyPress(EmulatorShortcut shortcut) @@ -125,22 +121,21 @@ void ShortcutKeyHandler::CheckMappedKeys() } } - //TODO - /* if(DetectKeyPress(EmulatorShortcut::FastForward)) { - settings->SetFlags(EmulationFlags::Turbo); + settings->SetFlag(EmulationFlags::Turbo); } else if(DetectKeyRelease(EmulatorShortcut::FastForward)) { - settings->ClearFlags(EmulationFlags::Turbo); + settings->ClearFlag(EmulationFlags::Turbo); } if(DetectKeyPress(EmulatorShortcut::ToggleFastForward)) { if(settings->CheckFlag(EmulationFlags::Turbo)) { - settings->ClearFlags(EmulationFlags::Turbo); + settings->ClearFlag(EmulationFlags::Turbo); } else { - settings->SetFlags(EmulationFlags::Turbo); + settings->SetFlag(EmulationFlags::Turbo); } } + /* if(DetectKeyPress(EmulatorShortcut::MoveToNextStateSlot)) { _console->GetSaveStateManager()->MoveToNextSlot(); } @@ -175,8 +170,7 @@ void ShortcutKeyHandler::CheckMappedKeys() } if(!isNetplayClient && !isMovieRecording) { - //TODO - /*shared_ptr rewindManager = _console->GetRewindManager(); + shared_ptr rewindManager = _console->GetRewindManager(); if(rewindManager) { if(DetectKeyPress(EmulatorShortcut::ToggleRewind)) { if(rewindManager->IsRewinding()) { @@ -195,7 +189,7 @@ void ShortcutKeyHandler::CheckMappedKeys() } else if(DetectKeyPress(EmulatorShortcut::RewindOneMin)) { rewindManager->RewindSeconds(60); } - }*/ + } } } diff --git a/Core/ShortcutKeyHandler.h b/Core/ShortcutKeyHandler.h index 48b5d66..0106199 100644 --- a/Core/ShortcutKeyHandler.h +++ b/Core/ShortcutKeyHandler.h @@ -19,7 +19,6 @@ private: vector _pressedKeys; vector _lastPressedKeys; bool _isKeyUp; - bool _keyboardMode; shared_ptr _runSingleFrameRepeatTimer; bool _repeatStarted; diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index c8d5d62..3c5c65a 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -3,6 +3,7 @@ #include "Console.h" #include "EmuSettings.h" #include "SoundResampler.h" +#include "RewindManager.h" #include "../Utilities/Equalizer.h" #include "../Utilities/blip_buf.h" @@ -65,11 +66,14 @@ void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount) } } - if(cfg.SampleRate == SoundResampler::SpcSampleRate && cfg.DisableDynamicSampleRate) { - _audioDevice->PlayBuffer(samples, sampleCount, cfg.SampleRate, true); - } else { - uint32_t resampledCount = _resampler->Resample(samples, sampleCount, cfg.SampleRate, _sampleBuffer); - _audioDevice->PlayBuffer(_sampleBuffer, resampledCount, cfg.SampleRate, true); + shared_ptr rewindManager = _console->GetRewindManager(); + if(rewindManager && rewindManager->SendAudio(samples, (uint32_t)sampleCount)) { + if(cfg.SampleRate == SoundResampler::SpcSampleRate && cfg.DisableDynamicSampleRate) { + _audioDevice->PlayBuffer(samples, sampleCount, cfg.SampleRate, true); + } else { + uint32_t resampledCount = _resampler->Resample(samples, sampleCount, cfg.SampleRate, _sampleBuffer); + _audioDevice->PlayBuffer(_sampleBuffer, resampledCount, cfg.SampleRate, true); + } } _audioDevice->ProcessEndOfFrame(); } diff --git a/Core/VideoDecoder.cpp b/Core/VideoDecoder.cpp index bc2dc3c..ddfcf37 100644 --- a/Core/VideoDecoder.cpp +++ b/Core/VideoDecoder.cpp @@ -5,6 +5,7 @@ #include "DefaultVideoFilter.h" #include "NotificationManager.h" #include "Console.h" +#include "RewindManager.h" #include "EmuSettings.h" #include "SettingTypes.h" #include "NtscFilter.h" @@ -78,7 +79,7 @@ void VideoDecoder::UpdateVideoFilter() }*/ } -void VideoDecoder::DecodeFrame(bool synchronous) +void VideoDecoder::DecodeFrame(bool forRewind) { UpdateVideoFilter(); @@ -117,7 +118,7 @@ void VideoDecoder::DecodeFrame(bool synchronous) _frameChanged = false; //Rewind manager will take care of sending the correct frame to the video renderer - _console->GetVideoRenderer()->UpdateFrame(outputBuffer, frameInfo.Width, frameInfo.Height); + _console->GetRewindManager()->SendFrame(outputBuffer, frameInfo.Width, frameInfo.Height, forRewind); } void VideoDecoder::DecodeThread() @@ -141,13 +142,13 @@ uint32_t VideoDecoder::GetFrameCount() return _frameCount; } -void VideoDecoder::UpdateFrameSync(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber) +void VideoDecoder::UpdateFrameSync(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool forRewind) { _baseFrameInfo.Width = width; _baseFrameInfo.Height = height; _frameNumber = frameNumber; _ppuOutputBuffer = ppuOutputBuffer; - DecodeFrame(true); + DecodeFrame(forRewind); _frameCount++; } diff --git a/Core/VideoDecoder.h b/Core/VideoDecoder.h index f5c5162..0d1c24e 100644 --- a/Core/VideoDecoder.h +++ b/Core/VideoDecoder.h @@ -55,7 +55,7 @@ public: FrameInfo GetFrameInfo(); ScreenSize GetScreenSize(bool ignoreScale); - void UpdateFrameSync(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber); + void UpdateFrameSync(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool forRewind); void UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber); bool IsRunning(); diff --git a/UI/Config/PreferencesConfig.cs b/UI/Config/PreferencesConfig.cs index 63d6829..fa122be 100644 --- a/UI/Config/PreferencesConfig.cs +++ b/UI/Config/PreferencesConfig.cs @@ -21,6 +21,8 @@ namespace Mesen.GUI.Config public bool AssociateMsmFiles = false; public bool AssociateMssFiles = false; + public UInt32 RewindBufferSize = 600; + public bool AlwaysOnTop = false; public bool ShowFps = false; @@ -128,17 +130,6 @@ namespace Mesen.GUI.Config FileAssociationHelper.UpdateFileAssociation("mss", this.AssociateMssFiles); } - ConfigApi.SetPreferences(new InteropPreferencesConfig() { - ShowFps = ShowFps, - ShowFrameCounter = ShowFrameCounter, - ShowGameTimer = ShowGameTimer, - ShowDebugInfo = ShowDebugInfo, - DisableOsd = DisableOsd, - SaveFolderOverride = OverrideSaveDataFolder ? SaveDataFolder : "", - SaveStateFolderOverride = OverrideSaveStateFolder ? SaveStateFolder : "", - ScreenshotFolderOverride = OverrideScreenshotFolder ? ScreenshotFolder : "" - }); - Application.OpenForms[0].TopMost = AlwaysOnTop; ShortcutKeyInfo[] shortcutKeys = new ShortcutKeyInfo[ShortcutKeys1.Count + ShortcutKeys2.Count]; @@ -150,6 +141,18 @@ namespace Mesen.GUI.Config shortcutKeys[i++] = shortcutInfo; } ConfigApi.SetShortcutKeys(shortcutKeys, (UInt32)shortcutKeys.Length); + + ConfigApi.SetPreferences(new InteropPreferencesConfig() { + ShowFps = ShowFps, + ShowFrameCounter = ShowFrameCounter, + ShowGameTimer = ShowGameTimer, + ShowDebugInfo = ShowDebugInfo, + DisableOsd = DisableOsd, + SaveFolderOverride = OverrideSaveDataFolder ? SaveDataFolder : "", + SaveStateFolderOverride = OverrideSaveStateFolder ? SaveStateFolder : "", + ScreenshotFolderOverride = OverrideScreenshotFolder ? ScreenshotFolder : "", + RewindBufferSize = RewindBufferSize + }); } } @@ -161,6 +164,8 @@ namespace Mesen.GUI.Config [MarshalAs(UnmanagedType.I1)] public bool ShowDebugInfo; [MarshalAs(UnmanagedType.I1)] public bool DisableOsd; + public UInt32 RewindBufferSize; + public string SaveFolderOverride; public string SaveStateFolderOverride; public string ScreenshotFolderOverride;