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:
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user