System: Add 'Low VRAM' rewind mode

i.e. use the software renderer for rewinding instead.

Works fairly well, and takes away the VRAM hit when upscaling.
This commit is contained in:
Stenzek
2025-11-21 01:50:49 +10:00
parent a8967b2b44
commit 654587ea38
11 changed files with 167 additions and 63 deletions

View File

@@ -63,8 +63,6 @@ enum : u32
#define PGXP_GTE_REGISTER(field) g_state.pgxp_gte[offsetof(GTE::Regs, field) / sizeof(u32)]
static bool ShouldSavePGXPState();
static double f16Sign(double val);
static double f16Unsign(double val);
static double f16Overflow(double val);
@@ -203,9 +201,6 @@ bool CPU::PGXP::ShouldSavePGXPState()
size_t CPU::PGXP::GetStateSize()
{
if (!ShouldSavePGXPState())
return 0;
const size_t base_size = sizeof(g_state.pgxp_gpr) + sizeof(g_state.pgxp_cop0) + sizeof(g_state.pgxp_gte) +
(sizeof(PGXPValue) * PGXP_MEM_SIZE);
const size_t vertex_cache_size = sizeof(PGXPValue) * VERTEX_CACHE_SIZE;

View File

@@ -11,6 +11,9 @@ void Initialize();
void Reset();
void Shutdown();
/// Returns true if PGXP state should be saved to memory save states.
bool ShouldSavePGXPState();
/// Returns memory usage to serialize additional PGXP state.
size_t GetStateSize();

View File

@@ -2804,6 +2804,11 @@ void FullscreenUI::DrawEmulationSettingsPage()
FSUI_VSTR("Saves state periodically so you can rewind any mistakes while playing."), "Main",
"RewindEnable", false, !runahead_enabled);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_PF_GPU_GRAPHICS_CARD, "Use Software Renderer (Low VRAM Mode)"),
FSUI_VSTR("Uses the software renderer when creating rewind states to prevent additional VRAM "
"usage. Especially useful when upscaling."),
"GPU", "UseSoftwareRendererForMemoryStates", false, rewind_enabled);
DrawFloatRangeSetting(
bsi, FSUI_ICONVSTR(ICON_FA_FLOPPY_DISK, "Rewind Save Frequency"),
FSUI_VSTR("How often a rewind state will be created. Higher frequencies have greater system requirements."), "Main",
@@ -2837,6 +2842,9 @@ void FullscreenUI::DrawEmulationSettingsPage()
else if (rewind_enabled)
{
const u32 resolution_scale = GetEffectiveUIntSetting(bsi, "GPU", "ResolutionScale", 1);
const u32 multisamples = GetEffectiveUIntSetting(bsi, "GPU", "Multisamples", 1);
const bool use_software_renderer = GetEffectiveBoolSetting(bsi, "GPU", "UseSoftwareRendererForMemoryStates", false);
const bool enable_8mb_ram = GetEffectiveBoolSetting(bsi, "Console", "Enable8MBRAM", false);
const float rewind_frequency = GetEffectiveFloatSetting(bsi, "Main", "RewindFrequency", 10.0f);
const s32 rewind_save_slots = GetEffectiveIntSetting(bsi, "Main", "RewindSaveSlots", 10);
const float duration =
@@ -2844,10 +2852,19 @@ void FullscreenUI::DrawEmulationSettingsPage()
static_cast<float>(rewind_save_slots);
u64 ram_usage, vram_usage;
System::CalculateRewindMemoryUsage(rewind_save_slots, resolution_scale, &ram_usage, &vram_usage);
rewind_summary.format(
FSUI_FSTR("Rewind for {0} frames, lasting {1:.2f} seconds will require up to {2} MB of RAM and {3} MB of VRAM."),
rewind_save_slots, duration, ram_usage / 1048576, vram_usage / 1048576);
System::CalculateRewindMemoryUsage(rewind_save_slots, resolution_scale, multisamples, use_software_renderer,
enable_8mb_ram, &ram_usage, &vram_usage);
if (vram_usage > 0)
{
rewind_summary.format(
FSUI_FSTR("Rewind for {0} frames, lasting {1:.2f} seconds will require {2} MB of RAM and {3} MB of VRAM."),
rewind_save_slots, duration, ram_usage / 1048576, vram_usage / 1048576);
}
else
{
rewind_summary.format(FSUI_FSTR("Rewind for {0} frames, lasting {1:.2f} seconds will require {2} MB of RAM."),
rewind_save_slots, duration, ram_usage / 1048576);
}
}
else
{

View File

@@ -589,7 +589,8 @@ TRANSLATE_NOOP("FullscreenUI", "Return to the previous menu.");
TRANSLATE_NOOP("FullscreenUI", "Reverses the game list sort order from the default (usually ascending to descending).");
TRANSLATE_NOOP("FullscreenUI", "Rewind Save Frequency");
TRANSLATE_NOOP("FullscreenUI", "Rewind Save Slots");
TRANSLATE_NOOP("FullscreenUI", "Rewind for {0} frames, lasting {1:.2f} seconds will require up to {2} MB of RAM and {3} MB of VRAM.");
TRANSLATE_NOOP("FullscreenUI", "Rewind for {0} frames, lasting {1:.2f} seconds will require {2} MB of RAM and {3} MB of VRAM.");
TRANSLATE_NOOP("FullscreenUI", "Rewind for {0} frames, lasting {1:.2f} seconds will require {2} MB of RAM.");
TRANSLATE_NOOP("FullscreenUI", "Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements.");
TRANSLATE_NOOP("FullscreenUI", "Rewind is not enabled. Please note that enabling rewind may significantly increase system requirements.");
TRANSLATE_NOOP("FullscreenUI", "Right: ");
@@ -780,6 +781,7 @@ TRANSLATE_NOOP("FullscreenUI", "Use Global Setting");
TRANSLATE_NOOP("FullscreenUI", "Use Old MDEC Routines");
TRANSLATE_NOOP("FullscreenUI", "Use Separate Disc Settings");
TRANSLATE_NOOP("FullscreenUI", "Use Single Card For Multi-Disc Games");
TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer (Low VRAM Mode)");
TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer For Readbacks");
TRANSLATE_NOOP("FullscreenUI", "User Name");
TRANSLATE_NOOP("FullscreenUI", "Uses OpenGL ES even when desktop OpenGL is supported. May improve performance on some SBC drivers.");
@@ -793,6 +795,7 @@ TRANSLATE_NOOP("FullscreenUI", "Uses perspective-correct interpolation for color
TRANSLATE_NOOP("FullscreenUI", "Uses perspective-correct interpolation for texture coordinates, straightening out warped textures.");
TRANSLATE_NOOP("FullscreenUI", "Uses screen positions to resolve PGXP data. May improve visuals in some games.");
TRANSLATE_NOOP("FullscreenUI", "Uses separate game settings for each disc of multi-disc games. Can only be set on the first/main disc.");
TRANSLATE_NOOP("FullscreenUI", "Uses the software renderer when creating rewind states to prevent additional VRAM usage. Especially useful when upscaling.");
TRANSLATE_NOOP("FullscreenUI", "Utilizes the chosen frame rate regardless of the game's setting.");
TRANSLATE_NOOP("FullscreenUI", "Value Range");
TRANSLATE_NOOP("FullscreenUI", "Value: {} | Default: {} | Minimum: {} | Maximum: {}");

View File

@@ -101,13 +101,14 @@ ALWAYS_INLINE_RELEASE static u32 GetBoxDownsampleScale(u32 resolution_scale)
ALWAYS_INLINE static bool ShouldDrawWithSoftwareRenderer()
{
return g_gpu_settings.gpu_use_software_renderer_for_readbacks;
return (g_gpu_settings.gpu_use_software_renderer_for_readbacks ||
g_gpu_settings.gpu_use_software_renderer_for_memory_states);
}
ALWAYS_INLINE static bool ShouldClampUVs(GPUTextureFilter texture_filter)
{
// We only need UV limits if PGXP is enabled, or texture filtering is enabled.
return g_gpu_settings.gpu_pgxp_enable || texture_filter != GPUTextureFilter::Nearest;
return (g_gpu_settings.gpu_pgxp_enable || texture_filter != GPUTextureFilter::Nearest);
}
ALWAYS_INLINE static bool ShouldAllowSpriteMode(u8 resolution_scale, GPUTextureFilter texture_filter,
@@ -390,17 +391,24 @@ void GPU_HW::LoadState(const GPUBackendLoadStateCommand* cmd)
bool GPU_HW::AllocateMemorySaveState(System::MemorySaveState& mss, Error* error)
{
mss.vram_texture = g_gpu_device->FetchTexture(
m_vram_texture->GetWidth(), m_vram_texture->GetHeight(), 1, 1, m_vram_texture->GetSamples(),
m_vram_texture->IsMultisampled() ? GPUTexture::Type::RenderTarget : GPUTexture::Type::Texture,
GPUTexture::Format::RGBA8, GPUTexture::Flags::None, nullptr, 0, error);
if (!mss.vram_texture) [[unlikely]]
if (!g_gpu_settings.gpu_use_software_renderer_for_memory_states)
{
Error::AddPrefix(error, "Failed to allocate VRAM texture for memory save state: ");
return false;
}
mss.vram_texture = g_gpu_device->FetchTexture(
m_vram_texture->GetWidth(), m_vram_texture->GetHeight(), 1, 1, m_vram_texture->GetSamples(),
m_vram_texture->IsMultisampled() ? GPUTexture::Type::RenderTarget : GPUTexture::Type::Texture,
GPUTexture::Format::RGBA8, GPUTexture::Flags::None, nullptr, 0, error);
if (!mss.vram_texture) [[unlikely]]
{
Error::AddPrefix(error, "Failed to allocate VRAM texture for memory save state: ");
return false;
}
GL_OBJECT_NAME(mss.vram_texture, "Memory save state VRAM copy");
GL_OBJECT_NAME(mss.vram_texture, "Memory save state VRAM copy");
}
else
{
mss.vram_texture.reset();
}
static constexpr u32 MAX_TC_SIZE = 1024 * 1024;
@@ -420,32 +428,19 @@ bool GPU_HW::AllocateMemorySaveState(System::MemorySaveState& mss, Error* error)
void GPU_HW::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss)
{
Assert(mss.vram_texture && mss.vram_texture->GetWidth() == m_vram_texture->GetWidth() &&
mss.vram_texture->GetHeight() == m_vram_texture->GetHeight() &&
mss.vram_texture->GetSamples() == m_vram_texture->GetSamples());
if (sw.IsReading())
{
if (m_batch_vertex_ptr)
UnmapGPUBuffer(0, 0);
g_gpu_device->CopyTextureRegion(m_vram_texture.get(), 0, 0, 0, 0, mss.vram_texture.get(), 0, 0, 0, 0,
m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
m_batch = {};
ClearVRAMDirtyRectangle();
SetFullVRAMDirtyRectangle();
UpdateVRAMReadTexture(true, false);
ClearVRAMDirtyRectangle();
ResetBatchVertexDepth();
}
else
{
FlushRender();
// saving state
g_gpu_device->CopyTextureRegion(mss.vram_texture.get(), 0, 0, 0, 0, m_vram_texture.get(), 0, 0, 0, 0,
m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
// don't bother flushing render if we're using the software renderer
if (mss.vram_texture)
FlushRender();
}
// Save VRAM/CLUT.
@@ -458,6 +453,37 @@ void GPU_HW::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss)
if (!GPUTextureCache::DoState(sw, false)) [[unlikely]]
Panic("Failed to process texture cache state.");
}
if (sw.IsReading())
{
if (mss.vram_texture)
{
Assert(mss.vram_texture->GetWidth() == m_vram_texture->GetWidth() &&
mss.vram_texture->GetHeight() == m_vram_texture->GetHeight() &&
mss.vram_texture->GetSamples() == m_vram_texture->GetSamples());
g_gpu_device->CopyTextureRegion(m_vram_texture.get(), 0, 0, 0, 0, mss.vram_texture.get(), 0, 0, 0, 0,
m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
}
else
{
UpdateVRAMOnGPU(0, 0, VRAM_WIDTH, VRAM_HEIGHT, g_vram, VRAM_WIDTH * sizeof(u16), false, false, VRAM_SIZE_RECT);
}
ClearVRAMDirtyRectangle();
SetFullVRAMDirtyRectangle();
UpdateVRAMReadTexture(true, false);
ClearVRAMDirtyRectangle();
}
else
{
// saving state
if (mss.vram_texture)
{
g_gpu_device->CopyTextureRegion(mss.vram_texture.get(), 0, 0, 0, 0, m_vram_texture.get(), 0, 0, 0, 0,
m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
}
}
}
void GPU_HW::RestoreDeviceContext()

View File

@@ -256,6 +256,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true);
gpu_max_queued_frames = static_cast<u8>(si.GetUIntValue("GPU", "MaxQueuedFrames", DEFAULT_GPU_MAX_QUEUED_FRAMES));
gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
gpu_use_software_renderer_for_memory_states = si.GetBoolValue("GPU", "UseSoftwareRendererForMemoryStates", false);
gpu_scaled_interlacing = si.GetBoolValue("GPU", "ScaledInterlacing", true);
gpu_force_round_texcoords = si.GetBoolValue("GPU", "ForceRoundTextureCoordinates", false);
gpu_texture_filter =
@@ -637,6 +638,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetUIntValue("GPU", "MaxQueuedFrames", gpu_max_queued_frames);
si.SetBoolValue("GPU", "UseThread", gpu_use_thread);
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
si.SetBoolValue("GPU", "UseSoftwareRendererForMemoryStates", gpu_use_software_renderer_for_memory_states);
si.SetBoolValue("GPU", "ScaledInterlacing", gpu_scaled_interlacing);
si.SetBoolValue("GPU", "ForceRoundTextureCoordinates", gpu_force_round_texcoords);
si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter));
@@ -1176,6 +1178,9 @@ void Settings::FixIncompatibleSettings(const SettingsInterface& si, bool display
cpu_recompiler_block_linking = false;
}
}
// Don't waste time running the software renderer for CPU-only rewind when rewind isn't enabled.
gpu_use_software_renderer_for_memory_states &= rewind_enable;
}
bool Settings::AreGPUDeviceSettingsChanged(const Settings& old_settings) const

