Savestates/rsx/IO: Resume emulation on long START press, enable "Start Paused" by defaut (#12881)

* Savestates: Enable "Start Paused" by default
* Emu/rsx/IO: Resume emulation on long START press
* rsx: fix missing graphics with savestates' "Start Paused" setting
* rsx/overlays: Add simple reference counting for messages to hide them manually
* Move some code in Emulator::Pause() so thread pausing is the first thing done by this function
This commit is contained in:
Elad Ashkenazi 2022-10-29 20:53:00 +03:00 committed by GitHub
parent c8620070b9
commit c214f45e14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 206 additions and 42 deletions

View File

@ -1028,7 +1028,7 @@ void GLGSRender::do_local_task(rsx::FIFO_state state)
if (m_overlay_manager) 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{}; rsx::display_flip_info_t info{};
info.buffer = current_display_buffer; info.buffer = current_display_buffer;

View File

@ -7,9 +7,10 @@ namespace rsx
namespace overlays namespace overlays
{ {
template <typename T> template <typename T>
message_item::message_item(T msg_id) message_item::message_item(T msg_id, u64 expiration, std::shared_ptr<atomic_t<u32>> 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_font("Arial", 16);
m_text.set_text(msg_id); m_text.set_text(msg_id);
@ -21,12 +22,13 @@ namespace rsx
m_fade_animation.duration = 2.f; m_fade_animation.duration = 2.f;
m_fade_animation.active = true; m_fade_animation.active = true;
} }
template message_item::message_item(std::string msg_id); template message_item::message_item(std::string msg_id, u64, std::shared_ptr<atomic_t<u32>>);
template message_item::message_item(localized_string_id msg_id); template message_item::message_item(localized_string_id msg_id, u64, std::shared_ptr<atomic_t<u32>>);
u64 message_item::get_expiration() const 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() compiled_resource message_item::get_compiled()

View File

@ -11,7 +11,7 @@ namespace rsx
{ {
public: public:
template <typename T> template <typename T>
message_item(T msg_id); message_item(T msg_id, u64 expiration, std::shared_ptr<atomic_t<u32>> refs);
void update(usz index, u64 time); void update(usz index, u64 time);
u64 get_expiration() const; u64 get_expiration() const;
@ -22,6 +22,7 @@ namespace rsx
animation_color_interpolate m_fade_animation; animation_color_interpolate m_fade_animation;
u64 m_expiration_time = 0; u64 m_expiration_time = 0;
std::shared_ptr<atomic_t<u32>> m_refs;
bool m_processed = false; bool m_processed = false;
usz m_cur_pos = umax; usz m_cur_pos = umax;
}; };
@ -33,7 +34,7 @@ namespace rsx
compiled_resource get_compiled() override; compiled_resource get_compiled() override;
template <typename T> template <typename T>
void queue_message(T msg_id) void queue_message(T msg_id, u64 expiration, std::shared_ptr<atomic_t<u32>> refs)
{ {
std::lock_guard lock(m_mutex_queue); std::lock_guard lock(m_mutex_queue);
@ -41,12 +42,12 @@ namespace rsx
{ {
for (auto id : msg_id) for (auto id : msg_id)
{ {
m_queue.emplace_back(id); m_queue.emplace_back(id, expiration, refs);
} }
} }
else else
{ {
m_queue.emplace_back(msg_id); m_queue.emplace_back(msg_id, expiration, std::move(refs));
} }
visible = true; visible = true;
@ -58,7 +59,7 @@ namespace rsx
}; };
template <typename T> template <typename T>
void queue_message(T msg_id) void queue_message(T msg_id, u64 expiration = 5'000'000, std::shared_ptr<atomic_t<u32>> refs = {})
{ {
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>()) if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{ {
@ -68,7 +69,7 @@ namespace rsx
msg_overlay = std::make_shared<rsx::overlays::message>(); msg_overlay = std::make_shared<rsx::overlays::message>();
msg_overlay = manager->add(msg_overlay); msg_overlay = manager->add(msg_overlay);
} }
msg_overlay->queue_message(msg_id); msg_overlay->queue_message(msg_id, expiration, std::move(refs));
} }
} }

View File

