System: Add memory-only save states and rewind

This commit is contained in:
Connor McLaughlin 2021-01-23 19:00:54 +10:00
parent 6c6fdeb15e
commit 516d685dd0
22 changed files with 537 additions and 70 deletions

View file

@ -75,7 +75,7 @@ std::tuple<u32, u32> GPU::GetEffectiveDisplayResolution()
return std::tie(m_crtc_state.display_vram_width, m_crtc_state.display_vram_height);
}
void GPU::Reset()
void GPU::Reset(bool clear_vram)
{
SoftReset();
m_set_texture_disable_mask = false;
@ -119,12 +119,12 @@ void GPU::SoftReset()
UpdateCommandTickEvent();
}
bool GPU::DoState(StateWrapper& sw, bool update_display)
bool GPU::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display)
{
if (sw.IsReading())
{
// perform a reset to discard all pending draws/fb state
Reset();
Reset(host_texture == nullptr);
}
sw.Do(&m_GPUSTAT.bits);
@ -214,16 +214,27 @@ bool GPU::DoState(StateWrapper& sw, bool update_display)
UpdateDMARequest();
}
if (!host_texture)
{
if (!sw.DoMarker("GPU-VRAM"))
return false;
if (sw.IsReading())
{
// Still need a temporary here.
HeapArray<u16, VRAM_WIDTH * VRAM_HEIGHT> temp;
HeapArray<u16, VRAM_WIDTH* VRAM_HEIGHT> temp;
sw.DoBytes(temp.data(), VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16));
UpdateVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT, temp.data(), false, false);
}
else
{
ReadVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT);
sw.DoBytes(m_vram_ptr, VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16));
}
}
if (sw.IsReading())
{
UpdateCRTCConfig();
if (update_display)
UpdateDisplay();
@ -231,11 +242,6 @@ bool GPU::DoState(StateWrapper& sw, bool update_display)
UpdateCRTCTickEvent();
UpdateCommandTickEvent();
}
else
{
ReadVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT);
sw.DoBytes(m_vram_ptr, VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16));
}
return !sw.HasError();
}

View file

@ -15,6 +15,7 @@
class StateWrapper;
class HostDisplay;
class HostDisplayTexture;
class TimingEvent;
class Timers;
@ -75,8 +76,8 @@ public:
virtual bool IsHardwareRenderer() const = 0;
virtual bool Initialize(HostDisplay* host_display);
virtual void Reset();
virtual bool DoState(StateWrapper& sw, bool update_display);
virtual void Reset(bool clear_vram);
virtual bool DoState(StateWrapper& sw, HostDisplayTexture** save_to_texture, bool update_display);
// Graphics API state reset/restore - call when drawing the UI etc.
virtual void ResetGraphicsAPIState();

View file

@ -19,7 +19,7 @@ bool GPUBackend::Initialize()
return true;
}
void GPUBackend::Reset()
void GPUBackend::Reset(bool clear_vram)
{
Sync();
m_drawing_area = {};

View file

@ -23,7 +23,7 @@ public:
virtual bool Initialize();
virtual void UpdateSettings();
virtual void Reset();
virtual void Reset(bool clear_vram);
virtual void Shutdown();
GPUBackendFillVRAMCommand* NewFillVRAMCommand();

View file

@ -91,9 +91,9 @@ bool GPU_HW::Initialize(HostDisplay* host_display)
return true;
}
void GPU_HW::Reset()
void GPU_HW::Reset(bool clear_vram)
{
GPU::Reset();
GPU::Reset(clear_vram);
m_batch_current_vertex_ptr = m_batch_start_vertex_ptr;
@ -107,9 +107,9 @@ void GPU_HW::Reset()
SetFullVRAMDirtyRectangle();
}
bool GPU_HW::DoState(StateWrapper& sw, bool update_display)
bool GPU_HW::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display)
{
if (!GPU::DoState(sw, update_display))
if (!GPU::DoState(sw, host_texture, update_display))
return false;
// invalidate the whole VRAM read texture when loading state

View file

@ -32,8 +32,8 @@ public:
virtual bool IsHardwareRenderer() const override;
virtual bool Initialize(HostDisplay* host_display) override;
virtual void Reset() override;
virtual bool DoState(StateWrapper& sw, bool update_display) override;
virtual void Reset(bool clear_vram) override;
virtual bool DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display) override;
void UpdateResolutionScale() override final;
std::tuple<u32, u32> GetEffectiveDisplayResolution() override final;

View file

@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/d3d11/shader_compiler.h"
#include "common/log.h"
#include "common/state_wrapper.h"
#include "common/timer.h"
#include "gpu_hw_shadergen.h"
#include "host_display.h"
@ -82,13 +83,51 @@ bool GPU_HW_D3D11::Initialize(HostDisplay* host_display)
return true;
}
void GPU_HW_D3D11::Reset()
void GPU_HW_D3D11::Reset(bool clear_vram)
{
GPU_HW::Reset();
GPU_HW::Reset(clear_vram);
if (clear_vram)
ClearFramebuffer();
}
bool GPU_HW_D3D11::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display)
{
if (host_texture)
{
const CD3D11_BOX src_box(0, 0, 0, static_cast<LONG>(m_vram_texture.GetWidth()),
static_cast<LONG>(m_vram_texture.GetHeight()), 1);
ComPtr<ID3D11Resource> resource;
if (sw.IsReading())
{
HostDisplayTexture* tex = *host_texture;
if (tex->GetWidth() != m_vram_texture.GetWidth() || tex->GetHeight() != m_vram_texture.GetHeight() ||
tex->GetSamples() != m_vram_texture.GetSamples())
{
return false;
}
static_cast<ID3D11ShaderResourceView*>(tex->GetHandle())->GetResource(resource.GetAddressOf());
m_context->CopySubresourceRegion(m_vram_texture.GetD3DTexture(), 0, 0, 0, 0, resource.Get(), 0, &src_box);
}
else
{
std::unique_ptr<HostDisplayTexture> tex =
m_host_display->CreateTexture(m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), 1, 1,
m_vram_texture.GetSamples(), HostDisplayPixelFormat::RGBA8, nullptr, 0, false);
if (!tex)
return false;
static_cast<ID3D11ShaderResourceView*>(tex->GetHandle())->GetResource(resource.GetAddressOf());
m_context->CopySubresourceRegion(resource.Get(), 0, 0, 0, 0, m_vram_texture.GetD3DTexture(), 0, &src_box);
*host_texture = tex.release();
}
}
return GPU_HW::DoState(sw, host_texture, update_display);
}
void GPU_HW_D3D11::ResetGraphicsAPIState()
{
GPU_HW::ResetGraphicsAPIState();

View file

@ -21,7 +21,8 @@ public:
~GPU_HW_D3D11() override;
bool Initialize(HostDisplay* host_display) override;
void Reset() override;
void Reset(bool clear_vram) override;
bool DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display) override;
void ResetGraphicsAPIState() override;
void RestoreGraphicsAPIState() override;

View file

