diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 9ff528e8a2..d87fc25957 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -1028,7 +1028,7 @@ void GLGSRender::do_local_task(rsx::FIFO_state state) if (m_overlay_manager) { - if (!in_begin_end && async_flip_requested & flip_request::native_ui) + if (!in_begin_end && async_flip_requested & flip_request::native_ui && !is_stopped()) { rsx::display_flip_info_t info{}; info.buffer = current_display_buffer; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message.cpp b/rpcs3/Emu/RSX/Overlays/overlay_message.cpp index aabe0f4b7c..4c2f98bf02 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_message.cpp @@ -7,9 +7,10 @@ namespace rsx namespace overlays { template - message_item::message_item(T msg_id) + message_item::message_item(T msg_id, u64 expiration, std::shared_ptr> refs) { - m_expiration_time = get_system_time() + 5'000'000; + m_expiration_time = expiration == umax ? expiration : get_system_time() + expiration; + m_refs = std::move(refs); m_text.set_font("Arial", 16); m_text.set_text(msg_id); @@ -21,12 +22,13 @@ namespace rsx m_fade_animation.duration = 2.f; m_fade_animation.active = true; } - template message_item::message_item(std::string msg_id); - template message_item::message_item(localized_string_id msg_id); + template message_item::message_item(std::string msg_id, u64, std::shared_ptr>); + template message_item::message_item(localized_string_id msg_id, u64, std::shared_ptr>); u64 message_item::get_expiration() const { - return m_expiration_time; + // If reference counting is enabled and reached 0 consider it expired + return m_refs && *m_refs == 0 ? 0 : m_expiration_time; } compiled_resource message_item::get_compiled() diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message.h b/rpcs3/Emu/RSX/Overlays/overlay_message.h index 62e429d819..1673ebd718 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_message.h @@ -11,7 +11,7 @@ namespace rsx { public: template - message_item(T msg_id); + message_item(T msg_id, u64 expiration, std::shared_ptr> refs); void update(usz index, u64 time); u64 get_expiration() const; @@ -22,6 +22,7 @@ namespace rsx animation_color_interpolate m_fade_animation; u64 m_expiration_time = 0; + std::shared_ptr> m_refs; bool m_processed = false; usz m_cur_pos = umax; }; @@ -33,7 +34,7 @@ namespace rsx compiled_resource get_compiled() override; template - void queue_message(T msg_id) + void queue_message(T msg_id, u64 expiration, std::shared_ptr> refs) { std::lock_guard lock(m_mutex_queue); @@ -41,12 +42,12 @@ namespace rsx { for (auto id : msg_id) { - m_queue.emplace_back(id); + m_queue.emplace_back(id, expiration, refs); } } else { - m_queue.emplace_back(msg_id); + m_queue.emplace_back(msg_id, expiration, std::move(refs)); } visible = true; @@ -58,7 +59,7 @@ namespace rsx }; template - void queue_message(T msg_id) + void queue_message(T msg_id, u64 expiration = 5'000'000, std::shared_ptr> refs = {}) { if (auto manager = g_fxo->try_get()) { @@ -68,7 +69,7 @@ namespace rsx msg_overlay = std::make_shared(); msg_overlay = manager->add(msg_overlay); } - msg_overlay->queue_message(msg_id); + msg_overlay->queue_message(msg_id, expiration, std::move(refs)); } } diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 78f8fc63bd..e12aec11ff 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -18,6 +18,7 @@ #include "Emu/Cell/lv2/sys_time.h" #include "Emu/Cell/Modules/cellGcmSys.h" #include "Overlays/overlay_perf_metrics.h" +#include "Overlays/overlay_message.h" #include "Program/GLSLCommon.h" #include "Utilities/date_time.h" #include "Utilities/StrUtil.h" @@ -256,6 +257,14 @@ namespace rsx } } + extern void set_native_ui_flip() + { + if (auto rsxthr = rsx::get_current_renderer()) + { + rsxthr->async_flip_requested |= rsx::thread::flip_request::native_ui; + } + } + std::pair interleaved_range_info::calculate_required_range(u32 first, u32 count) const { if (single_vertex) @@ -542,7 +551,8 @@ namespace rsx if (g_cfg.savestate.start_paused) { - m_pause_on_first_flip = true; + // Allow to render a whole frame within this emulation session so there won't be missing graphics + m_pause_after_x_flips = 2; } } @@ -701,15 +711,15 @@ namespace rsx wait_pause(); } - on_semaphore_acquire_wait(); - if ((state & (cpu_flag::dbg_global_pause + cpu_flag::exit)) == cpu_flag::dbg_global_pause) { // Wait 16ms during emulation pause. This reduces cpu load while still giving us the chance to render overlays. + do_local_task(rsx::FIFO_state::paused); thread_ctrl::wait_on(state, old, 16000); } else { + on_semaphore_acquire_wait(); std::this_thread::yield(); } } @@ -2670,10 +2680,9 @@ namespace rsx { performance_counters.sampled_frames++; - if (m_pause_on_first_flip) + if (m_pause_after_x_flips && m_pause_after_x_flips-- == 1) { Emu.Pause(); - m_pause_on_first_flip = false; } } diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 95d60dc171..dc60f5badf 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -181,7 +181,8 @@ namespace rsx empty = 1, // PUT == GET spinning = 2, // Puller continuously jumps to self addr (synchronization technique) nop = 3, // Puller is processing a NOP command - lock_wait = 4 // Puller is processing a lock acquire + lock_wait = 4,// Puller is processing a lock acquire + paused = 5, // Puller is paused externallly }; enum FIFO_hint : u8 @@ -540,8 +541,8 @@ namespace rsx rsx::profiling_timer m_profiler; frame_statistics_t m_frame_stats; - // Savestates vrelated - bool m_pause_on_first_flip = false; + // Savestates related + u32 m_pause_after_x_flips = 0; public: RsxDmaControl* ctrl = nullptr; diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 8c9fad5df0..a07f3c1e80 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -1710,7 +1710,7 @@ void VKGSRender::do_local_task(rsx::FIFO_state state) if (m_overlay_manager) { - if (!in_begin_end && async_flip_requested & flip_request::native_ui) + if (!in_begin_end && async_flip_requested & flip_request::native_ui && !is_stopped()) { flush_command_queue(true); rsx::display_flip_info_t info{}; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index f59fabbfca..d9a5a77f2b 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -26,6 +26,7 @@ #include "Emu/title.h" #include "Emu/IdManager.h" #include "Emu/RSX/Capture/rsx_replay.h" +#include "Emu/RSX/Overlays/overlay_message.h" #include "Loader/PSF.h" #include "Loader/TAR.h" @@ -101,6 +102,11 @@ namespace atomic_wait extern void parse_hashtable(bool(*cb)(u64 id, u32 refs, u64 ptr, u32 max_coll)); } +namespace rsx +{ + void set_native_ui_flip(); +} + template<> void fmt_class_string::format(std::string& out, u64 arg) { @@ -2050,8 +2056,53 @@ bool Emulator::Pause(bool freeze_emulation) // Signal profilers to print results (if enabled) cpu_thread::flush_profilers(); + auto on_select = [](u32, cpu_thread& cpu) + { + cpu.state += cpu_flag::dbg_global_pause; + }; + + idm::select>(on_select); + idm::select>(on_select); + + if (auto rsx = g_fxo->try_get()) + { + rsx->state += cpu_flag::dbg_global_pause; + } + GetCallbacks().on_pause(); + BlockingCallFromMainThread([this]() + { + if (IsStopped()) + { + return; + } + + auto msg_ref = std::make_shared>(1); + + // No timeout + rsx::overlays::queue_message(localized_string_id::EMULATION_PAUSED_RESUME_WITH_START, -1, msg_ref); + m_pause_msgs_refs.emplace_back(msg_ref); + + auto refresh_l = [this, msg_ref]() + { + while (*msg_ref && IsPaused()) + { + // Refresh Native UI + rsx::set_native_ui_flip(); + thread_ctrl::wait_for(33'000); + } + }; + + struct thread_t + { + std::unique_ptr> m_thread; + }; + + g_fxo->get().m_thread.reset(); + g_fxo->get().m_thread = std::make_unique>("Pause Message Thread"sv, std::move(refresh_l)); + }); + static atomic_t pause_mark = 0; if (freeze_emulation) @@ -2069,19 +2120,6 @@ bool Emulator::Pause(bool freeze_emulation) sys_log.error("Emulator::Pause() error: concurrent access"); } - auto on_select = [](u32, cpu_thread& cpu) - { - cpu.state += cpu_flag::dbg_global_pause; - }; - - idm::select>(on_select); - idm::select>(on_select); - - if (auto rsx = g_fxo->try_get()) - { - rsx->state += cpu_flag::dbg_global_pause; - } - // Always Enable display sleep, not only if it was prevented. enable_display_sleep(); @@ -2167,6 +2205,17 @@ void Emulator::Resume() sys_log.success("Emulation has been resumed!"); + BlockingCallFromMainThread([this]() + { + for (auto& ref : m_pause_msgs_refs) + { + // Delete the message queued on pause + *ref = 0; + } + + m_pause_msgs_refs.clear(); + }); + if (g_cfg.misc.prevent_display_sleep) { disable_display_sleep(); diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 4b7921e193..5ab1412dc1 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -144,6 +144,8 @@ class Emulator final bool m_state_inspection_savestate = false; + std::vector>> m_pause_msgs_refs; + std::vector> deferred_deserialization; void ExecDeserializationRemnants() diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 7bc2c33446..0e41713dc2 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -146,4 +146,7 @@ enum class localized_string_id RPCN_ERROR_INVALID_PROTOCOL_VERSION, RPCN_ERROR_UNKNOWN, RPCN_SUCCESS_LOGGED_ON, + + EMULATION_PAUSED_RESUME_WITH_START, + EMULATION_RESUMING, }; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index b3623872eb..7d64fc7bd3 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -315,7 +315,7 @@ struct cfg_root : cfg::node { node_savestate(cfg::node* _this) : cfg::node(_this, "Savestate") {} - cfg::_bool start_paused{ this, "Start Paused" }; // Pause on first frame + cfg::_bool start_paused{ this, "Start Paused 2", true }; // Pause on first frame cfg::_bool suspend_emu{ this, "Suspend Emulation Savestate Mode", false }; // Close emulation when saving, delete save after loading cfg::_bool state_inspection_mode{ this, "Inspection Mode Savestates" }; // Save memory stored in executable files, thus allowing to view state without any files (for debugging) cfg::_bool save_disc_game_data{ this, "Save Disc Game Data", false }; diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index e1b2f53eaf..b6ac42ab30 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -16,10 +16,12 @@ #include "Emu/Io/pad_config.h" #include "Emu/System.h" #include "Emu/system_config.h" +#include "Emu/RSX/Overlays/overlay_message.h" #include "Utilities/Thread.h" #include "util/atomic.hpp" LOG_CHANNEL(input_log, "Input"); +LOG_CHANNEL(sys_log, "SYS"); extern bool is_input_allowed(); @@ -33,6 +35,11 @@ namespace pad atomic_t g_enabled{true}; } +namespace rsx +{ + void set_native_ui_flip(); +} + struct pad_setting { u32 port_status = 0; @@ -272,15 +279,22 @@ void pad_thread::operator()() { while (thread_ctrl::state() != thread_state::aborting) { - if (!pad::g_enabled || Emu.IsPaused() || !is_input_allowed()) + if (!pad::g_enabled || !is_input_allowed()) { - thread_ctrl::wait_for(10'000); + thread_ctrl::wait_for(30'000); continue; } handler->process(); - thread_ctrl::wait_for(g_cfg.io.pad_sleep); + u64 pad_sleep = g_cfg.io.pad_sleep; + + if (Emu.IsPaused()) + { + pad_sleep = std::max(pad_sleep, 30'000); + } + + thread_ctrl::wait_for(pad_sleep); } })); } @@ -290,9 +304,11 @@ void pad_thread::operator()() while (thread_ctrl::state() != thread_state::aborting) { - if (!pad::g_enabled || Emu.IsPaused() || !is_input_allowed()) + if (!pad::g_enabled || !is_input_allowed()) { - thread_ctrl::wait_for(10000); + m_resume_emulation_flag = false; + m_mask_start_press_to_unpause = 0; + thread_ctrl::wait_for(30'000); continue; } @@ -353,7 +369,7 @@ void pad_thread::operator()() if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) continue; - for (auto& button : pad->m_buttons) + for (const auto& button : pad->m_buttons) { if (button.m_pressed && ( button.m_outKeyCode == CELL_PAD_CTRL_CROSS || @@ -375,7 +391,81 @@ void pad_thread::operator()() } } - thread_ctrl::wait_for(g_cfg.io.pad_sleep); + if (m_resume_emulation_flag) + { + m_resume_emulation_flag = false; + + Emu.BlockingCallFromMainThread([]() + { + Emu.Resume(); + }); + } + + u64 pad_sleep = g_cfg.io.pad_sleep; + + if (Emu.IsPaused()) + { + pad_sleep = std::max(pad_sleep, 30'000); + + u64 timestamp = get_system_time(); + u32 pressed_mask = 0; + + for (usz i = 0; i < m_pads.size(); i++) + { + const auto& pad = m_pads[i]; + + if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) + continue; + + for (const auto& button : pad->m_buttons) + { + if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_START && button.m_pressed) + { + pressed_mask |= 1u << i; + break; + } + } + } + + m_mask_start_press_to_unpause &= pressed_mask; + + if (!pressed_mask || timestamp - m_track_start_press_begin_timestamp >= 1'000'000) + { + m_track_start_press_begin_timestamp = timestamp; + + if (std::exchange(m_mask_start_press_to_unpause, u32{umax})) + { + m_mask_start_press_to_unpause = 0; + m_track_start_press_begin_timestamp = 0; + + sys_log.success("Unpausing emulation using the START button in a few seconds..."); + rsx::overlays::queue_message(localized_string_id::EMULATION_RESUMING, 2'000'000); + + m_resume_emulation_flag = true; + + for (u32 i = 0; i < 40; i++) + { + if (!Emu.IsPaused()) + { + // Abort if emulation has been resumed by other means + m_resume_emulation_flag = false; + break; + } + + thread_ctrl::wait_for(50'000); + rsx::set_native_ui_flip(); + } + } + } + } + else + { + // Reset unpause control if caught a state of unpaused emulation + m_mask_start_press_to_unpause = 0; + m_track_start_press_begin_timestamp = 0; + } + + thread_ctrl::wait_for(pad_sleep); } stop_threads(); diff --git a/rpcs3/Input/pad_thread.h b/rpcs3/Input/pad_thread.h index 4e822ea50e..35a3511c16 100644 --- a/rpcs3/Input/pad_thread.h +++ b/rpcs3/Input/pad_thread.h @@ -52,6 +52,11 @@ protected: std::array, CELL_PAD_MAX_PORT_NUM> m_pads; u32 num_ldd_pad = 0; + +private: + u32 m_mask_start_press_to_unpause = 0; + u64 m_track_start_press_begin_timestamp = 0; + bool m_resume_emulation_flag = false; }; namespace pad diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index f2fc286f71..e9f784d725 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -167,6 +167,8 @@ private: case localized_string_id::RPCN_ERROR_INVALID_PROTOCOL_VERSION: return tr("RPCN Misc Error: Protocol Version Error (outdated RPCS3?)"); case localized_string_id::RPCN_ERROR_UNKNOWN: return tr("RPCN: Unknown Error"); case localized_string_id::RPCN_SUCCESS_LOGGED_ON: return tr("Successfully logged on RPCN!"); + case localized_string_id::EMULATION_PAUSED_RESUME_WITH_START: return tr("Press and hold the START button to resume"); + case localized_string_id::EMULATION_RESUMING: return tr("Resuming...!"); case localized_string_id::INVALID: return tr("Invalid"); default: return tr("Unknown"); }