mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-04-22 04:24:44 +00:00
Devtools: Pause system
This commit is contained in:
parent
009f956d8d
commit
983cafb5f2
11 changed files with 257 additions and 61 deletions
|
@ -458,6 +458,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||
src/core/platform.h
|
||||
src/core/signals.cpp
|
||||
src/core/signals.h
|
||||
src/core/system.cpp
|
||||
src/core/system.h
|
||||
src/core/tls.cpp
|
||||
src/core/tls.h
|
||||
src/core/virtual_memory.cpp
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "core/libraries/kernel/threads/threads.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/system.h"
|
||||
#include "core/tls.h"
|
||||
#ifdef _WIN64
|
||||
#include <windows.h>
|
||||
|
@ -988,6 +989,7 @@ static void cleanup_thread(void* arg) {
|
|||
}
|
||||
Core::SetTcbBase(nullptr);
|
||||
thread->is_almost_done = true;
|
||||
Common::Singleton<SystemState>::Instance()->RemoveCurrentThreadFromGuestList();
|
||||
}
|
||||
|
||||
static void* run_thread(void* arg) {
|
||||
|
@ -998,6 +1000,7 @@ static void* run_thread(void* arg) {
|
|||
g_pthread_self = thread;
|
||||
pthread_cleanup_push(cleanup_thread, thread);
|
||||
thread->is_started = true;
|
||||
Common::Singleton<SystemState>::Instance()->AddCurrentThreadToGuestList();
|
||||
ret = linker->ExecuteGuest(thread->entry, thread->arg);
|
||||
pthread_cleanup_pop(1);
|
||||
return ret;
|
||||
|
|
|
@ -247,6 +247,17 @@ int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2,
|
|||
return SCE_OK;
|
||||
}
|
||||
|
||||
namespace Dev {
|
||||
u64& GetInitialPtc() {
|
||||
return initial_ptc;
|
||||
}
|
||||
|
||||
Common::NativeClock* GetClock() {
|
||||
return clock.get();
|
||||
}
|
||||
|
||||
} // namespace Dev
|
||||
|
||||
void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
||||
clock = std::make_unique<Common::NativeClock>();
|
||||
initial_ptc = clock->GetUptime();
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class NativeClock;
|
||||
}
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
@ -47,6 +51,12 @@ constexpr int ORBIS_CLOCK_EXT_DEBUG_NETWORK = 17;
|
|||
constexpr int ORBIS_CLOCK_EXT_AD_NETWORK = 18;
|
||||
constexpr int ORBIS_CLOCK_EXT_RAW_NETWORK = 19;
|
||||
|
||||
namespace Dev {
|
||||
u64& GetInitialPtc();
|
||||
|
||||
Common::NativeClock* GetClock();
|
||||
} // namespace Dev
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetTscFrequency();
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTime();
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
#include "common/assert.h"
|
||||
|
@ -281,10 +282,15 @@ void MsgDialogUi::Draw() {
|
|||
first_render = false;
|
||||
}
|
||||
|
||||
DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) {
|
||||
DialogResult result{};
|
||||
Status status = Status::RUNNING;
|
||||
MsgDialogUi dialog(&state, &status, &result);
|
||||
DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState p_state, bool block) {
|
||||
static DialogResult result{};
|
||||
static Status status;
|
||||
static MsgDialogUi dialog;
|
||||
static MsgDialogState state;
|
||||
dialog = MsgDialogUi{};
|
||||
status = Status::RUNNING;
|
||||
state = std::move(p_state);
|
||||
dialog = MsgDialogUi(&state, &status, &result);
|
||||
if (block) {
|
||||
while (status == Status::RUNNING) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "core/libraries/kernel/time_management.h"
|
||||
#include "core/libraries/videoout/driver.h"
|
||||
#include "core/platform.h"
|
||||
#include "core/system.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
|
||||
extern std::unique_ptr<Vulkan::RendererVulkan> renderer;
|
||||
|
@ -265,6 +266,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||
|
||||
Common::AccurateTimer timer{vblank_period};
|
||||
|
||||
auto systemState = Common::Singleton<SystemState>::Instance();
|
||||
|
||||
const auto receive_request = [this] -> Request {
|
||||
std::scoped_lock lk{mutex};
|
||||
if (!requests.empty()) {
|
||||
|
@ -284,7 +287,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
||||
const auto request = receive_request();
|
||||
if (!request) {
|
||||
if (!main_port.is_open) {
|
||||
if (!main_port.is_open || systemState->IsGuestThreadsPaused()) {
|
||||
DrawBlankFrame();
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "core/memory.h"
|
||||
#include "core/tls.h"
|
||||
#include "core/virtual_memory.h"
|
||||
#include "system.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
@ -88,6 +89,7 @@ void Linker::Execute() {
|
|||
|
||||
// Init primary thread.
|
||||
Common::SetCurrentThreadName("GAME_MainThread");
|
||||
Common::Singleton<SystemState>::Instance()->AddCurrentThreadToGuestList();
|
||||
Libraries::Kernel::pthreadInitSelfMainThread();
|
||||
EnsureThreadInitialized(true);
|
||||
|
||||
|
|
|
@ -83,6 +83,12 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
|
|||
fmt::ptr(code_address), DisassembleInstruction(code_address));
|
||||
}
|
||||
break;
|
||||
case SIGUSR1: { // Sleep thread until signal is received
|
||||
sigset_t sigset;
|
||||
sigemptyset(&sigset);
|
||||
sigaddset(&sigset, SIGUSR1);
|
||||
sigwait(&sigset, &sig);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -105,6 +111,8 @@ SignalDispatch::SignalDispatch() {
|
|||
"Failed to register access violation signal handler.");
|
||||
ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0,
|
||||
"Failed to register illegal instruction signal handler.");
|
||||
ASSERT_MSG(sigaction(SIGUSR1, &action, nullptr) == 0,
|
||||
"Failed to register sleep signal handler.");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
71
src/core/system.cpp
Normal file
71
src/core/system.cpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/native_clock.h"
|
||||
#include "libraries/kernel/event_queues.h"
|
||||
#include "libraries/kernel/time_management.h"
|
||||
#include "libraries/system/msgdialog.h"
|
||||
#include "system.h"
|
||||
|
||||
void SystemState::AddCurrentThreadToGuestList() {
|
||||
std::lock_guard lock{guest_threads_mutex};
|
||||
ThreadID id;
|
||||
#ifdef _WIN32
|
||||
id = GetCurrentThreadId();
|
||||
#else
|
||||
id = pthread_self();
|
||||
#endif
|
||||
guest_threads.push_back(id);
|
||||
}
|
||||
|
||||
void SystemState::RemoveCurrentThreadFromGuestList() {
|
||||
std::lock_guard lock{guest_threads_mutex};
|
||||
ThreadID id;
|
||||
#ifdef _WIN32
|
||||
id = GetCurrentThreadId();
|
||||
#else
|
||||
id = pthread_self();
|
||||
#endif
|
||||
std::erase_if(guest_threads, [&](const ThreadID& v) { return v == id; });
|
||||
}
|
||||
|
||||
void SystemState::PauseGuestThreads() {
|
||||
using namespace Libraries::MsgDialog;
|
||||
std::lock_guard lock{guest_threads_mutex};
|
||||
if (is_guest_threads_paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& id : guest_threads) {
|
||||
#ifdef _WIN32
|
||||
const HANDLE hd = OpenThread(THREAD_SUSPEND_RESUME, FALSE, id);
|
||||
SuspendThread(hd);
|
||||
CloseHandle(hd);
|
||||
#else
|
||||
pthread_kill(id, SIGUSR1);
|
||||
#endif
|
||||
}
|
||||
pause_time = Libraries::Kernel::Dev::GetClock()->GetUptime();
|
||||
is_guest_threads_paused = true;
|
||||
}
|
||||
|
||||
void SystemState::ResumeGuestThreads() {
|
||||
std::lock_guard lock{guest_threads_mutex};
|
||||
if (!is_guest_threads_paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 delta_time = Libraries::Kernel::Dev::GetClock()->GetUptime() - pause_time;
|
||||
Libraries::Kernel::Dev::GetInitialPtc() += delta_time;
|
||||
for (const auto& id : guest_threads) {
|
||||
#ifdef _WIN32
|
||||
const HANDLE hd = OpenThread(THREAD_SUSPEND_RESUME, FALSE, id);
|
||||
ResumeThread(hd);
|
||||
CloseHandle(hd);
|
||||
#else
|
||||
pthread_kill(id, SIGUSR1);
|
||||
#endif
|
||||
}
|
||||
is_guest_threads_paused = false;
|
||||
}
|
39
src/core/system.h
Normal file
39
src/core/system.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
using ThreadID = DWORD;
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
using ThreadID = pthread_t;
|
||||
#endif
|
||||
|
||||
class SystemState {
|
||||
std::mutex guest_threads_mutex{};
|
||||
std::vector<ThreadID> guest_threads{};
|
||||
bool is_guest_threads_paused = false;
|
||||
u64 pause_time{};
|
||||
|
||||
public:
|
||||
void AddCurrentThreadToGuestList();
|
||||
|
||||
void RemoveCurrentThreadFromGuestList();
|
||||
|
||||
void PauseGuestThreads();
|
||||
|
||||
void ResumeGuestThreads();
|
||||
|
||||
inline bool IsGuestThreadsPaused() const {
|
||||
return is_guest_threads_paused;
|
||||
}
|
||||
};
|
|
@ -4,7 +4,9 @@
|
|||
#include <imgui.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/system.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "video_info.h"
|
||||
|
||||
|
@ -18,7 +20,11 @@ struct FrameInfo {
|
|||
static bool show = false;
|
||||
static bool show_advanced = false;
|
||||
|
||||
static auto sysState = Common::Singleton<SystemState>::Instance();
|
||||
static u32 current_frame = 0;
|
||||
|
||||
namespace FrameGraph {
|
||||
|
||||
constexpr float TARGET_FPS = 60.0f;
|
||||
constexpr u32 FRAME_BUFFER_SIZE = 1024;
|
||||
constexpr float BAR_WIDTH_MULT = 1.4f;
|
||||
|
@ -27,78 +33,111 @@ constexpr float FRAME_GRAPH_PADDING_Y = 3.0f;
|
|||
static std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list;
|
||||
static float frame_graph_height = 50.0f;
|
||||
|
||||
static void Draw() {
|
||||
const auto& ctx = *GImGui;
|
||||
const auto& window = *ctx.CurrentWindow;
|
||||
auto& draw_list = *window.DrawList;
|
||||
|
||||
const float full_width = GetContentRegionAvail().x;
|
||||
// Frame graph - inspired by
|
||||
// https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times
|
||||
auto pos = GetCursorScreenPos();
|
||||
const ImVec2 size{full_width, frame_graph_height + FRAME_GRAPH_PADDING_Y * 2.0f};
|
||||
ItemSize(size);
|
||||
if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv());
|
||||
float cur_pos_x = pos.x + full_width;
|
||||
pos.y += FRAME_GRAPH_PADDING_Y;
|
||||
const float final_pos_y = pos.y + frame_graph_height;
|
||||
|
||||
draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y},
|
||||
{pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y},
|
||||
IM_COL32(0x33, 0x33, 0x33, 0xFF));
|
||||
draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true);
|
||||
for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) {
|
||||
const auto& frame_info = frame_list[(current_frame - i) % FRAME_BUFFER_SIZE];
|
||||
const float dt_factor = target_dt / frame_info.delta;
|
||||
|
||||
const float width = std::ceil(BAR_WIDTH_MULT / dt_factor);
|
||||
const float height =
|
||||
std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * frame_graph_height;
|
||||
|
||||
ImU32 color;
|
||||
if (dt_factor >= 0.95f) { // BLUE
|
||||
color = IM_COL32(0x33, 0x33, 0xFF, 0xFF);
|
||||
} else if (dt_factor >= 0.5f) { // GREEN <> YELLOW
|
||||
float t = 1.0f - (dt_factor - 0.5f) * 2.0f;
|
||||
int r = (int)(0xFF * t);
|
||||
color = IM_COL32(r, 0xFF, 0, 0xFF);
|
||||
} else { // YELLOW <> RED
|
||||
float t = dt_factor * 2.0f;
|
||||
int g = (int)(0xFF * t);
|
||||
color = IM_COL32(0xFF, g, 0, 0xFF);
|
||||
}
|
||||
draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height}, {cur_pos_x, final_pos_y},
|
||||
color);
|
||||
cur_pos_x -= width;
|
||||
if (cur_pos_x < width) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
draw_list.PopClipRect();
|
||||
}
|
||||
|
||||
} // namespace FrameGraph
|
||||
|
||||
static void DrawSimple() {
|
||||
const auto io = GetIO();
|
||||
Text("FPS: %.1f (%.3f ms)", io.Framerate, 1000.0f / io.Framerate);
|
||||
}
|
||||
|
||||
static void DrawAdvanced() {
|
||||
const auto& ctx = *GetCurrentContext();
|
||||
const auto& ctx = *GImGui;
|
||||
const auto& io = ctx.IO;
|
||||
const auto& window = *ctx.CurrentWindow;
|
||||
auto& draw_list = *window.DrawList;
|
||||
|
||||
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
||||
auto isSystemPaused = sysState->IsGuestThreadsPaused();
|
||||
|
||||
static float deltaTime;
|
||||
static float frameRate;
|
||||
|
||||
if (!isSystemPaused) {
|
||||
deltaTime = io.DeltaTime * 1000.0f;
|
||||
frameRate = io.Framerate;
|
||||
}
|
||||
|
||||
Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate);
|
||||
|
||||
SeparatorText("Frame graph");
|
||||
const float full_width = GetContentRegionAvail().x;
|
||||
{ // Frame graph - inspired by
|
||||
// https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times
|
||||
auto pos = GetCursorScreenPos();
|
||||
const ImVec2 size{full_width, frame_graph_height + FRAME_GRAPH_PADDING_Y * 2.0f};
|
||||
ItemSize(size);
|
||||
if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv());
|
||||
float cur_pos_x = pos.x + full_width;
|
||||
pos.y += FRAME_GRAPH_PADDING_Y;
|
||||
const float final_pos_y = pos.y + frame_graph_height;
|
||||
|
||||
draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y},
|
||||
{pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y},
|
||||
IM_COL32(0x33, 0x33, 0x33, 0xFF));
|
||||
draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true);
|
||||
for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) {
|
||||
const auto& frame_info = frame_list[(current_frame - i) % FRAME_BUFFER_SIZE];
|
||||
const float dt_factor = target_dt / frame_info.delta;
|
||||
|
||||
const float width = std::ceil(BAR_WIDTH_MULT / dt_factor);
|
||||
const float height =
|
||||
std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * frame_graph_height;
|
||||
|
||||
ImU32 color;
|
||||
if (dt_factor >= 0.95f) { // BLUE
|
||||
color = IM_COL32(0x33, 0x33, 0xFF, 0xFF);
|
||||
} else if (dt_factor >= 0.5f) { // GREEN <> YELLOW
|
||||
float t = 1.0f - (dt_factor - 0.5f) * 2.0f;
|
||||
int r = (int)(0xFF * t);
|
||||
color = IM_COL32(r, 0xFF, 0, 0xFF);
|
||||
} else { // YELLOW <> RED
|
||||
float t = dt_factor * 2.0f;
|
||||
int g = (int)(0xFF * t);
|
||||
color = IM_COL32(0xFF, g, 0, 0xFF);
|
||||
}
|
||||
draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height},
|
||||
{cur_pos_x, final_pos_y}, color);
|
||||
cur_pos_x -= width;
|
||||
if (cur_pos_x < width) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
draw_list.PopClipRect();
|
||||
FrameGraph::Draw();
|
||||
SeparatorText("System debug");
|
||||
BeginDisabled(isSystemPaused);
|
||||
if (Button("Pause")) {
|
||||
sysState->PauseGuestThreads();
|
||||
}
|
||||
EndDisabled();
|
||||
SameLine();
|
||||
BeginDisabled(!isSystemPaused);
|
||||
if (Button("Resume")) {
|
||||
sysState->ResumeGuestThreads();
|
||||
}
|
||||
EndDisabled();
|
||||
}
|
||||
|
||||
void Layers::VideoInfo::Draw() {
|
||||
const auto io = GetIO();
|
||||
|
||||
const FrameInfo frame_info{
|
||||
.num = ++current_frame,
|
||||
.delta = io.DeltaTime,
|
||||
};
|
||||
frame_list[current_frame % FRAME_BUFFER_SIZE] = frame_info;
|
||||
if (!sysState->IsGuestThreadsPaused()) {
|
||||
const FrameInfo frame_info{
|
||||
.num = ++current_frame,
|
||||
.delta = io.DeltaTime,
|
||||
};
|
||||
FrameGraph::frame_list[current_frame % FrameGraph::FRAME_BUFFER_SIZE] = frame_info;
|
||||
}
|
||||
|
||||
if (IsKeyPressed(ImGuiKey_F10, false)) {
|
||||
const bool changed_ctrl = io.KeyCtrl != show_advanced;
|
||||
|
@ -108,11 +147,13 @@ void Layers::VideoInfo::Draw() {
|
|||
|
||||
if (show) {
|
||||
if (show_advanced) {
|
||||
if (Begin("Video Debug Info", nullptr, ImGuiWindowFlags_NoDecoration)) {
|
||||
if (Begin("Video Debug Info", &show, 0)) {
|
||||
DrawAdvanced();
|
||||
}
|
||||
} else {
|
||||
if (Begin("Video Info", nullptr, ImGuiWindowFlags_NoDecoration)) {
|
||||
if (Begin("Video Info", nullptr,
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
DrawSimple();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue