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

@ -117,6 +117,9 @@ void AnalogController::SetAxisState(s32 axis_code, float value)
void AnalogController::SetAxisState(Axis axis, u8 value)
{
if (value != m_axis_state[static_cast<u8>(axis)])
System::SetRunaheadReplayFlag();
m_axis_state[static_cast<u8>(axis)] = value;
}
@ -131,10 +134,22 @@ void AnalogController::SetButtonState(Button button, bool pressed)
return;
}
const u16 bit = u16(1) << static_cast<u8>(button);
if (pressed)
m_button_state &= ~(u16(1) << static_cast<u8>(button));
{
if (m_button_state & bit)
System::SetRunaheadReplayFlag();
m_button_state &= ~(bit);
}
else
m_button_state |= u16(1) << static_cast<u8>(button);
{
if (!(m_button_state & bit))
System::SetRunaheadReplayFlag();
m_button_state |= bit;
}
}
void AnalogController::SetButtonState(s32 button_code, bool pressed)

View File

@ -83,6 +83,9 @@ void AnalogJoystick::SetAxisState(s32 axis_code, float value)
void AnalogJoystick::SetAxisState(Axis axis, u8 value)
{
if (m_axis_state[static_cast<u8>(axis)] != value)
System::SetRunaheadReplayFlag();
m_axis_state[static_cast<u8>(axis)] = value;
}
@ -96,10 +99,22 @@ void AnalogJoystick::SetButtonState(Button button, bool pressed)
return;
}
const u16 bit = u16(1) << static_cast<u8>(button);
if (pressed)
m_button_state &= ~(u16(1) << static_cast<u8>(button));
{
if (m_button_state & bit)
System::SetRunaheadReplayFlag();
m_button_state &= ~bit;
}
else
m_button_state |= (u16(1) << static_cast<u8>(button));
{
if (!(m_button_state & bit))
System::SetRunaheadReplayFlag();
m_button_state |= bit;
}
}
void AnalogJoystick::SetButtonState(s32 button_code, bool pressed)

View File

@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/state_wrapper.h"
#include "host_interface.h"
#include "system.h"
DigitalController::DigitalController() = default;
@ -45,10 +46,21 @@ void DigitalController::SetAxisState(s32 axis_code, float value) {}
void DigitalController::SetButtonState(Button button, bool pressed)
{
const u16 bit = u16(1) << static_cast<u8>(button);
if (pressed)
m_button_state &= ~(u16(1) << static_cast<u8>(button));
{
if (m_button_state & bit)
System::SetRunaheadReplayFlag();
m_button_state &= ~bit;
}
else
m_button_state |= u16(1) << static_cast<u8>(button);
{
if (!(m_button_state & bit))
System::SetRunaheadReplayFlag();
m_button_state |= bit;
}
}
void DigitalController::SetButtonState(s32 button_code, bool pressed)

View File

@ -75,6 +75,12 @@ bool GPU_SW::Initialize(HostDisplay* host_display)
return true;
}
bool GPU_SW::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display)
{
// ignore the host texture for software mode, since we want to save vram here
return GPU::DoState(sw, nullptr, update_display);
}
void GPU_SW::Reset(bool clear_vram)
{
GPU::Reset(clear_vram);

View File

@ -18,6 +18,7 @@ public:
bool IsHardwareRenderer() const override;
bool Initialize(HostDisplay* host_display) override;
bool DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display) override;
void Reset(bool clear_vram) override;
void UpdateSettings() override;

View File