@ -1,6 +1,7 @@
#include "gpu_hw_opengl.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/state_wrapper.h"
#include "common/timer.h"
#include "gpu_hw_shadergen.h"
#include "host_display.h"
@ -95,13 +96,108 @@ bool GPU_HW_OpenGL::Initialize(HostDisplay* host_display)
return true;
}
void GPU_HW_OpenGL::Reset()
void GPU_HW_OpenGL::Reset(bool clear_vram)
{
GPU_HW::Reset();
GPU_HW::Reset(clear_vram);
if (clear_vram)
ClearFramebuffer();
}
bool GPU_HW_OpenGL::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display)
{
if (host_texture)
{
if (sw.IsReading())
{
HostDisplayTexture* tex = *host_texture;
if (tex->GetWidth() != m_vram_texture.GetWidth() || tex->GetHeight() != m_vram_texture.GetHeight() ||
tex->GetSamples() != m_vram_texture.GetSamples())
{
return false;
}
CopyFramebufferForState(
m_vram_texture.GetGLTarget(), static_cast<GLuint>(reinterpret_cast<uintptr_t>(tex->GetHandle())), 0, 0, 0,
m_vram_texture.GetGLId(), m_vram_fbo_id, 0, 0, m_vram_texture.GetWidth(), m_vram_texture.GetHeight());
}
else
{
std::unique_ptr<HostDisplayTexture> tex =
m_host_display->CreateTexture(m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), 1, 1,
m_vram_texture.GetSamples(), HostDisplayPixelFormat::RGBA8, nullptr, 0, false);
if (!tex)
return false;
CopyFramebufferForState(m_vram_texture.GetGLTarget(), m_vram_texture.GetGLId(), m_vram_fbo_id, 0, 0,
static_cast<GLuint>(reinterpret_cast<uintptr_t>(tex->GetHandle())), 0, 0, 0,
m_vram_texture.GetWidth(), m_vram_texture.GetHeight());
*host_texture = tex.release();
}
}
return GPU_HW::DoState(sw, host_texture, update_display);
}
void GPU_HW_OpenGL::CopyFramebufferForState(GLenum target, GLuint src_texture, u32 src_fbo, u32 src_x, u32 src_y,
GLuint dst_texture, u32 dst_fbo, u32 dst_x, u32 dst_y, u32 width,
u32 height)
{
if (target != GL_TEXTURE_2D && GLAD_GL_VERSION_4_3)
{
glCopyImageSubData(src_texture, target, 0, src_x, src_y, 0, dst_texture, target, 0, dst_x, dst_y, 0, width, height,
1);
}
else if (target != GL_TEXTURE_2D && GLAD_GL_EXT_copy_image)
{
glCopyImageSubDataEXT(src_texture, target, 0, src_x, src_y, 0, dst_texture, target, 0, dst_x, dst_y, 0, width,
height, 1);
}
else if (target != GL_TEXTURE_2D && GLAD_GL_OES_copy_image)
{
glCopyImageSubDataOES(src_texture, target, 0, src_x, src_y, 0, dst_texture, target, 0, dst_x, dst_y, 0, width,
height, 1);
}
else
{
if (src_fbo == 0)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_state_copy_fbo_id);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, src_texture, 0);
}
else
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, src_fbo);
}
if (dst_fbo == 0)
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_state_copy_fbo_id);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, dst_texture, 0);
}
else
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_fbo);
}
glDisable(GL_SCISSOR_TEST);
glBlitFramebuffer(src_x, src_y, src_x + width, src_y + height, dst_x, dst_y, dst_x + width, dst_y + height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
glEnable(GL_SCISSOR_TEST);
if (src_fbo == 0)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
}
else if (dst_fbo == 0)
{
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo_id);
}
}
void GPU_HW_OpenGL::ResetGraphicsAPIState()
{
GPU_HW::ResetGraphicsAPIState();
@ -303,7 +399,9 @@ bool GPU_HW_OpenGL::CreateFramebuffer()
return false;
}
if (m_vram_fbo_id == 0)
glGenFramebuffers(1, &m_vram_fbo_id);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo_id);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_vram_texture.GetGLTarget(),
m_vram_texture.GetGLId(), 0);
@ -320,6 +418,9 @@ bool GPU_HW_OpenGL::CreateFramebuffer()
}
}
if (m_state_copy_fbo_id == 0)
glGenFramebuffers(1, &m_state_copy_fbo_id);
SetFullVRAMDirtyRectangle();
return true;
}

View file

@ -17,7 +17,8 @@ public:
~GPU_HW_OpenGL() override;
bool Initialize(HostDisplay* host_display) override;
void Reset() override;
void Reset(bool clear_vram) override;
bool DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display) override;
void ResetGraphicsAPIState() override;
void RestoreGraphicsAPIState() override;
@ -57,6 +58,8 @@ private:
void SetCapabilities(HostDisplay* host_display);
bool CreateFramebuffer();
void ClearFramebuffer();
void CopyFramebufferForState(GLenum target, GLuint src_texture, u32 src_fbo, u32 src_x, u32 src_y, GLuint dst_texture,
u32 dst_fbo, u32 dst_x, u32 dst_y, u32 width, u32 height);
bool CreateVertexBuffer();
bool CreateUniformBuffer();
@ -84,6 +87,7 @@ private:
GLuint m_vram_fbo_id = 0;
GLuint m_vao_id = 0;
GLuint m_attributeless_vao_id = 0;
GLuint m_state_copy_fbo_id = 0;
std::unique_ptr<GL::StreamBuffer> m_uniform_stream_buffer;

View file