View File

@@ -58,6 +58,7 @@ struct GPUSettings
u8 gpu_max_queued_frames = DEFAULT_GPU_MAX_QUEUED_FRAMES;
bool gpu_use_thread : 1 = true;
bool gpu_use_software_renderer_for_readbacks : 1 = false;
bool gpu_use_software_renderer_for_memory_states : 1 = false;
bool gpu_use_debug_device : 1 = false;
bool gpu_use_debug_device_gpu_validation : 1 = false;
bool gpu_prefer_gles_context : 1 = DEFAULT_GPU_PREFER_GLES_CONTEXT;

View File

@@ -2636,7 +2636,7 @@ bool System::AllocateMemoryStates(size_t state_count, bool recycle_old_textures)
// Allocate CPU buffers.
// TODO: Maybe look at host memory limits here...
const size_t size = GetMaxMemorySaveStateSize();
const size_t size = GetMaxMemorySaveStateSize(g_settings.cpu_enable_8mb_ram, CPU::PGXP::ShouldSavePGXPState());
for (MemorySaveState& mss : s_state.memory_save_states)
{
mss.state_size = 0;
@@ -2904,18 +2904,17 @@ bool System::SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error*
return true;
}
size_t System::GetMaxSaveStateSize()
size_t System::GetMaxSaveStateSize(bool enable_8mb_ram)
{
// 5 megabytes is sufficient for now, at the moment they're around 4.3MB, or 10.3MB with 8MB RAM enabled.
static constexpr u32 MAX_2MB_SAVE_STATE_SIZE = 5 * 1024 * 1024;
static constexpr u32 MAX_8MB_SAVE_STATE_SIZE = 11 * 1024 * 1024;
const bool is_8mb_ram = (System::IsValid() ? (Bus::g_ram_size > Bus::RAM_2MB_SIZE) : g_settings.cpu_enable_8mb_ram);
return is_8mb_ram ? MAX_8MB_SAVE_STATE_SIZE : MAX_2MB_SAVE_STATE_SIZE;
return enable_8mb_ram ? MAX_8MB_SAVE_STATE_SIZE : MAX_2MB_SAVE_STATE_SIZE;
}
size_t System::GetMaxMemorySaveStateSize()
size_t System::GetMaxMemorySaveStateSize(bool enable_8mb_ram, bool pgxp)
{
return GetMaxSaveStateSize() + CPU::PGXP::GetStateSize();
return GetMaxSaveStateSize(enable_8mb_ram) + (pgxp ? CPU::PGXP::GetStateSize() : 0);
}
std::string System::GetMediaPathFromSaveState(const char* path)
@@ -3448,7 +3447,7 @@ bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screen
// write data
if (buffer->state_data.empty())
buffer->state_data.resize(GetMaxSaveStateSize());
buffer->state_data.resize(GetMaxSaveStateSize(Bus::g_ram_size > Bus::RAM_2MB_SIZE));
return SaveStateDataToBuffer(buffer->state_data, &buffer->state_size, error);
}
@@ -4559,6 +4558,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
g_settings.gpu_max_queued_frames != old_settings.gpu_max_queued_frames ||
g_settings.gpu_use_software_renderer_for_readbacks !=
old_settings.gpu_use_software_renderer_for_readbacks ||
g_settings.gpu_use_software_renderer_for_memory_states !=
old_settings.gpu_use_software_renderer_for_memory_states ||
g_settings.gpu_scaled_interlacing != old_settings.gpu_scaled_interlacing ||
g_settings.gpu_force_round_texcoords != old_settings.gpu_force_round_texcoords ||
g_settings.gpu_texture_filter != old_settings.gpu_texture_filter ||
@@ -4596,9 +4597,19 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
GPUThread::UpdateSettings(true, false, false);
// NOTE: Must come after the GPU thread settings update, otherwise it allocs the wrong size textures.
const bool use_existing_textures = (g_settings.gpu_resolution_scale == old_settings.gpu_resolution_scale);
const bool use_existing_textures = (g_settings.gpu_resolution_scale == old_settings.gpu_resolution_scale &&
g_settings.gpu_use_software_renderer_for_memory_states ==
old_settings.gpu_use_software_renderer_for_memory_states);
FreeMemoryStateStorage(false, true, use_existing_textures);
ClearMemorySaveStates(true, true);
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.runahead_frames == old_settings.runahead_frames)
{
// done below if rewind settings changed
ClearMemorySaveStates(true, true);
}
if (IsPaused())
{
@@ -4633,7 +4644,15 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
FreeMemoryStateStorage(false, true, false);
StopMediaCapture();
GPUThread::UpdateSettings(true, true, g_settings.gpu_use_thread != old_settings.gpu_use_thread);
ClearMemorySaveStates(true, false);
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.runahead_frames == old_settings.runahead_frames)
{
// done below if rewind settings changed
ClearMemorySaveStates(true, false);
}
if (IsPaused())
{
@@ -5019,12 +5038,16 @@ void System::LogUnsafeSettingsToConsole(const SmallStringBase& messages)
WARNING_LOG(console_messages);
}
void System::CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_usage, u64* vram_usage)
void System::CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u32 multisamples,
bool use_software_renderer, bool enable_8mb_ram, u64* ram_usage,
u64* vram_usage)
{
const u64 real_resolution_scale = std::max<u64>(g_settings.gpu_resolution_scale, 1u);
*ram_usage = GetMaxMemorySaveStateSize() * static_cast<u64>(num_saves);
*vram_usage = ((VRAM_WIDTH * real_resolution_scale) * (VRAM_HEIGHT * real_resolution_scale) * 4) *
static_cast<u64>(g_settings.gpu_multisamples) * static_cast<u64>(num_saves);
const u32 real_resolution_scale = std::max<u32>(resolution_scale, 1u);
*ram_usage = GetMaxMemorySaveStateSize(enable_8mb_ram, false) * static_cast<u64>(num_saves);
*vram_usage = use_software_renderer ? 0 :
((static_cast<u64>(VRAM_WIDTH * real_resolution_scale) *
static_cast<u64>(VRAM_HEIGHT * real_resolution_scale) * 4) *
static_cast<u64>(multisamples) * static_cast<u64>(num_saves));
}
void System::UpdateMemorySaveStateSettings()
@@ -5048,7 +5071,9 @@ void System::UpdateMemorySaveStateSettings()
num_slots = g_settings.rewind_save_slots;
u64 ram_usage, vram_usage;
CalculateRewindMemoryUsage(g_settings.rewind_save_slots, g_settings.gpu_resolution_scale, &ram_usage, &vram_usage);
CalculateRewindMemoryUsage(g_settings.rewind_save_slots, g_settings.gpu_resolution_scale,
g_settings.gpu_multisamples, g_settings.gpu_use_software_renderer_for_memory_states,
g_settings.cpu_enable_8mb_ram, &ram_usage, &vram_usage);
INFO_LOG("Rewind is enabled, saving every {} frames, with {} slots and {}MB RAM and {}MB VRAM usage",
std::max(s_state.rewind_save_frequency, 1), g_settings.rewind_save_slots, ram_usage / 1048576,
vram_usage / 1048576);

