From 441f8b5a4b16dda4164ef35ce3fccfb5d3eeb39c Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 19 Feb 2017 18:37:14 -0800 Subject: [PATCH 01/13] Core: Remove unnecessary include in thread.h --- src/citra_qt/configure_system.cpp | 1 + src/core/hle/kernel/server_session.h | 1 + src/core/hle/kernel/thread.h | 1 - src/core/hle/service/ldr_ro/ldr_ro.cpp | 1 + 4 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configure_system.cpp index eb1276ef33..040185e82a 100644 --- a/src/citra_qt/configure_system.cpp +++ b/src/citra_qt/configure_system.cpp @@ -4,6 +4,7 @@ #include "citra_qt/configure_system.h" #include "citra_qt/ui_settings.h" +#include "core/core.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "ui_configure_system.h" diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index c088b9a199..4ffe97b780 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "common/assert.h" #include "common/common_types.h" diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c557a22797..6ab31c70bd 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -11,7 +11,6 @@ #include #include "common/common_types.h" #include "core/arm/arm_interface.h" -#include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/result.h" diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index 8d00a75778..7af76676b1 100644 --- a/src/core/hle/service/ldr_ro/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -6,6 +6,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/service/ldr_ro/cro_helper.h" From 1b28b2668274dc266a7f523760a264c1af1044de Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sat, 18 Feb 2017 12:09:14 -0800 Subject: [PATCH 02/13] Qt: Add (empty) status bar --- src/citra_qt/config.cpp | 2 ++ src/citra_qt/game_list.cpp | 1 + src/citra_qt/main.cpp | 16 ++++++++++++++++ src/citra_qt/main.h | 6 ++++++ src/citra_qt/main.ui | 10 +++++++++- src/citra_qt/ui_settings.h | 1 + 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index b65f57fdc2..5fe57dfa2d 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -146,6 +146,7 @@ void Config::ReadValues() { UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); + UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); @@ -252,6 +253,7 @@ void Config::SaveValues() { qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); + qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); qt_config->setValue("firstStart", UISettings::values.first_start); diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 222c82b1c8..f15083b0a7 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -44,6 +44,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { // with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType>("QList"); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(tree_view); setLayout(layout); } diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 513da80010..43530b2752 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -94,6 +94,17 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(); ui.horizontalLayout->addWidget(game_list); + + // Create status bar + emu_speed_label = new QLabel(); + game_fps_label = new QLabel(); + emu_frametime_label = new QLabel(); + + for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { + label->setVisible(false); + statusBar()->addPermanentWidget(label); + } + statusBar()->setVisible(true); } void GMainWindow::InitializeDebugWidgets() { @@ -229,6 +240,9 @@ void GMainWindow::RestoreUIState() { ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); + + ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); + statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); } void GMainWindow::ConnectWidgetEvents() { @@ -261,6 +275,7 @@ void GMainWindow::ConnectMenuEvents() { &GMainWindow::ToggleWindowMode); connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, &GMainWindow::OnDisplayTitleBars); + connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); } void GMainWindow::OnDisplayTitleBars(bool show) { @@ -624,6 +639,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { #endif UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); UISettings::values.first_start = false; game_list->SaveInterfaceLayout(); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 87637b92b1..3cbf4ea99e 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -132,12 +132,18 @@ private: GRenderWindow* render_window; GameList* game_list; + // Status bar elements + QLabel* emu_speed_label = nullptr; + QLabel* game_fps_label = nullptr; + QLabel* emu_frametime_label = nullptr; + std::unique_ptr config; // Whether emulation is currently running in Citra. bool emulation_running = false; std::unique_ptr emu_thread; + // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; DisassemblerWidget* disasmWidget; diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 4a95cda9a0..47dbb6ef74 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -88,6 +88,7 @@ + @@ -101,7 +102,6 @@ - Load File... @@ -167,6 +167,14 @@ Display Dock Widget Headers + + + true + + + Show Status Bar + + Select Game Directory... diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index ed7fdff7ef..6408ece2bf 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -27,6 +27,7 @@ struct Values { bool single_window_mode; bool display_titlebar; + bool show_status_bar; bool confirm_before_closing; bool first_start; From 21f4f49c7aebbd3c716a4065a6d5b2c94c022008 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sat, 18 Feb 2017 18:27:37 -0800 Subject: [PATCH 03/13] SynchronizedWrapper: Add Lock convenience method --- src/common/synchronized_wrapper.h | 43 ++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/common/synchronized_wrapper.h b/src/common/synchronized_wrapper.h index 04b4f2e511..4a1984c46e 100644 --- a/src/common/synchronized_wrapper.h +++ b/src/common/synchronized_wrapper.h @@ -9,25 +9,8 @@ namespace Common { -/** - * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no - * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a - * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type - * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). - */ template -class SynchronizedWrapper { -public: - template - SynchronizedWrapper(Args&&... args) : data(std::forward(args)...) {} - -private: - template - friend class SynchronizedRef; - - std::mutex mutex; - T data; -}; +class SynchronizedWrapper; /** * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This @@ -75,4 +58,28 @@ private: SynchronizedWrapper* wrapper; }; +/** + * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no + * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a + * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type + * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). + */ +template +class SynchronizedWrapper { +public: + template + SynchronizedWrapper(Args&&... args) : data(std::forward(args)...) {} + + SynchronizedRef Lock() { + return {*this}; + } + +private: + template + friend class SynchronizedRef; + + std::mutex mutex; + T data; +}; + } // namespace Common From c75ae6c585f651a1b7c162c2e1ecccd22a1c587d Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 19 Feb 2017 14:34:47 -0800 Subject: [PATCH 04/13] 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(); From 92c8bd4b1f650438274e50303a6d3f668924d071 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 19 Feb 2017 18:18:26 -0800 Subject: [PATCH 05/13] PerfStats: Add method to get the instantaneous time ratio --- src/core/frontend/emu_window.cpp | 5 ++--- src/core/perf_stats.cpp | 14 +++++++++++--- src/core/perf_stats.h | 10 +++++++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 6b46377415..b65d6ff580 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,7 +5,7 @@ #include #include #include "common/assert.h" -#include "common/profiler_reporting.h" +#include "core/core.h" #include "core/frontend/emu_window.h" #include "core/frontend/key_map.h" #include "video_core/video_core.h" @@ -104,8 +104,7 @@ void EmuWindow::AccelerometerChanged(float x, float y, float z) { void EmuWindow::GyroscopeChanged(float x, float y, float z) { constexpr float FULL_FPS = 60; float coef = GetGyroscopeRawToDpsCoefficient(); - float stretch = - FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; + float stretch = Core::System::GetInstance().perf_stats.Lock()->GetLastFrameTimeScale(); std::lock_guard lock(gyro_mutex); gyro_x = static_cast(x * coef * stretch); gyro_y = static_cast(y * coef * stretch); diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 6d9e603e4e..8d9e521a32 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -6,6 +6,9 @@ #include "core/hw/gpu.h" #include "core/perf_stats.h" +using DoubleSecs = std::chrono::duration; +using std::chrono::duration_cast; + namespace Core { void PerfStats::BeginSystemFrame() { @@ -16,6 +19,9 @@ void PerfStats::EndSystemFrame() { auto frame_end = Clock::now(); accumulated_frametime += frame_end - frame_begin; system_frames += 1; + + previous_frame_length = frame_end - previous_frame_end; + previous_frame_end = frame_end; } void PerfStats::EndGameFrame() { @@ -23,9 +29,6 @@ void PerfStats::EndGameFrame() { } 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(); @@ -50,4 +53,9 @@ PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { return results; } +double PerfStats::GetLastFrameTimeScale() { + constexpr double FRAME_LENGTH = 1.0 / GPU::SCREEN_REFRESH_RATE; + return duration_cast(previous_frame_length).count() / FRAME_LENGTH; +} + } // namespace Core diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 566a1419a0..8a03c511a6 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -30,11 +30,19 @@ public: Results GetAndResetStats(u64 current_system_time_us); + /** + * Gets the ratio between walltime and the emulated time of the previous system frame. This is + * useful for scaling inputs or outputs moving between the two time domains. + */ + double GetLastFrameTimeScale(); + private: Clock::time_point reset_point = Clock::now(); - Clock::time_point frame_begin; + Clock::time_point frame_begin = reset_point; + Clock::time_point previous_frame_end = reset_point; Clock::duration accumulated_frametime = Clock::duration::zero(); + Clock::duration previous_frame_length = Clock::duration::zero(); u64 reset_point_system_us = 0; u32 system_frames = 0; u32 game_frames = 0; From 3b4e4003336c94527339a2a9ad7d52875974258f Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 19 Feb 2017 18:35:04 -0800 Subject: [PATCH 06/13] Remove built-in (non-Microprofile) profiler --- src/citra_qt/CMakeLists.txt | 1 - src/citra_qt/debugger/profiler.cpp | 111 +----------------- src/citra_qt/debugger/profiler.h | 40 ------- src/citra_qt/debugger/profiler.ui | 33 ------ src/citra_qt/main.cpp | 5 - src/common/CMakeLists.txt | 2 - src/common/profiler.cpp | 101 ---------------- src/common/profiler_reporting.h | 83 ------------- .../renderer_opengl/renderer_opengl.cpp | 8 -- 9 files changed, 2 insertions(+), 382 deletions(-) delete mode 100644 src/citra_qt/debugger/profiler.ui delete mode 100644 src/common/profiler.cpp delete mode 100644 src/common/profiler_reporting.h diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d4460bf013..15a6ccf9af 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -69,7 +69,6 @@ set(HEADERS set(UIS debugger/callstack.ui debugger/disassembler.ui - debugger/profiler.ui debugger/registers.ui configure.ui configure_audio.ui diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index cee10403d6..f060bbe088 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include #include #include #include @@ -9,121 +11,12 @@ #include "citra_qt/util/util.h" #include "common/common_types.h" #include "common/microprofile.h" -#include "common/profiler_reporting.h" // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the // non-Qt frontends don't need it (and don't implement the UI drawing hooks either). #if MICROPROFILE_ENABLED #define MICROPROFILEUI_IMPL 1 #include "common/microprofileui.h" -#endif - -using namespace Common::Profiling; - -static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) { - static auto duration_to_float = [](Duration dur) -> float { - using FloatMs = std::chrono::duration; - return std::chrono::duration_cast(dur).count(); - }; - - switch (col) { - case 1: - return duration_to_float(duration.avg); - case 2: - return duration_to_float(duration.min); - case 3: - return duration_to_float(duration.max); - default: - return QVariant(); - } -} - -ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) { - updateProfilingInfo(); -} - -QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (section) { - case 0: - return tr("Category"); - case 1: - return tr("Avg"); - case 2: - return tr("Min"); - case 3: - return tr("Max"); - } - } - - return QVariant(); -} - -QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const { - return createIndex(row, column); -} - -QModelIndex ProfilerModel::parent(const QModelIndex& child) const { - return QModelIndex(); -} - -int ProfilerModel::columnCount(const QModelIndex& parent) const { - return 4; -} - -int ProfilerModel::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) { - return 0; - } else { - return 2; - } -} - -QVariant ProfilerModel::data(const QModelIndex& index, int role) const { - if (role == Qt::DisplayRole) { - if (index.row() == 0) { - if (index.column() == 0) { - return tr("Frame"); - } else { - return GetDataForColumn(index.column(), results.frame_time); - } - } else if (index.row() == 1) { - if (index.column() == 0) { - return tr("Frame (with swapping)"); - } else { - return GetDataForColumn(index.column(), results.interframe_time); - } - } - } - - return QVariant(); -} - -void ProfilerModel::updateProfilingInfo() { - results = GetTimingResultsAggregator()->GetAggregatedResults(); - emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); -} - -ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) { - ui.setupUi(this); - - model = new ProfilerModel(this); - ui.treeView->setModel(model); - - connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); - connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); -} - -void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) { - if (enable) { - update_timer.start(100); - model->updateProfilingInfo(); - } else { - update_timer.stop(); - } -} - -#if MICROPROFILE_ENABLED class MicroProfileWidget : public QWidget { public: diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index c8912fd5a4..eae1e9e3c6 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h @@ -8,46 +8,6 @@ #include #include #include "common/microprofile.h" -#include "common/profiler_reporting.h" -#include "ui_profiler.h" - -class ProfilerModel : public QAbstractItemModel { - Q_OBJECT - -public: - explicit ProfilerModel(QObject* parent); - - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, - const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& child) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - -public slots: - void updateProfilingInfo(); - -private: - Common::Profiling::AggregatedFrameResult results; -}; - -class ProfilerWidget : public QDockWidget { - Q_OBJECT - -public: - explicit ProfilerWidget(QWidget* parent = nullptr); - -private slots: - void setProfilingInfoUpdateEnabled(bool enable); - -private: - Ui::Profiler ui; - ProfilerModel* model; - - QTimer update_timer; -}; class MicroProfileDialog : public QWidget { Q_OBJECT diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui deleted file mode 100644 index d3c9a9a1fb..0000000000 --- a/src/citra_qt/debugger/profiler.ui +++ /dev/null @@ -1,33 +0,0 @@ - - - Profiler - - - - 0 - 0 - 400 - 300 - - - - Profiler - - - - - - - true - - - true - - - - - - - - - diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 41356a6caa..138763080d 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -113,11 +113,6 @@ void GMainWindow::InitializeDebugWidgets() { QMenu* debug_menu = ui.menu_View_Debugging; - profilerWidget = new ProfilerWidget(this); - addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); - profilerWidget->hide(); - debug_menu->addAction(profilerWidget->toggleViewAction()); - #if MICROPROFILE_ENABLED microProfileDialog = new MicroProfileDialog(this); microProfileDialog->hide(); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 592911c2b1..aca0ebe383 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -12,7 +12,6 @@ set(SRCS memory_util.cpp microprofile.cpp misc.cpp - profiler.cpp scm_rev.cpp string_util.cpp symbols.cpp @@ -45,7 +44,6 @@ set(HEADERS microprofile.h microprofileui.h platform.h - profiler_reporting.h quaternion.h scm_rev.h scope_exit.h diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp deleted file mode 100644 index b40e7205d9..0000000000 --- a/src/common/profiler.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include "common/assert.h" -#include "common/profiler_reporting.h" -#include "common/synchronized_wrapper.h" - -namespace Common { -namespace Profiling { - -ProfilingManager::ProfilingManager() - : last_frame_end(Clock::now()), this_frame_start(Clock::now()) {} - -void ProfilingManager::BeginFrame() { - this_frame_start = Clock::now(); -} - -void ProfilingManager::FinishFrame() { - Clock::time_point now = Clock::now(); - - results.interframe_time = now - last_frame_end; - results.frame_time = now - this_frame_start; - - last_frame_end = now; -} - -TimingResultsAggregator::TimingResultsAggregator(size_t window_size) - : max_window_size(window_size), window_size(0) { - interframe_times.resize(window_size, Duration::zero()); - frame_times.resize(window_size, Duration::zero()); -} - -void TimingResultsAggregator::Clear() { - window_size = cursor = 0; -} - -void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) { - interframe_times[cursor] = frame_result.interframe_time; - frame_times[cursor] = frame_result.frame_time; - - ++cursor; - if (cursor == max_window_size) - cursor = 0; - if (window_size < max_window_size) - ++window_size; -} - -static AggregatedDuration AggregateField(const std::vector& v, size_t len) { - AggregatedDuration result; - result.avg = Duration::zero(); - result.min = result.max = (len == 0 ? Duration::zero() : v[0]); - - for (size_t i = 0; i < len; ++i) { - Duration value = v[i]; - result.avg += value; - result.min = std::min(result.min, value); - result.max = std::max(result.max, value); - } - if (len != 0) - result.avg /= len; - - return result; -} - -static float tof(Common::Profiling::Duration dur) { - using FloatMs = std::chrono::duration; - return std::chrono::duration_cast(dur).count(); -} - -AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const { - AggregatedFrameResult result; - - result.interframe_time = AggregateField(interframe_times, window_size); - result.frame_time = AggregateField(frame_times, window_size); - - if (result.interframe_time.avg != Duration::zero()) { - result.fps = 1000.0f / tof(result.interframe_time.avg); - } else { - result.fps = 0.0f; - } - - return result; -} - -ProfilingManager& GetProfilingManager() { - // Takes advantage of "magic" static initialization for race-free initialization. - static ProfilingManager manager; - return manager; -} - -SynchronizedRef GetTimingResultsAggregator() { - static SynchronizedWrapper aggregator(30); - return SynchronizedRef(aggregator); -} - -} // namespace Profiling -} // namespace Common diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h deleted file mode 100644 index e9ce6d41c6..0000000000 --- a/src/common/profiler_reporting.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include "common/synchronized_wrapper.h" - -namespace Common { -namespace Profiling { - -using Clock = std::chrono::high_resolution_clock; -using Duration = Clock::duration; - -struct ProfilingFrameResult { - /// Time since the last delivered frame - Duration interframe_time; - - /// Time spent processing a frame, excluding VSync - Duration frame_time; -}; - -class ProfilingManager final { -public: - ProfilingManager(); - - /// This should be called after swapping screen buffers. - void BeginFrame(); - /// This should be called before swapping screen buffers. - void FinishFrame(); - - /// Get the timing results from the previous frame. This is updated when you call FinishFrame(). - const ProfilingFrameResult& GetPreviousFrameResults() const { - return results; - } - -private: - Clock::time_point last_frame_end; - Clock::time_point this_frame_start; - - ProfilingFrameResult results; -}; - -struct AggregatedDuration { - Duration avg, min, max; -}; - -struct AggregatedFrameResult { - /// Time since the last delivered frame - AggregatedDuration interframe_time; - - /// Time spent processing a frame, excluding VSync - AggregatedDuration frame_time; - - float fps; -}; - -class TimingResultsAggregator final { -public: - TimingResultsAggregator(size_t window_size); - - void Clear(); - - void AddFrame(const ProfilingFrameResult& frame_result); - - AggregatedFrameResult GetAggregatedResults() const; - - size_t max_window_size; - size_t window_size; - size_t cursor; - - std::vector interframe_times; - std::vector frame_times; -}; - -ProfilingManager& GetProfilingManager(); -SynchronizedRef GetTimingResultsAggregator(); - -} // namespace Profiling -} // namespace Common diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 0b90dcb3de..6bc142148b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -10,7 +10,6 @@ #include "common/assert.h" #include "common/bit_field.h" #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" @@ -146,12 +145,6 @@ void RendererOpenGL::SwapBuffers() { DrawScreens(); - auto& profiler = Common::Profiling::GetProfilingManager(); - profiler.FinishFrame(); - { - auto aggregator = Common::Profiling::GetTimingResultsAggregator(); - aggregator->AddFrame(profiler.GetPreviousFrameResults()); - } { auto perf_stats = Core::System::GetInstance().perf_stats.Lock(); perf_stats->EndSystemFrame(); @@ -163,7 +156,6 @@ void RendererOpenGL::SwapBuffers() { prev_state.Apply(); - profiler.BeginFrame(); { auto perf_stats = Core::System::GetInstance().perf_stats.Lock(); perf_stats->BeginSystemFrame(); From 008c709dbf66e748c8b7227ba34187939535e479 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 19 Feb 2017 18:56:26 -0800 Subject: [PATCH 07/13] Qt: Don't show fractional figures in the status bar They're not very important and this makes the display changes less often, making it less distracting. --- src/citra_qt/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 138763080d..955faf7486 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -627,8 +627,8 @@ void GMainWindow::UpdateStatusBar() { 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_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); + game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); emu_speed_label->setVisible(true); From f273959205fbafd80a256117bc53b3c79517cbdb Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 19 Feb 2017 19:09:46 -0800 Subject: [PATCH 08/13] Qt: Add tooltips to status bar displays --- src/citra_qt/main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 955faf7486..cbfb267abb 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -97,8 +97,15 @@ void GMainWindow::InitializeWidgets() { // Create status bar emu_speed_label = new QLabel(); + emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " + "indicate emulation is running faster or slower than a 3DS.")); game_fps_label = new QLabel(); + game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. " + "This will vary from game to game and scene to scene.")); emu_frametime_label = new QLabel(); + emu_frametime_label->setToolTip( + tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " + "full-speed emulation this should be at most 16.67 ms.")); for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { label->setVisible(false); From b285c2a4ed29a126b5bcfe46e2784bd1870bdf82 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Mon, 20 Feb 2017 13:56:58 -0800 Subject: [PATCH 09/13] Core: Make PerfStats internally locked More ergonomic to use and will be required for upcoming changes. --- src/core/core.cpp | 5 ++--- src/core/core.h | 3 +-- src/core/frontend/emu_window.cpp | 2 +- src/core/hle/service/gsp_gpu.cpp | 3 +-- src/core/perf_stats.cpp | 11 +++++++++++ src/core/perf_stats.h | 7 +++++++ src/video_core/renderer_opengl/renderer_opengl.cpp | 10 ++-------- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index ca2c28ce4e..140ff64511 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -110,8 +110,7 @@ void System::PrepareReschedule() { } PerfStats::Results System::GetAndResetPerfStats() { - auto perf_stats = this->perf_stats.Lock(); - return perf_stats->GetAndResetStats(CoreTiming::GetGlobalTimeUs()); + return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); } void System::Reschedule() { @@ -147,7 +146,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { // Reset counters and set time origin to current frame GetAndResetPerfStats(); - perf_stats.Lock()->BeginSystemFrame(); + perf_stats.BeginSystemFrame(); return ResultStatus::Success; } diff --git a/src/core/core.h b/src/core/core.h index 3efc20c3d6..db3b98a052 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -7,7 +7,6 @@ #include #include #include "common/common_types.h" -#include "common/synchronized_wrapper.h" #include "core/memory.h" #include "core/perf_stats.h" @@ -94,7 +93,7 @@ public: return *cpu_core; } - Common::SynchronizedWrapper perf_stats; + PerfStats perf_stats; private: /** diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index b65d6ff580..a155b657dd 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -104,7 +104,7 @@ void EmuWindow::AccelerometerChanged(float x, float y, float z) { void EmuWindow::GyroscopeChanged(float x, float y, float z) { constexpr float FULL_FPS = 60; float coef = GetGyroscopeRawToDpsCoefficient(); - float stretch = Core::System::GetInstance().perf_stats.Lock()->GetLastFrameTimeScale(); + float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); std::lock_guard lock(gyro_mutex); gyro_x = static_cast(x * coef * stretch); gyro_y = static_cast(y * coef * stretch); diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 67bab38da0..097ed87e4d 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -281,8 +281,7 @@ 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(); + Core::System::GetInstance().perf_stats.EndGameFrame(); } return RESULT_SUCCESS; diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 8d9e521a32..06bc788bd7 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include "core/hw/gpu.h" #include "core/perf_stats.h" @@ -12,10 +13,14 @@ using std::chrono::duration_cast; namespace Core { void PerfStats::BeginSystemFrame() { + std::lock_guard lock(object_mutex); + frame_begin = Clock::now(); } void PerfStats::EndSystemFrame() { + std::lock_guard lock(object_mutex); + auto frame_end = Clock::now(); accumulated_frametime += frame_end - frame_begin; system_frames += 1; @@ -25,10 +30,14 @@ void PerfStats::EndSystemFrame() { } void PerfStats::EndGameFrame() { + std::lock_guard lock(object_mutex); + game_frames += 1; } PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { + std::lock_guard lock(object_mutex); + auto now = Clock::now(); // Walltime elapsed since stats were reset auto interval = duration_cast(now - reset_point).count(); @@ -54,6 +63,8 @@ PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { } double PerfStats::GetLastFrameTimeScale() { + std::lock_guard lock(object_mutex); + constexpr double FRAME_LENGTH = 1.0 / GPU::SCREEN_REFRESH_RATE; return duration_cast(previous_frame_length).count() / FRAME_LENGTH; } diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 8a03c511a6..4098fc1f26 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -5,10 +5,15 @@ #pragma once #include +#include #include "common/common_types.h" namespace Core { +/** + * Class to manage and query performance/timing statistics. All public functions of this class are + * thread-safe unless stated otherwise. + */ class PerfStats { public: using Clock = std::chrono::high_resolution_clock; @@ -37,6 +42,8 @@ public: double GetLastFrameTimeScale(); private: + std::mutex object_mutex; + Clock::time_point reset_point = Clock::now(); Clock::time_point frame_begin = reset_point; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 6bc142148b..b3604106c4 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -145,10 +145,7 @@ void RendererOpenGL::SwapBuffers() { DrawScreens(); - { - auto perf_stats = Core::System::GetInstance().perf_stats.Lock(); - perf_stats->EndSystemFrame(); - } + Core::System::GetInstance().perf_stats.EndSystemFrame(); // Swap buffers render_window->PollEvents(); @@ -156,10 +153,7 @@ void RendererOpenGL::SwapBuffers() { prev_state.Apply(); - { - auto perf_stats = Core::System::GetInstance().perf_stats.Lock(); - perf_stats->BeginSystemFrame(); - } + Core::System::GetInstance().perf_stats.BeginSystemFrame(); RefreshRasterizerSetting(); From fb1979d7e26c20fe2b8d2c3d3dc998e5e00f2f61 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Mon, 20 Feb 2017 16:31:59 -0800 Subject: [PATCH 10/13] Core: Re-write frame limiter Now based on std::chrono, and also works in terms of emulated time instead of frames, so we can in the future frame-limit even when the display is disabled, etc. The frame limiter can also be enabled along with v-sync now, which should be useful for those with displays running at more than 60 Hz. --- src/core/core.h | 1 + src/core/hw/gpu.cpp | 39 ------------------- src/core/perf_stats.cpp | 33 ++++++++++++++++ src/core/perf_stats.h | 16 ++++++++ .../renderer_opengl/renderer_opengl.cpp | 6 +-- 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/core/core.h b/src/core/core.h index db3b98a052..6c9c936b52 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -94,6 +94,7 @@ public: } PerfStats perf_stats; + FrameLimiter frame_limiter; private: /** diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 7cf081aad4..42809c7310 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -8,17 +8,13 @@ #include "common/color.h" #include "common/common_types.h" #include "common/logging/log.h" -#include "common/math_util.h" #include "common/microprofile.h" -#include "common/thread.h" -#include "common/timer.h" #include "common/vector_math.h" #include "core/core_timing.h" #include "core/hle/service/gsp_gpu.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/memory.h" -#include "core/settings.h" #include "core/tracer/recorder.h" #include "video_core/command_processor.h" #include "video_core/debug_utils/debug_utils.h" @@ -35,16 +31,6 @@ Regs g_regs; const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE; /// Event id for CoreTiming static int vblank_event; -/// Total number of frames drawn -static u64 frame_count; -/// Start clock for frame limiter -static u32 time_point; -/// Total delay caused by slow frames -static float time_delay; -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; template inline void Read(T& var, const u32 raw_addr) { @@ -522,24 +508,8 @@ template void Write(u32 addr, const u32 data); template void Write(u32 addr, const u16 data); template void Write(u32 addr, const u8 data); -static void FrameLimiter() { - time_delay += FIXED_FRAME_TIME; - time_delay = MathUtil::Clamp(time_delay, -MAX_LAG_TIME, MAX_LAG_TIME); - s32 desired_time = static_cast(time_delay); - s32 elapsed_time = static_cast(Common::Timer::GetTimeMs() - time_point); - - if (elapsed_time < desired_time) { - Common::SleepCurrentThread(desired_time - elapsed_time); - } - - u32 frame_time = Common::Timer::GetTimeMs() - time_point; - - time_delay -= frame_time; -} - /// Update hardware static void VBlankCallback(u64 userdata, int cycles_late) { - frame_count++; VideoCore::g_renderer->SwapBuffers(); // Signal to GSP that GPU interrupt has occurred @@ -550,12 +520,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0); Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); - if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) { - FrameLimiter(); - } - - time_point = Common::Timer::GetTimeMs(); - // Reschedule recurrent event CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); } @@ -590,9 +554,6 @@ void Init() { framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8); framebuffer_sub.active_fb = 0; - frame_count = 0; - time_point = Common::Timer::GetTimeMs(); - vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); CoreTiming::ScheduleEvent(frame_ticks, vblank_event); diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 06bc788bd7..eb59a13321 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -4,11 +4,16 @@ #include #include +#include +#include "common/math_util.h" #include "core/hw/gpu.h" #include "core/perf_stats.h" +#include "core/settings.h" +using namespace std::chrono_literals; using DoubleSecs = std::chrono::duration; using std::chrono::duration_cast; +using std::chrono::microseconds; namespace Core { @@ -69,4 +74,32 @@ double PerfStats::GetLastFrameTimeScale() { return duration_cast(previous_frame_length).count() / FRAME_LENGTH; } +void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) { + // 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 microseconds MAX_LAG_TIME_US = 25ms; + + if (!Settings::values.toggle_framelimit) { + return; + } + + auto now = Clock::now(); + + frame_limiting_delta_err += microseconds(current_system_time_us - previous_system_time_us); + frame_limiting_delta_err -= duration_cast(now - previous_walltime); + frame_limiting_delta_err = + MathUtil::Clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US); + + if (frame_limiting_delta_err > microseconds::zero()) { + std::this_thread::sleep_for(frame_limiting_delta_err); + + auto now_after_sleep = Clock::now(); + frame_limiting_delta_err -= duration_cast(now_after_sleep - now); + now = now_after_sleep; + } + + previous_system_time_us = current_system_time_us; + previous_walltime = now; +} + } // namespace Core diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 4098fc1f26..b03adab68a 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -55,4 +55,20 @@ private: u32 game_frames = 0; }; +class FrameLimiter { +public: + using Clock = std::chrono::high_resolution_clock; + + void DoFrameLimiting(u64 current_system_time_us); + +private: + /// Emulated system time (in microseconds) at the last limiter invocation + u64 previous_system_time_us = 0; + /// Walltime at the last limiter invocation + Clock::time_point previous_walltime = Clock::now(); + + /// Accumulated difference between walltime and emulated time + std::chrono::microseconds frame_limiting_delta_err{0}; +}; + } // namespace Core diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index b3604106c4..e193754663 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -10,8 +10,8 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging/log.h" -#include "common/synchronized_wrapper.h" #include "core/core.h" +#include "core/core_timing.h" #include "core/frontend/emu_window.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" @@ -151,10 +151,10 @@ void RendererOpenGL::SwapBuffers() { render_window->PollEvents(); render_window->SwapBuffers(); - prev_state.Apply(); - + Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); Core::System::GetInstance().perf_stats.BeginSystemFrame(); + prev_state.Apply(); RefreshRasterizerSetting(); if (Pica::g_debug_context && Pica::g_debug_context->recorder) { From 915d69ac4c79a867cf1693b25567331e63650c27 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Mon, 20 Feb 2017 16:36:44 -0800 Subject: [PATCH 11/13] Qt: Increase status bar update interval to 2 seconds --- src/citra_qt/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index cbfb267abb..003cfc8394 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -405,7 +405,7 @@ void GMainWindow::BootGame(const QString& filename) { if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); } - status_bar_update_timer.start(1000); + status_bar_update_timer.start(2000); render_window->show(); render_window->setFocus(); From 1469b3212b7bbc320844e3ea747c995bf3437552 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Mon, 20 Feb 2017 16:53:40 -0800 Subject: [PATCH 12/13] Qt: Tweak status bar styling --- src/citra_qt/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 003cfc8394..e1661ca9ad 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -109,6 +109,8 @@ void GMainWindow::InitializeWidgets() { for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { label->setVisible(false); + label->setFrameStyle(QFrame::NoFrame); + label->setContentsMargins(4, 0, 4, 0); statusBar()->addPermanentWidget(label); } statusBar()->setVisible(true); From 174464a87f2e1709597bc1e0cb08c877487a771b Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sun, 26 Feb 2017 17:13:12 -0800 Subject: [PATCH 13/13] PerfStats: Re-order and document members better --- src/core/perf_stats.cpp | 2 +- src/core/perf_stats.h | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index eb59a13321..2cdfb9dedd 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -76,7 +76,7 @@ double PerfStats::GetLastFrameTimeScale() { void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) { // 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. + // values increase the time needed to recover and limit framerate again after spikes. constexpr microseconds MAX_LAG_TIME_US = 25ms; if (!Settings::values.toggle_framelimit) { diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index b03adab68a..362b205c86 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -44,15 +44,24 @@ public: private: std::mutex object_mutex; + /// Point when the cumulative counters were reset Clock::time_point reset_point = Clock::now(); - - Clock::time_point frame_begin = reset_point; - Clock::time_point previous_frame_end = reset_point; - Clock::duration accumulated_frametime = Clock::duration::zero(); - Clock::duration previous_frame_length = Clock::duration::zero(); + /// System time when the cumulative counters were reset u64 reset_point_system_us = 0; + + /// Cumulative duration (excluding v-sync/frame-limiting) of frames since last reset + Clock::duration accumulated_frametime = Clock::duration::zero(); + /// Cumulative number of system frames (LCD VBlanks) presented since last reset u32 system_frames = 0; + /// Cumulative number of game frames (GSP frame submissions) since last reset u32 game_frames = 0; + + /// Point when the previous system frame ended + Clock::time_point previous_frame_end = reset_point; + /// Point when the current system frame began + Clock::time_point frame_begin = reset_point; + /// Total visible duration (including frame-limiting, etc.) of the previous system frame + Clock::duration previous_frame_length = Clock::duration::zero(); }; class FrameLimiter {