From c75ae6c585f651a1b7c162c2e1ecccd22a1c587d Mon Sep 17 00:00:00 2001
From: Yuri Kunde Schlesner <yuriks@yuriks.net>
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> 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 <memory>
 #include <string>
-
 #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<PerfStats> 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 <chrono>
+#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<double, std::chrono::seconds::period>;
+    using std::chrono::duration_cast;
+
+    auto now = Clock::now();
+    // Walltime elapsed since stats were reset
+    auto interval = duration_cast<DoubleSecs>(now - reset_point).count();
+
+    auto system_us_per_second =
+        static_cast<double>(current_system_time_us - reset_point_system_us) / interval;
+
+    Results results{};
+    results.system_fps = static_cast<double>(system_frames) / interval;
+    results.game_fps = static_cast<double>(game_frames) / interval;
+    results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
+                        static_cast<double>(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 <chrono>
+#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();