mirror of
https://github.com/stenzek/duckstation.git
synced 2026-04-10 16:13:06 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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: {}");
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user