@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/log.h"
#include "common/scope_guard.h"
#include "common/state_wrapper.h"
#include "common/timer.h"
#include "common/vulkan/builders.h"
#include "common/vulkan/context.h"
@ -87,14 +88,77 @@ bool GPU_HW_Vulkan::Initialize(HostDisplay* host_display)
return true;
}
void GPU_HW_Vulkan::Reset()
void GPU_HW_Vulkan::Reset(bool clear_vram)
{
GPU_HW::Reset();
GPU_HW::Reset(clear_vram);
EndRenderPass();
if (clear_vram)
ClearFramebuffer();
}
bool GPU_HW_Vulkan::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display)
{
if (host_texture)
{
EndRenderPass();
const VkImageCopy ic{{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u},
{0, 0, 0},
{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u},
{0, 0, 0},
{m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), 1u}};
if (sw.IsReading())
{
Vulkan::Texture* tex = static_cast<Vulkan::Texture*>((*host_texture)->GetHandle());
if (tex->GetWidth() != m_vram_texture.GetWidth() || tex->GetHeight() != m_vram_texture.GetHeight() ||
tex->GetSamples() != m_vram_texture.GetSamples())
{
return false;
}
VkCommandBuffer buf = g_vulkan_context->GetCurrentCommandBuffer();
const VkImageLayout old_tex_layout = tex->GetLayout();
const VkImageLayout old_vram_layout = m_vram_texture.GetLayout();
tex->TransitionToLayout(buf, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
m_vram_texture.TransitionToLayout(buf, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkCmdCopyImage(g_vulkan_context->GetCurrentCommandBuffer(), tex->GetImage(), tex->GetLayout(),
m_vram_texture.GetImage(), m_vram_texture.GetLayout(), 1, &ic);
m_vram_texture.TransitionToLayout(buf, old_vram_layout);
tex->TransitionToLayout(buf, old_tex_layout);
}
else
{
std::unique_ptr<HostDisplayTexture> htex =
m_host_display->CreateTexture(m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), 1, 1,
m_vram_texture.GetSamples(), HostDisplayPixelFormat::RGBA8, nullptr, 0, false);
if (!htex)
return false;
Vulkan::Texture* tex = static_cast<Vulkan::Texture*>(htex->GetHandle());
if (tex->GetWidth() != m_vram_texture.GetWidth() || tex->GetHeight() != m_vram_texture.GetHeight() ||
tex->GetSamples() != m_vram_texture.GetSamples())
{
return false;
}
VkCommandBuffer buf = g_vulkan_context->GetCurrentCommandBuffer();
const VkImageLayout old_vram_layout = m_vram_texture.GetLayout();
tex->TransitionToLayout(buf, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
m_vram_texture.TransitionToLayout(buf, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
vkCmdCopyImage(g_vulkan_context->GetCurrentCommandBuffer(), m_vram_texture.GetImage(), m_vram_texture.GetLayout(),
tex->GetImage(), tex->GetLayout(), 1, &ic);
m_vram_texture.TransitionToLayout(buf, old_vram_layout);
tex->TransitionToLayout(buf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
*host_texture = htex.release();
}
}
return GPU_HW::DoState(sw, host_texture, update_display);
}
void GPU_HW_Vulkan::ResetGraphicsAPIState()
{
GPU_HW::ResetGraphicsAPIState();

View file

@ -16,7 +16,8 @@ public:
~GPU_HW_Vulkan() override;
bool Initialize(HostDisplay* host_display) override;
void Reset() override;
void Reset(bool clear_vram) override;
bool DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display) override;
void ResetGraphicsAPIState() override;
void RestoreGraphicsAPIState() override;

View file

@ -75,11 +75,11 @@ bool GPU_SW::Initialize(HostDisplay* host_display)
return true;
}
void GPU_SW::Reset()
void GPU_SW::Reset(bool clear_vram)
{
GPU::Reset();
GPU::Reset(clear_vram);
m_backend.Reset();
m_backend.Reset(clear_vram);
}
void GPU_SW::UpdateSettings()

View file

@ -18,7 +18,7 @@ public:
bool IsHardwareRenderer() const override;
bool Initialize(HostDisplay* host_display) override;
void Reset() override;
void Reset(bool clear_vram) override;
void UpdateSettings() override;
protected:

View file

@ -20,10 +20,11 @@ bool GPU_SW_Backend::Initialize()
return GPUBackend::Initialize();
}
void GPU_SW_Backend::Reset()
void GPU_SW_Backend::Reset(bool clear_vram)
{
GPUBackend::Reset();
GPUBackend::Reset(clear_vram);
if (clear_vram)
m_vram.fill(0);
}

View file

@ -11,7 +11,7 @@ public:
~GPU_SW_Backend() override;
bool Initialize() override;
void Reset() override;
void Reset(bool clear_vram) override;
ALWAYS_INLINE_RELEASE u16 GetPixel(const u32 x, const u32 y) const { return m_vram[VRAM_WIDTH * y + x]; }
ALWAYS_INLINE_RELEASE const u16* GetPixelPtr(const u32 x, const u32 y) const { return &m_vram[VRAM_WIDTH * y + x]; }

View file

@ -488,6 +488,9 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false);
si.SetBoolValue("Main", "ApplyGameSettings", true);
si.SetBoolValue("Main", "DisableAllEnhancements", false);
si.SetBoolValue("Main", "RewindEnable", false);
si.SetFloatValue("Main", "RewindFrequency", 10.0f);
si.SetIntValue("Main", "RewindSaveSlots", 10);
si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false);
@ -655,6 +658,13 @@ void HostInterface::FixIncompatibleSettings(bool display_osd_messages)
g_settings.cpu_fastmem_mode = CPUFastmemMode::LUT;
}
#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)
{
Log_WarningPrintf("Disabling mmap fastmem due to rewind being enabled");
g_settings.cpu_fastmem_mode = CPUFastmemMode::LUT;
}
}
void HostInterface::SaveSettings(SettingsInterface& si)
@ -676,6 +686,8 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
if (System::IsValid())
{
System::ClearMemorySaveStates();
if (g_settings.cpu_overclock_active != old_settings.cpu_overclock_active ||
(g_settings.cpu_overclock_active &&
(g_settings.cpu_overclock_numerator != old_settings.cpu_overclock_numerator ||
@ -755,7 +767,8 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
g_settings.display_active_start_offset != old_settings.display_active_start_offset ||
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.display_line_end_offset != old_settings.display_line_end_offset ||
g_settings.rewind_enable != old_settings.rewind_enable)
{
if (g_settings.IsUsingCodeCache())
CPU::CodeCache::Reinitialize();
@ -793,6 +806,13 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
System::UpdateMemoryCards();
}
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)
{
System::UpdateMemorySaveStateSettings();
}
if (g_settings.texture_replacements.enable_vram_write_replacements !=
old_settings.texture_replacements.enable_vram_write_replacements ||
g_settings.texture_replacements.preload_textures != old_settings.texture_replacements.preload_textures)
@ -999,6 +1019,7 @@ void HostInterface::ModifyResolutionScale(s32 increment)
g_gpu->RestoreGraphicsAPIState();
g_gpu->UpdateSettings();
g_gpu->ResetGraphicsAPIState();
System::ClearMemorySaveStates();
}
}