@ -16,6 +16,7 @@
#include "host_display.h"
#include "pgxp.h"
#include "save_state_version.h"
#include "spu.h"
#include "system.h"
#include "texture_replacements.h"
#include <cmath>
@ -78,6 +79,9 @@ void HostInterface::CreateAudioStream()
}
m_audio_stream->SetOutputVolume(GetAudioOutputVolume());
if (System::IsValid())
g_spu.SetAudioStream(m_audio_stream.get());
}
s32 HostInterface::GetAudioOutputVolume() const
@ -491,6 +495,8 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Main", "RewindEnable", false);
si.SetFloatValue("Main", "RewindFrequency", 10.0f);
si.SetIntValue("Main", "RewindSaveSlots", 10);
si.SetBoolValue("Main", "RunaheadEnable", false);
si.SetFloatValue("Main", "RunaheadFrames", 1);
si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false);
@ -660,7 +666,8 @@ void HostInterface::FixIncompatibleSettings(bool display_osd_messages)
#endif
// rewinding causes issues with mmap fastmem, so just use LUT
if (g_settings.rewind_enable && g_settings.IsUsingFastmem() && g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap)
if ((g_settings.rewind_enable || g_settings.runahead_enable) && g_settings.IsUsingFastmem() &&
g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap)
{
Log_WarningPrintf("Disabling mmap fastmem due to rewind being enabled");
g_settings.cpu_fastmem_mode = CPUFastmemMode::LUT;
@ -768,7 +775,8 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
g_settings.display_active_end_offset != old_settings.display_active_end_offset ||
g_settings.display_line_start_offset != old_settings.display_line_start_offset ||
g_settings.display_line_end_offset != old_settings.display_line_end_offset ||
g_settings.rewind_enable != old_settings.rewind_enable)
g_settings.rewind_enable != old_settings.rewind_enable ||
g_settings.runahead_enable != old_settings.runahead_enable)
{
if (g_settings.IsUsingCodeCache())
CPU::CodeCache::Reinitialize();
@ -808,7 +816,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
if (g_settings.rewind_enable != old_settings.rewind_enable ||
g_settings.rewind_save_frequency != old_settings.rewind_save_frequency ||
g_settings.rewind_save_slots != old_settings.rewind_save_slots)
g_settings.rewind_save_slots != old_settings.rewind_save_slots ||
g_settings.runahead_enable != old_settings.runahead_enable ||
g_settings.runahead_frames != old_settings.runahead_frames)
{
System::UpdateMemorySaveStateSettings();
}

View File

@ -126,6 +126,8 @@ void Settings::Load(SettingsInterface& si)
rewind_enable = si.GetBoolValue("Main", "RewindEnable", false);
rewind_save_frequency = si.GetFloatValue("Main", "RewindFrequency", 10.0f);
rewind_save_slots = static_cast<u32>(si.GetIntValue("Main", "RewindSaveSlots", 10));
runahead_enable = si.GetBoolValue("Main", "RunaheadEnable", false);
runahead_frames = static_cast<u32>(si.GetIntValue("Main", "RunaheadFrames", 1));
cpu_execution_mode =
ParseCPUExecutionMode(
@ -301,6 +303,8 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Main", "RewindEnable", rewind_enable);
si.SetFloatValue("Main", "RewindFrequency", rewind_save_frequency);
si.SetIntValue("Main", "RewindSaveSlots", rewind_save_slots);
si.SetBoolValue("Main", "RunaheadEnable", runahead_enable);
si.SetFloatValue("Main", "RunaheadFrames", runahead_frames);
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
si.SetBoolValue("CPU", "OverclockEnable", cpu_overclock_enable);

View File

@ -95,8 +95,10 @@ struct Settings
bool disable_all_enhancements = false;
bool rewind_enable = false;
bool runahead_enable = false;
float rewind_save_frequency = 10.0f;
u32 rewind_save_slots = 10;
u32 runahead_frames = 1;
GPURenderer gpu_renderer = GPURenderer::Software;
std::string gpu_adapter;

View File

@ -31,6 +31,7 @@ void SPU::Initialize()
"SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD,
[](void* param, TickCount ticks, TickCount ticks_late) { static_cast<SPU*>(param)->ExecuteTransfer(ticks); }, this,
false);
m_audio_stream = g_host_interface->GetAudioStream();
Reset();
}
@ -49,6 +50,7 @@ void SPU::Shutdown()
m_tick_event.reset();
m_transfer_event.reset();
m_dump_writer.reset();
m_audio_stream = nullptr;
}
void SPU::Reset()
@ -172,7 +174,6 @@ bool SPU::DoState(StateWrapper& sw)
if (sw.IsReading())
{
g_host_interface->GetAudioStream()->EmptyBuffers();
UpdateEventInterval();
UpdateTransferEvent();
}
@ -1731,10 +1732,9 @@ void SPU::Execute(TickCount ticks)
while (remaining_frames > 0)
{
AudioStream* const output_stream = g_host_interface->GetAudioStream();
s16* output_frame_start;
u32 output_frame_space = remaining_frames;
output_stream->BeginWrite(&output_frame_start, &output_frame_space);
m_audio_stream->BeginWrite(&output_frame_start, &output_frame_space);
s16* output_frame = output_frame_start;
const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space);
@ -1837,7 +1837,7 @@ void SPU::Execute(TickCount ticks)
if (m_dump_writer)
m_dump_writer->WriteFrames(output_frame_start, frames_in_this_batch);
output_stream->EndWrite(frames_in_this_batch);
m_audio_stream->EndWrite(frames_in_this_batch);
remaining_frames -= frames_in_this_batch;
}
}