@ -18,6 +18,7 @@
#include "Emu/Cell/lv2/sys_time.h" #include "Emu/Cell/lv2/sys_time.h"
#include "Emu/Cell/Modules/cellGcmSys.h" #include "Emu/Cell/Modules/cellGcmSys.h"
#include "Overlays/overlay_perf_metrics.h" #include "Overlays/overlay_perf_metrics.h"
#include "Overlays/overlay_message.h"
#include "Program/GLSLCommon.h" #include "Program/GLSLCommon.h"
#include "Utilities/date_time.h" #include "Utilities/date_time.h"
#include "Utilities/StrUtil.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<u32, u32> interleaved_range_info::calculate_required_range(u32 first, u32 count) const std::pair<u32, u32> interleaved_range_info::calculate_required_range(u32 first, u32 count) const
{ {
if (single_vertex) if (single_vertex)
@ -542,7 +551,8 @@ namespace rsx
if (g_cfg.savestate.start_paused) 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(); wait_pause();
} }
on_semaphore_acquire_wait();
if ((state & (cpu_flag::dbg_global_pause + cpu_flag::exit)) == cpu_flag::dbg_global_pause) 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. // 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); thread_ctrl::wait_on(state, old, 16000);
} }
else else
{ {
on_semaphore_acquire_wait();
std::this_thread::yield(); std::this_thread::yield();
} }
} }
@ -2670,10 +2680,9 @@ namespace rsx
{ {
performance_counters.sampled_frames++; performance_counters.sampled_frames++;
if (m_pause_on_first_flip) if (m_pause_after_x_flips && m_pause_after_x_flips-- == 1)
{ {
Emu.Pause(); Emu.Pause();
m_pause_on_first_flip = false;
} }
} }

View File

@ -181,7 +181,8 @@ namespace rsx
empty = 1, // PUT == GET empty = 1, // PUT == GET
spinning = 2, // Puller continuously jumps to self addr (synchronization technique) spinning = 2, // Puller continuously jumps to self addr (synchronization technique)
nop = 3, // Puller is processing a NOP command 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 enum FIFO_hint : u8
@ -540,8 +541,8 @@ namespace rsx
rsx::profiling_timer m_profiler; rsx::profiling_timer m_profiler;
frame_statistics_t m_frame_stats; frame_statistics_t m_frame_stats;
// Savestates vrelated // Savestates related
bool m_pause_on_first_flip = false; u32 m_pause_after_x_flips = 0;
public: public:
RsxDmaControl* ctrl = nullptr; RsxDmaControl* ctrl = nullptr;

View File

@ -1710,7 +1710,7 @@ void VKGSRender::do_local_task(rsx::FIFO_state state)
if (m_overlay_manager) 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); flush_command_queue(true);
rsx::display_flip_info_t info{}; rsx::display_flip_info_t info{};

View File