View file

@ -123,6 +123,9 @@ void Settings::Load(SettingsInterface& si)
apply_game_settings = si.GetBoolValue("Main", "ApplyGameSettings", true);
auto_load_cheats = si.GetBoolValue("Main", "AutoLoadCheats", false);
disable_all_enhancements = si.GetBoolValue("Main", "DisableAllEnhancements", false);
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));
cpu_execution_mode =
ParseCPUExecutionMode(
@ -295,6 +298,9 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Main", "ApplyGameSettings", apply_game_settings);
si.SetBoolValue("Main", "AutoLoadCheats", auto_load_cheats);
si.SetBoolValue("Main", "DisableAllEnhancements", disable_all_enhancements);
si.SetBoolValue("Main", "RewindEnable", rewind_enable);
si.SetFloatValue("Main", "RewindFrequency", rewind_save_frequency);
si.SetIntValue("Main", "RewindSaveSlots", rewind_save_slots);
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
si.SetBoolValue("CPU", "OverclockEnable", cpu_overclock_enable);

View file

@ -94,6 +94,10 @@ struct Settings
bool auto_load_cheats = false;
bool disable_all_enhancements = false;
bool rewind_enable = false;
float rewind_save_frequency = 10.0f;
u32 rewind_save_slots = 10;
GPURenderer gpu_renderer = GPURenderer::Software;
std::string gpu_adapter;
std::string display_post_process_chain;

View file

@ -9,6 +9,7 @@
#include "common/log.h"
#include "common/state_wrapper.h"
#include "common/string_util.h"
#include "common/timestamp.h"
#include "controller.h"
#include "cpu_code_cache.h"
#include "cpu_core.h"
@ -30,7 +31,10 @@
#include "texture_replacements.h"
#include "timers.h"
#include <cctype>
#include <cinttypes>
#include <cmath>
#include <cstdio>
#include <deque>
#include <fstream>
#include <limits>
Log_SetChannel(System);
@ -51,6 +55,12 @@ SystemBootParameters::~SystemBootParameters() = default;
namespace System {
struct MemorySaveState
{
std::unique_ptr<HostDisplayTexture> vram_texture;
std::unique_ptr<GrowableMemoryByteStream> state_stream;
};
static bool LoadEXE(const char* filename);
static bool SetExpansionROM(const char* filename);
@ -58,7 +68,10 @@ static bool SetExpansionROM(const char* filename);
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, 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 Initialize(bool force_software_renderer);
@ -104,6 +117,15 @@ static std::string s_media_playlist_filename;
static std::unique_ptr<CheatList> s_cheat_list;
static bool s_memory_saves_enabled = false;
static std::deque<MemorySaveState> s_rewind_states;
static s32 s_rewind_load_frequency = -1;
static s32 s_rewind_load_counter = -1;
static s32 s_rewind_save_frequency = -1;
static s32 s_rewind_save_counter = -1;
static bool s_rewinding_first_save = false;
State GetState()
{
return s_state;
@ -521,12 +543,13 @@ std::optional<DiscRegion> GetRegionForPath(const char* image_path)
bool RecreateGPU(GPURenderer renderer, bool update_display /* = true*/)
{
ClearMemorySaveStates();
g_gpu->RestoreGraphicsAPIState();
// save current state
std::unique_ptr<ByteStream> state_stream = ByteStream_CreateGrowableMemoryStream();
StateWrapper sw(state_stream.get(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
const bool state_valid = g_gpu->DoState(sw, false) && TimingEvents::DoState(sw);
const bool state_valid = g_gpu->DoState(sw, nullptr, false) && TimingEvents::DoState(sw);
if (!state_valid)
Log_ErrorPrintf("Failed to save old GPU state when switching renderers");
@ -548,7 +571,7 @@ bool RecreateGPU(GPURenderer renderer, bool update_display /* = true*/)
state_stream->SeekAbsolute(0);
sw.SetMode(StateWrapper::Mode::Read);
g_gpu->RestoreGraphicsAPIState();
g_gpu->DoState(sw, update_display);
g_gpu->DoState(sw, nullptr, update_display);
TimingEvents::DoState(sw);
g_gpu->ResetGraphicsAPIState();
}
@ -802,6 +825,7 @@ bool Initialize(bool force_software_renderer)
}
UpdateThrottlePeriod();
UpdateMemorySaveStateSettings();
return true;
}
@ -810,6 +834,8 @@ void Shutdown()
if (s_state == State::Shutdown)
return;
ClearMemorySaveStates();
g_texture_replacements.Shutdown();
g_sio.Shutdown();
@ -868,11 +894,11 @@ bool CreateGPU(GPURenderer renderer)
}
// we put this here rather than in Initialize() because of the virtual calls
g_gpu->Reset();
g_gpu->Reset(true);
return true;
}
bool DoState(StateWrapper& sw, bool update_display)
bool DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool update_display)
{
if (!sw.DoMarker("System"))
return false;
@ -897,7 +923,7 @@ bool DoState(StateWrapper& sw, bool update_display)
return false;
g_gpu->RestoreGraphicsAPIState();
const bool gpu_result = sw.DoMarker("GPU") && g_gpu->DoState(sw, update_display);
const bool gpu_result = sw.DoMarker("GPU") && g_gpu->DoState(sw, host_texture, update_display);
g_gpu->ResetGraphicsAPIState();
if (!gpu_result)
return false;
@ -963,7 +989,7 @@ void Reset()
Bus::Reset();
g_dma.Reset();
g_interrupt_controller.Reset();
g_gpu->Reset();
g_gpu->Reset(true);
g_cdrom.Reset();
g_pad.Reset();
g_timers.Reset();
@ -1076,6 +1102,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di
}
UpdateRunningGame(media_filename.c_str(), media.get());
ClearMemorySaveStates();
if (s_state == State::Starting)
{
@ -1117,7 +1144,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di
return false;
StateWrapper sw(state, StateWrapper::Mode::Read, header.version);
if (!DoState(sw, update_display))
if (!DoState(sw, nullptr, update_display))
return false;
if (s_state == State::Starting)
@ -1184,7 +1211,7 @@ bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */)
g_gpu->RestoreGraphicsAPIState();
StateWrapper sw(state, StateWrapper::Mode::Write, SAVE_STATE_VERSION);
const bool result = DoState(sw, false);
const bool result = DoState(sw, nullptr, false);
g_gpu->ResetGraphicsAPIState();
@ -1224,10 +1251,8 @@ void SingleStepCPU()
g_gpu->ResetGraphicsAPIState();
}
void RunFrame()
void DoRunFrame()
{
s_frame_timer.Reset();
g_gpu->RestoreGraphicsAPIState();
if (CPU::g_state.use_debug_dispatcher)
@ -1266,6 +1291,22 @@ void RunFrame()
g_gpu->ResetGraphicsAPIState();
}
void RunFrame()
{
s_frame_timer.Reset();
if (s_rewind_load_counter >= 0)
{
DoRewind();
return;
}
DoRunFrame();
if (s_memory_saves_enabled)
DoMemorySaveStates();
}
float GetTargetSpeed()
{
return s_target_speed;
@ -1739,12 +1780,14 @@ bool InsertMedia(const char* path)
if (g_settings.IsUsingCodeCache())
CPU::CodeCache::Reinitialize();
ClearMemorySaveStates();
return true;
}
void RemoveMedia()
{
g_cdrom.RemoveMedia();
ClearMemorySaveStates();
}
void UpdateRunningGame(const char* path, CDImage* image)
@ -1907,4 +1950,152 @@ void SetCheatList(std::unique_ptr<CheatList> cheats)
s_cheat_list = std::move(cheats);
}
void CalculateRewindMemoryUsage(u32 num_saves, u64* ram_usage, u64* vram_usage)
{
*ram_usage = MAX_SAVE_STATE_SIZE * static_cast<u64>(num_saves);
*vram_usage = (VRAM_WIDTH * VRAM_HEIGHT * 4) * static_cast<u64>(std::max(g_settings.gpu_resolution_scale, 1u)) *
static_cast<u64>(num_saves);
}
void ClearMemorySaveStates()
{
s_rewind_states.clear();
}
void UpdateMemorySaveStateSettings()
{
ClearMemorySaveStates();
s_memory_saves_enabled = g_settings.rewind_enable;
if (g_settings.rewind_enable)
{
s_rewind_save_frequency = static_cast<s32>(std::ceil(g_settings.rewind_save_frequency * s_throttle_frequency));
s_rewind_save_counter = 0;
u64 ram_usage, vram_usage;
CalculateRewindMemoryUsage(g_settings.rewind_save_slots, &ram_usage, &vram_usage);
Log_InfoPrintf(
"Rewind is enabled, saving every %d frames, with %u slots and %" PRIu64 "MB RAM and %" PRIu64 "MB VRAM usage",
std::max(s_rewind_save_frequency, 1), g_settings.rewind_save_slots, ram_usage / 1048576, vram_usage / 1048576);
}
else
{
s_rewind_save_frequency = -1;
s_rewind_save_counter = -1;
}
}
bool SaveRewindState()
{
Common::Timer save_timer;
const u32 save_slots = g_settings.rewind_save_slots;
while (s_rewind_states.size() >= save_slots)
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.");
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(),
save_timer.GetTimeMilliseconds());
return true;
}
bool LoadRewindState(u32 skip_saves /*= 0*/, bool consume_state /*=true */)
{
while (skip_saves > 0 && !s_rewind_states.empty())
{
s_rewind_states.pop_back();
skip_saves--;
}
if (s_rewind_states.empty())
return false;
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();
return false;
}
if (consume_state)
s_rewind_states.pop_back();
Log_DevPrintf("Rewind load took %.4f ms", load_timer.GetTimeMilliseconds());
return true;
}
void SetRewinding(bool enabled)
{
if (enabled)
{
// Try to rewind at the replay speed, or one per second maximum.
const float load_frequency = std::min(g_settings.rewind_save_frequency, 1.0f);
s_rewind_load_frequency = static_cast<s32>(std::ceil(load_frequency * s_throttle_frequency));
s_rewind_load_counter = 0;
}
else
{
s_rewind_load_frequency = -1;
s_rewind_load_counter = -1;
}
s_rewinding_first_save = true;
}
void DoRewind()
{
s_frame_timer.Reset();
if (s_rewind_load_counter == 0)
{
const u32 skip_saves = BoolToUInt32(!s_rewinding_first_save);
s_rewinding_first_save = false;
LoadRewindState(skip_saves, false);
ResetPerformanceCounters();
s_rewind_load_counter = s_rewind_load_frequency;
}
else
{
s_rewind_load_counter--;
}
}
void DoMemorySaveStates()
{
if (s_rewind_save_counter >= 0)
{
if (s_rewind_save_counter == 0)
{
SaveRewindState();
s_rewind_save_counter = s_rewind_save_frequency;
}
else
{
s_rewind_save_counter--;
}
}
}
} // namespace System

