diff options
28 files changed, 321 insertions, 449 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d4460bf01..15a6ccf9a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt | |||
| @@ -69,7 +69,6 @@ set(HEADERS | |||
| 69 | set(UIS | 69 | set(UIS |
| 70 | debugger/callstack.ui | 70 | debugger/callstack.ui |
| 71 | debugger/disassembler.ui | 71 | debugger/disassembler.ui |
| 72 | debugger/profiler.ui | ||
| 73 | debugger/registers.ui | 72 | debugger/registers.ui |
| 74 | configure.ui | 73 | configure.ui |
| 75 | configure_audio.ui | 74 | configure_audio.ui |
diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index b65f57fdc..5fe57dfa2 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp | |||
| @@ -146,6 +146,7 @@ void Config::ReadValues() { | |||
| 146 | 146 | ||
| 147 | UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); | 147 | UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); |
| 148 | UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); | 148 | UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); |
| 149 | UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); | ||
| 149 | UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); | 150 | UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); |
| 150 | UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); | 151 | UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); |
| 151 | 152 | ||
| @@ -252,6 +253,7 @@ void Config::SaveValues() { | |||
| 252 | 253 | ||
| 253 | qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); | 254 | qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); |
| 254 | qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); | 255 | qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); |
| 256 | qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); | ||
| 255 | qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); | 257 | qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); |
| 256 | qt_config->setValue("firstStart", UISettings::values.first_start); | 258 | qt_config->setValue("firstStart", UISettings::values.first_start); |
| 257 | 259 | ||
diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configure_system.cpp index eb1276ef3..040185e82 100644 --- a/src/citra_qt/configure_system.cpp +++ b/src/citra_qt/configure_system.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #include "citra_qt/configure_system.h" | 5 | #include "citra_qt/configure_system.h" |
| 6 | #include "citra_qt/ui_settings.h" | 6 | #include "citra_qt/ui_settings.h" |
| 7 | #include "core/core.h" | ||
| 7 | #include "core/hle/service/cfg/cfg.h" | 8 | #include "core/hle/service/cfg/cfg.h" |
| 8 | #include "core/hle/service/fs/archive.h" | 9 | #include "core/hle/service/fs/archive.h" |
| 9 | #include "ui_configure_system.h" | 10 | #include "ui_configure_system.h" |
diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index cee10403d..f060bbe08 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp | |||
| @@ -2,6 +2,8 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <QAction> | ||
| 6 | #include <QLayout> | ||
| 5 | #include <QMouseEvent> | 7 | #include <QMouseEvent> |
| 6 | #include <QPainter> | 8 | #include <QPainter> |
| 7 | #include <QString> | 9 | #include <QString> |
| @@ -9,121 +11,12 @@ | |||
| 9 | #include "citra_qt/util/util.h" | 11 | #include "citra_qt/util/util.h" |
| 10 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 11 | #include "common/microprofile.h" | 13 | #include "common/microprofile.h" |
| 12 | #include "common/profiler_reporting.h" | ||
| 13 | 14 | ||
| 14 | // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the | 15 | // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the |
| 15 | // non-Qt frontends don't need it (and don't implement the UI drawing hooks either). | 16 | // non-Qt frontends don't need it (and don't implement the UI drawing hooks either). |
| 16 | #if MICROPROFILE_ENABLED | 17 | #if MICROPROFILE_ENABLED |
| 17 | #define MICROPROFILEUI_IMPL 1 | 18 | #define MICROPROFILEUI_IMPL 1 |
| 18 | #include "common/microprofileui.h" | 19 | #include "common/microprofileui.h" |
| 19 | #endif | ||
| 20 | |||
| 21 | using namespace Common::Profiling; | ||
| 22 | |||
| 23 | static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) { | ||
| 24 | static auto duration_to_float = [](Duration dur) -> float { | ||
| 25 | using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; | ||
| 26 | return std::chrono::duration_cast<FloatMs>(dur).count(); | ||
| 27 | }; | ||
| 28 | |||
| 29 | switch (col) { | ||
| 30 | case 1: | ||
| 31 | return duration_to_float(duration.avg); | ||
| 32 | case 2: | ||
| 33 | return duration_to_float(duration.min); | ||
| 34 | case 3: | ||
| 35 | return duration_to_float(duration.max); | ||
| 36 | default: | ||
| 37 | return QVariant(); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) { | ||
| 42 | updateProfilingInfo(); | ||
| 43 | } | ||
| 44 | |||
| 45 | QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const { | ||
| 46 | if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { | ||
| 47 | switch (section) { | ||
| 48 | case 0: | ||
| 49 | return tr("Category"); | ||
| 50 | case 1: | ||
| 51 | return tr("Avg"); | ||
| 52 | case 2: | ||
| 53 | return tr("Min"); | ||
| 54 | case 3: | ||
| 55 | return tr("Max"); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | return QVariant(); | ||
| 60 | } | ||
| 61 | |||
| 62 | QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const { | ||
| 63 | return createIndex(row, column); | ||
| 64 | } | ||
| 65 | |||
| 66 | QModelIndex ProfilerModel::parent(const QModelIndex& child) const { | ||
| 67 | return QModelIndex(); | ||
| 68 | } | ||
| 69 | |||
| 70 | int ProfilerModel::columnCount(const QModelIndex& parent) const { | ||
| 71 | return 4; | ||
| 72 | } | ||
| 73 | |||
| 74 | int ProfilerModel::rowCount(const QModelIndex& parent) const { | ||
| 75 | if (parent.isValid()) { | ||
| 76 | return 0; | ||
| 77 | } else { | ||
| 78 | return 2; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | QVariant ProfilerModel::data(const QModelIndex& index, int role) const { | ||
| 83 | if (role == Qt::DisplayRole) { | ||
| 84 | if (index.row() == 0) { | ||
| 85 | if (index.column() == 0) { | ||
| 86 | return tr("Frame"); | ||
| 87 | } else { | ||
| 88 | return GetDataForColumn(index.column(), results.frame_time); | ||
| 89 | } | ||
| 90 | } else if (index.row() == 1) { | ||
| 91 | if (index.column() == 0) { | ||
| 92 | return tr("Frame (with swapping)"); | ||
| 93 | } else { | ||
| 94 | return GetDataForColumn(index.column(), results.interframe_time); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | return QVariant(); | ||
| 100 | } | ||
| 101 | |||
| 102 | void ProfilerModel::updateProfilingInfo() { | ||
| 103 | results = GetTimingResultsAggregator()->GetAggregatedResults(); | ||
| 104 | emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); | ||
| 105 | } | ||
| 106 | |||
| 107 | ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) { | ||
| 108 | ui.setupUi(this); | ||
| 109 | |||
| 110 | model = new ProfilerModel(this); | ||
| 111 | ui.treeView->setModel(model); | ||
| 112 | |||
| 113 | connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); | ||
| 114 | connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); | ||
| 115 | } | ||
| 116 | |||
| 117 | void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) { | ||
| 118 | if (enable) { | ||
| 119 | update_timer.start(100); | ||
| 120 | model->updateProfilingInfo(); | ||
| 121 | } else { | ||
| 122 | update_timer.stop(); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | #if MICROPROFILE_ENABLED | ||
| 127 | 20 | ||
| 128 | class MicroProfileWidget : public QWidget { | 21 | class MicroProfileWidget : public QWidget { |
| 129 | public: | 22 | public: |
diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index c8912fd5a..eae1e9e3c 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h | |||
| @@ -8,46 +8,6 @@ | |||
| 8 | #include <QDockWidget> | 8 | #include <QDockWidget> |
| 9 | #include <QTimer> | 9 | #include <QTimer> |
| 10 | #include "common/microprofile.h" | 10 | #include "common/microprofile.h" |
| 11 | #include "common/profiler_reporting.h" | ||
| 12 | #include "ui_profiler.h" | ||
| 13 | |||
| 14 | class ProfilerModel : public QAbstractItemModel { | ||
| 15 | Q_OBJECT | ||
| 16 | |||
| 17 | public: | ||
| 18 | explicit ProfilerModel(QObject* parent); | ||
| 19 | |||
| 20 | QVariant headerData(int section, Qt::Orientation orientation, | ||
| 21 | int role = Qt::DisplayRole) const override; | ||
| 22 | QModelIndex index(int row, int column, | ||
| 23 | const QModelIndex& parent = QModelIndex()) const override; | ||
| 24 | QModelIndex parent(const QModelIndex& child) const override; | ||
| 25 | int columnCount(const QModelIndex& parent = QModelIndex()) const override; | ||
| 26 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; | ||
| 27 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; | ||
| 28 | |||
| 29 | public slots: | ||
| 30 | void updateProfilingInfo(); | ||
| 31 | |||
| 32 | private: | ||
| 33 | Common::Profiling::AggregatedFrameResult results; | ||
| 34 | }; | ||
| 35 | |||
| 36 | class ProfilerWidget : public QDockWidget { | ||
| 37 | Q_OBJECT | ||
| 38 | |||
| 39 | public: | ||
| 40 | explicit ProfilerWidget(QWidget* parent = nullptr); | ||
| 41 | |||
| 42 | private slots: | ||
| 43 | void setProfilingInfoUpdateEnabled(bool enable); | ||
| 44 | |||
| 45 | private: | ||
| 46 | Ui::Profiler ui; | ||
| 47 | ProfilerModel* model; | ||
| 48 | |||
| 49 | QTimer update_timer; | ||
| 50 | }; | ||
| 51 | 11 | ||
| 52 | class MicroProfileDialog : public QWidget { | 12 | class MicroProfileDialog : public QWidget { |
| 53 | Q_OBJECT | 13 | Q_OBJECT |
diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui deleted file mode 100644 index d3c9a9a1f..000000000 --- a/src/citra_qt/debugger/profiler.ui +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <ui version="4.0"> | ||
| 3 | <class>Profiler</class> | ||
| 4 | <widget class="QDockWidget" name="Profiler"> | ||
| 5 | <property name="geometry"> | ||
| 6 | <rect> | ||
| 7 | <x>0</x> | ||
| 8 | <y>0</y> | ||
| 9 | <width>400</width> | ||
| 10 | <height>300</height> | ||
| 11 | </rect> | ||
| 12 | </property> | ||
| 13 | <property name="windowTitle"> | ||
| 14 | <string>Profiler</string> | ||
| 15 | </property> | ||
| 16 | <widget class="QWidget" name="dockWidgetContents"> | ||
| 17 | <layout class="QVBoxLayout" name="verticalLayout"> | ||
| 18 | <item> | ||
| 19 | <widget class="QTreeView" name="treeView"> | ||
| 20 | <property name="alternatingRowColors"> | ||
| 21 | <bool>true</bool> | ||
| 22 | </property> | ||
| 23 | <property name="uniformRowHeights"> | ||
| 24 | <bool>true</bool> | ||
| 25 | </property> | ||
| 26 | </widget> | ||
| 27 | </item> | ||
| 28 | </layout> | ||
| 29 | </widget> | ||
| 30 | </widget> | ||
| 31 | <resources/> | ||
| 32 | <connections/> | ||
| 33 | </ui> | ||
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index db6f920ff..a9ec9e830 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp | |||
| @@ -45,6 +45,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { | |||
| 45 | // with signals/slots. In this case, QList falls under the umbrells of custom types. | 45 | // with signals/slots. In this case, QList falls under the umbrells of custom types. |
| 46 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | 46 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); |
| 47 | 47 | ||
| 48 | layout->setContentsMargins(0, 0, 0, 0); | ||
| 48 | layout->addWidget(tree_view); | 49 | layout->addWidget(tree_view); |
| 49 | setLayout(layout); | 50 | setLayout(layout); |
| 50 | } | 51 | } |
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 7a80af890..fd51659b9 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp | |||
| @@ -95,6 +95,26 @@ void GMainWindow::InitializeWidgets() { | |||
| 95 | 95 | ||
| 96 | game_list = new GameList(); | 96 | game_list = new GameList(); |
| 97 | ui.horizontalLayout->addWidget(game_list); | 97 | ui.horizontalLayout->addWidget(game_list); |
| 98 | |||
| 99 | // Create status bar | ||
| 100 | emu_speed_label = new QLabel(); | ||
| 101 | emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " | ||
| 102 | "indicate emulation is running faster or slower than a 3DS.")); | ||
| 103 | game_fps_label = new QLabel(); | ||
| 104 | game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. " | ||
| 105 | "This will vary from game to game and scene to scene.")); | ||
| 106 | emu_frametime_label = new QLabel(); | ||
| 107 | emu_frametime_label->setToolTip( | ||
| 108 | tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " | ||
| 109 | "full-speed emulation this should be at most 16.67 ms.")); | ||
| 110 | |||
| 111 | for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { | ||
| 112 | label->setVisible(false); | ||
| 113 | label->setFrameStyle(QFrame::NoFrame); | ||
| 114 | label->setContentsMargins(4, 0, 4, 0); | ||
| 115 | statusBar()->addPermanentWidget(label); | ||
| 116 | } | ||
| 117 | statusBar()->setVisible(true); | ||
| 98 | } | 118 | } |
| 99 | 119 | ||
| 100 | void GMainWindow::InitializeDebugWidgets() { | 120 | void GMainWindow::InitializeDebugWidgets() { |
| @@ -103,11 +123,6 @@ void GMainWindow::InitializeDebugWidgets() { | |||
| 103 | 123 | ||
| 104 | QMenu* debug_menu = ui.menu_View_Debugging; | 124 | QMenu* debug_menu = ui.menu_View_Debugging; |
| 105 | 125 | ||
| 106 | profilerWidget = new ProfilerWidget(this); | ||
| 107 | addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); | ||
| 108 | profilerWidget->hide(); | ||
| 109 | debug_menu->addAction(profilerWidget->toggleViewAction()); | ||
| 110 | |||
| 111 | #if MICROPROFILE_ENABLED | 126 | #if MICROPROFILE_ENABLED |
| 112 | microProfileDialog = new MicroProfileDialog(this); | 127 | microProfileDialog = new MicroProfileDialog(this); |
| 113 | microProfileDialog->hide(); | 128 | microProfileDialog->hide(); |
| @@ -230,6 +245,9 @@ void GMainWindow::RestoreUIState() { | |||
| 230 | 245 | ||
| 231 | ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); | 246 | ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); |
| 232 | OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); | 247 | OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); |
| 248 | |||
| 249 | ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); | ||
| 250 | statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); | ||
| 233 | } | 251 | } |
| 234 | 252 | ||
| 235 | void GMainWindow::ConnectWidgetEvents() { | 253 | void GMainWindow::ConnectWidgetEvents() { |
| @@ -240,6 +258,8 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 240 | connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, | 258 | connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, |
| 241 | SLOT(OnEmulationStarting(EmuThread*))); | 259 | SLOT(OnEmulationStarting(EmuThread*))); |
| 242 | connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); | 260 | connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); |
| 261 | |||
| 262 | connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); | ||
| 243 | } | 263 | } |
| 244 | 264 | ||
| 245 | void GMainWindow::ConnectMenuEvents() { | 265 | void GMainWindow::ConnectMenuEvents() { |
| @@ -262,6 +282,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 262 | &GMainWindow::ToggleWindowMode); | 282 | &GMainWindow::ToggleWindowMode); |
| 263 | connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, | 283 | connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, |
| 264 | &GMainWindow::OnDisplayTitleBars); | 284 | &GMainWindow::OnDisplayTitleBars); |
| 285 | connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); | ||
| 265 | } | 286 | } |
| 266 | 287 | ||
| 267 | void GMainWindow::OnDisplayTitleBars(bool show) { | 288 | void GMainWindow::OnDisplayTitleBars(bool show) { |
| @@ -387,6 +408,8 @@ void GMainWindow::BootGame(const QString& filename) { | |||
| 387 | if (ui.action_Single_Window_Mode->isChecked()) { | 408 | if (ui.action_Single_Window_Mode->isChecked()) { |
| 388 | game_list->hide(); | 409 | game_list->hide(); |
| 389 | } | 410 | } |
| 411 | status_bar_update_timer.start(2000); | ||
| 412 | |||
| 390 | render_window->show(); | 413 | render_window->show(); |
| 391 | render_window->setFocus(); | 414 | render_window->setFocus(); |
| 392 | 415 | ||
| @@ -421,6 +444,12 @@ void GMainWindow::ShutdownGame() { | |||
| 421 | render_window->hide(); | 444 | render_window->hide(); |
| 422 | game_list->show(); | 445 | game_list->show(); |
| 423 | 446 | ||
| 447 | // Disable status bar updates | ||
| 448 | status_bar_update_timer.stop(); | ||
| 449 | emu_speed_label->setVisible(false); | ||
| 450 | game_fps_label->setVisible(false); | ||
| 451 | emu_frametime_label->setVisible(false); | ||
| 452 | |||
| 424 | emulation_running = false; | 453 | emulation_running = false; |
| 425 | } | 454 | } |
| 426 | 455 | ||
| @@ -600,6 +629,23 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { | |||
| 600 | graphicsSurfaceViewerWidget->show(); | 629 | graphicsSurfaceViewerWidget->show(); |
| 601 | } | 630 | } |
| 602 | 631 | ||
| 632 | void GMainWindow::UpdateStatusBar() { | ||
| 633 | if (emu_thread == nullptr) { | ||
| 634 | status_bar_update_timer.stop(); | ||
| 635 | return; | ||
| 636 | } | ||
| 637 | |||
| 638 | auto results = Core::System::GetInstance().GetAndResetPerfStats(); | ||
| 639 | |||
| 640 | emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); | ||
| 641 | game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); | ||
| 642 | emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); | ||
| 643 | |||
| 644 | emu_speed_label->setVisible(true); | ||
| 645 | game_fps_label->setVisible(true); | ||
| 646 | emu_frametime_label->setVisible(true); | ||
| 647 | } | ||
| 648 | |||
| 603 | bool GMainWindow::ConfirmClose() { | 649 | bool GMainWindow::ConfirmClose() { |
| 604 | if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) | 650 | if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) |
| 605 | return true; | 651 | return true; |
| @@ -625,6 +671,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { | |||
| 625 | #endif | 671 | #endif |
| 626 | UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); | 672 | UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); |
| 627 | UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); | 673 | UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); |
| 674 | UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); | ||
| 628 | UISettings::values.first_start = false; | 675 | UISettings::values.first_start = false; |
| 629 | 676 | ||
| 630 | game_list->SaveInterfaceLayout(); | 677 | game_list->SaveInterfaceLayout(); |
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 87637b92b..ec841eaa5 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h | |||
| @@ -127,17 +127,26 @@ private slots: | |||
| 127 | void OnCreateGraphicsSurfaceViewer(); | 127 | void OnCreateGraphicsSurfaceViewer(); |
| 128 | 128 | ||
| 129 | private: | 129 | private: |
| 130 | void UpdateStatusBar(); | ||
| 131 | |||
| 130 | Ui::MainWindow ui; | 132 | Ui::MainWindow ui; |
| 131 | 133 | ||
| 132 | GRenderWindow* render_window; | 134 | GRenderWindow* render_window; |
| 133 | GameList* game_list; | 135 | GameList* game_list; |
| 134 | 136 | ||
| 137 | // Status bar elements | ||
| 138 | QLabel* emu_speed_label = nullptr; | ||
| 139 | QLabel* game_fps_label = nullptr; | ||
| 140 | QLabel* emu_frametime_label = nullptr; | ||
| 141 | QTimer status_bar_update_timer; | ||
| 142 | |||
| 135 | std::unique_ptr<Config> config; | 143 | std::unique_ptr<Config> config; |
| 136 | 144 | ||
| 137 | // Whether emulation is currently running in Citra. | 145 | // Whether emulation is currently running in Citra. |
| 138 | bool emulation_running = false; | 146 | bool emulation_running = false; |
| 139 | std::unique_ptr<EmuThread> emu_thread; | 147 | std::unique_ptr<EmuThread> emu_thread; |
| 140 | 148 | ||
| 149 | // Debugger panes | ||
| 141 | ProfilerWidget* profilerWidget; | 150 | ProfilerWidget* profilerWidget; |
| 142 | MicroProfileDialog* microProfileDialog; | 151 | MicroProfileDialog* microProfileDialog; |
| 143 | DisassemblerWidget* disasmWidget; | 152 | DisassemblerWidget* disasmWidget; |
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 4a95cda9a..47dbb6ef7 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui | |||
| @@ -88,6 +88,7 @@ | |||
| 88 | </widget> | 88 | </widget> |
| 89 | <addaction name="action_Single_Window_Mode"/> | 89 | <addaction name="action_Single_Window_Mode"/> |
| 90 | <addaction name="action_Display_Dock_Widget_Headers"/> | 90 | <addaction name="action_Display_Dock_Widget_Headers"/> |
| 91 | <addaction name="action_Show_Status_Bar"/> | ||
| 91 | <addaction name="menu_View_Debugging"/> | 92 | <addaction name="menu_View_Debugging"/> |
| 92 | </widget> | 93 | </widget> |
| 93 | <widget class="QMenu" name="menu_Help"> | 94 | <widget class="QMenu" name="menu_Help"> |
| @@ -101,7 +102,6 @@ | |||
| 101 | <addaction name="menu_View"/> | 102 | <addaction name="menu_View"/> |
| 102 | <addaction name="menu_Help"/> | 103 | <addaction name="menu_Help"/> |
| 103 | </widget> | 104 | </widget> |
| 104 | <widget class="QStatusBar" name="statusbar"/> | ||
| 105 | <action name="action_Load_File"> | 105 | <action name="action_Load_File"> |
| 106 | <property name="text"> | 106 | <property name="text"> |
| 107 | <string>Load File...</string> | 107 | <string>Load File...</string> |
| @@ -167,6 +167,14 @@ | |||
| 167 | <string>Display Dock Widget Headers</string> | 167 | <string>Display Dock Widget Headers</string> |
| 168 | </property> | 168 | </property> |
| 169 | </action> | 169 | </action> |
| 170 | <action name="action_Show_Status_Bar"> | ||
| 171 | <property name="checkable"> | ||
| 172 | <bool>true</bool> | ||
| 173 | </property> | ||
| 174 | <property name="text"> | ||
| 175 | <string>Show Status Bar</string> | ||
| 176 | </property> | ||
| 177 | </action> | ||
| 170 | <action name="action_Select_Game_List_Root"> | 178 | <action name="action_Select_Game_List_Root"> |
| 171 | <property name="text"> | 179 | <property name="text"> |
| 172 | <string>Select Game Directory...</string> | 180 | <string>Select Game Directory...</string> |
diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index ed7fdff7e..6408ece2b 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h | |||
| @@ -27,6 +27,7 @@ struct Values { | |||
| 27 | 27 | ||
| 28 | bool single_window_mode; | 28 | bool single_window_mode; |
| 29 | bool display_titlebar; | 29 | bool display_titlebar; |
| 30 | bool show_status_bar; | ||
| 30 | 31 | ||
| 31 | bool confirm_before_closing; | 32 | bool confirm_before_closing; |
| 32 | bool first_start; | 33 | bool first_start; |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 26c83efda..8a6170257 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -35,7 +35,6 @@ set(SRCS | |||
| 35 | memory_util.cpp | 35 | memory_util.cpp |
| 36 | microprofile.cpp | 36 | microprofile.cpp |
| 37 | misc.cpp | 37 | misc.cpp |
| 38 | profiler.cpp | ||
| 39 | scm_rev.cpp | 38 | scm_rev.cpp |
| 40 | string_util.cpp | 39 | string_util.cpp |
| 41 | symbols.cpp | 40 | symbols.cpp |
| @@ -68,7 +67,6 @@ set(HEADERS | |||
| 68 | microprofile.h | 67 | microprofile.h |
| 69 | microprofileui.h | 68 | microprofileui.h |
| 70 | platform.h | 69 | platform.h |
| 71 | profiler_reporting.h | ||
| 72 | quaternion.h | 70 | quaternion.h |
| 73 | scm_rev.h | 71 | scm_rev.h |
| 74 | scope_exit.h | 72 | scope_exit.h |
diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp deleted file mode 100644 index b40e7205d..000000000 --- a/src/common/profiler.cpp +++ /dev/null | |||
| @@ -1,101 +0,0 @@ | |||
| 1 | // Copyright 2015 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <vector> | ||
| 8 | #include "common/assert.h" | ||
| 9 | #include "common/profiler_reporting.h" | ||
| 10 | #include "common/synchronized_wrapper.h" | ||
| 11 | |||
| 12 | namespace Common { | ||
| 13 | namespace Profiling { | ||
| 14 | |||
| 15 | ProfilingManager::ProfilingManager() | ||
| 16 | : last_frame_end(Clock::now()), this_frame_start(Clock::now()) {} | ||
| 17 | |||
| 18 | void ProfilingManager::BeginFrame() { | ||
| 19 | this_frame_start = Clock::now(); | ||
| 20 | } | ||
| 21 | |||
| 22 | void ProfilingManager::FinishFrame() { | ||
| 23 | Clock::time_point now = Clock::now(); | ||
| 24 | |||
| 25 | results.interframe_time = now - last_frame_end; | ||
| 26 | results.frame_time = now - this_frame_start; | ||
| 27 | |||
| 28 | last_frame_end = now; | ||
| 29 | } | ||
| 30 | |||
| 31 | TimingResultsAggregator::TimingResultsAggregator(size_t window_size) | ||
| 32 | : max_window_size(window_size), window_size(0) { | ||
| 33 | interframe_times.resize(window_size, Duration::zero()); | ||
| 34 | frame_times.resize(window_size, Duration::zero()); | ||
| 35 | } | ||
| 36 | |||
| 37 | void TimingResultsAggregator::Clear() { | ||
| 38 | window_size = cursor = 0; | ||
| 39 | } | ||
| 40 | |||
| 41 | void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) { | ||
| 42 | interframe_times[cursor] = frame_result.interframe_time; | ||
| 43 | frame_times[cursor] = frame_result.frame_time; | ||
| 44 | |||
| 45 | ++cursor; | ||
| 46 | if (cursor == max_window_size) | ||
| 47 | cursor = 0; | ||
| 48 | if (window_size < max_window_size) | ||
| 49 | ++window_size; | ||
| 50 | } | ||
| 51 | |||
| 52 | static AggregatedDuration AggregateField(const std::vector<Duration>& v, size_t len) { | ||
| 53 | AggregatedDuration result; | ||
| 54 | result.avg = Duration::zero(); | ||
| 55 | result.min = result.max = (len == 0 ? Duration::zero() : v[0]); | ||
| 56 | |||
| 57 | for (size_t i = 0; i < len; ++i) { | ||
| 58 | Duration value = v[i]; | ||
| 59 | result.avg += value; | ||
| 60 | result.min = std::min(result.min, value); | ||
| 61 | result.max = std::max(result.max, value); | ||
| 62 | } | ||
| 63 | if (len != 0) | ||
| 64 | result.avg /= len; | ||
| 65 | |||
| 66 | return result; | ||
| 67 | } | ||
| 68 | |||
| 69 | static float tof(Common::Profiling::Duration dur) { | ||
| 70 | using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; | ||
| 71 | return std::chrono::duration_cast<FloatMs>(dur).count(); | ||
| 72 | } | ||
| 73 | |||
| 74 | AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const { | ||
| 75 | AggregatedFrameResult result; | ||
| 76 | |||
| 77 | result.interframe_time = AggregateField(interframe_times, window_size); | ||
| 78 | result.frame_time = AggregateField(frame_times, window_size); | ||
| 79 | |||
| 80 | if (result.interframe_time.avg != Duration::zero()) { | ||
| 81 | result.fps = 1000.0f / tof(result.interframe_time.avg); | ||
| 82 | } else { | ||
| 83 | result.fps = 0.0f; | ||
| 84 | } | ||
| 85 | |||
| 86 | return result; | ||
| 87 | } | ||
| 88 | |||
| 89 | ProfilingManager& GetProfilingManager() { | ||
| 90 | // Takes advantage of "magic" static initialization for race-free initialization. | ||
| 91 | static ProfilingManager manager; | ||
| 92 | return manager; | ||
| 93 | } | ||
| 94 | |||
| 95 | SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator() { | ||
| 96 | static SynchronizedWrapper<TimingResultsAggregator> aggregator(30); | ||
| 97 | return SynchronizedRef<TimingResultsAggregator>(aggregator); | ||
| 98 | } | ||
| 99 | |||
| 100 | } // namespace Profiling | ||
| 101 | } // namespace Common | ||
diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h deleted file mode 100644 index e9ce6d41c..000000000 --- a/src/common/profiler_reporting.h +++ /dev/null | |||
| @@ -1,83 +0,0 @@ | |||
| 1 | // Copyright 2015 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <chrono> | ||
| 8 | #include <cstddef> | ||
| 9 | #include <vector> | ||
| 10 | #include "common/synchronized_wrapper.h" | ||
| 11 | |||
| 12 | namespace Common { | ||
| 13 | namespace Profiling { | ||
| 14 | |||
| 15 | using Clock = std::chrono::high_resolution_clock; | ||
| 16 | using Duration = Clock::duration; | ||
| 17 | |||
| 18 | struct ProfilingFrameResult { | ||
| 19 | /// Time since the last delivered frame | ||
| 20 | Duration interframe_time; | ||
| 21 | |||
| 22 | /// Time spent processing a frame, excluding VSync | ||
| 23 | Duration frame_time; | ||
| 24 | }; | ||
| 25 | |||
| 26 | class ProfilingManager final { | ||
| 27 | public: | ||
| 28 | ProfilingManager(); | ||
| 29 | |||
| 30 | /// This should be called after swapping screen buffers. | ||
| 31 | void BeginFrame(); | ||
| 32 | /// This should be called before swapping screen buffers. | ||
| 33 | void FinishFrame(); | ||
| 34 | |||
| 35 | /// Get the timing results from the previous frame. This is updated when you call FinishFrame(). | ||
| 36 | const ProfilingFrameResult& GetPreviousFrameResults() const { | ||
| 37 | return results; | ||
| 38 | } | ||
| 39 | |||
| 40 | private: | ||
| 41 | Clock::time_point last_frame_end; | ||
| 42 | Clock::time_point this_frame_start; | ||
| 43 | |||
| 44 | ProfilingFrameResult results; | ||
| 45 | }; | ||
| 46 | |||
| 47 | struct AggregatedDuration { | ||
| 48 | Duration avg, min, max; | ||
| 49 | }; | ||
| 50 | |||
| 51 | struct AggregatedFrameResult { | ||
| 52 | /// Time since the last delivered frame | ||
| 53 | AggregatedDuration interframe_time; | ||
| 54 | |||
| 55 | /// Time spent processing a frame, excluding VSync | ||
| 56 | AggregatedDuration frame_time; | ||
| 57 | |||
| 58 | float fps; | ||
| 59 | }; | ||
| 60 | |||
| 61 | class TimingResultsAggregator final { | ||
| 62 | public: | ||
| 63 | TimingResultsAggregator(size_t window_size); | ||
| 64 | |||
| 65 | void Clear(); | ||
| 66 | |||
| 67 | void AddFrame(const ProfilingFrameResult& frame_result); | ||
| 68 | |||
| 69 | AggregatedFrameResult GetAggregatedResults() const; | ||
| 70 | |||
| 71 | size_t max_window_size; | ||
| 72 | size_t window_size; | ||
| 73 | size_t cursor; | ||
| 74 | |||
| 75 | std::vector<Duration> interframe_times; | ||
| 76 | std::vector<Duration> frame_times; | ||
| 77 | }; | ||
| 78 | |||
| 79 | ProfilingManager& GetProfilingManager(); | ||
| 80 | SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator(); | ||
| 81 | |||
| 82 | } // namespace Profiling | ||
| 83 | } // namespace Common | ||
diff --git a/src/common/synchronized_wrapper.h b/src/common/synchronized_wrapper.h index 04b4f2e51..4a1984c46 100644 --- a/src/common/synchronized_wrapper.h +++ b/src/common/synchronized_wrapper.h | |||
| @@ -9,25 +9,8 @@ | |||
| 9 | 9 | ||
| 10 | namespace Common { | 10 | namespace Common { |
| 11 | 11 | ||
| 12 | /** | ||
| 13 | * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no | ||
| 14 | * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a | ||
| 15 | * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type | ||
| 16 | * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). | ||
| 17 | */ | ||
| 18 | template <typename T> | 12 | template <typename T> |
| 19 | class SynchronizedWrapper { | 13 | class SynchronizedWrapper; |
| 20 | public: | ||
| 21 | template <typename... Args> | ||
| 22 | SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} | ||
| 23 | |||
| 24 | private: | ||
| 25 | template <typename U> | ||
| 26 | friend class SynchronizedRef; | ||
| 27 | |||
| 28 | std::mutex mutex; | ||
| 29 | T data; | ||
| 30 | }; | ||
| 31 | 14 | ||
| 32 | /** | 15 | /** |
| 33 | * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This | 16 | * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This |
| @@ -75,4 +58,28 @@ private: | |||
| 75 | SynchronizedWrapper<T>* wrapper; | 58 | SynchronizedWrapper<T>* wrapper; |
| 76 | }; | 59 | }; |
| 77 | 60 | ||
| 61 | /** | ||
| 62 | * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no | ||
| 63 | * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a | ||
| 64 | * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type | ||
| 65 | * (http://doc.rust-lang.org/std/sync/struct.Mutex.html). | ||
| 66 | */ | ||
| 67 | template <typename T> | ||
| 68 | class SynchronizedWrapper { | ||
| 69 | public: | ||
| 70 | template <typename... Args> | ||
| 71 | SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {} | ||
| 72 | |||
| 73 | SynchronizedRef<T> Lock() { | ||
| 74 | return {*this}; | ||
| 75 | } | ||
| 76 | |||
| 77 | private: | ||
| 78 | template <typename U> | ||
| 79 | friend class SynchronizedRef; | ||
| 80 | |||
| 81 | std::mutex mutex; | ||
| 82 | T data; | ||
| 83 | }; | ||
| 84 | |||
| 78 | } // namespace Common | 85 | } // namespace Common |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8334fece9..ffd67f074 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -173,6 +173,7 @@ set(SRCS | |||
| 173 | loader/smdh.cpp | 173 | loader/smdh.cpp |
| 174 | tracer/recorder.cpp | 174 | tracer/recorder.cpp |
| 175 | memory.cpp | 175 | memory.cpp |
| 176 | perf_stats.cpp | ||
| 176 | settings.cpp | 177 | settings.cpp |
| 177 | ) | 178 | ) |
| 178 | 179 | ||
| @@ -363,6 +364,7 @@ set(HEADERS | |||
| 363 | memory.h | 364 | memory.h |
| 364 | memory_setup.h | 365 | memory_setup.h |
| 365 | mmio.h | 366 | mmio.h |
| 367 | perf_stats.h | ||
| 366 | settings.h | 368 | settings.h |
| 367 | ) | 369 | ) |
| 368 | 370 | ||
diff --git a/src/core/core.cpp b/src/core/core.cpp index c9c9b7615..140ff6451 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -109,6 +109,10 @@ void System::PrepareReschedule() { | |||
| 109 | reschedule_pending = true; | 109 | reschedule_pending = true; |
| 110 | } | 110 | } |
| 111 | 111 | ||
| 112 | PerfStats::Results System::GetAndResetPerfStats() { | ||
| 113 | return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); | ||
| 114 | } | ||
| 115 | |||
| 112 | void System::Reschedule() { | 116 | void System::Reschedule() { |
| 113 | if (!reschedule_pending) { | 117 | if (!reschedule_pending) { |
| 114 | return; | 118 | return; |
| @@ -140,6 +144,10 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | |||
| 140 | 144 | ||
| 141 | LOG_DEBUG(Core, "Initialized OK"); | 145 | LOG_DEBUG(Core, "Initialized OK"); |
| 142 | 146 | ||
| 147 | // Reset counters and set time origin to current frame | ||
| 148 | GetAndResetPerfStats(); | ||
| 149 | perf_stats.BeginSystemFrame(); | ||
| 150 | |||
| 143 | return ResultStatus::Success; | 151 | return ResultStatus::Success; |
| 144 | } | 152 | } |
| 145 | 153 | ||
diff --git a/src/core/core.h b/src/core/core.h index 17572a74f..6c9c936b5 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -6,9 +6,9 @@ | |||
| 6 | 6 | ||
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include <string> | 8 | #include <string> |
| 9 | |||
| 10 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 11 | #include "core/memory.h" | 10 | #include "core/memory.h" |
| 11 | #include "core/perf_stats.h" | ||
| 12 | 12 | ||
| 13 | class EmuWindow; | 13 | class EmuWindow; |
| 14 | class ARM_Interface; | 14 | class ARM_Interface; |
| @@ -83,6 +83,8 @@ public: | |||
| 83 | /// Prepare the core emulation for a reschedule | 83 | /// Prepare the core emulation for a reschedule |
| 84 | void PrepareReschedule(); | 84 | void PrepareReschedule(); |
| 85 | 85 | ||
| 86 | PerfStats::Results GetAndResetPerfStats(); | ||
| 87 | |||
| 86 | /** | 88 | /** |
| 87 | * Gets a reference to the emulated CPU. | 89 | * Gets a reference to the emulated CPU. |
| 88 | * @returns A reference to the emulated CPU. | 90 | * @returns A reference to the emulated CPU. |
| @@ -91,6 +93,9 @@ public: | |||
| 91 | return *cpu_core; | 93 | return *cpu_core; |
| 92 | } | 94 | } |
| 93 | 95 | ||
| 96 | PerfStats perf_stats; | ||
| 97 | FrameLimiter frame_limiter; | ||
| 98 | |||
| 94 | private: | 99 | private: |
| 95 | /** | 100 | /** |
| 96 | * Initialize the emulated system. | 101 | * Initialize the emulated system. |
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 6b4637741..a155b657d 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <cmath> | 6 | #include <cmath> |
| 7 | #include "common/assert.h" | 7 | #include "common/assert.h" |
| 8 | #include "common/profiler_reporting.h" | 8 | #include "core/core.h" |
| 9 | #include "core/frontend/emu_window.h" | 9 | #include "core/frontend/emu_window.h" |
| 10 | #include "core/frontend/key_map.h" | 10 | #include "core/frontend/key_map.h" |
| 11 | #include "video_core/video_core.h" | 11 | #include "video_core/video_core.h" |
| @@ -104,8 +104,7 @@ void EmuWindow::AccelerometerChanged(float x, float y, float z) { | |||
| 104 | void EmuWindow::GyroscopeChanged(float x, float y, float z) { | 104 | void EmuWindow::GyroscopeChanged(float x, float y, float z) { |
| 105 | constexpr float FULL_FPS = 60; | 105 | constexpr float FULL_FPS = 60; |
| 106 | float coef = GetGyroscopeRawToDpsCoefficient(); | 106 | float coef = GetGyroscopeRawToDpsCoefficient(); |
| 107 | float stretch = | 107 | float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); |
| 108 | FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; | ||
| 109 | std::lock_guard<std::mutex> lock(gyro_mutex); | 108 | std::lock_guard<std::mutex> lock(gyro_mutex); |
| 110 | gyro_x = static_cast<s16>(x * coef * stretch); | 109 | gyro_x = static_cast<s16>(x * coef * stretch); |
| 111 | gyro_y = static_cast<s16>(y * coef * stretch); | 110 | gyro_y = static_cast<s16>(y * coef * stretch); |
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index c088b9a19..4ffe97b78 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <memory> | ||
| 7 | #include <string> | 8 | #include <string> |
| 8 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c557a2279..6ab31c70b 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h | |||
| @@ -11,7 +11,6 @@ | |||
| 11 | #include <boost/container/flat_set.hpp> | 11 | #include <boost/container/flat_set.hpp> |
| 12 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 13 | #include "core/arm/arm_interface.h" | 13 | #include "core/arm/arm_interface.h" |
| 14 | #include "core/core.h" | ||
| 15 | #include "core/hle/kernel/kernel.h" | 14 | #include "core/hle/kernel/kernel.h" |
| 16 | #include "core/hle/result.h" | 15 | #include "core/hle/result.h" |
| 17 | 16 | ||
diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 1457518d4..097ed87e4 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #include "common/bit_field.h" | 5 | #include "common/bit_field.h" |
| 6 | #include "common/microprofile.h" | 6 | #include "common/microprofile.h" |
| 7 | #include "core/core.h" | ||
| 7 | #include "core/hle/kernel/event.h" | 8 | #include "core/hle/kernel/event.h" |
| 8 | #include "core/hle/kernel/shared_memory.h" | 9 | #include "core/hle/kernel/shared_memory.h" |
| 9 | #include "core/hle/result.h" | 10 | #include "core/hle/result.h" |
| @@ -280,6 +281,7 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { | |||
| 280 | 281 | ||
| 281 | if (screen_id == 0) { | 282 | if (screen_id == 0) { |
| 282 | MicroProfileFlip(); | 283 | MicroProfileFlip(); |
| 284 | Core::System::GetInstance().perf_stats.EndGameFrame(); | ||
| 283 | } | 285 | } |
| 284 | 286 | ||
| 285 | return RESULT_SUCCESS; | 287 | return RESULT_SUCCESS; |
diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index 8d00a7577..7af76676b 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 @@ | |||
| 6 | #include "common/common_types.h" | 6 | #include "common/common_types.h" |
| 7 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 8 | #include "core/arm/arm_interface.h" | 8 | #include "core/arm/arm_interface.h" |
| 9 | #include "core/core.h" | ||
| 9 | #include "core/hle/kernel/process.h" | 10 | #include "core/hle/kernel/process.h" |
| 10 | #include "core/hle/kernel/vm_manager.h" | 11 | #include "core/hle/kernel/vm_manager.h" |
| 11 | #include "core/hle/service/ldr_ro/cro_helper.h" | 12 | #include "core/hle/service/ldr_ro/cro_helper.h" |
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index fa8c13d36..42809c731 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp | |||
| @@ -8,17 +8,13 @@ | |||
| 8 | #include "common/color.h" | 8 | #include "common/color.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/math_util.h" | ||
| 12 | #include "common/microprofile.h" | 11 | #include "common/microprofile.h" |
| 13 | #include "common/thread.h" | ||
| 14 | #include "common/timer.h" | ||
| 15 | #include "common/vector_math.h" | 12 | #include "common/vector_math.h" |
| 16 | #include "core/core_timing.h" | 13 | #include "core/core_timing.h" |
| 17 | #include "core/hle/service/gsp_gpu.h" | 14 | #include "core/hle/service/gsp_gpu.h" |
| 18 | #include "core/hw/gpu.h" | 15 | #include "core/hw/gpu.h" |
| 19 | #include "core/hw/hw.h" | 16 | #include "core/hw/hw.h" |
| 20 | #include "core/memory.h" | 17 | #include "core/memory.h" |
| 21 | #include "core/settings.h" | ||
| 22 | #include "core/tracer/recorder.h" | 18 | #include "core/tracer/recorder.h" |
| 23 | #include "video_core/command_processor.h" | 19 | #include "video_core/command_processor.h" |
| 24 | #include "video_core/debug_utils/debug_utils.h" | 20 | #include "video_core/debug_utils/debug_utils.h" |
| @@ -32,19 +28,9 @@ namespace GPU { | |||
| 32 | Regs g_regs; | 28 | Regs g_regs; |
| 33 | 29 | ||
| 34 | /// 268MHz CPU clocks / 60Hz frames per second | 30 | /// 268MHz CPU clocks / 60Hz frames per second |
| 35 | const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60; | 31 | const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE; |
| 36 | /// Event id for CoreTiming | 32 | /// Event id for CoreTiming |
| 37 | static int vblank_event; | 33 | static int vblank_event; |
| 38 | /// Total number of frames drawn | ||
| 39 | static u64 frame_count; | ||
| 40 | /// Start clock for frame limiter | ||
| 41 | static u32 time_point; | ||
| 42 | /// Total delay caused by slow frames | ||
| 43 | static float time_delay; | ||
| 44 | constexpr float FIXED_FRAME_TIME = 1000.0f / 60; | ||
| 45 | // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher | ||
| 46 | // values increases time needed to limit frame rate after spikes | ||
| 47 | constexpr float MAX_LAG_TIME = 18; | ||
| 48 | 34 | ||
| 49 | template <typename T> | 35 | template <typename T> |
| 50 | inline void Read(T& var, const u32 raw_addr) { | 36 | inline void Read(T& var, const u32 raw_addr) { |
| @@ -522,24 +508,8 @@ template void Write<u32>(u32 addr, const u32 data); | |||
| 522 | template void Write<u16>(u32 addr, const u16 data); | 508 | template void Write<u16>(u32 addr, const u16 data); |
| 523 | template void Write<u8>(u32 addr, const u8 data); | 509 | template void Write<u8>(u32 addr, const u8 data); |
| 524 | 510 | ||
| 525 | static void FrameLimiter() { | ||
| 526 | time_delay += FIXED_FRAME_TIME; | ||
| 527 | time_delay = MathUtil::Clamp(time_delay, -MAX_LAG_TIME, MAX_LAG_TIME); | ||
| 528 | s32 desired_time = static_cast<s32>(time_delay); | ||
| 529 | s32 elapsed_time = static_cast<s32>(Common::Timer::GetTimeMs() - time_point); | ||
| 530 | |||
| 531 | if (elapsed_time < desired_time) { | ||
| 532 | Common::SleepCurrentThread(desired_time - elapsed_time); | ||
| 533 | } | ||
| 534 | |||
| 535 | u32 frame_time = Common::Timer::GetTimeMs() - time_point; | ||
| 536 | |||
| 537 | time_delay -= frame_time; | ||
| 538 | } | ||
| 539 | |||
| 540 | /// Update hardware | 511 | /// Update hardware |
| 541 | static void VBlankCallback(u64 userdata, int cycles_late) { | 512 | static void VBlankCallback(u64 userdata, int cycles_late) { |
| 542 | frame_count++; | ||
| 543 | VideoCore::g_renderer->SwapBuffers(); | 513 | VideoCore::g_renderer->SwapBuffers(); |
| 544 | 514 | ||
| 545 | // Signal to GSP that GPU interrupt has occurred | 515 | // Signal to GSP that GPU interrupt has occurred |
| @@ -550,12 +520,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { | |||
| 550 | Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0); | 520 | Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0); |
| 551 | Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); | 521 | Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); |
| 552 | 522 | ||
| 553 | if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) { | ||
| 554 | FrameLimiter(); | ||
| 555 | } | ||
| 556 | |||
| 557 | time_point = Common::Timer::GetTimeMs(); | ||
| 558 | |||
| 559 | // Reschedule recurrent event | 523 | // Reschedule recurrent event |
| 560 | CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); | 524 | CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); |
| 561 | } | 525 | } |
| @@ -590,9 +554,6 @@ void Init() { | |||
| 590 | framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8); | 554 | framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8); |
| 591 | framebuffer_sub.active_fb = 0; | 555 | framebuffer_sub.active_fb = 0; |
| 592 | 556 | ||
| 593 | frame_count = 0; | ||
| 594 | time_point = Common::Timer::GetTimeMs(); | ||
| 595 | |||
| 596 | vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); | 557 | vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); |
| 597 | CoreTiming::ScheduleEvent(frame_ticks, vblank_event); | 558 | CoreTiming::ScheduleEvent(frame_ticks, vblank_event); |
| 598 | 559 | ||
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index d53381216..bdd997b2a 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h | |||
| @@ -13,6 +13,8 @@ | |||
| 13 | 13 | ||
| 14 | namespace GPU { | 14 | namespace GPU { |
| 15 | 15 | ||
| 16 | constexpr float SCREEN_REFRESH_RATE = 60; | ||
| 17 | |||
| 16 | // Returns index corresponding to the Regs member labeled by field_name | 18 | // Returns index corresponding to the Regs member labeled by field_name |
| 17 | // TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions | 19 | // TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions |
| 18 | // when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])). | 20 | // 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 000000000..2cdfb9ded --- /dev/null +++ b/src/core/perf_stats.cpp | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <chrono> | ||
| 6 | #include <mutex> | ||
| 7 | #include <thread> | ||
| 8 | #include "common/math_util.h" | ||
| 9 | #include "core/hw/gpu.h" | ||
| 10 | #include "core/perf_stats.h" | ||
| 11 | #include "core/settings.h" | ||
| 12 | |||
| 13 | using namespace std::chrono_literals; | ||
| 14 | using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>; | ||
| 15 | using std::chrono::duration_cast; | ||
| 16 | using std::chrono::microseconds; | ||
| 17 | |||
| 18 | namespace Core { | ||
| 19 | |||
| 20 | void PerfStats::BeginSystemFrame() { | ||
| 21 | std::lock_guard<std::mutex> lock(object_mutex); | ||
| 22 | |||
| 23 | frame_begin = Clock::now(); | ||
| 24 | } | ||
| 25 | |||
| 26 | void PerfStats::EndSystemFrame() { | ||
| 27 | std::lock_guard<std::mutex> lock(object_mutex); | ||
| 28 | |||
| 29 | auto frame_end = Clock::now(); | ||
| 30 | accumulated_frametime += frame_end - frame_begin; | ||
| 31 | system_frames += 1; | ||
| 32 | |||
| 33 | previous_frame_length = frame_end - previous_frame_end; | ||
| 34 | previous_frame_end = frame_end; | ||
| 35 | } | ||
| 36 | |||
| 37 | void PerfStats::EndGameFrame() { | ||
| 38 | std::lock_guard<std::mutex> lock(object_mutex); | ||
| 39 | |||
| 40 | game_frames += 1; | ||
| 41 | } | ||
| 42 | |||
| 43 | PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) { | ||
| 44 | std::lock_guard<std::mutex> lock(object_mutex); | ||
| 45 | |||
| 46 | auto now = Clock::now(); | ||
| 47 | // Walltime elapsed since stats were reset | ||
| 48 | auto interval = duration_cast<DoubleSecs>(now - reset_point).count(); | ||
| 49 | |||
| 50 | auto system_us_per_second = | ||
| 51 | static_cast<double>(current_system_time_us - reset_point_system_us) / interval; | ||
| 52 | |||
| 53 | Results results{}; | ||
| 54 | results.system_fps = static_cast<double>(system_frames) / interval; | ||
| 55 | results.game_fps = static_cast<double>(game_frames) / interval; | ||
| 56 | results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / | ||
| 57 | static_cast<double>(system_frames); | ||
| 58 | results.emulation_speed = system_us_per_second / 1'000'000.0; | ||
| 59 | |||
| 60 | // Reset counters | ||
| 61 | reset_point = now; | ||
| 62 | reset_point_system_us = current_system_time_us; | ||
| 63 | accumulated_frametime = Clock::duration::zero(); | ||
| 64 | system_frames = 0; | ||
| 65 | game_frames = 0; | ||
| 66 | |||
| 67 | return results; | ||
| 68 | } | ||
| 69 | |||
| 70 | double PerfStats::GetLastFrameTimeScale() { | ||
| 71 | std::lock_guard<std::mutex> lock(object_mutex); | ||
| 72 | |||
| 73 | constexpr double FRAME_LENGTH = 1.0 / GPU::SCREEN_REFRESH_RATE; | ||
| 74 | return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH; | ||
| 75 | } | ||
| 76 | |||
| 77 | void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) { | ||
| 78 | // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher | ||
| 79 | // values increase the time needed to recover and limit framerate again after spikes. | ||
| 80 | constexpr microseconds MAX_LAG_TIME_US = 25ms; | ||
| 81 | |||
| 82 | if (!Settings::values.toggle_framelimit) { | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | |||
| 86 | auto now = Clock::now(); | ||
| 87 | |||
| 88 | frame_limiting_delta_err += microseconds(current_system_time_us - previous_system_time_us); | ||
| 89 | frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime); | ||
| 90 | frame_limiting_delta_err = | ||
| 91 | MathUtil::Clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US); | ||
| 92 | |||
| 93 | if (frame_limiting_delta_err > microseconds::zero()) { | ||
| 94 | std::this_thread::sleep_for(frame_limiting_delta_err); | ||
| 95 | |||
| 96 | auto now_after_sleep = Clock::now(); | ||
| 97 | frame_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now); | ||
| 98 | now = now_after_sleep; | ||
| 99 | } | ||
| 100 | |||
| 101 | previous_system_time_us = current_system_time_us; | ||
| 102 | previous_walltime = now; | ||
| 103 | } | ||
| 104 | |||
| 105 | } // namespace Core | ||
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h new file mode 100644 index 000000000..362b205c8 --- /dev/null +++ b/src/core/perf_stats.h | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <chrono> | ||
| 8 | #include <mutex> | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace Core { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Class to manage and query performance/timing statistics. All public functions of this class are | ||
| 15 | * thread-safe unless stated otherwise. | ||
| 16 | */ | ||
| 17 | class PerfStats { | ||
| 18 | public: | ||
| 19 | using Clock = std::chrono::high_resolution_clock; | ||
| 20 | |||
| 21 | struct Results { | ||
| 22 | /// System FPS (LCD VBlanks) in Hz | ||
| 23 | double system_fps; | ||
| 24 | /// Game FPS (GSP frame submissions) in Hz | ||
| 25 | double game_fps; | ||
| 26 | /// Walltime per system frame, in seconds, excluding any waits | ||
| 27 | double frametime; | ||
| 28 | /// Ratio of walltime / emulated time elapsed | ||
| 29 | double emulation_speed; | ||
| 30 | }; | ||
| 31 | |||
| 32 | void BeginSystemFrame(); | ||
| 33 | void EndSystemFrame(); | ||
| 34 | void EndGameFrame(); | ||
| 35 | |||
| 36 | Results GetAndResetStats(u64 current_system_time_us); | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Gets the ratio between walltime and the emulated time of the previous system frame. This is | ||
| 40 | * useful for scaling inputs or outputs moving between the two time domains. | ||
| 41 | */ | ||
| 42 | double GetLastFrameTimeScale(); | ||
| 43 | |||
| 44 | private: | ||
| 45 | std::mutex object_mutex; | ||
| 46 | |||
| 47 | /// Point when the cumulative counters were reset | ||
| 48 | Clock::time_point reset_point = Clock::now(); | ||
| 49 | /// System time when the cumulative counters were reset | ||
| 50 | u64 reset_point_system_us = 0; | ||
| 51 | |||
| 52 | /// Cumulative duration (excluding v-sync/frame-limiting) of frames since last reset | ||
| 53 | Clock::duration accumulated_frametime = Clock::duration::zero(); | ||
| 54 | /// Cumulative number of system frames (LCD VBlanks) presented since last reset | ||
| 55 | u32 system_frames = 0; | ||
| 56 | /// Cumulative number of game frames (GSP frame submissions) since last reset | ||
| 57 | u32 game_frames = 0; | ||
| 58 | |||
| 59 | /// Point when the previous system frame ended | ||
| 60 | Clock::time_point previous_frame_end = reset_point; | ||
| 61 | /// Point when the current system frame began | ||
| 62 | Clock::time_point frame_begin = reset_point; | ||
| 63 | /// Total visible duration (including frame-limiting, etc.) of the previous system frame | ||
| 64 | Clock::duration previous_frame_length = Clock::duration::zero(); | ||
| 65 | }; | ||
| 66 | |||
| 67 | class FrameLimiter { | ||
| 68 | public: | ||
| 69 | using Clock = std::chrono::high_resolution_clock; | ||
| 70 | |||
| 71 | void DoFrameLimiting(u64 current_system_time_us); | ||
| 72 | |||
| 73 | private: | ||
| 74 | /// Emulated system time (in microseconds) at the last limiter invocation | ||
| 75 | u64 previous_system_time_us = 0; | ||
| 76 | /// Walltime at the last limiter invocation | ||
| 77 | Clock::time_point previous_walltime = Clock::now(); | ||
| 78 | |||
| 79 | /// Accumulated difference between walltime and emulated time | ||
| 80 | std::chrono::microseconds frame_limiting_delta_err{0}; | ||
| 81 | }; | ||
| 82 | |||
| 83 | } // namespace Core | ||
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 2aa90e5c1..e19375466 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp | |||
| @@ -10,8 +10,8 @@ | |||
| 10 | #include "common/assert.h" | 10 | #include "common/assert.h" |
| 11 | #include "common/bit_field.h" | 11 | #include "common/bit_field.h" |
| 12 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 13 | #include "common/profiler_reporting.h" | 13 | #include "core/core.h" |
| 14 | #include "common/synchronized_wrapper.h" | 14 | #include "core/core_timing.h" |
| 15 | #include "core/frontend/emu_window.h" | 15 | #include "core/frontend/emu_window.h" |
| 16 | #include "core/hw/gpu.h" | 16 | #include "core/hw/gpu.h" |
| 17 | #include "core/hw/hw.h" | 17 | #include "core/hw/hw.h" |
| @@ -145,21 +145,16 @@ void RendererOpenGL::SwapBuffers() { | |||
| 145 | 145 | ||
| 146 | DrawScreens(); | 146 | DrawScreens(); |
| 147 | 147 | ||
| 148 | auto& profiler = Common::Profiling::GetProfilingManager(); | 148 | Core::System::GetInstance().perf_stats.EndSystemFrame(); |
| 149 | profiler.FinishFrame(); | ||
| 150 | { | ||
| 151 | auto aggregator = Common::Profiling::GetTimingResultsAggregator(); | ||
| 152 | aggregator->AddFrame(profiler.GetPreviousFrameResults()); | ||
| 153 | } | ||
| 154 | 149 | ||
| 155 | // Swap buffers | 150 | // Swap buffers |
| 156 | render_window->PollEvents(); | 151 | render_window->PollEvents(); |
| 157 | render_window->SwapBuffers(); | 152 | render_window->SwapBuffers(); |
| 158 | 153 | ||
| 159 | prev_state.Apply(); | 154 | Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); |
| 160 | 155 | Core::System::GetInstance().perf_stats.BeginSystemFrame(); | |
| 161 | profiler.BeginFrame(); | ||
| 162 | 156 | ||
| 157 | prev_state.Apply(); | ||
| 163 | RefreshRasterizerSetting(); | 158 | RefreshRasterizerSetting(); |
| 164 | 159 | ||
| 165 | if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | 160 | if (Pica::g_debug_context && Pica::g_debug_context->recorder) { |