mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-02-11 15:40:51 +00:00
Savestates: Optimize SPU pausing
This commit is contained in:
parent
2381e33236
commit
099c74481d
@ -1365,10 +1365,33 @@ u32 CPUDisAsm::DisAsmBranchTarget(s32 /*imm*/)
|
|||||||
|
|
||||||
extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock)
|
extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock)
|
||||||
{
|
{
|
||||||
const u64 start = get_system_time();
|
auto get_spus = [old_counter = u64{umax}, spu_list = std::vector<std::shared_ptr<named_thread<spu_thread>>>()](bool can_collect) mutable
|
||||||
|
{
|
||||||
|
const u64 new_counter = cpu_thread::g_threads_created + cpu_thread::g_threads_deleted;
|
||||||
|
|
||||||
|
if (old_counter != new_counter)
|
||||||
|
{
|
||||||
|
if (!can_collect)
|
||||||
|
{
|
||||||
|
return decltype(&spu_list){};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch SPU contexts
|
||||||
|
spu_list.clear();
|
||||||
|
|
||||||
|
idm::select<named_thread<spu_thread>>([&](u32 id, spu_thread&)
|
||||||
|
{
|
||||||
|
spu_list.emplace_back(ensure(idm::get_unlocked<named_thread<spu_thread>>(id)));
|
||||||
|
});
|
||||||
|
|
||||||
|
old_counter = new_counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &spu_list;
|
||||||
|
};
|
||||||
|
|
||||||
// Attempt to lock for half a second, if somehow takes longer abort it
|
// Attempt to lock for half a second, if somehow takes longer abort it
|
||||||
do
|
for (u64 start = 0, passed_count = 0; passed_count < 10; std::this_thread::yield())
|
||||||
{
|
{
|
||||||
if (revert_lock)
|
if (revert_lock)
|
||||||
{
|
{
|
||||||
@ -1376,27 +1399,75 @@ extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool reve
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cpu_thread::suspend_all(nullptr, {}, []()
|
if (!start)
|
||||||
{
|
{
|
||||||
return idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
|
start = get_system_time();
|
||||||
{
|
}
|
||||||
if (!spu.unsavable)
|
else if (get_system_time() - start >= 100'000)
|
||||||
{
|
{
|
||||||
if (Emu.IsPaused())
|
passed_count++;
|
||||||
{
|
start = 0;
|
||||||
// If emulation is paused, we can only hope it's already in a state compatible with savestates
|
continue;
|
||||||
return !!(spu.state & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause));
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ensure(!spu.state.test_and_set(cpu_flag::dbg_global_pause));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
// Try to fetch SPUs out of critical section
|
||||||
|
const auto spu_list = get_spus(true);
|
||||||
|
|
||||||
|
// Avoid using suspend_all when more than one thread is known to be unsavable
|
||||||
|
u32 unsavable_threads = 0;
|
||||||
|
|
||||||
|
for (auto& spu : *spu_list)
|
||||||
|
{
|
||||||
|
if (spu->unsavable && !spu->state.all_of(cpu_flag::wait + cpu_flag::exit))
|
||||||
|
{
|
||||||
|
unsavable_threads++;
|
||||||
|
|
||||||
|
if (unsavable_threads >= 3)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsavable_threads >= 3)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag for optimization
|
||||||
|
bool paused_anyone = false;
|
||||||
|
|
||||||
|
if (cpu_thread::suspend_all(nullptr, {}, [&]()
|
||||||
|
{
|
||||||
|
if (!get_spus(false))
|
||||||
|
{
|
||||||
|
// Avoid locking IDM here because this is a critical section
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& spu : *spu_list)
|
||||||
|
{
|
||||||
|
if (spu->unsavable && !spu->state.all_of(cpu_flag::wait + cpu_flag::exit))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
if (Emu.IsPaused())
|
||||||
}).ret;
|
{
|
||||||
|
// If emulation is paused, we can only hope it's already in a state compatible with savestates
|
||||||
|
if (!(spu->state & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paused_anyone = true;
|
||||||
|
ensure(!spu->state.test_and_set(cpu_flag::dbg_global_pause));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
if (Emu.IsPaused())
|
if (Emu.IsPaused())
|
||||||
@ -1404,36 +1475,39 @@ extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool reve
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's faster to lock once
|
if (!paused_anyone)
|
||||||
reader_lock lock(id_manager::g_mutex);
|
|
||||||
|
|
||||||
idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
|
|
||||||
{
|
{
|
||||||
spu.state -= cpu_flag::dbg_global_pause;
|
// Need not do anything
|
||||||
}, idm::unlocked);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// For faster signalling, first remove state flags then batch notifications
|
// For faster signalling, first remove state flags then batch notifications
|
||||||
idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
|
for (auto& spu : *spu_list)
|
||||||
{
|
{
|
||||||
if (spu.state & cpu_flag::wait)
|
spu->state -= cpu_flag::dbg_global_pause;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& spu : *spu_list)
|
||||||
|
{
|
||||||
|
if (spu->state & cpu_flag::wait)
|
||||||
{
|
{
|
||||||
spu.state.notify_one();
|
spu->state.notify_one();
|
||||||
}
|
}
|
||||||
}, idm::unlocked);
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} while (std::this_thread::yield(), get_system_time() - start <= 500'000);
|
}
|
||||||
|
|
||||||
idm::select<named_thread<spu_thread>>([&](u32, named_thread<spu_thread>& spu)
|
for (auto& spu : *get_spus(true))
|
||||||
{
|
{
|
||||||
if (spu.state.test_and_reset(cpu_flag::dbg_global_pause))
|
if (spu->state.test_and_reset(cpu_flag::dbg_global_pause))
|
||||||
{
|
{
|
||||||
spu.state.notify_one();
|
spu->state.notify_one();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2628,23 +2628,73 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta
|
|||||||
extern bool try_lock_vdec_context_creation();
|
extern bool try_lock_vdec_context_creation();
|
||||||
extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock = false);
|
extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock = false);
|
||||||
|
|
||||||
void Emulator::Kill(bool allow_autoexit, bool savestate)
|
void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_stage)
|
||||||
{
|
{
|
||||||
if (!IsStopped() && savestate && !try_lock_spu_threads_in_a_state_compatible_with_savestates())
|
static const auto make_ptr = [](auto ptr)
|
||||||
{
|
{
|
||||||
sys_log.error("Failed to savestate: failed to lock SPU threads execution.");
|
return std::shared_ptr<std::remove_pointer_t<decltype(ptr)>>(ptr);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!IsStopped() && savestate && !try_lock_vdec_context_creation())
|
if (!IsStopped() && savestate)
|
||||||
{
|
{
|
||||||
try_lock_spu_threads_in_a_state_compatible_with_savestates(true);
|
if (!save_stage || !save_stage->prepared)
|
||||||
|
{
|
||||||
|
if (m_savestate_pending.exchange(true))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sys_log.error("Failed to savestate: HLE VDEC (video decoder) context(s) exist."
|
std::shared_ptr<std::shared_ptr<void>> pause_thread = std::make_shared<std::shared_ptr<void>>();
|
||||||
"\nLLE libvdec.sprx by selecting it in Advanced tab -> Firmware Libraries."
|
|
||||||
"\nYou need to close the game for it to take effect."
|
|
||||||
"\nIf you cannot close the game due to losing important progress, your best chance is to skip the current cutscenes if any are played and retry.");
|
|
||||||
|
|
||||||
return;
|
*pause_thread = make_ptr(new named_thread("Savestate Prepare Thread"sv, [pause_thread, allow_autoexit, this]() mutable
|
||||||
|
{
|
||||||
|
if (!try_lock_spu_threads_in_a_state_compatible_with_savestates())
|
||||||
|
{
|
||||||
|
sys_log.error("Failed to savestate: failed to lock SPU threads execution.");
|
||||||
|
m_savestate_pending = false;
|
||||||
|
|
||||||
|
CallFromMainThread([pause = std::move(pause_thread)]()
|
||||||
|
{
|
||||||
|
// Join thread
|
||||||
|
}, nullptr, false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsStopped() && !try_lock_vdec_context_creation())
|
||||||
|
{
|
||||||
|
try_lock_spu_threads_in_a_state_compatible_with_savestates(true);
|
||||||
|
|
||||||
|
sys_log.error("Failed to savestate: HLE VDEC (video decoder) context(s) exist."
|
||||||
|
"\nLLE libvdec.sprx by selecting it in Advanced tab -> Firmware Libraries."
|
||||||
|
"\nYou need to close the game for it to take effect."
|
||||||
|
"\nIf you cannot close the game due to losing important progress, your best chance is to skip the current cutscenes if any are played and retry.");
|
||||||
|
|
||||||
|
m_savestate_pending = false;
|
||||||
|
|
||||||
|
CallFromMainThread([pause = std::move(pause_thread)]()
|
||||||
|
{
|
||||||
|
// Join thread
|
||||||
|
}, nullptr, false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CallFromMainThread([allow_autoexit, this]()
|
||||||
|
{
|
||||||
|
savestate_stage stage{};
|
||||||
|
stage.prepared = true;
|
||||||
|
Kill(allow_autoexit, true, &stage);
|
||||||
|
});
|
||||||
|
|
||||||
|
CallFromMainThread([pause = std::move(pause_thread)]()
|
||||||
|
{
|
||||||
|
// Join thread
|
||||||
|
}, nullptr, false);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_tls_log_prefix = []()
|
g_tls_log_prefix = []()
|
||||||
@ -2683,6 +2733,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate)
|
|||||||
m_config_mode = cfg_mode::custom;
|
m_config_mode = cfg_mode::custom;
|
||||||
read_used_savestate_versions();
|
read_used_savestate_versions();
|
||||||
m_savestate_extension_flags1 = {};
|
m_savestate_extension_flags1 = {};
|
||||||
|
m_savestate_pending = false;
|
||||||
|
|
||||||
// Enable logging
|
// Enable logging
|
||||||
rpcs3::utils::configure_logs(true);
|
rpcs3::utils::configure_logs(true);
|
||||||
@ -2729,11 +2780,6 @@ void Emulator::Kill(bool allow_autoexit, bool savestate)
|
|||||||
// There is no race condition because it is only accessed by the same thread
|
// There is no race condition because it is only accessed by the same thread
|
||||||
std::shared_ptr<std::shared_ptr<void>> join_thread = std::make_shared<std::shared_ptr<void>>();
|
std::shared_ptr<std::shared_ptr<void>> join_thread = std::make_shared<std::shared_ptr<void>>();
|
||||||
|
|
||||||
auto make_ptr = [](auto ptr)
|
|
||||||
{
|
|
||||||
return std::shared_ptr<std::remove_pointer_t<decltype(ptr)>>(ptr);
|
|
||||||
};
|
|
||||||
|
|
||||||
*join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, savestate, allow_autoexit, this]() mutable
|
*join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, savestate, allow_autoexit, this]() mutable
|
||||||
{
|
{
|
||||||
named_thread stop_watchdog("Stop Watchdog"sv, [this]()
|
named_thread stop_watchdog("Stop Watchdog"sv, [this]()
|
||||||
@ -3081,6 +3127,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate)
|
|||||||
m_ar.reset();
|
m_ar.reset();
|
||||||
read_used_savestate_versions();
|
read_used_savestate_versions();
|
||||||
m_savestate_extension_flags1 = {};
|
m_savestate_extension_flags1 = {};
|
||||||
|
m_savestate_pending = false;
|
||||||
|
|
||||||
// Complete the operation
|
// Complete the operation
|
||||||
m_state = system_state::stopped;
|
m_state = system_state::stopped;
|
||||||
|
@ -108,6 +108,7 @@ class Emulator final
|
|||||||
atomic_t<u64> m_pause_start_time{0}; // set when paused
|
atomic_t<u64> m_pause_start_time{0}; // set when paused
|
||||||
atomic_t<u64> m_pause_amend_time{0}; // increased when resumed
|
atomic_t<u64> m_pause_amend_time{0}; // increased when resumed
|
||||||
atomic_t<u64> m_stop_ctr{1}; // Increments when emulation is stopped
|
atomic_t<u64> m_stop_ctr{1}; // Increments when emulation is stopped
|
||||||
|
atomic_t<bool> m_savestate_pending = false;
|
||||||
|
|
||||||
games_config m_games_config;
|
games_config m_games_config;
|
||||||
|
|
||||||
@ -331,10 +332,17 @@ public:
|
|||||||
void FixGuestTime();
|
void FixGuestTime();
|
||||||
void FinalizeRunRequest();
|
void FinalizeRunRequest();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct savestate_stage
|
||||||
|
{
|
||||||
|
bool prepared = false;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
|
||||||
bool Pause(bool freeze_emulation = false, bool show_resume_message = true);
|
bool Pause(bool freeze_emulation = false, bool show_resume_message = true);
|
||||||
void Resume();
|
void Resume();
|
||||||
void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false);
|
void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false);
|
||||||
void Kill(bool allow_autoexit = true, bool savestate = false);
|
void Kill(bool allow_autoexit = true, bool savestate = false, savestate_stage* stage = nullptr);
|
||||||
game_boot_result Restart(bool graceful = true);
|
game_boot_result Restart(bool graceful = true);
|
||||||
bool Quit(bool force_quit);
|
bool Quit(bool force_quit);
|
||||||
static void CleanUp();
|
static void CleanUp();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user