View File

@ -57,6 +57,9 @@ public:
const std::array<u8, RAM_SIZE>& GetRAM() const { return m_ram; }
std::array<u8, RAM_SIZE>& GetRAM() { return m_ram; }
/// Change output stream - used for runahead.
ALWAYS_INLINE void SetAudioStream(AudioStream* stream) { m_audio_stream = stream; }
private:
static constexpr u32 SPU_BASE = 0x1F801C00;
static constexpr u32 NUM_VOICES = 24;
@ -373,6 +376,7 @@ private:
std::unique_ptr<TimingEvent> m_tick_event;
std::unique_ptr<TimingEvent> m_transfer_event;
std::unique_ptr<Common::WAVWriter> m_dump_writer;
AudioStream* m_audio_stream = nullptr;
TickCount m_ticks_carry = 0;
TickCount m_cpu_ticks_per_spu_tick = 0;
TickCount m_cpu_tick_divider = 0;

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

View File

@ -234,8 +234,8 @@ void SetCheatList(std::unique_ptr<CheatList> cheats);
void CalculateRewindMemoryUsage(u32 num_saves, u64* ram_usage, u64* vram_usage);
void ClearMemorySaveStates();
void UpdateMemorySaveStateSettings();
bool SaveRewindState();
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
void SetRewinding(bool enabled);
void SetRunaheadReplayFlag();
} // namespace System

View File

@ -18,6 +18,8 @@ EmulationSettingsWidget::EmulationSettingsWidget(QtHostInterface* host_interface
SettingWidgetBinder::BindWidgetToFloatSetting(m_host_interface, m_ui.rewindSaveFrequency, "Main", "RewindFrequency",
10.0f);
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.rewindSaveSlots, "Main", "RewindSaveSlots", 10);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.runaheadEnable, "Main", "RunaheadEnable", false);
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.runaheadFrames, "Main", "RunaheadFrames", 1);
QtUtils::FillComboBoxWithEmulationSpeeds(m_ui.emulationSpeed);
const int emulation_speed_index =
@ -46,6 +48,7 @@ EmulationSettingsWidget::EmulationSettingsWidget(QtHostInterface* host_interface
&EmulationSettingsWidget::updateRewindSummaryLabel);
connect(m_ui.rewindSaveSlots, QOverload<int>::of(&QSpinBox::valueChanged), this,
&EmulationSettingsWidget::updateRewindSummaryLabel);
connect(m_ui.runaheadEnable, &QCheckBox::stateChanged, this, &EmulationSettingsWidget::updateRunaheadFields);
dialog->registerWidgetHelp(
m_ui.emulationSpeed, tr("Emulation Speed"), "100%",
@ -67,6 +70,7 @@ EmulationSettingsWidget::EmulationSettingsWidget(QtHostInterface* host_interface
"should disable this option."));
updateRewindSummaryLabel();
updateRunaheadFields();
}
EmulationSettingsWidget::~EmulationSettingsWidget() = default;
@ -124,3 +128,8 @@ void EmulationSettingsWidget::updateRewindSummaryLabel()
m_ui.rewindSaveSlots->setEnabled(false);
}
}
void EmulationSettingsWidget::updateRunaheadFields()
{
m_ui.runaheadFrames->setEnabled(m_ui.runaheadEnable->isChecked());
}

View File

@ -20,6 +20,7 @@ private Q_SLOTS:
void onFastForwardSpeedIndexChanged(int index);
void onTurboSpeedIndexChanged(int index);
void updateRewindSummaryLabel();
void updateRunaheadFields();
private:

View File

@ -138,6 +138,42 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Runahead</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="runaheadEnable">
<property name="text">
<string>Enable Runahead</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Runahead Frames:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="runaheadFrames">
<property name="suffix">
<string> Frames</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">