From c75ae6c585f651a1b7c162c2e1ecccd22a1c587d Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 19 Feb 2017 14:34:47 -0800 Subject: [PATCH] Add performance statistics to status bar --- src/citra_qt/main.cpp | 27 ++++++++++ src/citra_qt/main.h | 3 ++ src/core/CMakeLists.txt | 2 + src/core/core.cpp | 9 ++++ src/core/core.h | 7 ++- src/core/hle/service/gsp_gpu.cpp | 3 ++ src/core/hw/gpu.cpp | 4 +- src/core/hw/gpu.h | 2 + src/core/perf_stats.cpp | 53 +++++++++++++++++++ src/core/perf_stats.h | 43 +++++++++++++++ .../renderer_opengl/renderer_opengl.cpp | 9 ++++ 11 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/core/perf_stats.cpp create mode 100644 src/core/perf_stats.h diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 43530b2752..41356a6caa 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -253,6 +253,8 @@ void GMainWindow::ConnectWidgetEvents() { connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, SLOT(OnEmulationStarting(EmuThread*))); connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); + + connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); } void GMainWindow::ConnectMenuEvents() { @@ -401,6 +403,8 @@ void GMainWindow::BootGame(const QString& filename) { if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); } + status_bar_update_timer.start(1000); + render_window->show(); render_window->setFocus(); @@ -435,6 +439,12 @@ void GMainWindow::ShutdownGame() { render_window->hide(); game_list->show(); + // Disable status bar updates + status_bar_update_timer.stop(); + emu_speed_label->setVisible(false); + game_fps_label->setVisible(false); + emu_frametime_label->setVisible(false); + emulation_running = false; } @@ -614,6 +624,23 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { graphicsSurfaceViewerWidget->show(); } +void GMainWindow::UpdateStatusBar() { + if (emu_thread == nullptr) { + status_bar_update_timer.stop(); + return; + } + + auto results = Core::System::GetInstance().GetAndResetPerfStats(); + + emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 2)); + game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 1)); + emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); + + emu_speed_label->setVisible(true); + game_fps_label->setVisible(true); + emu_frametime_label->setVisible(true); +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 3cbf4ea99e..ec841eaa54 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -127,6 +127,8 @@ private slots: void OnCreateGraphicsSurfaceViewer(); private: + void UpdateStatusBar(); + Ui::MainWindow ui; GRenderWindow* render_window; @@ -136,6 +138,7 @@ private: QLabel* emu_speed_label = nullptr; QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; + QTimer status_bar_update_timer; std::unique_ptr config; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5332e35a39..1adc78d8d1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -170,6 +170,7 @@ set(SRCS loader/smdh.cpp tracer/recorder.cpp memory.cpp + perf_stats.cpp settings.cpp ) @@ -357,6 +358,7 @@ set(HEADERS memory.h memory_setup.h mmio.h + perf_stats.h settings.h ) diff --git a/src/core/core.cpp b/src/core/core.cpp index c9c9b76150..ca2c28ce4e 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -109,6 +109,11 @@ void System::PrepareReschedule() { reschedule_pending = true; } +PerfStats::Results System::GetAndResetPerfStats() { + auto perf_stats = this->perf_stats.Lock(); + return perf_stats->GetAndResetStats(CoreTiming::GetGlobalTimeUs()); +} + void System::Reschedule() { if (!reschedule_pending) { return; @@ -140,6 +145,10 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { LOG_DEBUG(Core, "Initialized OK"); + // Reset counters and set time origin to current frame + GetAndResetPerfStats(); + perf_stats.Lock()->BeginSystemFrame(); + return ResultStatus::Success; } diff --git a/src/core/core.h b/src/core/core.h index 17572a74f1..3efc20c3d6 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -6,9 +6,10 @@ #include #include - #include "common/common_types.h" +#include "common/synchronized_wrapper.h" #include "core/memory.h" +#include "core/perf_stats.h" class EmuWindow; class ARM_Interface; @@ -83,6 +84,8 @@ public: /// Prepare the core emulation for a reschedule void PrepareReschedule(); + PerfStats::Results GetAndResetPerfStats(); + /** * Gets a reference to the emulated CPU. * @returns A reference to the emulated CPU. @@ -91,6 +94,8 @@ public: return *cpu_core; } + Common::SynchronizedWrapper perf_stats; + private: /** * Initialize the emulated system. diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 1457518d45..67bab38da0 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -4,6 +4,7 @@ #include "common/bit_field.h" #include "common/microprofile.h" +#include "core/core.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" @@ -280,6 +281,8 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { if (screen_id == 0) { MicroProfileFlip(); + auto perf_stats = Core::System::GetInstance().perf_stats.Lock(); + perf_stats->EndGameFrame(); } return RESULT_SUCCESS; diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index fa8c13d366..7cf081aad4 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -32,7 +32,7 @@ namespace GPU { Regs g_regs; /// 268MHz CPU clocks / 60Hz frames per second -const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60; +const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE; /// Event id for CoreTiming static int vblank_event; /// Total number of frames drawn @@ -41,7 +41,7 @@ static u64 frame_count; static u32 time_point; /// Total delay caused by slow frames static float time_delay; -constexpr float FIXED_FRAME_TIME = 1000.0f / 60; +constexpr float FIXED_FRAME_TIME = 1000.0f / SCREEN_REFRESH_RATE; // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher // values increases time needed to limit frame rate after spikes constexpr float MAX_LAG_TIME = 18; diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index d533812168..bdd997b2a0 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -13,6 +13,8 @@ namespace GPU { +constexpr float SCREEN_REFRESH_RATE = 60; + // Returns index corresponding to the Regs member labeled by field_name // TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions // when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])). diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp new file mode 100644 index 0000000000..6d9e603e4e --- /dev/null +++ b/src/core/perf_stats.cpp @@ -0,0 +1,53 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "core/hw/gpu.h" +#include "core/perf_stats.h" + +namespace Core { + +void PerfStats::BeginSystemFrame() { + frame_begin = Clock::now(); +} + +void PerfStats::EndSystemFrame() { + auto frame_end = Clock::now(); + accumulated_frametime += frame_end - frame_begin; + system_frames += 1; +} + +void PerfStats::EndGameFrame() { + game_frames += 1; +} + +PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { + using DoubleSecs = std::chrono::duration; + using std::chrono::duration_cast; + + auto now = Clock::now(); + // Walltime elapsed since stats were reset + auto interval = duration_cast(now - reset_point).count(); + + auto system_us_per_second = + static_cast(current_system_time_us - reset_point_system_us) / interval; + + Results results{}; + results.system_fps = static_cast(system_frames) / interval; + results.game_fps = static_cast(game_frames) / interval; + results.frametime = duration_cast(accumulated_frametime).count() / + static_cast(system_frames); + results.emulation_speed = system_us_per_second / 1'000'000.0; + + // Reset counters + reset_point = now; + reset_point_system_us = current_system_time_us; + accumulated_frametime = Clock::duration::zero(); + system_frames = 0; + game_frames = 0; + + return results; +} + +} // namespace Core diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h new file mode 100644 index 0000000000..566a1419a0 --- /dev/null +++ b/src/core/perf_stats.h @@ -0,0 +1,43 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" + +namespace Core { + +class PerfStats { +public: + using Clock = std::chrono::high_resolution_clock; + + struct Results { + /// System FPS (LCD VBlanks) in Hz + double system_fps; + /// Game FPS (GSP frame submissions) in Hz + double game_fps; + /// Walltime per system frame, in seconds, excluding any waits + double frametime; + /// Ratio of walltime / emulated time elapsed + double emulation_speed; + }; + + void BeginSystemFrame(); + void EndSystemFrame(); + void EndGameFrame(); + + Results GetAndResetStats(u64 current_system_time_us); + +private: + Clock::time_point reset_point = Clock::now(); + + Clock::time_point frame_begin; + Clock::duration accumulated_frametime = Clock::duration::zero(); + u64 reset_point_system_us = 0; + u32 system_frames = 0; + u32 game_frames = 0; +}; + +} // namespace Core diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 2aa90e5c13..0b90dcb3de 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -12,6 +12,7 @@ #include "common/logging/log.h" #include "common/profiler_reporting.h" #include "common/synchronized_wrapper.h" +#include "core/core.h" #include "core/frontend/emu_window.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" @@ -151,6 +152,10 @@ void RendererOpenGL::SwapBuffers() { auto aggregator = Common::Profiling::GetTimingResultsAggregator(); aggregator->AddFrame(profiler.GetPreviousFrameResults()); } + { + auto perf_stats = Core::System::GetInstance().perf_stats.Lock(); + perf_stats->EndSystemFrame(); + } // Swap buffers render_window->PollEvents(); @@ -159,6 +164,10 @@ void RendererOpenGL::SwapBuffers() { prev_state.Apply(); profiler.BeginFrame(); + { + auto perf_stats = Core::System::GetInstance().perf_stats.Lock(); + perf_stats->BeginSystemFrame(); + } RefreshRasterizerSetting();