View File

@@ -267,10 +267,10 @@ void ResetSystem();
bool CanPauseSystem(bool display_message);
/// Returns the maximum size of a save state, considering the current configuration.
size_t GetMaxSaveStateSize();
size_t GetMaxSaveStateSize(bool enable_8mb_ram);
/// Returns the maximum size of a save state that is not expected to be serialized to file.
size_t GetMaxMemorySaveStateSize();
size_t GetMaxMemorySaveStateSize(bool enable_8mb_ram, bool pgxp);
/// Loads state from the specified path.
bool LoadState(const char* path, Error* error, bool save_undo_state, bool force_update_display);
@@ -440,7 +440,8 @@ std::string GetImageForLoadingScreen(const std::string& game_path);
//////////////////////////////////////////////////////////////////////////
// Memory Save States (Rewind and Runahead)
//////////////////////////////////////////////////////////////////////////
void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_usage, u64* vram_usage);
void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u32 multisamples, bool use_software_renderer,
bool enable_8mb_ram, u64* ram_usage, u64* vram_usage);
void ClearMemorySaveStates(bool reallocate_resources, bool recycle_textures);
void SetRunaheadReplayFlag(bool is_analog_input);

View File

@@ -27,6 +27,8 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.preFrameSleepBuffer, "Display", "PreFrameSleepBuffer",
Settings::DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.rewindEnable, "Main", "RewindEnable", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useSoftwareRendererForMemoryStates, "GPU",
"UseSoftwareRendererForMemoryStates", false);
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.rewindSaveFrequency, "Main", "RewindFrequency", 10.0f);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.rewindSaveSlots, "Main", "RewindSaveSlots", 10);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.runaheadFrames, "Main", "RunaheadFrameCount", 0);
@@ -85,6 +87,8 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
connect(m_ui.preFrameSleep, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::onPreFrameSleepChanged);
connect(m_ui.rewindEnable, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::updateRewind);
connect(m_ui.useSoftwareRendererForMemoryStates, &QCheckBox::checkStateChanged, this,
&EmulationSettingsWidget::updateRewind);
connect(m_ui.rewindSaveFrequency, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
&EmulationSettingsWidget::updateRewind);
connect(m_ui.rewindSaveSlots, QOverload<int>::of(&QSpinBox::valueChanged), this,
@@ -141,6 +145,12 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
"requirements.<br> "
"<b>Rewind Buffer Size:</b> How many saves will be kept for rewinding. Higher values have greater memory "
"requirements."));
dialog->registerWidgetHelp(m_ui.useSoftwareRendererForMemoryStates, tr("Use Software Renderer (Low VRAM Mode)"),
tr("Unchecked"),
tr("Uses the software renderer when creating rewind states to prevent additional VRAM "
"usage. Especially useful when upscaling, as this will significantly reduce the system "
"requirements for rewinding."));
dialog->registerWidgetHelp(
m_ui.runaheadFrames, tr("Runahead"), tr("Disabled"),
tr(
@@ -242,25 +252,36 @@ void EmulationSettingsWidget::updateRewind()
{
const bool rewind_enabled = m_dialog->getEffectiveBoolValue("Main", "RewindEnable", false);
const bool runahead_enabled = m_dialog->getIntValue("Main", "RunaheadFrameCount", 0) > 0;
const bool rewind_active = (!runahead_enabled && rewind_enabled);
m_ui.rewindEnable->setEnabled(!runahead_enabled);
m_ui.runaheadForAnalogInput->setEnabled(runahead_enabled);
m_ui.useSoftwareRendererForMemoryStates->setEnabled(rewind_active);
if (!runahead_enabled && rewind_enabled)
if (rewind_active)
{
const u32 resolution_scale = static_cast<u32>(m_dialog->getEffectiveIntValue("GPU", "ResolutionScale", 1));
const u32 multisamples = m_dialog->getEffectiveIntValue("GPU", "Multisamples", 1);
const bool use_software_renderer =
m_dialog->getEffectiveBoolValue("GPU", "UseSoftwareRendererForMemoryStates", false);
const bool enable_8mb_ram = m_dialog->getEffectiveBoolValue("Console", "Enable8MBRAM", false);
const u32 frames = static_cast<u32>(m_ui.rewindSaveSlots->value());
const float frequency = static_cast<float>(m_ui.rewindSaveFrequency->value());
const float duration =
((frequency <= std::numeric_limits<float>::epsilon()) ? (1.0f / 60.0f) : frequency) * static_cast<float>(frames);
u64 ram_usage, vram_usage;
System::CalculateRewindMemoryUsage(frames, resolution_scale, &ram_usage, &vram_usage);
System::CalculateRewindMemoryUsage(frames, resolution_scale, multisamples, use_software_renderer, enable_8mb_ram,
&ram_usage, &vram_usage);
m_ui.rewindSummary->setText(
tr("Rewind for %n frame(s), lasting %1 second(s) will require up to %2MB of RAM and %3MB of VRAM.", "", frames)
.arg(duration)
.arg(ram_usage / 1048576)
.arg(vram_usage / 1048576));
(vram_usage > 0) ?
tr("Rewind for %n frame(s), lasting %1 second(s) will require %2MB of RAM and %3MB of VRAM.", "", frames)
.arg(duration)
.arg(ram_usage / 1048576)
.arg(vram_usage / 1048576) :
tr("Rewind for %n frame(s), lasting %1 second(s) will require %2MB of RAM.", "", frames)
.arg(duration)
.arg(ram_usage / 1048576));
m_ui.rewindSaveFrequency->setEnabled(true);
m_ui.rewindSaveSlots->setEnabled(true);
}

View File

@@ -186,13 +186,20 @@
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<item row="0" column="0">
<widget class="QCheckBox" name="rewindEnable">
<property name="text">
<string>Enable Rewinding</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="useSoftwareRendererForMemoryStates">
<property name="text">
<string>Use Software Renderer (Low VRAM Mode)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>