mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge pull request #2587 from yuriks/status-bar
Replace built-in Profiler with indicators in status bar
This commit is contained in:
		
						commit
						b250ce21b9
					
				
					 28 changed files with 321 additions and 449 deletions
				
			
		|  | @ -69,7 +69,6 @@ set(HEADERS | ||||||
| set(UIS | set(UIS | ||||||
|             debugger/callstack.ui |             debugger/callstack.ui | ||||||
|             debugger/disassembler.ui |             debugger/disassembler.ui | ||||||
|             debugger/profiler.ui |  | ||||||
|             debugger/registers.ui |             debugger/registers.ui | ||||||
|             configure.ui |             configure.ui | ||||||
|             configure_audio.ui |             configure_audio.ui | ||||||
|  |  | ||||||
|  | @ -146,6 +146,7 @@ void Config::ReadValues() { | ||||||
| 
 | 
 | ||||||
|     UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); |     UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); | ||||||
|     UISettings::values.display_titlebar = qt_config->value("displayTitleBars", 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.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); | ||||||
|     UISettings::values.first_start = qt_config->value("firstStart", 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("singleWindowMode", UISettings::values.single_window_mode); | ||||||
|     qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); |     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("confirmClose", UISettings::values.confirm_before_closing); | ||||||
|     qt_config->setValue("firstStart", UISettings::values.first_start); |     qt_config->setValue("firstStart", UISettings::values.first_start); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "citra_qt/configure_system.h" | #include "citra_qt/configure_system.h" | ||||||
| #include "citra_qt/ui_settings.h" | #include "citra_qt/ui_settings.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/hle/service/cfg/cfg.h" | #include "core/hle/service/cfg/cfg.h" | ||||||
| #include "core/hle/service/fs/archive.h" | #include "core/hle/service/fs/archive.h" | ||||||
| #include "ui_configure_system.h" | #include "ui_configure_system.h" | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #include <QAction> | ||||||
|  | #include <QLayout> | ||||||
| #include <QMouseEvent> | #include <QMouseEvent> | ||||||
| #include <QPainter> | #include <QPainter> | ||||||
| #include <QString> | #include <QString> | ||||||
|  | @ -9,121 +11,12 @@ | ||||||
| #include "citra_qt/util/util.h" | #include "citra_qt/util/util.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/microprofile.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
 | // 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).
 | // non-Qt frontends don't need it (and don't implement the UI drawing hooks either).
 | ||||||
| #if MICROPROFILE_ENABLED | #if MICROPROFILE_ENABLED | ||||||
| #define MICROPROFILEUI_IMPL 1 | #define MICROPROFILEUI_IMPL 1 | ||||||
| #include "common/microprofileui.h" | #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<float, std::chrono::milliseconds::period>; |  | ||||||
|         return std::chrono::duration_cast<FloatMs>(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 { | class MicroProfileWidget : public QWidget { | ||||||
| public: | public: | ||||||
|  |  | ||||||
|  | @ -8,46 +8,6 @@ | ||||||
| #include <QDockWidget> | #include <QDockWidget> | ||||||
| #include <QTimer> | #include <QTimer> | ||||||
| #include "common/microprofile.h" | #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 { | class MicroProfileDialog : public QWidget { | ||||||
|     Q_OBJECT |     Q_OBJECT | ||||||
|  |  | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <ui version="4.0"> |  | ||||||
|  <class>Profiler</class> |  | ||||||
|  <widget class="QDockWidget" name="Profiler"> |  | ||||||
|   <property name="geometry"> |  | ||||||
|    <rect> |  | ||||||
|     <x>0</x> |  | ||||||
|     <y>0</y> |  | ||||||
|     <width>400</width> |  | ||||||
|     <height>300</height> |  | ||||||
|    </rect> |  | ||||||
|   </property> |  | ||||||
|   <property name="windowTitle"> |  | ||||||
|    <string>Profiler</string> |  | ||||||
|   </property> |  | ||||||
|   <widget class="QWidget" name="dockWidgetContents"> |  | ||||||
|    <layout class="QVBoxLayout" name="verticalLayout"> |  | ||||||
|     <item> |  | ||||||
|      <widget class="QTreeView" name="treeView"> |  | ||||||
|       <property name="alternatingRowColors"> |  | ||||||
|        <bool>true</bool> |  | ||||||
|       </property> |  | ||||||
|       <property name="uniformRowHeights"> |  | ||||||
|        <bool>true</bool> |  | ||||||
|       </property> |  | ||||||
|      </widget> |  | ||||||
|     </item> |  | ||||||
|    </layout> |  | ||||||
|   </widget> |  | ||||||
|  </widget> |  | ||||||
|  <resources/> |  | ||||||
|  <connections/> |  | ||||||
| </ui> |  | ||||||
|  | @ -45,6 +45,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { | ||||||
|     // with signals/slots. In this case, QList falls under the umbrells of custom types.
 |     // with signals/slots. In this case, QList falls under the umbrells of custom types.
 | ||||||
|     qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); |     qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | ||||||
| 
 | 
 | ||||||
|  |     layout->setContentsMargins(0, 0, 0, 0); | ||||||
|     layout->addWidget(tree_view); |     layout->addWidget(tree_view); | ||||||
|     setLayout(layout); |     setLayout(layout); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -95,6 +95,26 @@ void GMainWindow::InitializeWidgets() { | ||||||
| 
 | 
 | ||||||
|     game_list = new GameList(); |     game_list = new GameList(); | ||||||
|     ui.horizontalLayout->addWidget(game_list); |     ui.horizontalLayout->addWidget(game_list); | ||||||
|  | 
 | ||||||
|  |     // 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); | ||||||
|  |         label->setFrameStyle(QFrame::NoFrame); | ||||||
|  |         label->setContentsMargins(4, 0, 4, 0); | ||||||
|  |         statusBar()->addPermanentWidget(label); | ||||||
|  |     } | ||||||
|  |     statusBar()->setVisible(true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GMainWindow::InitializeDebugWidgets() { | void GMainWindow::InitializeDebugWidgets() { | ||||||
|  | @ -103,11 +123,6 @@ void GMainWindow::InitializeDebugWidgets() { | ||||||
| 
 | 
 | ||||||
|     QMenu* debug_menu = ui.menu_View_Debugging; |     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 | #if MICROPROFILE_ENABLED | ||||||
|     microProfileDialog = new MicroProfileDialog(this); |     microProfileDialog = new MicroProfileDialog(this); | ||||||
|     microProfileDialog->hide(); |     microProfileDialog->hide(); | ||||||
|  | @ -230,6 +245,9 @@ void GMainWindow::RestoreUIState() { | ||||||
| 
 | 
 | ||||||
|     ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); |     ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); | ||||||
|     OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); |     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() { | void GMainWindow::ConnectWidgetEvents() { | ||||||
|  | @ -240,6 +258,8 @@ void GMainWindow::ConnectWidgetEvents() { | ||||||
|     connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, |     connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, | ||||||
|             SLOT(OnEmulationStarting(EmuThread*))); |             SLOT(OnEmulationStarting(EmuThread*))); | ||||||
|     connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); |     connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); | ||||||
|  | 
 | ||||||
|  |     connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GMainWindow::ConnectMenuEvents() { | void GMainWindow::ConnectMenuEvents() { | ||||||
|  | @ -262,6 +282,7 @@ void GMainWindow::ConnectMenuEvents() { | ||||||
|             &GMainWindow::ToggleWindowMode); |             &GMainWindow::ToggleWindowMode); | ||||||
|     connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, |     connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, | ||||||
|             &GMainWindow::OnDisplayTitleBars); |             &GMainWindow::OnDisplayTitleBars); | ||||||
|  |     connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GMainWindow::OnDisplayTitleBars(bool show) { | void GMainWindow::OnDisplayTitleBars(bool show) { | ||||||
|  | @ -387,6 +408,8 @@ void GMainWindow::BootGame(const QString& filename) { | ||||||
|     if (ui.action_Single_Window_Mode->isChecked()) { |     if (ui.action_Single_Window_Mode->isChecked()) { | ||||||
|         game_list->hide(); |         game_list->hide(); | ||||||
|     } |     } | ||||||
|  |     status_bar_update_timer.start(2000); | ||||||
|  | 
 | ||||||
|     render_window->show(); |     render_window->show(); | ||||||
|     render_window->setFocus(); |     render_window->setFocus(); | ||||||
| 
 | 
 | ||||||
|  | @ -421,6 +444,12 @@ void GMainWindow::ShutdownGame() { | ||||||
|     render_window->hide(); |     render_window->hide(); | ||||||
|     game_list->show(); |     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; |     emulation_running = false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -600,6 +629,23 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { | ||||||
|     graphicsSurfaceViewerWidget->show(); |     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', 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); | ||||||
|  |     game_fps_label->setVisible(true); | ||||||
|  |     emu_frametime_label->setVisible(true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool GMainWindow::ConfirmClose() { | bool GMainWindow::ConfirmClose() { | ||||||
|     if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) |     if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) | ||||||
|         return true; |         return true; | ||||||
|  | @ -625,6 +671,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { | ||||||
| #endif | #endif | ||||||
|     UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); |     UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); | ||||||
|     UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->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; |     UISettings::values.first_start = false; | ||||||
| 
 | 
 | ||||||
|     game_list->SaveInterfaceLayout(); |     game_list->SaveInterfaceLayout(); | ||||||
|  |  | ||||||
|  | @ -127,17 +127,26 @@ private slots: | ||||||
|     void OnCreateGraphicsSurfaceViewer(); |     void OnCreateGraphicsSurfaceViewer(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |     void UpdateStatusBar(); | ||||||
|  | 
 | ||||||
|     Ui::MainWindow ui; |     Ui::MainWindow ui; | ||||||
| 
 | 
 | ||||||
|     GRenderWindow* render_window; |     GRenderWindow* render_window; | ||||||
|     GameList* game_list; |     GameList* game_list; | ||||||
| 
 | 
 | ||||||
|  |     // Status bar elements
 | ||||||
|  |     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; |     std::unique_ptr<Config> config; | ||||||
| 
 | 
 | ||||||
|     // Whether emulation is currently running in Citra.
 |     // Whether emulation is currently running in Citra.
 | ||||||
|     bool emulation_running = false; |     bool emulation_running = false; | ||||||
|     std::unique_ptr<EmuThread> emu_thread; |     std::unique_ptr<EmuThread> emu_thread; | ||||||
| 
 | 
 | ||||||
|  |     // Debugger panes
 | ||||||
|     ProfilerWidget* profilerWidget; |     ProfilerWidget* profilerWidget; | ||||||
|     MicroProfileDialog* microProfileDialog; |     MicroProfileDialog* microProfileDialog; | ||||||
|     DisassemblerWidget* disasmWidget; |     DisassemblerWidget* disasmWidget; | ||||||
|  |  | ||||||
|  | @ -88,6 +88,7 @@ | ||||||
|     </widget> |     </widget> | ||||||
|     <addaction name="action_Single_Window_Mode"/> |     <addaction name="action_Single_Window_Mode"/> | ||||||
|     <addaction name="action_Display_Dock_Widget_Headers"/> |     <addaction name="action_Display_Dock_Widget_Headers"/> | ||||||
|  |     <addaction name="action_Show_Status_Bar"/> | ||||||
|     <addaction name="menu_View_Debugging"/> |     <addaction name="menu_View_Debugging"/> | ||||||
|    </widget> |    </widget> | ||||||
|    <widget class="QMenu" name="menu_Help"> |    <widget class="QMenu" name="menu_Help"> | ||||||
|  | @ -101,7 +102,6 @@ | ||||||
|    <addaction name="menu_View"/> |    <addaction name="menu_View"/> | ||||||
|    <addaction name="menu_Help"/> |    <addaction name="menu_Help"/> | ||||||
|   </widget> |   </widget> | ||||||
|   <widget class="QStatusBar" name="statusbar"/> |  | ||||||
|   <action name="action_Load_File"> |   <action name="action_Load_File"> | ||||||
|    <property name="text"> |    <property name="text"> | ||||||
|     <string>Load File...</string> |     <string>Load File...</string> | ||||||
|  | @ -167,6 +167,14 @@ | ||||||
|     <string>Display Dock Widget Headers</string> |     <string>Display Dock Widget Headers</string> | ||||||
|    </property> |    </property> | ||||||
|   </action> |   </action> | ||||||
|  |   <action name="action_Show_Status_Bar"> | ||||||
|  |    <property name="checkable"> | ||||||
|  |     <bool>true</bool> | ||||||
|  |    </property> | ||||||
|  |    <property name="text"> | ||||||
|  |     <string>Show Status Bar</string> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|   <action name="action_Select_Game_List_Root"> |   <action name="action_Select_Game_List_Root"> | ||||||
|    <property name="text"> |    <property name="text"> | ||||||
|     <string>Select Game Directory...</string> |     <string>Select Game Directory...</string> | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ struct Values { | ||||||
| 
 | 
 | ||||||
|     bool single_window_mode; |     bool single_window_mode; | ||||||
|     bool display_titlebar; |     bool display_titlebar; | ||||||
|  |     bool show_status_bar; | ||||||
| 
 | 
 | ||||||
|     bool confirm_before_closing; |     bool confirm_before_closing; | ||||||
|     bool first_start; |     bool first_start; | ||||||
|  |  | ||||||
|  | @ -35,7 +35,6 @@ set(SRCS | ||||||
|             memory_util.cpp |             memory_util.cpp | ||||||
|             microprofile.cpp |             microprofile.cpp | ||||||
|             misc.cpp |             misc.cpp | ||||||
|             profiler.cpp |  | ||||||
|             scm_rev.cpp |             scm_rev.cpp | ||||||
|             string_util.cpp |             string_util.cpp | ||||||
|             symbols.cpp |             symbols.cpp | ||||||
|  | @ -68,7 +67,6 @@ set(HEADERS | ||||||
|             microprofile.h |             microprofile.h | ||||||
|             microprofileui.h |             microprofileui.h | ||||||
|             platform.h |             platform.h | ||||||
|             profiler_reporting.h |  | ||||||
|             quaternion.h |             quaternion.h | ||||||
|             scm_rev.h |             scm_rev.h | ||||||
|             scope_exit.h |             scope_exit.h | ||||||
|  |  | ||||||
|  | @ -1,101 +0,0 @@ | ||||||
| // Copyright 2015 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <algorithm> |  | ||||||
| #include <cstddef> |  | ||||||
| #include <vector> |  | ||||||
| #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<Duration>& 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<float, std::chrono::milliseconds::period>; |  | ||||||
|     return std::chrono::duration_cast<FloatMs>(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<TimingResultsAggregator> GetTimingResultsAggregator() { |  | ||||||
|     static SynchronizedWrapper<TimingResultsAggregator> aggregator(30); |  | ||||||
|     return SynchronizedRef<TimingResultsAggregator>(aggregator); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace Profiling
 |  | ||||||
| } // namespace Common
 |  | ||||||
|  | @ -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 <chrono> |  | ||||||
| #include <cstddef> |  | ||||||
| #include <vector> |  | ||||||
| #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<Duration> interframe_times; |  | ||||||
|     std::vector<Duration> frame_times; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| ProfilingManager& GetProfilingManager(); |  | ||||||
| SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator(); |  | ||||||
| 
 |  | ||||||
| } // namespace Profiling
 |  | ||||||
| } // namespace Common
 |  | ||||||
|  | @ -9,25 +9,8 @@ | ||||||
| 
 | 
 | ||||||
| namespace Common { | 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 <typename T> | template <typename T> | ||||||
| class SynchronizedWrapper { | class SynchronizedWrapper; | ||||||
| public: |  | ||||||
|     template <typename... Args> |  | ||||||
|     SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     template <typename U> |  | ||||||
|     friend class SynchronizedRef; |  | ||||||
| 
 |  | ||||||
|     std::mutex mutex; |  | ||||||
|     T data; |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This |  * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This | ||||||
|  | @ -75,4 +58,28 @@ private: | ||||||
|     SynchronizedWrapper<T>* wrapper; |     SynchronizedWrapper<T>* 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 <typename T> | ||||||
|  | class SynchronizedWrapper { | ||||||
|  | public: | ||||||
|  |     template <typename... Args> | ||||||
|  |     SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} | ||||||
|  | 
 | ||||||
|  |     SynchronizedRef<T> Lock() { | ||||||
|  |         return {*this}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     template <typename U> | ||||||
|  |     friend class SynchronizedRef; | ||||||
|  | 
 | ||||||
|  |     std::mutex mutex; | ||||||
|  |     T data; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| } // namespace Common
 | } // namespace Common
 | ||||||
|  |  | ||||||
|  | @ -173,6 +173,7 @@ set(SRCS | ||||||
|             loader/smdh.cpp |             loader/smdh.cpp | ||||||
|             tracer/recorder.cpp |             tracer/recorder.cpp | ||||||
|             memory.cpp |             memory.cpp | ||||||
|  |             perf_stats.cpp | ||||||
|             settings.cpp |             settings.cpp | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  | @ -363,6 +364,7 @@ set(HEADERS | ||||||
|             memory.h |             memory.h | ||||||
|             memory_setup.h |             memory_setup.h | ||||||
|             mmio.h |             mmio.h | ||||||
|  |             perf_stats.h | ||||||
|             settings.h |             settings.h | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -109,6 +109,10 @@ void System::PrepareReschedule() { | ||||||
|     reschedule_pending = true; |     reschedule_pending = true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | PerfStats::Results System::GetAndResetPerfStats() { | ||||||
|  |     return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void System::Reschedule() { | void System::Reschedule() { | ||||||
|     if (!reschedule_pending) { |     if (!reschedule_pending) { | ||||||
|         return; |         return; | ||||||
|  | @ -140,6 +144,10 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | ||||||
| 
 | 
 | ||||||
|     LOG_DEBUG(Core, "Initialized OK"); |     LOG_DEBUG(Core, "Initialized OK"); | ||||||
| 
 | 
 | ||||||
|  |     // Reset counters and set time origin to current frame
 | ||||||
|  |     GetAndResetPerfStats(); | ||||||
|  |     perf_stats.BeginSystemFrame(); | ||||||
|  | 
 | ||||||
|     return ResultStatus::Success; |     return ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,9 +6,9 @@ | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| 
 |  | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
|  | #include "core/perf_stats.h" | ||||||
| 
 | 
 | ||||||
| class EmuWindow; | class EmuWindow; | ||||||
| class ARM_Interface; | class ARM_Interface; | ||||||
|  | @ -83,6 +83,8 @@ public: | ||||||
|     /// Prepare the core emulation for a reschedule
 |     /// Prepare the core emulation for a reschedule
 | ||||||
|     void PrepareReschedule(); |     void PrepareReschedule(); | ||||||
| 
 | 
 | ||||||
|  |     PerfStats::Results GetAndResetPerfStats(); | ||||||
|  | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Gets a reference to the emulated CPU. |      * Gets a reference to the emulated CPU. | ||||||
|      * @returns A reference to the emulated CPU. |      * @returns A reference to the emulated CPU. | ||||||
|  | @ -91,6 +93,9 @@ public: | ||||||
|         return *cpu_core; |         return *cpu_core; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     PerfStats perf_stats; | ||||||
|  |     FrameLimiter frame_limiter; | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     /**
 |     /**
 | ||||||
|      * Initialize the emulated system. |      * Initialize the emulated system. | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cmath> | #include <cmath> | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/profiler_reporting.h" | #include "core/core.h" | ||||||
| #include "core/frontend/emu_window.h" | #include "core/frontend/emu_window.h" | ||||||
| #include "core/frontend/key_map.h" | #include "core/frontend/key_map.h" | ||||||
| #include "video_core/video_core.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) { | void EmuWindow::GyroscopeChanged(float x, float y, float z) { | ||||||
|     constexpr float FULL_FPS = 60; |     constexpr float FULL_FPS = 60; | ||||||
|     float coef = GetGyroscopeRawToDpsCoefficient(); |     float coef = GetGyroscopeRawToDpsCoefficient(); | ||||||
|     float stretch = |     float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); | ||||||
|         FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; |  | ||||||
|     std::lock_guard<std::mutex> lock(gyro_mutex); |     std::lock_guard<std::mutex> lock(gyro_mutex); | ||||||
|     gyro_x = static_cast<s16>(x * coef * stretch); |     gyro_x = static_cast<s16>(x * coef * stretch); | ||||||
|     gyro_y = static_cast<s16>(y * coef * stretch); |     gyro_y = static_cast<s16>(y * coef * stretch); | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ | ||||||
| #include <boost/container/flat_set.hpp> | #include <boost/container/flat_set.hpp> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/arm/arm_interface.h" | #include "core/arm/arm_interface.h" | ||||||
| #include "core/core.h" |  | ||||||
| #include "core/hle/kernel/kernel.h" | #include "core/hle/kernel/kernel.h" | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "common/bit_field.h" | #include "common/bit_field.h" | ||||||
| #include "common/microprofile.h" | #include "common/microprofile.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/hle/kernel/event.h" | #include "core/hle/kernel/event.h" | ||||||
| #include "core/hle/kernel/shared_memory.h" | #include "core/hle/kernel/shared_memory.h" | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
|  | @ -280,6 +281,7 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { | ||||||
| 
 | 
 | ||||||
|     if (screen_id == 0) { |     if (screen_id == 0) { | ||||||
|         MicroProfileFlip(); |         MicroProfileFlip(); | ||||||
|  |         Core::System::GetInstance().perf_stats.EndGameFrame(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "core/arm/arm_interface.h" | #include "core/arm/arm_interface.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/kernel/vm_manager.h" | #include "core/hle/kernel/vm_manager.h" | ||||||
| #include "core/hle/service/ldr_ro/cro_helper.h" | #include "core/hle/service/ldr_ro/cro_helper.h" | ||||||
|  |  | ||||||
|  | @ -8,17 +8,13 @@ | ||||||
| #include "common/color.h" | #include "common/color.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/math_util.h" |  | ||||||
| #include "common/microprofile.h" | #include "common/microprofile.h" | ||||||
| #include "common/thread.h" |  | ||||||
| #include "common/timer.h" |  | ||||||
| #include "common/vector_math.h" | #include "common/vector_math.h" | ||||||
| #include "core/core_timing.h" | #include "core/core_timing.h" | ||||||
| #include "core/hle/service/gsp_gpu.h" | #include "core/hle/service/gsp_gpu.h" | ||||||
| #include "core/hw/gpu.h" | #include "core/hw/gpu.h" | ||||||
| #include "core/hw/hw.h" | #include "core/hw/hw.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| #include "core/settings.h" |  | ||||||
| #include "core/tracer/recorder.h" | #include "core/tracer/recorder.h" | ||||||
| #include "video_core/command_processor.h" | #include "video_core/command_processor.h" | ||||||
| #include "video_core/debug_utils/debug_utils.h" | #include "video_core/debug_utils/debug_utils.h" | ||||||
|  | @ -32,19 +28,9 @@ namespace GPU { | ||||||
| Regs g_regs; | Regs g_regs; | ||||||
| 
 | 
 | ||||||
| /// 268MHz CPU clocks / 60Hz frames per second
 | /// 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
 | /// Event id for CoreTiming
 | ||||||
| static int vblank_event; | 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 / 60; |  | ||||||
| // 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 <typename T> | template <typename T> | ||||||
| inline void Read(T& var, const u32 raw_addr) { | inline void Read(T& var, const u32 raw_addr) { | ||||||
|  | @ -522,24 +508,8 @@ template void Write<u32>(u32 addr, const u32 data); | ||||||
| template void Write<u16>(u32 addr, const u16 data); | template void Write<u16>(u32 addr, const u16 data); | ||||||
| template void Write<u8>(u32 addr, const u8 data); | template void Write<u8>(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<s32>(time_delay); |  | ||||||
|     s32 elapsed_time = static_cast<s32>(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
 | /// Update hardware
 | ||||||
| static void VBlankCallback(u64 userdata, int cycles_late) { | static void VBlankCallback(u64 userdata, int cycles_late) { | ||||||
|     frame_count++; |  | ||||||
|     VideoCore::g_renderer->SwapBuffers(); |     VideoCore::g_renderer->SwapBuffers(); | ||||||
| 
 | 
 | ||||||
|     // Signal to GSP that GPU interrupt has occurred
 |     // 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::PDC0); | ||||||
|     Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); |     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
 |     // Reschedule recurrent event
 | ||||||
|     CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_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.color_format.Assign(Regs::PixelFormat::RGB8); | ||||||
|     framebuffer_sub.active_fb = 0; |     framebuffer_sub.active_fb = 0; | ||||||
| 
 | 
 | ||||||
|     frame_count = 0; |  | ||||||
|     time_point = Common::Timer::GetTimeMs(); |  | ||||||
| 
 |  | ||||||
|     vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); |     vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); | ||||||
|     CoreTiming::ScheduleEvent(frame_ticks, vblank_event); |     CoreTiming::ScheduleEvent(frame_ticks, vblank_event); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -13,6 +13,8 @@ | ||||||
| 
 | 
 | ||||||
| namespace GPU { | namespace GPU { | ||||||
| 
 | 
 | ||||||
|  | constexpr float SCREEN_REFRESH_RATE = 60; | ||||||
|  | 
 | ||||||
| // Returns index corresponding to the Regs member labeled by field_name
 | // Returns index corresponding to the Regs member labeled by field_name
 | ||||||
| // TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions
 | // 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])).
 | //       when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])).
 | ||||||
|  |  | ||||||
							
								
								
									
										105
									
								
								src/core/perf_stats.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/core/perf_stats.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,105 @@ | ||||||
|  | // Copyright 2017 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <chrono> | ||||||
|  | #include <mutex> | ||||||
|  | #include <thread> | ||||||
|  | #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<double, std::chrono::seconds::period>; | ||||||
|  | using std::chrono::duration_cast; | ||||||
|  | using std::chrono::microseconds; | ||||||
|  | 
 | ||||||
|  | namespace Core { | ||||||
|  | 
 | ||||||
|  | void PerfStats::BeginSystemFrame() { | ||||||
|  |     std::lock_guard<std::mutex> lock(object_mutex); | ||||||
|  | 
 | ||||||
|  |     frame_begin = Clock::now(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PerfStats::EndSystemFrame() { | ||||||
|  |     std::lock_guard<std::mutex> lock(object_mutex); | ||||||
|  | 
 | ||||||
|  |     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() { | ||||||
|  |     std::lock_guard<std::mutex> lock(object_mutex); | ||||||
|  | 
 | ||||||
|  |     game_frames += 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { | ||||||
|  |     std::lock_guard<std::mutex> lock(object_mutex); | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double PerfStats::GetLastFrameTimeScale() { | ||||||
|  |     std::lock_guard<std::mutex> lock(object_mutex); | ||||||
|  | 
 | ||||||
|  |     constexpr double FRAME_LENGTH = 1.0 / GPU::SCREEN_REFRESH_RATE; | ||||||
|  |     return duration_cast<DoubleSecs>(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 increase the time needed to recover and limit framerate again 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<microseconds>(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<microseconds>(now_after_sleep - now); | ||||||
|  |         now = now_after_sleep; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     previous_system_time_us = current_system_time_us; | ||||||
|  |     previous_walltime = now; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Core
 | ||||||
							
								
								
									
										83
									
								
								src/core/perf_stats.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/core/perf_stats.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | ||||||
|  | // Copyright 2017 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <chrono> | ||||||
|  | #include <mutex> | ||||||
|  | #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; | ||||||
|  | 
 | ||||||
|  |     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); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * 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: | ||||||
|  |     std::mutex object_mutex; | ||||||
|  | 
 | ||||||
|  |     /// Point when the cumulative counters were reset
 | ||||||
|  |     Clock::time_point reset_point = Clock::now(); | ||||||
|  |     /// 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 { | ||||||
|  | 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
 | ||||||
|  | @ -10,8 +10,8 @@ | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/bit_field.h" | #include "common/bit_field.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/profiler_reporting.h" | #include "core/core.h" | ||||||
| #include "common/synchronized_wrapper.h" | #include "core/core_timing.h" | ||||||
| #include "core/frontend/emu_window.h" | #include "core/frontend/emu_window.h" | ||||||
| #include "core/hw/gpu.h" | #include "core/hw/gpu.h" | ||||||
| #include "core/hw/hw.h" | #include "core/hw/hw.h" | ||||||
|  | @ -145,21 +145,16 @@ void RendererOpenGL::SwapBuffers() { | ||||||
| 
 | 
 | ||||||
|     DrawScreens(); |     DrawScreens(); | ||||||
| 
 | 
 | ||||||
|     auto& profiler = Common::Profiling::GetProfilingManager(); |     Core::System::GetInstance().perf_stats.EndSystemFrame(); | ||||||
|     profiler.FinishFrame(); |  | ||||||
|     { |  | ||||||
|         auto aggregator = Common::Profiling::GetTimingResultsAggregator(); |  | ||||||
|         aggregator->AddFrame(profiler.GetPreviousFrameResults()); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // Swap buffers
 |     // Swap buffers
 | ||||||
|     render_window->PollEvents(); |     render_window->PollEvents(); | ||||||
|     render_window->SwapBuffers(); |     render_window->SwapBuffers(); | ||||||
| 
 | 
 | ||||||
|  |     Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); | ||||||
|  |     Core::System::GetInstance().perf_stats.BeginSystemFrame(); | ||||||
|  | 
 | ||||||
|     prev_state.Apply(); |     prev_state.Apply(); | ||||||
| 
 |  | ||||||
|     profiler.BeginFrame(); |  | ||||||
| 
 |  | ||||||
|     RefreshRasterizerSetting(); |     RefreshRasterizerSetting(); | ||||||
| 
 | 
 | ||||||
|     if (Pica::g_debug_context && Pica::g_debug_context->recorder) { |     if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue