System: Implement runahead

This commit is contained in:
Connor McLaughlin
2021-01-24 02:52:52 +10:00
parent 689b62e065
commit e01d66d18e
15 changed files with 278 additions and 35 deletions

View File

@ -61,6 +61,9 @@ struct MemorySaveState
std::unique_ptr<GrowableMemoryByteStream> state_stream;
};
static bool SaveMemoryState(MemorySaveState* mss);
static bool LoadMemoryState(const MemorySaveState& mss);
static bool LoadEXE(const char* filename);
static bool SetExpansionROM(const char* filename);
@ -70,10 +73,16 @@ static std::unique_ptr<CDImage> OpenCDImage(const char* path, bool force_preload
static bool DoLoadState(ByteStream* stream, bool force_software_renderer, bool update_display);
static bool DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display);
static void DoRunFrame();
static void DoRewind();
static void DoMemorySaveStates();
static bool CreateGPU(GPURenderer renderer);
static bool SaveRewindState();
static void DoRewind();
static void SaveRunaheadState();
static void DoRunahead();
static void DoMemorySaveStates();
static bool Initialize(bool force_software_renderer);
static void UpdateRunningGame(const char* path, CDImage* image);
@ -126,6 +135,11 @@ static s32 s_rewind_save_frequency = -1;
static s32 s_rewind_save_counter = -1;
static bool s_rewinding_first_save = false;
static std::deque<MemorySaveState> s_runahead_states;
static std::unique_ptr<AudioStream> s_runahead_audio_stream;
static bool s_runahead_replay_pending = false;
static u32 s_runahead_frames = 0;
State GetState()
{
return s_state;
@ -835,6 +849,7 @@ void Shutdown()
return;
ClearMemorySaveStates();
s_runahead_audio_stream.reset();
g_texture_replacements.Shutdown();
@ -1150,6 +1165,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di
if (s_state == State::Starting)
s_state = State::Running;
g_host_interface->GetAudioStream()->EmptyBuffers();
return true;
}
@ -1301,6 +1317,9 @@ void RunFrame()
return;
}
if (s_runahead_frames > 0)
DoRunahead();
DoRunFrame();
if (s_memory_saves_enabled)
@ -1960,6 +1979,7 @@ void CalculateRewindMemoryUsage(u32 num_saves, u64* ram_usage, u64* vram_usage)
void ClearMemorySaveStates()
{
s_rewind_states.clear();
s_runahead_states.clear();
}
void UpdateMemorySaveStateSettings()
@ -1984,6 +2004,60 @@ void UpdateMemorySaveStateSettings()
s_rewind_save_frequency = -1;
s_rewind_save_counter = -1;
}
s_rewind_load_frequency = -1;
s_rewind_load_counter = -1;
s_runahead_frames = g_settings.runahead_enable ? g_settings.runahead_frames : 0;
s_runahead_replay_pending = false;
if (s_runahead_frames > 0)
{
Log_InfoPrintf("Runahead is active with %u frames", s_runahead_frames);
if (!s_runahead_audio_stream)
{
// doesn't matter if it's not resampled here since it eats everything anyway, nom nom nom.
s_runahead_audio_stream = AudioStream::CreateNullAudioStream();
s_runahead_audio_stream->Reconfigure(HostInterface::AUDIO_SAMPLE_RATE, HostInterface::AUDIO_SAMPLE_RATE,
HostInterface::AUDIO_CHANNELS);
}
}
else
{
s_runahead_audio_stream.reset();
}
}
bool LoadMemoryState(const MemorySaveState& mss)
{
mss.state_stream->SeekAbsolute(0);
StateWrapper sw(mss.state_stream.get(), StateWrapper::Mode::Read, SAVE_STATE_VERSION);
HostDisplayTexture* host_texture = mss.vram_texture.get();
if (!DoState(sw, &host_texture, true))
{
g_host_interface->ReportError("Failed to load memory save state, resetting.");
Reset();
return false;
}
return true;
}
bool SaveMemoryState(MemorySaveState* mss)
{
mss->state_stream = std::make_unique<GrowableMemoryByteStream>(nullptr, MAX_SAVE_STATE_SIZE);
HostDisplayTexture* host_texture = nullptr;
StateWrapper sw(mss->state_stream.get(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
if (!DoState(sw, &host_texture, false))
{
Log_ErrorPrint("Failed to create rewind state.");
return false;
}
mss->vram_texture.reset(host_texture);
return true;
}
bool SaveRewindState()
@ -1995,17 +2069,9 @@ bool SaveRewindState()
s_rewind_states.pop_front();
MemorySaveState mss;
mss.state_stream = std::make_unique<GrowableMemoryByteStream>(nullptr, MAX_SAVE_STATE_SIZE);
HostDisplayTexture* host_texture = nullptr;
StateWrapper sw(mss.state_stream.get(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
if (!DoState(sw, &host_texture, false))
{
Log_ErrorPrint("Failed to create rewind state.");
if (!SaveMemoryState(&mss))
return false;
}
mss.vram_texture.reset(host_texture);
s_rewind_states.push_back(std::move(mss));
Log_DevPrintf("Saved rewind state (%u bytes, took %.4f ms)", s_rewind_states.back().state_stream->GetSize(),
@ -2027,17 +2093,8 @@ bool LoadRewindState(u32 skip_saves /*= 0*/, bool consume_state /*=true */)
Common::Timer load_timer;
const MemorySaveState& mss = s_rewind_states.back();
mss.state_stream->SeekAbsolute(0);
StateWrapper sw(mss.state_stream.get(), StateWrapper::Mode::Read, SAVE_STATE_VERSION);
HostDisplayTexture* host_texture = mss.vram_texture.get();
if (!DoState(sw, &host_texture, true))
{
g_host_interface->ReportError("Failed to load rewind state from memory, resetting.");
Reset();
if (!LoadMemoryState(s_rewind_states.back()))
return false;
}
if (consume_state)
s_rewind_states.pop_back();
@ -2082,6 +2139,68 @@ void DoRewind()
}
}
void SaveRunaheadState()
{
if (s_runahead_states.size() >= s_runahead_frames)
s_runahead_states.pop_front();
MemorySaveState mss;
if (!SaveMemoryState(&mss))
{
Log_ErrorPrint("Failed to save runahead state.");
return;
}
s_runahead_states.push_back(std::move(mss));
}
void DoRunahead()
{
Common::Timer timer;
Log_DevPrintf("runahead starting at frame %u", s_frame_number);
if (s_runahead_replay_pending)
{
// we need to replay and catch up - load the state,
s_runahead_replay_pending = false;
if (!LoadMemoryState(s_runahead_states.front()))
return;
// and throw away all the states, forcing us to catch up below
// TODO: can we leave one frame here and run, avoiding the extra save?
s_runahead_states.clear();
Log_VerbosePrintf("Rewound to frame %u, took %.2f ms", s_frame_number, timer.GetTimeMilliseconds());
}
// run the frames with no audio
s32 frames_to_run = static_cast<s32>(s_runahead_frames) - static_cast<s32>(s_runahead_states.size());
if (frames_to_run > 0)
{
Common::Timer timer2;
const s32 temp = frames_to_run;
g_spu.SetAudioStream(s_runahead_audio_stream.get());
while (frames_to_run > 0)
{
DoRunFrame();
SaveRunaheadState();
frames_to_run--;
}
g_spu.SetAudioStream(g_host_interface->GetAudioStream());
Log_VerbosePrintf("Running %d frames to catch up took %.2f ms", temp, timer2.GetTimeMilliseconds());
}
else
{
// save this frame
SaveRunaheadState();
}
Log_DevPrintf("runahead ending at frame %u, took %.2f ms", s_frame_number, timer.GetTimeMilliseconds());
}
void DoMemorySaveStates()
{
if (s_rewind_save_counter >= 0)
@ -2096,6 +2215,15 @@ void DoMemorySaveStates()
s_rewind_save_counter--;
}
}
if (s_runahead_frames > 0)
SaveRunaheadState();
}
void SetRunaheadReplayFlag()
{
Log_DevPrintf("Runahead rewind pending...");
s_runahead_replay_pending = true;
}
} // namespace System