View file

@ -228,4 +228,14 @@ void ApplyCheatCode(const CheatCode& code);
/// Sets or clears the provided cheat list, applying every frame.
void SetCheatList(std::unique_ptr<CheatList> cheats);
//////////////////////////////////////////////////////////////////////////
// Memory Save States (Rewind and Runahead)
//////////////////////////////////////////////////////////////////////////
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);
} // namespace System

View file

@ -1549,7 +1549,7 @@ void CommonHostInterface::RegisterGeneralHotkeys()
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("ToggleCheats"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle Cheats")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
DoToggleCheats();
});
@ -1584,7 +1584,7 @@ void CommonHostInterface::RegisterGeneralHotkeys()
#else
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("TogglePatchCodes"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle Patch Codes")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
DoToggleCheats();
});
#endif
@ -1603,25 +1603,39 @@ void CommonHostInterface::RegisterGeneralHotkeys()
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("FrameStep"),
StaticString(TRANSLATABLE("Hotkeys", "Frame Step")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
DoFrameStep();
});
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("Rewind"),
StaticString(TRANSLATABLE("Hotkeys", "Rewind")), [this](bool pressed) {
if (System::IsValid())
{
AddOSDMessage(pressed ? TranslateStdString("OSDMessage", "Rewinding...") :
TranslateStdString("OSDMessage", "Stopped rewinding."),
5.0f);
System::SetRewinding(pressed);
}
});
}
void CommonHostInterface::RegisterGraphicsHotkeys()
{
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("ToggleSoftwareRendering"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle Software Rendering")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
ToggleSoftwareRendering();
});
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("TogglePGXP"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle PGXP")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
{
g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable;
g_gpu->RestoreGraphicsAPIState();
g_gpu->UpdateSettings();
g_gpu->ResetGraphicsAPIState();
System::ClearMemorySaveStates();
AddOSDMessage(g_settings.gpu_pgxp_enable ?
TranslateStdString("OSDMessage", "PGXP is now enabled.") :
TranslateStdString("OSDMessage", "PGXP is now disabled"),
@ -1640,13 +1654,16 @@ void CommonHostInterface::RegisterGraphicsHotkeys()
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("TogglePGXPDepth"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle PGXP Depth Buffer")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
{
g_settings.gpu_pgxp_depth_buffer = !g_settings.gpu_pgxp_depth_buffer;
if (!g_settings.gpu_pgxp_enable)
return;
g_gpu->RestoreGraphicsAPIState();
g_gpu->UpdateSettings();
g_gpu->ResetGraphicsAPIState();
System::ClearMemorySaveStates();
AddOSDMessage(g_settings.gpu_pgxp_depth_buffer ?
TranslateStdString("OSDMessage", "PGXP Depth Buffer is now enabled.") :
TranslateStdString("OSDMessage", "PGXP Depth Buffer is now disabled."),
@ -1656,31 +1673,31 @@ void CommonHostInterface::RegisterGraphicsHotkeys()
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("IncreaseResolutionScale"),
StaticString(TRANSLATABLE("Hotkeys", "Increase Resolution Scale")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
ModifyResolutionScale(1);
});
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("DecreaseResolutionScale"),
StaticString(TRANSLATABLE("Hotkeys", "Decrease Resolution Scale")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
ModifyResolutionScale(-1);
});
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("TogglePostProcessing"),
StaticString(TRANSLATABLE("Hotkeys", "Toggle Post-Processing")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
TogglePostProcessing();
});
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("ReloadPostProcessingShaders"),
StaticString(TRANSLATABLE("Hotkeys", "Reload Post Processing Shaders")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
ReloadPostProcessingShaders();
});
RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Graphics")), StaticString("ReloadTextureReplacements"),
StaticString(TRANSLATABLE("Hotkeys", "Reload Texture Replacements")), [this](bool pressed) {
if (pressed)
if (pressed && System::IsValid())
{
AddOSDMessage(TranslateStdString("OSDMessage", "Texture replacements reloaded."), 10.0f);
g_texture_replacements.Reload();