1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

Savestates: Optimize SPU pausing

This commit is contained in:
Eladash 2023-09-29 02:27:45 +03:00 committed by Elad Ashkenazi
parent 2381e33236
commit 099c74481d
3 changed files with 180 additions and 51 deletions

View File

@ -1365,10 +1365,33 @@ u32 CPUDisAsm::DisAsmBranchTarget(s32 /*imm*/)
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
do
for (u64 start = 0, passed_count = 0; passed_count < 10; std::this_thread::yield())
{
if (revert_lock)
{
@ -1376,27 +1399,75 @@ extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool reve
break;
}
if (cpu_thread::suspend_all(nullptr, {}, []()
if (!start)
{
return idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
{
if (!spu.unsavable)
{
if (Emu.IsPaused())
{
// If emulation is paused, we can only hope it's already in a state compatible with savestates
return !!(spu.state & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause));
}
else
{
ensure(!spu.state.test_and_set(cpu_flag::dbg_global_pause));
}
start = get_system_time();
}
else if (get_system_time() - start >= 100'000)
{
passed_count++;
start = 0;
continue;
}
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;
}).ret;
if (Emu.IsPaused())
{
// 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())
@ -1404,36 +1475,39 @@ extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool reve
return false;
}
// It's faster to lock once
reader_lock lock(id_manager::g_mutex);
idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
if (!paused_anyone)
{
spu.state -= cpu_flag::dbg_global_pause;
}, idm::unlocked);
// Need not do anything
continue;
}
// 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;
}
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;
}

View File

@ -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_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."
"\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.");
std::shared_ptr<std::shared_ptr<void>> pause_thread = std::make_shared<std::shared_ptr<void>>();
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 = []()
@ -2683,6 +2733,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate)
m_config_mode = cfg_mode::custom;
read_used_savestate_versions();
m_savestate_extension_flags1 = {};
m_savestate_pending = false;
// Enable logging
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
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
{
named_thread stop_watchdog("Stop Watchdog"sv, [this]()
@ -3081,6 +3127,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate)
m_ar.reset();
read_used_savestate_versions();
m_savestate_extension_flags1 = {};
m_savestate_pending = false;
// Complete the operation
m_state = system_state::stopped;

View File

@ -108,6 +108,7 @@ class Emulator final
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_stop_ctr{1}; // Increments when emulation is stopped
atomic_t<bool> m_savestate_pending = false;
games_config m_games_config;
@ -331,10 +332,17 @@ public:
void FixGuestTime();
void FinalizeRunRequest();
private:
struct savestate_stage
{
bool prepared = false;
};
public:
bool Pause(bool freeze_emulation = false, bool show_resume_message = true);
void Resume();
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);
bool Quit(bool force_quit);
static void CleanUp();