diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index fdf3bbc5ff..dee57ac893 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -3,6 +3,7 @@ #include "Emu/VFS.h" #include "Emu/IdManager.h" #include "Emu/localized_string.h" +#include "Emu/savestate_utils.hpp" #include "Emu/Cell/lv2/sys_fs.h" #include "Emu/Cell/lv2/sys_sync.h" #include "Emu/Cell/lv2/sys_process.h" @@ -272,6 +273,14 @@ static std::vector get_save_entries(const std::string& base_dir, static error_code select_and_delete(ppu_thread& ppu) { + std::unique_lock hle_lock(g_fxo->get(), std::try_to_lock); + + if (!hle_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + std::unique_lock lock(g_fxo->get().mutex, std::try_to_lock); if (!lock) @@ -699,6 +708,14 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v return {CELL_SAVEDATA_ERROR_PARAM, " (error %d)", ecode}; } + std::unique_lock hle_lock(g_fxo->get(), std::try_to_lock); + + if (!hle_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + std::unique_lock lock(g_fxo->get().mutex, std::try_to_lock); if (!lock) diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index d60b9d41fa..ce9bff4320 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -5,9 +5,9 @@ #include "Emu/Cell/lv2/sys_sync.h" #include "Emu/Cell/lv2/sys_ppu_thread.h" #include "Emu/Cell/lv2/sys_process.h" +#include "Emu/savestate_utils.hpp" #include "sysPrxForUser.h" #include "util/media_utils.h" -#include "util/init_mutex.hpp" #ifdef _MSC_VER #pragma warning(push, 0) @@ -625,38 +625,16 @@ struct vdec_context final } }; -struct vdec_creation_lock +extern bool check_if_vdec_contexts_exist() { - stx::init_mutex locked; + bool context_exists = false; - vdec_creation_lock() + idm::select([&](u32, vdec_context&) { - auto lk = locked.init(); - } -}; + context_exists = true; + }); -extern bool try_lock_vdec_context_creation() -{ - auto& lock = g_fxo->get(); - auto reset = lock.locked.reset(); - - if (reset) - { - bool context_exists = false; - - idm::select([&](u32, vdec_context&) - { - context_exists = true; - }); - - if (context_exists) - { - reset.set_init(); - return false; - } - } - - return true; + return context_exists; } extern void vdecEntry(ppu_thread& ppu, u32 vid) @@ -890,7 +868,7 @@ static error_code vdecOpen(ppu_thread& ppu, T type, U res, vm::cptr // Create decoder context std::shared_ptr vdec; - if (auto access = g_fxo->get().locked.access(); access) + if (std::unique_lock lock{g_fxo->get(), std::try_to_lock}) { vdec = idm::make_ptr(type->codecType, type->profileLevel, res->memAddr, res->memSize, cb->cbFunc, cb->cbArg); } @@ -943,6 +921,14 @@ error_code cellVdecClose(ppu_thread& ppu, u32 handle) { cellVdec.warning("cellVdecClose(handle=0x%x)", handle); + std::unique_lock lock_hle{g_fxo->get(), std::try_to_lock}; + + if (!lock_hle) + { + ppu.state += cpu_flag::again; + return {}; + } + auto vdec = idm::get(handle); if (!vdec) diff --git a/rpcs3/Emu/Cell/PPUModule.h b/rpcs3/Emu/Cell/PPUModule.h index fa7ec784f9..daa2d05d14 100644 --- a/rpcs3/Emu/Cell/PPUModule.h +++ b/rpcs3/Emu/Cell/PPUModule.h @@ -289,9 +289,9 @@ inline RT ppu_execute(ppu_thread& ppu, Args... args) return func(ppu, args...); } -#define BIND_FUNC_WITH_BLR(func) BIND_FUNC(func, if (cpu_flag::again - ppu.state) ppu.cia = static_cast(ppu.lr) & ~3) +#define BIND_FUNC_WITH_BLR(func, _module) BIND_FUNC(func, if (cpu_flag::again - ppu.state) ppu.cia = static_cast(ppu.lr) & ~3; else ppu.current_module = _module) -#define REG_FNID(_module, nid, func) ppu_module_manager::register_static_function<&func>(#_module, ppu_select_name(#func, nid), BIND_FUNC_WITH_BLR(func), ppu_generate_id(nid)) +#define REG_FNID(_module, nid, func) ppu_module_manager::register_static_function<&func>(#_module, ppu_select_name(#func, nid), BIND_FUNC_WITH_BLR(func, #_module), ppu_generate_id(nid)) #define REG_FUNC(_module, func) REG_FNID(_module, #func, func) @@ -299,7 +299,7 @@ inline RT ppu_execute(ppu_thread& ppu, Args... args) #define REG_VAR(_module, var) REG_VNID(_module, #var, var) -#define REG_HIDDEN_FUNC(func) ppu_function_manager::register_function(BIND_FUNC_WITH_BLR(func)) +#define REG_HIDDEN_FUNC(func) ppu_function_manager::register_function(BIND_FUNC_WITH_BLR(func, "")) #define REG_HIDDEN_FUNC_PURE(func) ppu_function_manager::register_function(func) diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index c180c00e8c..bdfc1bd932 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -2421,6 +2421,14 @@ ppu_thread::ppu_thread(utils::serial& ar) ar(lv2_obj::g_priority_order_tag); } + if (version >= 3) + { + // Function and module for HLE function relocation + // TODO: Use it + ar.pop(); + ar.pop(); + } + serialize_common(ar); // Restore jm_mask @@ -2595,6 +2603,17 @@ void ppu_thread::save(utils::serial& ar) ar(lv2_obj::g_priority_order_tag); } + if (current_module && current_module[0]) + { + ar(std::string{current_module}); + ar(std::string{last_function}); + } + else + { + ar(std::string{}); + ar(std::string{}); + } + serialize_common(ar); auto [status, order] = lv2_obj::ppu_state(this, false); diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index 00961f7ddc..c513a0950d 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -295,6 +295,7 @@ public: u64 syscall_args[8]{0}; // Last syscall arguments stored const char* current_function{}; // Current function name for diagnosis, optimized for speed. const char* last_function{}; // Sticky copy of current_function, is not cleared on function return + const char* current_module{}; // Current module name, for savestates. const bool is_interrupt_thread; // True for interrupts-handler threads diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index ace93740d0..603041356a 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -2819,7 +2819,7 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta } } -extern bool try_lock_vdec_context_creation(); +extern bool check_if_vdec_contexts_exist(); extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock = false); void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_stage) @@ -2861,14 +2861,35 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s return; } - if (!IsStopped() && !try_lock_vdec_context_creation()) + bool savedata_error = false; + bool vdec_error = false; + + if (!g_fxo->get().try_finalize([&]() { + // List of conditions required for emulation to save properly + vdec_error = check_if_vdec_contexts_exist(); + return !vdec_error; + })) + { + // Unlock SPUs 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."); + savedata_error = !vdec_error; // For now it is implied a savedata error + + if (vdec_error) + { + 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."); + } + + if (savedata_error) + { + sys_log.error("Failed to savestate: Savedata operation is active." + "\nYour best chance is to wait for the current game saving operation to finish and retry." + "\nThe game is probably displaying a saving cicrle or other gesture to indicate that it is saving."); + } m_emu_state_close_pending = false; diff --git a/rpcs3/Emu/savestate_utils.cpp b/rpcs3/Emu/savestate_utils.cpp index 7bf6787f16..3b2fef0baf 100644 --- a/rpcs3/Emu/savestate_utils.cpp +++ b/rpcs3/Emu/savestate_utils.cpp @@ -42,7 +42,7 @@ static std::array s_serial_versions; } SERIALIZATION_VER(global_version, 0, 16) // For stuff not listed here -SERIALIZATION_VER(ppu, 1, 1, 2/*PPU sleep order*/) +SERIALIZATION_VER(ppu, 1, 1, 2/*PPU sleep order*/, 3/*PPU FNID and module*/) SERIALIZATION_VER(spu, 2, 1) SERIALIZATION_VER(lv2_sync, 3, 1) SERIALIZATION_VER(lv2_vm, 4, 1) @@ -340,3 +340,74 @@ extern u16 serial_breathe_and_tag(utils::serial& ar, std::string_view name, bool { return ::stx::serial_breathe_and_tag(ar, name, tag_bit); } + +[[noreturn]] void hle_locks_t::lock() +{ + // Unreachable + ensure(false); +} + +bool hle_locks_t::try_lock() +{ + while (true) + { + auto [old, success] = lock_val.fetch_op([](s64& value) + { + if (value >= 0) + { + value++; + return true; + } + + return false; + }); + + if (success) + { + return true; + } + + if (old == finalized) + { + break; + } + + lock_val.wait(old); + } + + return false; +} + +void hle_locks_t::unlock() +{ + lock_val--; +} + +bool hle_locks_t::try_finalize(std::function test) +{ + if (!test()) + { + return false; + } + + if (!lock_val.compare_and_swap_test(0, waiting_for_evaluation)) + { + return false; + } + + if (!test()) + { + // Failed + ensure(lock_val.compare_and_swap_test(waiting_for_evaluation, 0)); + return false; + } + + ensure(lock_val.compare_and_swap_test(waiting_for_evaluation, finalized)); + + // Sanity check when debugging (the result is not expected to change after finalization) + //ensure(test()); + + lock_val.notify_all(); + return true; +} + diff --git a/rpcs3/Emu/savestate_utils.hpp b/rpcs3/Emu/savestate_utils.hpp index 2715bfc024..eec58e6b30 100644 --- a/rpcs3/Emu/savestate_utils.hpp +++ b/rpcs3/Emu/savestate_utils.hpp @@ -10,6 +10,23 @@ struct version_entry ENABLE_BITWISE_SERIALIZATION; }; + +struct hle_locks_t +{ + atomic_t lock_val{0}; + + enum states : s64 + { + waiting_for_evaluation = -1, + finalized = -2, + }; + + void lock(); + bool try_lock(); + void unlock(); + bool try_finalize(std::function test); +}; + bool load_and_check_reserved(utils::serial& ar, usz size); bool is_savestate_version_compatible(const std::vector& data, bool is_boot_check); std::vector get_savestate_versioning_data(fs::file&& file, std::string_view filepath);