@ -26,6 +26,7 @@
#include "Emu/title.h" #include "Emu/title.h"
#include "Emu/IdManager.h" #include "Emu/IdManager.h"
#include "Emu/RSX/Capture/rsx_replay.h" #include "Emu/RSX/Capture/rsx_replay.h"
#include "Emu/RSX/Overlays/overlay_message.h"
#include "Loader/PSF.h" #include "Loader/PSF.h"
#include "Loader/TAR.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)); extern void parse_hashtable(bool(*cb)(u64 id, u32 refs, u64 ptr, u32 max_coll));
} }
namespace rsx
{
void set_native_ui_flip();
}
template<> template<>
void fmt_class_string<game_boot_result>::format(std::string& out, u64 arg) void fmt_class_string<game_boot_result>::format(std::string& out, u64 arg)
{ {
@ -2050,8 +2056,53 @@ bool Emulator::Pause(bool freeze_emulation)
// Signal profilers to print results (if enabled) // Signal profilers to print results (if enabled)
cpu_thread::flush_profilers(); cpu_thread::flush_profilers();
auto on_select = [](u32, cpu_thread& cpu)
{
cpu.state += cpu_flag::dbg_global_pause;
};
idm::select<named_thread<ppu_thread>>(on_select);
idm::select<named_thread<spu_thread>>(on_select);
if (auto rsx = g_fxo->try_get<rsx::thread>())
{
rsx->state += cpu_flag::dbg_global_pause;
}
GetCallbacks().on_pause(); GetCallbacks().on_pause();
BlockingCallFromMainThread([this]()
{
if (IsStopped())
{
return;
}
auto msg_ref = std::make_shared<atomic_t<u32>>(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<named_thread<decltype(refresh_l)>> m_thread;
};
g_fxo->get<thread_t>().m_thread.reset();
g_fxo->get<thread_t>().m_thread = std::make_unique<named_thread<decltype(refresh_l)>>("Pause Message Thread"sv, std::move(refresh_l));
});
static atomic_t<u32> pause_mark = 0; static atomic_t<u32> pause_mark = 0;
if (freeze_emulation) if (freeze_emulation)
@ -2069,19 +2120,6 @@ bool Emulator::Pause(bool freeze_emulation)
sys_log.error("Emulator::Pause() error: concurrent access"); sys_log.error("Emulator::Pause() error: concurrent access");
} }
auto on_select = [](u32, cpu_thread& cpu)
{
cpu.state += cpu_flag::dbg_global_pause;
};
idm::select<named_thread<ppu_thread>>(on_select);
idm::select<named_thread<spu_thread>>(on_select);
if (auto rsx = g_fxo->try_get<rsx::thread>())
{
rsx->state += cpu_flag::dbg_global_pause;
}
// Always Enable display sleep, not only if it was prevented. // Always Enable display sleep, not only if it was prevented.
enable_display_sleep(); enable_display_sleep();
@ -2167,6 +2205,17 @@ void Emulator::Resume()
sys_log.success("Emulation has been resumed!"); 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) if (g_cfg.misc.prevent_display_sleep)
{ {
disable_display_sleep(); disable_display_sleep();

View File

@ -144,6 +144,8 @@ class Emulator final
bool m_state_inspection_savestate = false; bool m_state_inspection_savestate = false;
std::vector<std::shared_ptr<atomic_t<u32>>> m_pause_msgs_refs;
std::vector<std::function<void()>> deferred_deserialization; std::vector<std::function<void()>> deferred_deserialization;
void ExecDeserializationRemnants() void ExecDeserializationRemnants()

View File

@ -146,4 +146,7 @@ enum class localized_string_id
RPCN_ERROR_INVALID_PROTOCOL_VERSION, RPCN_ERROR_INVALID_PROTOCOL_VERSION,
RPCN_ERROR_UNKNOWN, RPCN_ERROR_UNKNOWN,
RPCN_SUCCESS_LOGGED_ON, RPCN_SUCCESS_LOGGED_ON,
EMULATION_PAUSED_RESUME_WITH_START,
EMULATION_RESUMING,
}; };

View File

@ -315,7 +315,7 @@ struct cfg_root : cfg::node
{ {
node_savestate(cfg::node* _this) : cfg::node(_this, "Savestate") {} 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 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 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 }; cfg::_bool save_disc_game_data{ this, "Save Disc Game Data", false };

View File

@ -16,10 +16,12 @@
#include "Emu/Io/pad_config.h" #include "Emu/Io/pad_config.h"
#include "Emu/System.h" #include "Emu/System.h"
#include "Emu/system_config.h" #include "Emu/system_config.h"
#include "Emu/RSX/Overlays/overlay_message.h"
#include "Utilities/Thread.h" #include "Utilities/Thread.h"
#include "util/atomic.hpp" #include "util/atomic.hpp"
LOG_CHANNEL(input_log, "Input"); LOG_CHANNEL(input_log, "Input");
LOG_CHANNEL(sys_log, "SYS");
extern bool is_input_allowed(); extern bool is_input_allowed();
@ -33,6 +35,11 @@ namespace pad
atomic_t<bool> g_enabled{true}; atomic_t<bool> g_enabled{true};
} }
namespace rsx
{
void set_native_ui_flip();
}
struct pad_setting struct pad_setting
{ {
u32 port_status = 0; u32 port_status = 0;
@ -272,15 +279,22 @@ void pad_thread::operator()()
{ {
while (thread_ctrl::state() != thread_state::aborting) 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; continue;
} }
handler->process(); 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<u64>(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) 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; continue;
} }
@ -353,7 +369,7 @@ void pad_thread::operator()()
if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
continue; continue;
for (auto& button : pad->m_buttons) for (const auto& button : pad->m_buttons)
{ {
if (button.m_pressed && ( if (button.m_pressed && (
button.m_outKeyCode == CELL_PAD_CTRL_CROSS || 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<u64>(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(); stop_threads();

View File

@ -52,6 +52,11 @@ protected:
std::array<std::shared_ptr<Pad>, CELL_PAD_MAX_PORT_NUM> m_pads; std::array<std::shared_ptr<Pad>, CELL_PAD_MAX_PORT_NUM> m_pads;
u32 num_ldd_pad = 0; 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 namespace pad

View File

@ -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_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_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::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"); case localized_string_id::INVALID: return tr("Invalid");
default: return tr("Unknown"); default: return tr("Unknown");
} }