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 <QAction>
6 #include <QLayout>
7 #include <QMouseEvent>
8 #include <QPainter>
9 #include <QString>
10 #include <QTimer>
11 #include "common/common_types.h"
12 #include "common/microprofile.h"
13 #include "yuzu/debugger/profiler.h"
14 #include "yuzu/util/util.h"
15 
16 // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the
17 // non-Qt frontends don't need it (and don't implement the UI drawing hooks either).
18 #if MICROPROFILE_ENABLED
19 #define MICROPROFILEUI_IMPL 1
20 #include "common/microprofileui.h"
21 
22 class MicroProfileWidget : public QWidget {
23 public:
24     MicroProfileWidget(QWidget* parent = nullptr);
25 
26 protected:
27     void paintEvent(QPaintEvent* ev) override;
28     void showEvent(QShowEvent* ev) override;
29     void hideEvent(QHideEvent* ev) override;
30 
31     void mouseMoveEvent(QMouseEvent* ev) override;
32     void mousePressEvent(QMouseEvent* ev) override;
33     void mouseReleaseEvent(QMouseEvent* ev) override;
34     void wheelEvent(QWheelEvent* ev) override;
35 
36     void keyPressEvent(QKeyEvent* ev) override;
37     void keyReleaseEvent(QKeyEvent* ev) override;
38 
39 private:
40     /// This timer is used to redraw the widget's contents continuously. To save resources, it only
41     /// runs while the widget is visible.
42     QTimer update_timer;
43     /// Scale the coordinate system appropriately when dpi != 96.
44     qreal x_scale = 1.0, y_scale = 1.0;
45 };
46 
47 #endif
48 
MicroProfileDialog(QWidget * parent)49 MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
50     setObjectName(QStringLiteral("MicroProfile"));
51     setWindowTitle(tr("MicroProfile"));
52     resize(1000, 600);
53     // Remove the "?" button from the titlebar and enable the maximize button
54     setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) |
55                    Qt::WindowMaximizeButtonHint);
56 
57 #if MICROPROFILE_ENABLED
58 
59     MicroProfileWidget* widget = new MicroProfileWidget(this);
60 
61     QLayout* layout = new QVBoxLayout(this);
62     layout->setContentsMargins(0, 0, 0, 0);
63     layout->addWidget(widget);
64     setLayout(layout);
65 
66     // Configure focus so that widget is focusable and the dialog automatically forwards focus to
67     // it.
68     setFocusProxy(widget);
69     widget->setFocusPolicy(Qt::StrongFocus);
70     widget->setFocus();
71 #endif
72 }
73 
toggleViewAction()74 QAction* MicroProfileDialog::toggleViewAction() {
75     if (toggle_view_action == nullptr) {
76         toggle_view_action = new QAction(windowTitle(), this);
77         toggle_view_action->setCheckable(true);
78         toggle_view_action->setChecked(isVisible());
79         connect(toggle_view_action, &QAction::toggled, this, &MicroProfileDialog::setVisible);
80     }
81 
82     return toggle_view_action;
83 }
84 
showEvent(QShowEvent * ev)85 void MicroProfileDialog::showEvent(QShowEvent* ev) {
86     if (toggle_view_action) {
87         toggle_view_action->setChecked(isVisible());
88     }
89     QWidget::showEvent(ev);
90 }
91 
hideEvent(QHideEvent * ev)92 void MicroProfileDialog::hideEvent(QHideEvent* ev) {
93     if (toggle_view_action) {
94         toggle_view_action->setChecked(isVisible());
95     }
96     QWidget::hideEvent(ev);
97 }
98 
99 #if MICROPROFILE_ENABLED
100 
101 /// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the
102 /// QPainter available inside the drawing callbacks.
103 static QPainter* mp_painter = nullptr;
104 
MicroProfileWidget(QWidget * parent)105 MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
106     // Send mouse motion events even when not dragging.
107     setMouseTracking(true);
108 
109     MicroProfileSetDisplayMode(1); // Timers screen
110     MicroProfileInitUI();
111 
112     connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update));
113 }
114 
paintEvent(QPaintEvent * ev)115 void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
116     QPainter painter(this);
117 
118     // The units used by Microprofile for drawing are based in pixels on a 96 dpi display.
119     x_scale = qreal(painter.device()->logicalDpiX()) / 96.0;
120     y_scale = qreal(painter.device()->logicalDpiY()) / 96.0;
121     painter.scale(x_scale, y_scale);
122 
123     painter.setBackground(Qt::black);
124     painter.eraseRect(rect());
125 
126     QFont font = GetMonospaceFont();
127     font.setPixelSize(MICROPROFILE_TEXT_HEIGHT);
128     painter.setFont(font);
129 
130     mp_painter = &painter;
131     MicroProfileDraw(rect().width() / x_scale, rect().height() / y_scale);
132     mp_painter = nullptr;
133 }
134 
showEvent(QShowEvent * ev)135 void MicroProfileWidget::showEvent(QShowEvent* ev) {
136     update_timer.start(15); // ~60 Hz
137     QWidget::showEvent(ev);
138 }
139 
hideEvent(QHideEvent * ev)140 void MicroProfileWidget::hideEvent(QHideEvent* ev) {
141     update_timer.stop();
142     QWidget::hideEvent(ev);
143 }
144 
mouseMoveEvent(QMouseEvent * ev)145 void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) {
146     MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
147     ev->accept();
148 }
149 
mousePressEvent(QMouseEvent * ev)150 void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) {
151     MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
152     MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
153     ev->accept();
154 }
155 
mouseReleaseEvent(QMouseEvent * ev)156 void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) {
157     MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
158     MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
159     ev->accept();
160 }
161 
wheelEvent(QWheelEvent * ev)162 void MicroProfileWidget::wheelEvent(QWheelEvent* ev) {
163     MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120);
164     ev->accept();
165 }
166 
keyPressEvent(QKeyEvent * ev)167 void MicroProfileWidget::keyPressEvent(QKeyEvent* ev) {
168     if (ev->key() == Qt::Key_Control) {
169         // Inform MicroProfile that the user is holding Ctrl.
170         MicroProfileModKey(1);
171     }
172     QWidget::keyPressEvent(ev);
173 }
174 
keyReleaseEvent(QKeyEvent * ev)175 void MicroProfileWidget::keyReleaseEvent(QKeyEvent* ev) {
176     if (ev->key() == Qt::Key_Control) {
177         MicroProfileModKey(0);
178     }
179     QWidget::keyReleaseEvent(ev);
180 }
181 
182 // These functions are called by MicroProfileDraw to draw the interface elements on the screen.
183 
MicroProfileDrawText(int x,int y,u32 hex_color,const char * text,u32 text_length)184 void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 text_length) {
185     // hex_color does not include an alpha, so it must be assumed to be 255
186     mp_painter->setPen(QColor::fromRgb(hex_color));
187 
188     // It's impossible to draw a string using a monospaced font with a fixed width per cell in a
189     // way that's reliable across different platforms and fonts as far as I (yuriks) can tell, so
190     // draw each character individually in order to precisely control the text advance.
191     for (u32 i = 0; i < text_length; ++i) {
192         // Position the text baseline 1 pixel above the bottom of the text cell, this gives nice
193         // vertical alignment of text for a wide range of tested fonts.
194         mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QString{QLatin1Char{text[i]}});
195         x += MICROPROFILE_TEXT_WIDTH + 1;
196     }
197 }
198 
MicroProfileDrawBox(int left,int top,int right,int bottom,u32 hex_color,MicroProfileBoxType type)199 void MicroProfileDrawBox(int left, int top, int right, int bottom, u32 hex_color,
200                          MicroProfileBoxType type) {
201     QColor color = QColor::fromRgba(hex_color);
202     QBrush brush = color;
203     if (type == MicroProfileBoxTypeBar) {
204         QLinearGradient gradient(left, top, left, bottom);
205         gradient.setColorAt(0.f, color.lighter(125));
206         gradient.setColorAt(1.f, color.darker(125));
207         brush = gradient;
208     }
209     mp_painter->fillRect(left, top, right - left, bottom - top, brush);
210 }
211 
MicroProfileDrawLine2D(u32 vertices_length,float * vertices,u32 hex_color)212 void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color) {
213     // Temporary vector used to convert between the float array and QPointF. Marked static to reuse
214     // the allocation across calls.
215     static std::vector<QPointF> point_buf;
216 
217     for (u32 i = 0; i < vertices_length; ++i) {
218         point_buf.emplace_back(vertices[i * 2 + 0], vertices[i * 2 + 1]);
219     }
220 
221     // hex_color does not include an alpha, so it must be assumed to be 255
222     mp_painter->setPen(QColor::fromRgb(hex_color));
223     mp_painter->drawPolyline(point_buf.data(), vertices_length);
224     point_buf.clear();
225 }
226 #endif
227