diff --git a/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp new file mode 100644 index 0000000000..5283dd19fa --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp @@ -0,0 +1,233 @@ +#include "stdafx.h" +#include "overlays.h" +#include "../GSRender.h" + +#include "Emu/Cell/SPUThread.h" +#include "Emu/Cell/RawSPUThread.h" +#include "Emu/Cell/PPUThread.h" +#include "Utilities/sysinfo.h" + +namespace rsx +{ + namespace overlays + { + void perf_metrics_overlay::reset_body() + { + m_body.set_font("n023055ms.ttf", m_font_size); + m_body.set_pos(50, 50); + m_body.fore_color = {0xFF / 255.f, 0xe1 / 255.f, 0x38 / 255.f, 1.0f}; + m_body.back_color = {0x00 / 255.f, 0x23 / 255.f, 0x39 / 255.f, 0.7f}; + m_body.padding_top = 0; + m_body.padding_bottom = -14; + m_body.set_margin(5, 5, 0, 0); + } + + void perf_metrics_overlay::reset_titles() + { + m_titles.set_font("n023055ms.ttf", m_font_size); + m_titles.set_pos(50, 50); + m_titles.fore_color = {0xf2 / 256.0, 0x6C / 256.0, 0x24 / 256.0, 1.0f}; + m_titles.back_color = {0.0f, 0.0f, 0.0f, 0.0f}; + m_titles.padding_top = 0; + m_titles.padding_bottom = -14; + m_titles.set_margin(5, 5, 0, 0); + + switch (m_detail) + { + case detail_level::minimal: + case detail_level::low: m_titles.text = ""; break; + case detail_level::medium: m_titles.text = fmt::format("\n\n%s", title1_medium); break; + case detail_level::high: m_titles.text = fmt::format("\n\n%s\n\n\n\n\n\n%s", title1_high, title2); break; + } + m_titles.auto_resize(); + m_titles.refresh(); + } + + void perf_metrics_overlay::init() + { + reset_titles(); + reset_body(); + + force_next_update(); + update(); + m_update_timer.Start(); + } + + perf_metrics_overlay::perf_metrics_overlay(bool initialize) + { + // Default values, will change based on config options + m_update_interval = 350; + m_detail = detail_level::high; + m_font_size = 10; + + if (initialize) + init(); + } + + // In ms + void perf_metrics_overlay::set_update_interval(u32 update_interval) + { + m_update_interval = update_interval; + } + + void perf_metrics_overlay::set_detail_level(detail_level level) + { + m_detail = level; + reset_titles(); + } + + void perf_metrics_overlay::set_font_size(u32 font_size) + { + m_font_size = font_size; + reset_titles(); + reset_body(); + } + + void perf_metrics_overlay::force_next_update() + { + m_force_update = true; + } + + void perf_metrics_overlay::update() + { + const auto elapsed = m_update_timer.GetElapsedTimeInMilliSec(); + + if (!m_force_update) + { + ++m_frames; + } + + if (elapsed >= m_update_interval || m_force_update) + { + f32 fps{0}; + f32 frametime{0}; + + u64 ppu_cycles{0}; + u64 spu_cycles{0}; + u64 rsx_cycles{0}; + u64 total_cycles{0}; + + u32 ppus{0}; + u32 spus{0}; + u32 rawspus{0}; + + f32 cpu_usage{-1.f}; + u32 total_threads{0}; + + f32 ppu_usage{0}; + f32 spu_usage{0}; + f32 rsx_usage{0}; + u32 rsx_load{0}; + + std::shared_ptr rsx_thread; + + std::string perf_text; + + // 1. Fetch/calculate metrics we'll need + switch (m_detail) + { + case detail_level::high: + { + frametime = m_force_update ? 0 : std::max(0.0, elapsed / m_frames); + + rsx_thread = fxm::get(); + rsx_load = rsx_thread->get_load(); + + total_threads = CPUStats::get_thread_count(); + + // fallthrough + } + case detail_level::medium: + { + ppus = idm::select([&ppu_cycles](u32, ppu_thread& ppu) { ppu_cycles += ppu.get()->get_cycles(); }); + + spus = idm::select([&spu_cycles](u32, SPUThread& spu) { spu_cycles += spu.get()->get_cycles(); }); + + rawspus = idm::select([&spu_cycles](u32, RawSPUThread& rawspu) { spu_cycles += rawspu.get()->get_cycles(); }); + + if (!rsx_thread) + rsx_thread = fxm::get(); + + rsx_cycles += rsx_thread->get()->get_cycles(); + + total_cycles = ppu_cycles + spu_cycles + rsx_cycles; + cpu_usage = m_cpu_stats.get_usage(); + + ppu_usage = std::clamp(cpu_usage * ppu_cycles / total_cycles, 0.f, 100.f); + spu_usage = std::clamp(cpu_usage * spu_cycles / total_cycles, 0.f, 100.f); + rsx_usage = std::clamp(cpu_usage * rsx_cycles / total_cycles, 0.f, 100.f); + + // fallthrough + } + case detail_level::low: + { + if (cpu_usage == -1.f) + cpu_usage = m_cpu_stats.get_usage(); + + // fallthrough + } + case detail_level::minimal: + { + fps = m_force_update ? 0 : std::max(0.0, static_cast(m_frames) / (elapsed / 1000)); + } + } + + // 2. Format output string + switch (m_detail) + { + case detail_level::minimal: + { + perf_text += fmt::format("FPS : %05.2f", fps); + break; + } + case detail_level::low: + { + perf_text += fmt::format("FPS : %05.2f\n" + "CPU : %04.1f %%", + fps, cpu_usage); + break; + } + case detail_level::medium: + { + perf_text += fmt::format("FPS : %05.2f\n\n" + "%s\n" + " PPU : %04.1f %%\n" + " SPU : %04.1f %%\n" + " RSX : %04.1f %%\n" + " Total : %04.1f %%", + fps, std::string(title1_medium.size(), ' '), ppu_usage, spu_usage, rsx_usage, cpu_usage, std::string(title2.size(), ' ')); + break; + } + case detail_level::high: + { + perf_text += fmt::format("FPS : %05.2f (%03.1fms)\n\n" + "%s\n" + " PPU : %04.1f %% (%2u)\n" + " SPU : %04.1f %% (%2u)\n" + " RSX : %04.1f %% ( 1)\n" + " Total : %04.1f %% (%2u)\n\n" + "%s\n" + " RSX : %02u %%", + fps, frametime, std::string(title1_high.size(), ' '), ppu_usage, ppus, spu_usage, spus + rawspus, rsx_usage, cpu_usage, total_threads, std::string(title2.size(), ' '), rsx_load); + break; + } + } + + m_body.text = perf_text; + m_body.auto_resize(); + m_body.refresh(); + + if (!m_force_update) + { + m_frames = 0; + m_update_timer.Start(); + } + else + { + // Only force once + m_force_update = false; + } + } + } + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlays.h b/rpcs3/Emu/RSX/Overlays/overlays.h index 9197f773a0..ee8a354cb8 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.h +++ b/rpcs3/Emu/RSX/Overlays/overlays.h @@ -11,6 +11,8 @@ #include "Emu/Cell/Modules/cellSaveData.h" #include "Emu/Cell/Modules/cellMsgDialog.h" #include "Emu/Cell/Modules/sceNpTrophy.h" +#include "Utilities/CPUStats.h" +#include "Utilities/Timer.h" #include @@ -32,10 +34,10 @@ namespace rsx virtual ~overlay() = default; - void refresh(); virtual void update() {} - virtual compiled_resource get_compiled() = 0; + + void refresh(); }; // Interactable UI element @@ -67,14 +69,15 @@ namespace rsx s32 return_code = CELL_OK; std::function on_close; - void close(); - virtual void update() override {} + virtual compiled_resource get_compiled() override = 0; virtual void on_button_pressed(pad_button /*button_press*/) { close(); - }; + } + + void close(); s32 run_input_loop() { @@ -345,7 +348,7 @@ namespace rsx m_dirty_list.erase ( - std::remove_if(m_dirty_list.begin(), m_dirty_list.end(), [&uids](std::unique_ptr& e) + std::remove_if(m_dirty_list.begin(), m_dirty_list.end(), [&uids](std::unique_ptr& e) { return std::find(uids.begin(), uids.end(), e->uid) != uids.end(); }) @@ -403,27 +406,50 @@ namespace rsx } }; - struct fps_display : user_interface + struct perf_metrics_overlay : overlay { - label m_display; + private: + /* + minimal - fps + low - fps, total cpu usage + medium - fps, detailed cpu usage + high - fps, frametime, detailed cpu usage, thread number, rsx load + */ + detail_level m_detail; - fps_display() - { - m_display.w = 150; - m_display.h = 30; - m_display.set_font("Arial", 16); - m_display.set_pos(1100, 20); - } + label m_body; + label m_titles; - void update(std::string current_fps) - { - m_display.text = current_fps; - m_display.refresh(); - } + CPUStats m_cpu_stats; + Timer m_update_timer; + u32 m_update_interval; // in ms + u32 m_frames{ 0 }; + u32 m_font_size; + + bool m_force_update; + + const std::string title1_medium{"CPU Utilization:"}; + const std::string title1_high{"Host Utilization (CPU):"}; + const std::string title2{"Guest Utilization (PS3):"}; + + void reset_body(); + void reset_titles(); + + public: + perf_metrics_overlay(bool initialize = true); + + void init(); + void set_detail_level(detail_level level); + void set_update_interval(u32 update_interval); + void set_font_size(u32 font_size); + void force_next_update(); + + void update() override; compiled_resource get_compiled() override { - return m_display.get_compiled(); + m_body.get_compiled().add(m_titles.get_compiled()); + return m_body.get_compiled(); } }; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 7f74406fdc..0a89936bd3 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -355,6 +355,16 @@ namespace rsx if (supports_native_ui) { m_overlay_manager = fxm::make_always(); + + if (g_cfg.video.perf_overlay.perf_overlay_enabled) + { + auto perf_overlay = m_overlay_manager->create(false); + + perf_overlay->set_detail_level(g_cfg.video.perf_overlay.level); + perf_overlay->set_update_interval(g_cfg.video.perf_overlay.update_interval); + perf_overlay->set_font_size(g_cfg.video.perf_overlay.font_size); + perf_overlay->init(); + } } on_init_thread(); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index c7f5505583..4197dfb654 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -198,6 +198,23 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +inline void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](detail_level value) + { + switch (value) + { + case detail_level::minimal: return "Minimal"; + case detail_level::low: return "Low"; + case detail_level::medium: return "Medium"; + case detail_level::high: return "High"; + } + + return unknown; + }); +} + void Emulator::Init() { if (!g_tty) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index bbb1c7f2fe..b831d9ce96 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -147,6 +147,14 @@ enum class frame_limit_type _auto, }; +enum class detail_level +{ + minimal, + low, + medium, + high, +}; + enum CellNetCtlState : s32; enum CellSysutilLang : s32; @@ -393,6 +401,17 @@ struct cfg_root : cfg::node } vk{this}; + struct node_perf_overlay : cfg::node + { + node_perf_overlay(cfg::node* _this) : cfg::node(_this, "Perfomance Overlay") {} + + cfg::_bool perf_overlay_enabled{this, "Enabled", false}; + cfg::_enum level{this, "Detail level", detail_level::high}; + cfg::_int<30, 5000> update_interval{this, "Metrics update interval (ms)", 350}; + cfg::_int<4, 36> font_size{this, "Font size (px)", 10}; + + } perf_overlay{this}; + } video{this}; struct node_audio : cfg::node diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 5b6ca8b473..248199a4dc 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -295,6 +295,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index a7fd8fb7ea..8bf7eb2b67 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -749,6 +749,9 @@ Emu\GPU\RSX\Overlays + + Emu\GPU\RSX\Overlays +