profiler.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. // Copyright 2015 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <QMouseEvent>
  5. #include <QPainter>
  6. #include <QString>
  7. #include "citra_qt/debugger/profiler.h"
  8. #include "citra_qt/util/util.h"
  9. #include "common/microprofile.h"
  10. #include "common/profiler_reporting.h"
  11. // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the
  12. // non-Qt frontends don't need it (and don't implement the UI drawing hooks either).
  13. #define MICROPROFILEUI_IMPL 1
  14. #include "common/microprofileui.h"
  15. using namespace Common::Profiling;
  16. static QVariant GetDataForColumn(int col, const AggregatedDuration& duration)
  17. {
  18. static auto duration_to_float = [](Duration dur) -> float {
  19. using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>;
  20. return std::chrono::duration_cast<FloatMs>(dur).count();
  21. };
  22. switch (col) {
  23. case 1: return duration_to_float(duration.avg);
  24. case 2: return duration_to_float(duration.min);
  25. case 3: return duration_to_float(duration.max);
  26. default: return QVariant();
  27. }
  28. }
  29. static const TimingCategoryInfo* GetCategoryInfo(int id)
  30. {
  31. const auto& categories = GetProfilingManager().GetTimingCategoriesInfo();
  32. if ((size_t)id >= categories.size()) {
  33. return nullptr;
  34. } else {
  35. return &categories[id];
  36. }
  37. }
  38. ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent)
  39. {
  40. updateProfilingInfo();
  41. const auto& categories = GetProfilingManager().GetTimingCategoriesInfo();
  42. results.time_per_category.resize(categories.size());
  43. }
  44. QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const
  45. {
  46. if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
  47. switch (section) {
  48. case 0: return tr("Category");
  49. case 1: return tr("Avg");
  50. case 2: return tr("Min");
  51. case 3: return tr("Max");
  52. }
  53. }
  54. return QVariant();
  55. }
  56. QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const
  57. {
  58. return createIndex(row, column);
  59. }
  60. QModelIndex ProfilerModel::parent(const QModelIndex& child) const
  61. {
  62. return QModelIndex();
  63. }
  64. int ProfilerModel::columnCount(const QModelIndex& parent) const
  65. {
  66. return 4;
  67. }
  68. int ProfilerModel::rowCount(const QModelIndex& parent) const
  69. {
  70. if (parent.isValid()) {
  71. return 0;
  72. } else {
  73. return static_cast<int>(results.time_per_category.size() + 2);
  74. }
  75. }
  76. QVariant ProfilerModel::data(const QModelIndex& index, int role) const
  77. {
  78. if (role == Qt::DisplayRole) {
  79. if (index.row() == 0) {
  80. if (index.column() == 0) {
  81. return tr("Frame");
  82. } else {
  83. return GetDataForColumn(index.column(), results.frame_time);
  84. }
  85. } else if (index.row() == 1) {
  86. if (index.column() == 0) {
  87. return tr("Frame (with swapping)");
  88. } else {
  89. return GetDataForColumn(index.column(), results.interframe_time);
  90. }
  91. } else {
  92. if (index.column() == 0) {
  93. const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2);
  94. return info != nullptr ? QString(info->name) : QVariant();
  95. } else {
  96. if (index.row() - 2 < (int)results.time_per_category.size()) {
  97. return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]);
  98. } else {
  99. return QVariant();
  100. }
  101. }
  102. }
  103. }
  104. return QVariant();
  105. }
  106. void ProfilerModel::updateProfilingInfo()
  107. {
  108. results = GetTimingResultsAggregator()->GetAggregatedResults();
  109. emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3));
  110. }
  111. ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent)
  112. {
  113. ui.setupUi(this);
  114. model = new ProfilerModel(this);
  115. ui.treeView->setModel(model);
  116. connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool)));
  117. connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo()));
  118. }
  119. void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable)
  120. {
  121. if (enable) {
  122. update_timer.start(100);
  123. model->updateProfilingInfo();
  124. } else {
  125. update_timer.stop();
  126. }
  127. }
  128. class MicroProfileWidget : public QWidget {
  129. public:
  130. MicroProfileWidget(QWidget* parent = nullptr);
  131. protected:
  132. void paintEvent(QPaintEvent* ev) override;
  133. void showEvent(QShowEvent* ev) override;
  134. void hideEvent(QHideEvent* ev) override;
  135. void mouseMoveEvent(QMouseEvent* ev) override;
  136. void mousePressEvent(QMouseEvent* ev) override;
  137. void mouseReleaseEvent(QMouseEvent* ev) override;
  138. void wheelEvent(QWheelEvent* ev) override;
  139. void keyPressEvent(QKeyEvent* ev) override;
  140. void keyReleaseEvent(QKeyEvent* ev) override;
  141. private:
  142. /// This timer is used to redraw the widget's contents continuously. To save resources, it only
  143. /// runs while the widget is visible.
  144. QTimer update_timer;
  145. };
  146. MicroProfileDialog::MicroProfileDialog(QWidget* parent)
  147. : QWidget(parent, Qt::Dialog)
  148. {
  149. setObjectName("MicroProfile");
  150. setWindowTitle(tr("MicroProfile"));
  151. resize(1000, 600);
  152. // Remove the "?" button from the titlebar and enable the maximize button
  153. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint | Qt::WindowMaximizeButtonHint);
  154. MicroProfileWidget* widget = new MicroProfileWidget(this);
  155. QLayout* layout = new QVBoxLayout(this);
  156. layout->setContentsMargins(0, 0, 0, 0);
  157. layout->addWidget(widget);
  158. setLayout(layout);
  159. // Configure focus so that widget is focusable and the dialog automatically forwards focus to it.
  160. setFocusProxy(widget);
  161. widget->setFocusPolicy(Qt::StrongFocus);
  162. widget->setFocus();
  163. }
  164. QAction* MicroProfileDialog::toggleViewAction() {
  165. if (toggle_view_action == nullptr) {
  166. toggle_view_action = new QAction(windowTitle(), this);
  167. toggle_view_action->setCheckable(true);
  168. toggle_view_action->setChecked(isVisible());
  169. connect(toggle_view_action, SIGNAL(toggled(bool)), SLOT(setVisible(bool)));
  170. }
  171. return toggle_view_action;
  172. }
  173. void MicroProfileDialog::showEvent(QShowEvent* ev) {
  174. if (toggle_view_action) {
  175. toggle_view_action->setChecked(isVisible());
  176. }
  177. QWidget::showEvent(ev);
  178. }
  179. void MicroProfileDialog::hideEvent(QHideEvent* ev) {
  180. if (toggle_view_action) {
  181. toggle_view_action->setChecked(isVisible());
  182. }
  183. QWidget::hideEvent(ev);
  184. }
  185. /// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the
  186. /// QPainter available inside the drawing callbacks.
  187. static QPainter* mp_painter = nullptr;
  188. MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
  189. // Send mouse motion events even when not dragging.
  190. setMouseTracking(true);
  191. MicroProfileSetDisplayMode(1); // Timers screen
  192. MicroProfileInitUI();
  193. connect(&update_timer, SIGNAL(timeout()), SLOT(update()));
  194. }
  195. void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
  196. QPainter painter(this);
  197. painter.setBackground(Qt::black);
  198. painter.eraseRect(rect());
  199. QFont font = GetMonospaceFont();
  200. font.setPixelSize(MICROPROFILE_TEXT_HEIGHT);
  201. painter.setFont(font);
  202. mp_painter = &painter;
  203. MicroProfileDraw(rect().width(), rect().height());
  204. mp_painter = nullptr;
  205. }
  206. void MicroProfileWidget::showEvent(QShowEvent* ev) {
  207. update_timer.start(15); // ~60 Hz
  208. QWidget::showEvent(ev);
  209. }
  210. void MicroProfileWidget::hideEvent(QHideEvent* ev) {
  211. update_timer.stop();
  212. QWidget::hideEvent(ev);
  213. }
  214. void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) {
  215. MicroProfileMousePosition(ev->x(), ev->y(), 0);
  216. ev->accept();
  217. }
  218. void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) {
  219. MicroProfileMousePosition(ev->x(), ev->y(), 0);
  220. MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
  221. ev->accept();
  222. }
  223. void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) {
  224. MicroProfileMousePosition(ev->x(), ev->y(), 0);
  225. MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
  226. ev->accept();
  227. }
  228. void MicroProfileWidget::wheelEvent(QWheelEvent* ev) {
  229. MicroProfileMousePosition(ev->x(), ev->y(), ev->delta() / 120);
  230. ev->accept();
  231. }
  232. void MicroProfileWidget::keyPressEvent(QKeyEvent* ev) {
  233. if (ev->key() == Qt::Key_Control) {
  234. // Inform MicroProfile that the user is holding Ctrl.
  235. MicroProfileModKey(1);
  236. }
  237. QWidget::keyPressEvent(ev);
  238. }
  239. void MicroProfileWidget::keyReleaseEvent(QKeyEvent* ev) {
  240. if (ev->key() == Qt::Key_Control) {
  241. MicroProfileModKey(0);
  242. }
  243. QWidget::keyReleaseEvent(ev);
  244. }
  245. // These functions are called by MicroProfileDraw to draw the interface elements on the screen.
  246. void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 text_length) {
  247. // hex_color does not include an alpha, so it must be assumed to be 255
  248. mp_painter->setPen(QColor::fromRgb(hex_color));
  249. // It's impossible to draw a string using a monospaced font with a fixed width per cell in a
  250. // way that's reliable across different platforms and fonts as far as I (yuriks) can tell, so
  251. // draw each character individually in order to precisely control the text advance.
  252. for (u32 i = 0; i < text_length; ++i) {
  253. // Position the text baseline 1 pixel above the bottom of the text cell, this gives nice
  254. // vertical alignment of text for a wide range of tested fonts.
  255. mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QChar(text[i]));
  256. x += MICROPROFILE_TEXT_WIDTH + 1;
  257. }
  258. }
  259. void MicroProfileDrawBox(int left, int top, int right, int bottom, u32 hex_color, MicroProfileBoxType type) {
  260. QColor color = QColor::fromRgba(hex_color);
  261. QBrush brush = color;
  262. if (type == MicroProfileBoxTypeBar) {
  263. QLinearGradient gradient(left, top, left, bottom);
  264. gradient.setColorAt(0.f, color.lighter(125));
  265. gradient.setColorAt(1.f, color.darker(125));
  266. brush = gradient;
  267. }
  268. mp_painter->fillRect(left, top, right - left, bottom - top, brush);
  269. }
  270. void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color) {
  271. // Temporary vector used to convert between the float array and QPointF. Marked static to reuse
  272. // the allocation across calls.
  273. static std::vector<QPointF> point_buf;
  274. for (u32 i = 0; i < vertices_length; ++i) {
  275. point_buf.emplace_back(vertices[i*2 + 0], vertices[i*2 + 1]);
  276. }
  277. // hex_color does not include an alpha, so it must be assumed to be 255
  278. mp_painter->setPen(QColor::fromRgb(hex_color));
  279. mp_painter->drawPolyline(point_buf.data(), vertices_length);
  280. point_buf.clear();
  281. }