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 (!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;

View File

@ -7,9 +7,10 @@ namespace rsx
namespace overlays
{
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_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<atomic_t<u32>>);
template message_item::message_item(localized_string_id msg_id, u64, std::shared_ptr<atomic_t<u32>>);
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()

View File

@ -11,7 +11,7 @@ namespace rsx
{
public:
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);
u64 get_expiration() const;
@ -22,6 +22,7 @@ namespace rsx
animation_color_interpolate m_fade_animation;
u64 m_expiration_time = 0;
std::shared_ptr<atomic_t<u32>> m_refs;
bool m_processed = false;
usz m_cur_pos = umax;
};
@ -33,7 +34,7 @@ namespace rsx
compiled_resource get_compiled() override;
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);
@ -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 <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>())
{
@ -68,7 +69,7 @@ namespace rsx
msg_overlay = std::make_shared<rsx::overlays::message>();
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/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<u32, u32> 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;
}
}

View File

@ -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;

View File

@ -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{};

View File

@ -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<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)
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();
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;
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<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.
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();

View File

@ -144,6 +144,8 @@ class Emulator final
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;
void ExecDeserializationRemnants()

View File

@ -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,
};

View File

@ -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 };

View File

@ -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<bool> 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<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)
{
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<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();

View File

@ -52,6 +52,11 @@ protected:
std::array<std::shared_ptr<Pad>, 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

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_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");
}