Added rewind + fast forward support
This commit is contained in:
parent
73913e1f0c
commit
7d068963a4
19 changed files with 657 additions and 52 deletions
|
@ -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<SoundMixer> Console::GetSoundMixer()
|
||||
|
@ -295,6 +304,11 @@ shared_ptr<SaveStateManager> Console::GetSaveStateManager()
|
|||
return _saveStateManager;
|
||||
}
|
||||
|
||||
shared_ptr<RewindManager> Console::GetRewindManager()
|
||||
{
|
||||
return _rewindManager;
|
||||
}
|
||||
|
||||
shared_ptr<DebugHud> Console::GetDebugHud()
|
||||
{
|
||||
return _debugHud;
|
||||
|
|
|
@ -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> _debugHud;
|
||||
shared_ptr<EmuSettings> _settings;
|
||||
shared_ptr<SaveStateManager> _saveStateManager;
|
||||
shared_ptr<RewindManager> _rewindManager;
|
||||
|
||||
thread::id _emulationThreadId;
|
||||
|
||||
|
@ -83,7 +85,7 @@ public:
|
|||
shared_ptr<NotificationManager> GetNotificationManager();
|
||||
shared_ptr<EmuSettings> GetSettings();
|
||||
shared_ptr<SaveStateManager> GetSaveStateManager();
|
||||
|
||||
shared_ptr<RewindManager> GetRewindManager();
|
||||
shared_ptr<DebugHud> GetDebugHud();
|
||||
|
||||
shared_ptr<Cpu> GetCpu();
|
||||
|
|
|
@ -105,6 +105,8 @@
|
|||
<ClInclude Include="PpuTypes.h" />
|
||||
<ClInclude Include="RamHandler.h" />
|
||||
<ClInclude Include="RegisterHandlerA.h" />
|
||||
<ClInclude Include="RewindData.h" />
|
||||
<ClInclude Include="RewindManager.h" />
|
||||
<ClInclude Include="RomHandler.h" />
|
||||
<ClInclude Include="SaveStateManager.h" />
|
||||
<ClInclude Include="ScaleFilter.h" />
|
||||
|
@ -157,6 +159,8 @@
|
|||
<ClCompile Include="Ppu.cpp" />
|
||||
<ClCompile Include="PpuTools.cpp" />
|
||||
<ClCompile Include="RegisterHandlerB.cpp" />
|
||||
<ClCompile Include="RewindData.cpp" />
|
||||
<ClCompile Include="RewindManager.cpp" />
|
||||
<ClCompile Include="SaveStateManager.cpp" />
|
||||
<ClCompile Include="ScaleFilter.cpp" />
|
||||
<ClCompile Include="ShortcutKeyHandler.cpp" />
|
||||
|
|
|
@ -236,6 +236,12 @@
|
|||
<ClInclude Include="ConsoleLock.h">
|
||||
<Filter>Misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RewindData.h">
|
||||
<Filter>Misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RewindManager.h">
|
||||
<Filter>Misc</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp" />
|
||||
|
@ -378,6 +384,12 @@
|
|||
<Filter>Misc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ConsoleLock.cpp" />
|
||||
<ClCompile Include="RewindData.cpp">
|
||||
<Filter>Misc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RewindManager.cpp">
|
||||
<Filter>Misc</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="SNES">
|
||||
|
|
|
@ -129,9 +129,22 @@ vector<KeyCombination> 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;
|
||||
}
|
|
@ -10,6 +10,8 @@ private:
|
|||
EmulationConfig _emulation;
|
||||
PreferencesConfig _preferences;
|
||||
|
||||
atomic<uint32_t> _flags;
|
||||
|
||||
string _audioDevice;
|
||||
string _saveFolder;
|
||||
string _saveStateFolder;
|
||||
|
@ -42,6 +44,12 @@ public:
|
|||
KeyCombination GetShortcutKey(EmulatorShortcut shortcut, int keySetIndex);
|
||||
vector<KeyCombination> 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);
|
||||
};
|
|
@ -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];
|
||||
|
|
52
Core/RewindData.cpp
Normal file
52
Core/RewindData.cpp
Normal file
|
@ -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> &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<uint8_t>& 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<uint8_t>(compressedData, compressedData + compressedSize);
|
||||
delete[] compressedData;
|
||||
}
|
||||
|
||||
void RewindData::SaveState(shared_ptr<Console> &console)
|
||||
{
|
||||
std::stringstream state;
|
||||
console->Serialize(state);
|
||||
|
||||
string stateData = state.str();
|
||||
vector<uint8_t> compressedState;
|
||||
CompressState(stateData, compressedState);
|
||||
SaveStateData = compressedState;
|
||||
OriginalSaveStateSize = (uint32_t)stateData.size();
|
||||
FrameCount = 0;
|
||||
}
|
25
Core/RewindData.h
Normal file
25
Core/RewindData.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include "BaseControlDevice.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class RewindData
|
||||
{
|
||||
private:
|
||||
vector<uint8_t> SaveStateData;
|
||||
uint32_t OriginalSaveStateSize = 0;
|
||||
|
||||
void CompressState(string stateData, vector<uint8_t> &compressedState);
|
||||
|
||||
public:
|
||||
std::deque<ControlDeviceState> InputLogs[BaseControlDevice::PortCount];
|
||||
int32_t FrameCount = 0;
|
||||
bool EndOfSegment = false;
|
||||
|
||||
void GetStateData(stringstream &stateData);
|
||||
|
||||
void LoadState(shared_ptr<Console> &console);
|
||||
void SaveState(shared_ptr<Console> &console);
|
||||
};
|
354
Core/RewindManager.cpp
Normal file
354
Core/RewindManager.cpp
Normal file
|
@ -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 = 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>((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<shared_ptr<BaseControlDevice>> devices)
|
||||
{
|
||||
if(_settings->GetRewindBufferSize() > 0 && _rewindState == RewindState::Stopped) {
|
||||
for(shared_ptr<BaseControlDevice> &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);
|
||||
}
|
75
Core/RewindManager.h
Normal file
75
Core/RewindManager.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#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> _console;
|
||||
shared_ptr<EmuSettings> _settings;
|
||||
|
||||
bool _hasHistory;
|
||||
|
||||
std::deque<RewindData> _history;
|
||||
std::deque<RewindData> _historyBackup;
|
||||
RewindData _currentHistory;
|
||||
|
||||
RewindState _rewindState;
|
||||
int32_t _framesToFastForward;
|
||||
|
||||
std::deque<vector<uint32_t>> _videoHistory;
|
||||
vector<vector<uint32_t>> _videoHistoryBuilder;
|
||||
std::deque<int16_t> _audioHistory;
|
||||
vector<int16_t> _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> console);
|
||||
virtual ~RewindManager();
|
||||
|
||||
void ProcessNotification(ConsoleNotificationType type, void* parameter) override;
|
||||
void ProcessEndOfFrame();
|
||||
|
||||
void RecordInput(vector<shared_ptr<BaseControlDevice>> 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);
|
||||
};
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "VideoDecoder.h"
|
||||
#include "ControlManager.h"
|
||||
#include "Console.h"
|
||||
#include "RewindManager.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
ShortcutKeyHandler::ShortcutKeyHandler(shared_ptr<Console> console)
|
||||
|
@ -12,7 +13,6 @@ ShortcutKeyHandler::ShortcutKeyHandler(shared_ptr<Console> 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> rewindManager = _console->GetRewindManager();
|
||||
shared_ptr<RewindManager> 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);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ private:
|
|||
vector<uint32_t> _pressedKeys;
|
||||
vector<uint32_t> _lastPressedKeys;
|
||||
bool _isKeyUp;
|
||||
bool _keyboardMode;
|
||||
|
||||
shared_ptr<Timer> _runSingleFrameRepeatTimer;
|
||||
bool _repeatStarted;
|
||||
|
|
|
@ -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> 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();
|
||||
}
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue