diff --git a/CMakeLists.txt b/CMakeLists.txt index 598f59e11..45e92b6e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index e6b9a5c13..e336b7ba7 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.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 @@ -988,6 +989,7 @@ static void cleanup_thread(void* arg) { } Core::SetTcbBase(nullptr); thread->is_almost_done = true; + Common::Singleton::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::Instance()->AddCurrentThreadToGuestList(); ret = linker->ExecuteGuest(thread->entry, thread->arg); pthread_cleanup_pop(1); return ret; diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 5e5e0ef27..5fa26b789 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -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(); initial_ptc = clock->GetUptime(); diff --git a/src/core/libraries/kernel/time_management.h b/src/core/libraries/kernel/time_management.h index a28e6e558..f2216f3d3 100644 --- a/src/core/libraries/kernel/time_management.h +++ b/src/core/libraries/kernel/time_management.h @@ -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(); diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index 15d6f4dbd..ae1dced12 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #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)); diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 940a8c7d3..9b2280bf4 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -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 renderer; @@ -265,6 +266,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) { Common::AccurateTimer timer{vblank_period}; + auto systemState = Common::Singleton::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 { diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 9f4ae0627..aa561c91b 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -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::Instance()->AddCurrentThreadToGuestList(); Libraries::Kernel::pthreadInitSelfMainThread(); EnsureThreadInitialized(true); diff --git a/src/core/signals.cpp b/src/core/signals.cpp index 87f56c85a..8faf794ed 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -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 } diff --git a/src/core/system.cpp b/src/core/system.cpp new file mode 100644 index 000000000..d36bd2af4 --- /dev/null +++ b/src/core/system.cpp @@ -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; +} diff --git a/src/core/system.h b/src/core/system.h new file mode 100644 index 000000000..ff0dc22d7 --- /dev/null +++ b/src/core/system.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +using ThreadID = DWORD; +#else +#include +#include +using ThreadID = pthread_t; +#endif + +class SystemState { + std::mutex guest_threads_mutex{}; + std::vector 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; + } +}; diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp index 6707af9fd..ff52da5ab 100644 --- a/src/imgui/layer/video_info.cpp +++ b/src/imgui/layer/video_info.cpp @@ -4,7 +4,9 @@ #include #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::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 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(); } }