diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ee052d70a..07a2740fc 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -102,6 +102,8 @@ add_library(core pad.h pcdrv.cpp pcdrv.h + performance_counters.cpp + performance_counters.h playstation_mouse.cpp playstation_mouse.h psf_loader.cpp diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 1aa26de27..8b12fec78 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -79,6 +79,7 @@ Create + @@ -160,6 +161,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 38904fbac..b0d8a5369 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -70,6 +70,7 @@ + @@ -146,6 +147,7 @@ + diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index c5a12b650..405a6ce20 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -8,6 +8,7 @@ #include "gpu_sw_rasterizer.h" #include "host.h" #include "interrupt_controller.h" +#include "performance_counters.h" #include "settings.h" #include "system.h" #include "timers.h" @@ -146,10 +147,10 @@ void GPU::UpdateSettings(const Settings& old_settings) if (g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode) DestroyDeinterlaceTextures(); - if (!CompileDisplayPipelines(g_settings.display_scaling != old_settings.display_scaling, - g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode, - g_settings.display_24bit_chroma_smoothing != - old_settings.display_24bit_chroma_smoothing, nullptr)) + if (!CompileDisplayPipelines( + g_settings.display_scaling != old_settings.display_scaling, + g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode, + g_settings.display_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing, nullptr)) { Panic("Failed to compile display pipeline on settings change."); } @@ -707,7 +708,7 @@ void GPU::UpdateCRTCConfig() cs.current_scanline %= cs.vertical_total; - System::SetThrottleFrequency(ComputeVerticalFrequency()); + System::SetVideoFrameRate(ComputeVerticalFrequency()); UpdateCRTCDisplayParameters(); UpdateCRTCTickEvent(); @@ -2915,8 +2916,9 @@ bool GPU::StartRecordingGPUDump(const char* path, u32 num_frames /* = 1 */) // +1 because we want to actually see the buffer swap... if (num_frames != 0) { - num_frames = std::max(num_frames, static_cast(static_cast(num_frames + 1) * - std::ceil(System::GetVPS() / System::GetFPS()))); + num_frames = + std::max(num_frames, static_cast(static_cast(num_frames + 1) * + std::ceil(PerformanceCounters::GetVPS() / PerformanceCounters::GetFPS()))); } // ensure vram is up to date diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 7c15b7ad8..a366751c9 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -11,6 +11,7 @@ #include "gpu.h" #include "host.h" #include "mdec.h" +#include "performance_counters.h" #include "settings.h" #include "spu.h" #include "system.h" @@ -346,9 +347,9 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float const System::State state = System::GetState(); if (state == System::State::Running) { - const float speed = System::GetEmulationSpeed(); + const float speed = PerformanceCounters::GetEmulationSpeed(); if (g_settings.display_show_fps) - text.append_format("G: {:.2f} | V: {:.2f}", System::GetFPS(), System::GetVPS()); + text.append_format("G: {:.2f} | V: {:.2f}", PerformanceCounters::GetFPS(), PerformanceCounters::GetVPS()); if (g_settings.display_show_speed) { text.append_format("{}{}%", text.empty() ? "" : " | ", static_cast(std::round(speed))); @@ -400,8 +401,8 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float if (g_settings.display_show_cpu_usage) { - text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", System::GetMinimumFrameTime(), System::GetAverageFrameTime(), - System::GetMaximumFrameTime()); + text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", PerformanceCounters::GetMinimumFrameTime(), + PerformanceCounters::GetAverageFrameTime(), PerformanceCounters::GetMaximumFrameTime()); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); if (g_settings.cpu_overclock_active || CPU::g_state.using_interpreter || @@ -450,13 +451,15 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float { text.assign("CPU: "); } - FormatProcessorStat(text, System::GetCPUThreadUsage(), System::GetCPUThreadAverageTime()); + FormatProcessorStat(text, PerformanceCounters::GetCPUThreadUsage(), + PerformanceCounters::GetCPUThreadAverageTime()); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); if (g_gpu->GetSWThread()) { text.assign("SW: "); - FormatProcessorStat(text, System::GetSWThreadUsage(), System::GetSWThreadAverageTime()); + FormatProcessorStat(text, PerformanceCounters::GetSWThreadUsage(), + PerformanceCounters::GetSWThreadAverageTime()); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); } @@ -473,7 +476,7 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float if (g_settings.display_show_gpu_usage && g_gpu_device->IsGPUTimingEnabled()) { text.assign("GPU: "); - FormatProcessorStat(text, System::GetGPUUsage(), System::GetGPUAverageTime()); + FormatProcessorStat(text, PerformanceCounters::GetGPUUsage(), PerformanceCounters::GetGPUAverageTime()); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); } @@ -645,7 +648,7 @@ void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float ma { ImGui::PushFont(fixed_font); - auto [min, max] = GetMinMax(System::GetFrameTimeHistory()); + auto [min, max] = GetMinMax(PerformanceCounters::GetFrameTimeHistory()); // add a little bit of space either side, so we're not constantly resizing if ((max - min) < 4.0f) @@ -659,10 +662,10 @@ void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float ma ImGui::PlotEx( ImGuiPlotType_Lines, "##frame_times", [](void*, int idx) -> float { - return System::GetFrameTimeHistory()[((System::GetFrameTimeHistoryPos() + idx) % - System::NUM_FRAME_TIME_SAMPLES)]; + return PerformanceCounters::GetFrameTimeHistory()[((PerformanceCounters::GetFrameTimeHistoryPos() + idx) % + PerformanceCounters::NUM_FRAME_TIME_SAMPLES)]; }, - nullptr, System::NUM_FRAME_TIME_SAMPLES, 0, nullptr, min, max, history_size); + nullptr, PerformanceCounters::NUM_FRAME_TIME_SAMPLES, 0, nullptr, min, max, history_size); ImDrawList* win_dl = ImGui::GetCurrentWindow()->DrawList; const ImVec2 wpos(ImGui::GetCurrentWindow()->Pos); diff --git a/src/core/performance_counters.cpp b/src/core/performance_counters.cpp new file mode 100644 index 000000000..44059c886 --- /dev/null +++ b/src/core/performance_counters.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "performance_counters.h" +#include "gpu.h" +#include "system.h" + +#include "util/media_capture.h" + +#include "common/log.h" +#include "common/threading.h" +#include "common/timer.h" + +#include + +LOG_CHANNEL(PerfMon); + +namespace PerformanceCounters { + +namespace { + +struct State +{ + Common::Timer::Value last_update_time; + Common::Timer::Value last_frame_time; + + u32 last_frame_number; + u32 last_internal_frame_number; + u32 presents_since_last_update; + + float average_frame_time_accumulator; + float minimum_frame_time_accumulator; + float maximum_frame_time_accumulator; + + float vps; + float fps; + float speed; + + float minimum_frame_time; + float maximum_frame_time; + float average_frame_time; + + u64 last_cpu_time; + float cpu_thread_usage; + float cpu_thread_time; + + u64 last_sw_time; + float sw_thread_usage; + float sw_thread_time; + + float average_gpu_time; + float accumulated_gpu_time; + float gpu_usage; + + FrameTimeHistory frame_time_history; + u32 frame_time_history_pos; +}; + +} // namespace + +static constexpr const float PERFORMANCE_COUNTER_UPDATE_INTERVAL = 1.0f; + +ALIGN_TO_CACHE_LINE State s_state = {}; + +} // namespace PerformanceCounters + +float PerformanceCounters::GetFPS() +{ + return s_state.fps; +} + +float PerformanceCounters::GetVPS() +{ + return s_state.vps; +} + +float PerformanceCounters::GetEmulationSpeed() +{ + return s_state.speed; +} + +float PerformanceCounters::GetAverageFrameTime() +{ + return s_state.average_frame_time; +} + +float PerformanceCounters::GetMinimumFrameTime() +{ + return s_state.minimum_frame_time; +} + +float PerformanceCounters::GetMaximumFrameTime() +{ + return s_state.maximum_frame_time; +} + +float PerformanceCounters::GetCPUThreadUsage() +{ + return s_state.cpu_thread_usage; +} + +float PerformanceCounters::GetCPUThreadAverageTime() +{ + return s_state.cpu_thread_time; +} + +float PerformanceCounters::GetSWThreadUsage() +{ + return s_state.sw_thread_usage; +} + +float PerformanceCounters::GetSWThreadAverageTime() +{ + return s_state.sw_thread_time; +} + +float PerformanceCounters::GetGPUUsage() +{ + return s_state.gpu_usage; +} + +float PerformanceCounters::GetGPUAverageTime() +{ + return s_state.average_gpu_time; +} + +const PerformanceCounters::FrameTimeHistory& PerformanceCounters::GetFrameTimeHistory() +{ + return s_state.frame_time_history; +} + +u32 PerformanceCounters::GetFrameTimeHistoryPos() +{ + return s_state.frame_time_history_pos; +} + +void PerformanceCounters::Clear() +{ + s_state = {}; +} + +void PerformanceCounters::Reset() +{ + const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue(); + + s_state.last_frame_time = now_ticks; + s_state.last_update_time = now_ticks; + + s_state.last_frame_number = System::GetFrameNumber(); + s_state.last_internal_frame_number = System::GetInternalFrameNumber(); + s_state.last_cpu_time = System::Internal::GetCPUThreadHandle().GetCPUTime(); + if (const Threading::Thread* sw_thread = g_gpu->GetSWThread(); sw_thread) + s_state.last_sw_time = sw_thread->GetCPUTime(); + else + s_state.last_sw_time = 0; + + s_state.average_frame_time_accumulator = 0.0f; + s_state.minimum_frame_time_accumulator = 0.0f; + s_state.maximum_frame_time_accumulator = 0.0f; +} + +void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number) +{ + const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue(); + + const float frame_time = static_cast( + Common::Timer::ConvertValueToMilliseconds(now_ticks - std::exchange(s_state.last_frame_time, now_ticks))); + s_state.minimum_frame_time_accumulator = (s_state.minimum_frame_time_accumulator == 0.0f) ? + frame_time : + std::min(s_state.minimum_frame_time_accumulator, frame_time); + s_state.average_frame_time_accumulator += frame_time; + s_state.maximum_frame_time_accumulator = std::max(s_state.maximum_frame_time_accumulator, frame_time); + s_state.frame_time_history[s_state.frame_time_history_pos] = frame_time; + s_state.frame_time_history_pos = (s_state.frame_time_history_pos + 1) % NUM_FRAME_TIME_SAMPLES; + + // update fps counter + const Common::Timer::Value ticks_diff = now_ticks - s_state.last_update_time; + const float time = static_cast(Common::Timer::ConvertValueToSeconds(ticks_diff)); + if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL) + return; + + s_state.last_update_time = now_ticks; + + const u32 frames_run = frame_number - std::exchange(s_state.last_frame_number, frame_number); + const u32 internal_frames_run = + internal_frame_number - std::exchange(s_state.last_internal_frame_number, internal_frame_number); + const float frames_runf = static_cast(frames_run); + + // TODO: Make the math here less rubbish + const double pct_divider = + 100.0 * (1.0 / ((static_cast(ticks_diff) * static_cast(Threading::GetThreadTicksPerSecond())) / + Common::Timer::GetFrequency() / 1000000000.0)); + const double time_divider = 1000.0 * (1.0 / static_cast(Threading::GetThreadTicksPerSecond())) * + (1.0 / static_cast(frames_runf)); + + s_state.minimum_frame_time = std::exchange(s_state.minimum_frame_time_accumulator, 0.0f); + s_state.average_frame_time = std::exchange(s_state.average_frame_time_accumulator, 0.0f) / frames_runf; + s_state.maximum_frame_time = std::exchange(s_state.maximum_frame_time_accumulator, 0.0f); + + s_state.vps = static_cast(frames_runf / time); + s_state.fps = static_cast(internal_frames_run) / time; + s_state.speed = (s_state.vps / System::GetVideoFrameRate()) * 100.0f; + + const Threading::Thread* sw_thread = g_gpu->GetSWThread(); + const u64 cpu_time = System::Internal::GetCPUThreadHandle().GetCPUTime(); + const u64 sw_time = sw_thread ? sw_thread->GetCPUTime() : 0; + const u64 cpu_delta = cpu_time - s_state.last_cpu_time; + const u64 sw_delta = sw_time - s_state.last_sw_time; + s_state.last_cpu_time = cpu_time; + s_state.last_sw_time = sw_time; + + s_state.cpu_thread_usage = static_cast(static_cast(cpu_delta) * pct_divider); + s_state.cpu_thread_time = static_cast(static_cast(cpu_delta) * time_divider); + s_state.sw_thread_usage = static_cast(static_cast(sw_delta) * pct_divider); + s_state.sw_thread_time = static_cast(static_cast(sw_delta) * time_divider); + + if (MediaCapture* cap = System::GetMediaCapture()) + cap->UpdateCaptureThreadUsage(pct_divider, time_divider); + + if (g_gpu_device->IsGPUTimingEnabled()) + { + s_state.average_gpu_time = + s_state.accumulated_gpu_time / static_cast(std::max(s_state.presents_since_last_update, 1u)); + s_state.gpu_usage = s_state.accumulated_gpu_time / (time * 10.0f); + } + s_state.accumulated_gpu_time = 0.0f; + s_state.presents_since_last_update = 0; + + if (g_settings.display_show_gpu_stats) + g_gpu->UpdateStatistics(frames_run); + + VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", + s_state.fps, s_state.vps, s_state.cpu_thread_usage, s_state.gpu_usage, s_state.average_frame_time, + s_state.minimum_frame_time, s_state.maximum_frame_time); + + Host::OnPerformanceCountersUpdated(); +} + +void PerformanceCounters::AccumulateGPUTime() +{ + s_state.accumulated_gpu_time += g_gpu_device->GetAndResetAccumulatedGPUTime(); + s_state.presents_since_last_update++; +} diff --git a/src/core/performance_counters.h b/src/core/performance_counters.h new file mode 100644 index 000000000..db3827b5b --- /dev/null +++ b/src/core/performance_counters.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "common/types.h" + +namespace PerformanceCounters +{ +static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150; +using FrameTimeHistory = std::array; + +float GetFPS(); +float GetVPS(); +float GetEmulationSpeed(); +float GetAverageFrameTime(); +float GetMinimumFrameTime(); +float GetMaximumFrameTime(); +float GetCPUThreadUsage(); +float GetCPUThreadAverageTime(); +float GetSWThreadUsage(); +float GetSWThreadAverageTime(); +float GetGPUUsage(); +float GetGPUAverageTime(); +const FrameTimeHistory& GetFrameTimeHistory(); +u32 GetFrameTimeHistoryPos(); + +void Clear(); +void Reset(); +void Update(u32 frame_number, u32 internal_frame_number); +void AccumulateGPUTime(); + +} // namespace Host diff --git a/src/core/system.cpp b/src/core/system.cpp index 37240dfc6..6740df10d 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -28,6 +28,7 @@ #include "multitap.h" #include "pad.h" #include "pcdrv.h" +#include "performance_counters.h" #include "psf_loader.h" #include "save_state_version.h" #include "sio.h" @@ -111,8 +112,10 @@ SystemBootParameters::SystemBootParameters(std::string filename_) : filename(std SystemBootParameters::~SystemBootParameters() = default; namespace System { + /// Memory save states - only for internal use. namespace { + struct SaveStateBuffer { std::string serial; @@ -132,6 +135,7 @@ struct MemorySaveState size_t state_size; #endif }; + } // namespace static void CheckCacheLineSize(); @@ -180,11 +184,8 @@ static void ResetThrottler(); /// Throttles the system, i.e. sleeps until it's time to execute the next frame. static void Throttle(Common::Timer::Value current_time); -static void UpdatePerformanceCounters(); -static void AccumulatePreFrameSleepTime(); -static void UpdatePreFrameSleepTime(); +static void AccumulatePreFrameSleepTime(Common::Timer::Value current_time); static void UpdateDisplayVSync(); -static void ResetPerformanceCounters(); static bool UpdateGameSettingsLayer(); static void UpdateRunningGame(const std::string_view path, CDImage* image, bool booting); @@ -239,7 +240,7 @@ static void PollDiscordPresence(); #endif } // namespace System -static constexpr const float PERFORMANCE_COUNTER_UPDATE_INTERVAL = 1.0f; +static constexpr float PRE_FRAME_SLEEP_UPDATE_INTERVAL = 1.0f; static constexpr const char FALLBACK_EXE_NAME[] = "PSX.EXE"; static constexpr u32 MAX_SKIPPED_DUPLICATE_FRAME_COUNT = 2; // 20fps minimum static constexpr u32 MAX_SKIPPED_TIMEOUT_FRAME_COUNT = 1; // 30fps minimum @@ -285,7 +286,7 @@ static bool s_skip_presenting_duplicate_frames = false; static u32 s_skipped_frame_count = 0; static u32 s_last_presented_internal_frame_number = 0; -static float s_throttle_frequency = 0.0f; +static float s_video_frame_rate = 0.0f; static float s_target_speed = 0.0f; static Common::Timer::Value s_frame_period = 0; @@ -295,33 +296,8 @@ static Common::Timer::Value s_frame_start_time = 0; static Common::Timer::Value s_last_active_frame_time = 0; static Common::Timer::Value s_pre_frame_sleep_time = 0; static Common::Timer::Value s_max_active_frame_time = 0; +static Common::Timer::Value s_last_pre_frame_sleep_update_time = 0; -static float s_average_frame_time_accumulator = 0.0f; -static float s_minimum_frame_time_accumulator = 0.0f; -static float s_maximum_frame_time_accumulator = 0.0f; - -static float s_vps = 0.0f; -static float s_fps = 0.0f; -static float s_speed = 0.0f; -static float s_minimum_frame_time = 0.0f; -static float s_maximum_frame_time = 0.0f; -static float s_average_frame_time = 0.0f; -static float s_cpu_thread_usage = 0.0f; -static float s_cpu_thread_time = 0.0f; -static float s_sw_thread_usage = 0.0f; -static float s_sw_thread_time = 0.0f; -static float s_average_gpu_time = 0.0f; -static float s_accumulated_gpu_time = 0.0f; -static float s_gpu_usage = 0.0f; -static System::FrameTimeHistory s_frame_time_history; -static u32 s_frame_time_history_pos = 0; -static u32 s_last_frame_number = 0; -static u32 s_last_internal_frame_number = 0; -static u64 s_last_cpu_time = 0; -static u64 s_last_sw_time = 0; -static u32 s_presents_since_last_update = 0; -static Common::Timer s_fps_timer; -static Common::Timer s_frame_timer; static Threading::ThreadHandle s_cpu_thread_handle; static std::unique_ptr s_media_capture; @@ -559,6 +535,11 @@ void System::Internal::CPUThreadShutdown() #endif } +const Threading::ThreadHandle& System::Internal::GetCPUThreadHandle() +{ + return s_cpu_thread_handle; +} + void System::Internal::IdlePollUpdate() { InputManager::PollSources(); @@ -795,67 +776,6 @@ const BIOS::ImageInfo* System::GetBIOSImageInfo() return s_bios_image_info; } -float System::GetFPS() -{ - return s_fps; -} -float System::GetVPS() -{ - return s_vps; -} -float System::GetEmulationSpeed() -{ - return s_speed; -} -float System::GetAverageFrameTime() -{ - return s_average_frame_time; -} -float System::GetMinimumFrameTime() -{ - return s_minimum_frame_time; -} -float System::GetMaximumFrameTime() -{ - return s_maximum_frame_time; -} -float System::GetThrottleFrequency() -{ - return s_throttle_frequency; -} -float System::GetCPUThreadUsage() -{ - return s_cpu_thread_usage; -} -float System::GetCPUThreadAverageTime() -{ - return s_cpu_thread_time; -} -float System::GetSWThreadUsage() -{ - return s_sw_thread_usage; -} -float System::GetSWThreadAverageTime() -{ - return s_sw_thread_time; -} -float System::GetGPUUsage() -{ - return s_gpu_usage; -} -float System::GetGPUAverageTime() -{ - return s_average_gpu_time; -} -const System::FrameTimeHistory& System::GetFrameTimeHistory() -{ - return s_frame_time_history; -} -u32 System::GetFrameTimeHistoryPos() -{ - return s_frame_time_history_pos; -} - bool System::IsExePath(std::string_view path) { return (StringUtil::EndsWithNoCase(path, ".exe") || StringUtil::EndsWithNoCase(path, ".psexe") || @@ -1622,7 +1542,8 @@ void System::ResetSystem() Host::AddIconOSDMessage("SystemReset", ICON_FA_POWER_OFF, TRANSLATE_STR("OSDMessage", "System reset."), Host::OSD_QUICK_DURATION); - ResetPerformanceCounters(); + PerformanceCounters::Reset(); + ResetThrottler(); InterruptExecution(); } @@ -1677,7 +1598,7 @@ void System::PauseSystem(bool paused) Host::OnIdleStateChanged(); UpdateDisplayVSync(); - ResetPerformanceCounters(); + PerformanceCounters::Reset(); ResetThrottler(); } } @@ -1927,7 +1848,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) UpdateSpeedLimiterState(); ImGuiManager::UpdateDebugWindowConfig(); - ResetPerformanceCounters(); + PerformanceCounters::Reset(); + ResetThrottler(); return true; } @@ -1940,7 +1862,7 @@ bool System::Initialize(std::unique_ptr disc, DiscRegion disc_region, b s_internal_frame_number = 0; s_target_speed = g_settings.emulation_speed; - s_throttle_frequency = 60.0f; + s_video_frame_rate = 60.0f; s_frame_period = 0; s_next_frame_time = 0; s_turbo_enabled = false; @@ -1950,32 +1872,6 @@ bool System::Initialize(std::unique_ptr disc, DiscRegion disc_region, b s_rewind_load_counter = -1; s_rewinding_first_save = true; - s_average_frame_time_accumulator = 0.0f; - s_minimum_frame_time_accumulator = 0.0f; - s_maximum_frame_time_accumulator = 0.0f; - - s_vps = 0.0f; - s_fps = 0.0f; - s_speed = 0.0f; - s_minimum_frame_time = 0.0f; - s_maximum_frame_time = 0.0f; - s_average_frame_time = 0.0f; - s_cpu_thread_usage = 0.0f; - s_cpu_thread_time = 0.0f; - s_sw_thread_usage = 0.0f; - s_sw_thread_time = 0.0f; - s_average_gpu_time = 0.0f; - s_accumulated_gpu_time = 0.0f; - s_gpu_usage = 0.0f; - s_last_frame_number = 0; - s_last_internal_frame_number = 0; - s_presents_since_last_update = 0; - s_last_cpu_time = 0; - s_fps_timer.Reset(); - s_frame_timer.Reset(); - s_frame_time_history.fill(0.0f); - s_frame_time_history_pos = 0; - TimingEvents::Initialize(); Bus::Initialize(); @@ -2014,6 +1910,9 @@ bool System::Initialize(std::unique_ptr disc, DiscRegion disc_region, b UpdateThrottlePeriod(); UpdateMemorySaveStateSettings(); + + PerformanceCounters::Clear(); + return true; } @@ -2050,8 +1949,6 @@ void System::DestroySystem() if (g_settings.inhibit_screensaver) PlatformMisc::ResumeScreensaver(); - s_cpu_thread_usage = {}; - ClearMemorySaveStates(); Cheats::UnloadAll(); @@ -2224,7 +2121,7 @@ void System::FrameDone() // Kick off media capture early, might take a while. if (s_media_capture && s_media_capture->IsCapturingVideo()) [[unlikely]] { - if (s_media_capture->GetVideoFPS() != GetThrottleFrequency()) [[unlikely]] + if (s_media_capture->GetVideoFPS() != s_video_frame_rate) [[unlikely]] { const std::string next_capture_path = s_media_capture->GetNextCapturePath(); INFO_LOG("Video frame rate changed, switching to new capture file {}", Path::GetFileName(next_capture_path)); @@ -2250,7 +2147,7 @@ void System::FrameDone() const Common::Timer::Value pre_frame_sleep_until = s_next_frame_time + s_pre_frame_sleep_time; s_last_active_frame_time = current_time - s_frame_start_time; if (s_pre_frame_sleep) - AccumulatePreFrameSleepTime(); + AccumulatePreFrameSleepTime(current_time); // explicit present (frame pacing) const bool is_unique_frame = (s_last_presented_internal_frame_number != s_internal_frame_number); @@ -2328,15 +2225,20 @@ void System::FrameDone() // Update perf counters *after* throttling, we want to measure from start-of-frame // to start-of-frame, not end-of-frame to end-of-frame (will be noisy due to different // amounts of computation happening in each frame). - System::UpdatePerformanceCounters(); + PerformanceCounters::Update(s_frame_number, s_internal_frame_number); } -void System::SetThrottleFrequency(float frequency) +float System::GetVideoFrameRate() { - if (s_throttle_frequency == frequency) + return s_video_frame_rate; +} + +void System::SetVideoFrameRate(float frequency) +{ + if (s_video_frame_rate == frequency) return; - s_throttle_frequency = frequency; + s_video_frame_rate = frequency; UpdateThrottlePeriod(); } @@ -2346,7 +2248,7 @@ void System::UpdateThrottlePeriod() { const double target_speed = std::max(static_cast(s_target_speed), std::numeric_limits::epsilon()); s_frame_period = - Common::Timer::ConvertSecondsToValue(1.0 / (static_cast(s_throttle_frequency) * target_speed)); + Common::Timer::ConvertSecondsToValue(1.0 / (static_cast(s_video_frame_rate) * target_speed)); } else { @@ -2790,17 +2692,11 @@ bool System::LoadState(const char* path, Error* error, bool save_undo_state) return false; } - ResetPerformanceCounters(); - ResetThrottler(); - - if (IsPaused()) - InvalidateDisplay(); - VERBOSE_LOG("Loading state took {:.2f} msec", load_timer.GetTimeMilliseconds()); return true; } -bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bool update_display) +bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bool update_display_if_paused) { Assert(IsValid()); @@ -2866,15 +2762,20 @@ bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bo Achievements::DisableHardcoreMode(); StateWrapper sw(buffer.state_data.cspan(0, buffer.state_size), StateWrapper::Mode::Read, buffer.version); - if (!DoState(sw, nullptr, update_display, false)) + if (!DoState(sw, nullptr, update_display_if_paused && IsPaused(), false)) { Error::SetStringView(error, "Save state stream is corrupted."); return false; } InterruptExecution(); - ResetPerformanceCounters(); + + PerformanceCounters::Reset(); ResetThrottler(); + + if (update_display_if_paused && IsPaused()) + InvalidateDisplay(); + return true; } @@ -3340,100 +3241,7 @@ float System::GetAudioNominalRate() return (s_throttler_enabled || s_syncing_to_host_with_vsync) ? s_target_speed : 1.0f; } -void System::UpdatePerformanceCounters() -{ - const float frame_time = static_cast(s_frame_timer.GetTimeMillisecondsAndReset()); - s_minimum_frame_time_accumulator = - (s_minimum_frame_time_accumulator == 0.0f) ? frame_time : std::min(s_minimum_frame_time_accumulator, frame_time); - s_average_frame_time_accumulator += frame_time; - s_maximum_frame_time_accumulator = std::max(s_maximum_frame_time_accumulator, frame_time); - s_frame_time_history[s_frame_time_history_pos] = frame_time; - s_frame_time_history_pos = (s_frame_time_history_pos + 1) % NUM_FRAME_TIME_SAMPLES; - - // update fps counter - const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue(); - const Common::Timer::Value ticks_diff = now_ticks - s_fps_timer.GetStartValue(); - const float time = static_cast(Common::Timer::ConvertValueToSeconds(ticks_diff)); - if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL) - return; - - const u32 frames_run = s_frame_number - s_last_frame_number; - const float frames_runf = static_cast(frames_run); - - // TODO: Make the math here less rubbish - const double pct_divider = - 100.0 * (1.0 / ((static_cast(ticks_diff) * static_cast(Threading::GetThreadTicksPerSecond())) / - Common::Timer::GetFrequency() / 1000000000.0)); - const double time_divider = 1000.0 * (1.0 / static_cast(Threading::GetThreadTicksPerSecond())) * - (1.0 / static_cast(frames_runf)); - - s_minimum_frame_time = std::exchange(s_minimum_frame_time_accumulator, 0.0f); - s_average_frame_time = std::exchange(s_average_frame_time_accumulator, 0.0f) / frames_runf; - s_maximum_frame_time = std::exchange(s_maximum_frame_time_accumulator, 0.0f); - - s_vps = static_cast(frames_runf / time); - s_last_frame_number = s_frame_number; - s_fps = static_cast(s_internal_frame_number - s_last_internal_frame_number) / time; - s_last_internal_frame_number = s_internal_frame_number; - s_speed = (s_vps / s_throttle_frequency) * 100.0f; - - const Threading::Thread* sw_thread = g_gpu->GetSWThread(); - const u64 cpu_time = s_cpu_thread_handle ? s_cpu_thread_handle.GetCPUTime() : 0; - const u64 sw_time = sw_thread ? sw_thread->GetCPUTime() : 0; - const u64 cpu_delta = cpu_time - s_last_cpu_time; - const u64 sw_delta = sw_time - s_last_sw_time; - s_last_cpu_time = cpu_time; - s_last_sw_time = sw_time; - - s_cpu_thread_usage = static_cast(static_cast(cpu_delta) * pct_divider); - s_cpu_thread_time = static_cast(static_cast(cpu_delta) * time_divider); - s_sw_thread_usage = static_cast(static_cast(sw_delta) * pct_divider); - s_sw_thread_time = static_cast(static_cast(sw_delta) * time_divider); - - if (s_media_capture) - s_media_capture->UpdateCaptureThreadUsage(pct_divider, time_divider); - - s_fps_timer.ResetTo(now_ticks); - - if (g_gpu_device->IsGPUTimingEnabled()) - { - s_average_gpu_time = s_accumulated_gpu_time / static_cast(std::max(s_presents_since_last_update, 1u)); - s_gpu_usage = s_accumulated_gpu_time / (time * 10.0f); - } - s_accumulated_gpu_time = 0.0f; - s_presents_since_last_update = 0; - - if (g_settings.display_show_gpu_stats) - g_gpu->UpdateStatistics(frames_run); - - if (s_pre_frame_sleep) - UpdatePreFrameSleepTime(); - - VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Average: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", s_fps, - s_vps, s_cpu_thread_usage, s_gpu_usage, s_average_frame_time, s_minimum_frame_time, s_maximum_frame_time); - - Host::OnPerformanceCountersUpdated(); -} - -void System::ResetPerformanceCounters() -{ - s_last_frame_number = s_frame_number; - s_last_internal_frame_number = s_internal_frame_number; - s_last_cpu_time = s_cpu_thread_handle ? s_cpu_thread_handle.GetCPUTime() : 0; - if (const Threading::Thread* sw_thread = g_gpu->GetSWThread(); sw_thread) - s_last_sw_time = sw_thread->GetCPUTime(); - else - s_last_sw_time = 0; - - s_average_frame_time_accumulator = 0.0f; - s_minimum_frame_time_accumulator = 0.0f; - s_maximum_frame_time_accumulator = 0.0f; - s_frame_timer.Reset(); - s_fps_timer.Reset(); - ResetThrottler(); -} - -void System::AccumulatePreFrameSleepTime() +void System::AccumulatePreFrameSleepTime(Common::Timer::Value current_time) { DebugAssert(s_pre_frame_sleep); @@ -3450,21 +3258,22 @@ void System::AccumulatePreFrameSleepTime() Common::Timer::ConvertValueToMilliseconds(s_pre_frame_sleep_time), Common::Timer::ConvertValueToMilliseconds(s_last_active_frame_time)); } -} -void System::UpdatePreFrameSleepTime() -{ - DebugAssert(s_pre_frame_sleep); + if (Common::Timer::ConvertValueToSeconds(current_time - s_last_pre_frame_sleep_update_time) >= + PRE_FRAME_SLEEP_UPDATE_INTERVAL) + { + s_last_pre_frame_sleep_update_time = current_time; - const Common::Timer::Value expected_frame_time = - s_max_active_frame_time + Common::Timer::ConvertMillisecondsToValue(g_settings.display_pre_frame_sleep_buffer); - s_pre_frame_sleep_time = Common::AlignDown(s_frame_period - std::min(expected_frame_time, s_frame_period), - static_cast(Common::Timer::ConvertMillisecondsToValue(1))); - DEV_LOG("Set pre-frame time to {} ms (expected frame time of {} ms)", - Common::Timer::ConvertValueToMilliseconds(s_pre_frame_sleep_time), - Common::Timer::ConvertValueToMilliseconds(expected_frame_time)); + const Common::Timer::Value expected_frame_time = + s_max_active_frame_time + Common::Timer::ConvertMillisecondsToValue(g_settings.display_pre_frame_sleep_buffer); + s_pre_frame_sleep_time = Common::AlignDown(s_frame_period - std::min(expected_frame_time, s_frame_period), + static_cast(Common::Timer::ConvertMillisecondsToValue(1))); + DEV_LOG("Set pre-frame time to {} ms (expected frame time of {} ms)", + Common::Timer::ConvertValueToMilliseconds(s_pre_frame_sleep_time), + Common::Timer::ConvertValueToMilliseconds(expected_frame_time)); - s_max_active_frame_time = 0; + s_max_active_frame_time = 0; + } } void System::FormatLatencyStats(SmallStringBase& str) @@ -3505,10 +3314,10 @@ void System::UpdateSpeedLimiterState() const float host_refresh_rate = g_gpu_device->GetMainSwapChain()->GetWindowInfo().surface_refresh_rate; if (host_refresh_rate > 0.0f) { - const float ratio = host_refresh_rate / System::GetThrottleFrequency(); + const float ratio = host_refresh_rate / s_video_frame_rate; s_can_sync_to_host = (ratio >= 0.95f && ratio <= 1.05f); - INFO_LOG("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate, System::GetThrottleFrequency(), - ratio, s_can_sync_to_host ? "can sync" : "can't sync"); + INFO_LOG("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate, s_video_frame_rate, ratio, + s_can_sync_to_host ? "can sync" : "can't sync"); s_syncing_to_host = (s_can_sync_to_host && g_settings.sync_to_host_refresh_rate && s_target_speed == 1.0f); if (s_syncing_to_host) @@ -4911,7 +4720,7 @@ void System::UpdateMemorySaveStateSettings() if (g_settings.rewind_enable) { - s_rewind_save_frequency = static_cast(std::ceil(g_settings.rewind_save_frequency * s_throttle_frequency)); + s_rewind_save_frequency = static_cast(std::ceil(g_settings.rewind_save_frequency * s_video_frame_rate)); s_rewind_save_counter = 0; u64 ram_usage, vram_usage; @@ -5024,6 +4833,9 @@ bool System::LoadRewindState(u32 skip_saves /*= 0*/, bool consume_state /*=true if (consume_state) s_rewind_states.pop_back(); + // back in time, need to reset perf counters + PerformanceCounters::Reset(); + #ifdef PROFILE_MEMORY_SAVE_STATES DEV_LOG("Rewind load took {:.4f} ms", load_timer.GetTimeMilliseconds()); #endif @@ -5044,7 +4856,7 @@ void System::SetRewinding(bool enabled) // Try to rewind at the replay speed, or one per second maximum. const float load_frequency = std::min(g_settings.rewind_save_frequency, 1.0f); - s_rewind_load_frequency = static_cast(std::ceil(load_frequency * s_throttle_frequency)); + s_rewind_load_frequency = static_cast(std::ceil(load_frequency * s_video_frame_rate)); s_rewind_load_counter = 0; if (!was_enabled && s_system_executing) @@ -5065,7 +4877,6 @@ void System::DoRewind() const u32 skip_saves = BoolToUInt32(!s_rewinding_first_save); s_rewinding_first_save = false; LoadRewindState(skip_saves, false); - ResetPerformanceCounters(); s_rewind_load_counter = s_rewind_load_frequency; } else @@ -5394,7 +5205,6 @@ bool System::StartMediaCapture(std::string path, bool capture_video, bool captur Host::GetUIntSettingValue("MediaCapture", "VideoHeight", Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_HEIGHT); const GPUTexture::Format capture_format = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8; - const float fps = System::GetThrottleFrequency(); if (capture_video) { // TODO: This will be a mess with GPU thread. @@ -5429,8 +5239,8 @@ bool System::StartMediaCapture(std::string path, bool capture_video, bool captur s_media_capture = MediaCapture::Create(backend, &error); if (!s_media_capture || !s_media_capture->BeginCapture( - fps, aspect, capture_width, capture_height, capture_format, SPU::SAMPLE_RATE, std::move(path), capture_video, - Host::GetSmallStringSettingValue("MediaCapture", "VideoCodec"), + s_video_frame_rate, aspect, capture_width, capture_height, capture_format, SPU::SAMPLE_RATE, std::move(path), + capture_video, Host::GetSmallStringSettingValue("MediaCapture", "VideoCodec"), Host::GetUIntSettingValue("MediaCapture", "VideoBitrate", Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_BITRATE), Host::GetBoolSettingValue("MediaCapture", "VideoCodecUseArgs", false) ? Host::GetStringSettingValue("MediaCapture", "AudioCodecArgs") : @@ -5779,11 +5589,8 @@ void System::ToggleSoftwareRendering() Host::OSD_QUICK_DURATION); RecreateGPU(new_renderer); - // Might have a thread change. - if (const Threading::Thread* sw_thread = g_gpu->GetSWThread(); sw_thread) - s_last_sw_time = sw_thread->GetCPUTime(); - else - s_last_sw_time = 0; + // TODO: GPU-THREAD: Drop this + PerformanceCounters::Reset(); g_gpu->UpdateResolutionScale(); } @@ -5852,10 +5659,7 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time) g_gpu_device->EndPresent(g_gpu_device->GetMainSwapChain(), explicit_present, present_time); if (g_gpu_device->IsGPUTimingEnabled()) - { - s_accumulated_gpu_time += g_gpu_device->GetAndResetAccumulatedGPUTime(); - s_presents_since_last_update++; - } + PerformanceCounters::AccumulateGPUTime(); } else { diff --git a/src/core/system.h b/src/core/system.h index dfa730eae..781ac8bae 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -13,7 +13,10 @@ #include #include -class ByteStream; +namespace Threading { +class ThreadHandle; +} + class CDImage; class Error; class SmallStringBase; @@ -231,24 +234,6 @@ u64 GetSessionPlayedTime(); const BIOS::ImageInfo* GetBIOSImageInfo(); -static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150; -using FrameTimeHistory = std::array; - -float GetFPS(); -float GetVPS(); -float GetEmulationSpeed(); -float GetAverageFrameTime(); -float GetMinimumFrameTime(); -float GetMaximumFrameTime(); -float GetThrottleFrequency(); -float GetCPUThreadUsage(); -float GetCPUThreadAverageTime(); -float GetSWThreadUsage(); -float GetSWThreadAverageTime(); -float GetGPUUsage(); -float GetGPUAverageTime(); -const FrameTimeHistory& GetFrameTimeHistory(); -u32 GetFrameTimeHistoryPos(); void FormatLatencyStats(SmallStringBase& str); /// Loads global settings (i.e. EmuConfig). @@ -292,7 +277,8 @@ float GetAudioNominalRate(); bool IsRunningAtNonStandardSpeed(); /// Adjusts the throttle frequency, i.e. how many times we should sleep per second. -void SetThrottleFrequency(float frequency); +float GetVideoFrameRate(); +void SetVideoFrameRate(float frequency); // Access controllers for simulating input. Controller* GetController(u32 slot); @@ -467,6 +453,9 @@ bool CPUThreadInitialize(Error* error); /// Called on CPU thread shutdown. void CPUThreadShutdown(); +/// Returns a handle to the CPU thread. +const Threading::ThreadHandle& GetCPUThreadHandle(); + /// Polls input, updates subsystems which are present while paused/inactive. void IdlePollUpdate(); } // namespace Internal diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 50fd7eac3..8b755f25f 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -23,6 +23,7 @@ #include "core/host.h" #include "core/imgui_overlays.h" #include "core/memory_card.h" +#include "core/performance_counters.h" #include "core/spu.h" #include "core/system.h" @@ -2111,7 +2112,7 @@ void EmuThread::updatePerformanceCounters() m_last_render_height = render_height; } - const float gfps = System::GetFPS(); + const float gfps = PerformanceCounters::GetFPS(); if (gfps != m_last_game_fps) { QMetaObject::invokeMethod(g_main_window->getStatusFPSWidget(), "setText", Qt::QueuedConnection, @@ -2119,8 +2120,8 @@ void EmuThread::updatePerformanceCounters() m_last_game_fps = gfps; } - const float speed = System::GetEmulationSpeed(); - const float vfps = System::GetVPS(); + const float speed = PerformanceCounters::GetEmulationSpeed(); + const float vfps = PerformanceCounters::GetVPS(); if (speed != m_last_speed || vfps != m_last_video_fps) { QMetaObject::invokeMethod(