]> git.cworth.org Git - apitrace/blob - gui/graphwidget.cpp
401c03464efdcc1ecbd9071533b902e93d8c39d0
[apitrace] / gui / graphwidget.cpp
1 #include "graphwidget.h"
2 #include "timelinewidget.h"
3 #include "profiledialog.h"
4
5 #include <qmath.h>
6 #include <QLocale>
7 #include <QPainter>
8 #include <QToolTip>
9 #include <QMouseEvent>
10 #include <QApplication>
11
12 typedef trace::Profile::Call Call;
13 typedef trace::Profile::Frame Frame;
14 typedef trace::Profile::Program Program;
15
16 GraphWidget::GraphWidget(QWidget *parent)
17     : QWidget(parent),
18       m_profile(0),
19       m_axisWidth(80),
20       m_axisHeight(30),
21       m_axisForeground(Qt::black),
22       m_axisBackground(Qt::lightGray)
23 {
24     setBackgroundRole(QPalette::Base);
25     setAutoFillBackground(true);
26     setMouseTracking(true);
27
28     m_graphGradientGpu.setColorAt(0.9, QColor(255, 0, 0));
29     m_graphGradientGpu.setColorAt(0.0, QColor(255, 200, 200));
30
31     m_graphGradientCpu.setColorAt(0.9, QColor(0, 0, 255));
32     m_graphGradientCpu.setColorAt(0.0, QColor(200, 200, 255));
33 }
34
35
36 /**
37  * Setup graph data from profile results
38  */
39 void GraphWidget::setProfile(trace::Profile* profile, GraphType type)
40 {
41     m_type = type;
42     m_profile = profile;
43     m_maxTime = 0;
44
45     /* Find longest call to use as y axis */
46     if (m_type == GraphGpu) {
47         for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
48             const Call& call = *itr;
49
50             if (call.gpuDuration > m_maxTime) {
51                 m_maxTime = call.gpuDuration;
52             }
53         }
54     } else {
55         for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
56             const Call& call = *itr;
57
58             if (call.cpuDuration > m_maxTime) {
59                 m_maxTime = call.cpuDuration;
60             }
61         }
62     }
63
64     m_minCall = 0;
65     m_maxCall = m_profile->calls.size();
66
67     m_minCallWidth = 10;
68     m_maxCallWidth = m_maxCall - m_minCall;
69
70     m_call = m_minCall;
71     m_callWidth = m_maxCallWidth;
72
73     update();
74 }
75
76
77 /**
78  * Slot to synchronise with other graph views
79  */
80 void GraphWidget::changeView(int call, int width)
81 {
82     m_call = call;
83     m_callWidth = width;
84     update();
85 }
86
87
88 /**
89  * Calculate the maxTime variable when model is updated
90  */
91 void GraphWidget::update()
92 {
93     m_maxTime = 0;
94
95     if (m_type == GraphGpu) {
96         for (int i = m_call; i < m_call + m_callWidth; ++i) {
97             const Call& call =  m_profile->calls[i];
98
99             if (call.gpuDuration > m_maxTime) {
100                 m_maxTime = call.gpuDuration;
101             }
102         }
103     } else {
104         for (int i = m_call; i < m_call + m_callWidth; ++i) {
105             const Call& call =  m_profile->calls[i];
106
107             if (call.cpuDuration > m_maxTime) {
108                 m_maxTime = call.cpuDuration;
109             }
110         }
111     }
112
113     QWidget::update();
114 }
115
116
117 /**
118  * Find the call at (x, y) position
119  */
120 const Call* GraphWidget::callAtPosition(const QPoint& pos)
121 {
122     int left, right, size;
123     int64_t time;
124
125     if (!m_profile) {
126         return NULL;
127     }
128
129     int posX = qMax(0, pos.x() - m_axisWidth);
130     int posY = qMax(0, pos.y() - m_axisHeight);
131
132     time  = ((m_graphHeight - posY) / (double)m_graphHeight) * m_maxTime;
133     time -= (2 * m_maxTime) / m_graphHeight;
134
135     size  = m_callWidth / (double)m_graphWidth;
136
137     left  = m_call + (posX / (double)m_graphWidth) * m_callWidth;
138     left  = qMax(m_minCall, left - size);
139
140     right = qMin(m_maxCall - 1, left + size * 2);
141
142     if (m_type == GraphGpu) {
143         const Call* longest = NULL;
144
145         for (int i = left; i <= right; ++i) {
146             const Call& call = m_profile->calls[i];
147
148             if (call.pixels >= 0) {
149                 if (!longest || call.gpuDuration > longest->gpuDuration) {
150                     longest = &call;
151                 }
152             }
153         }
154
155         if (longest && time < longest->gpuDuration) {
156             return longest;
157         }
158     } else {
159         const Call* longest = NULL;
160
161         for (int i = left; i <= right; ++i) {
162             const Call& call = m_profile->calls[i];
163
164             if (!longest || call.cpuDuration > longest->cpuDuration) {
165                 longest = &call;
166             }
167         }
168
169         if (longest && time < longest->cpuDuration) {
170             return longest;
171         }
172     }
173
174     return NULL;
175 }
176
177
178 void GraphWidget::mousePressEvent(QMouseEvent *e)
179 {
180     if (e->button() == Qt::LeftButton) {
181         m_mousePressPosition = e->pos();
182         m_mousePressCall = m_call;
183     }
184 }
185
186
187 void GraphWidget::mouseMoveEvent(QMouseEvent *e)
188 {
189     if (!m_profile) {
190         return;
191     }
192
193     if (e->pos().x() < m_axisWidth || e->pos().y() < m_axisHeight) {
194         return;
195     }
196
197     if (e->buttons().testFlag(Qt::LeftButton)) {
198         /* Horizontal scroll */
199         double dcdx = m_callWidth / (double)m_graphWidth;
200         dcdx *= m_mousePressPosition.x() - e->pos().x();
201
202         m_call = m_mousePressCall + dcdx;
203         m_call = qBound(m_minCall, m_call, m_maxCall - m_callWidth);
204
205         emit viewChanged(m_call, m_callWidth);
206         update();
207     }
208
209     const Call* call = callAtPosition(e->pos());
210
211     if (e->button() == Qt::NoButton && call) {
212         QString text;
213         text  = QString::fromStdString(call->name);
214         text += QString("\nCall: %1").arg(call->no);
215         text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
216
217         if (call->pixels >= 0) {
218             text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
219             text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
220         }
221
222         QToolTip::showText(e->globalPos(), text);
223     } else {
224         QToolTip::hideText();
225     }
226 }
227
228
229 void GraphWidget::wheelEvent(QWheelEvent *e)
230 {
231     if (!m_profile) {
232         return;
233     }
234
235     if (e->pos().x() < m_axisWidth || e->pos().y() < m_axisHeight) {
236         return;
237     }
238
239     int zoomPercent = 10;
240
241     /* If holding Ctrl key then zoom 2x faster */
242     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
243         zoomPercent = 20;
244     }
245
246     /* Zoom view by adjusting width */
247     double dt = m_callWidth;
248     double size = dt * -e->delta();
249
250     /* Zoom deltas normally come in increments of 120 */
251     size /= 120 * (100 / zoomPercent);
252
253     m_callWidth += size;
254     m_callWidth = qBound(m_minCallWidth, m_callWidth, m_maxCallWidth);
255
256     /* Scroll view to zoom around mouse */
257     dt -= m_callWidth;
258     dt *= e->x() - m_axisWidth;
259     dt /= m_graphWidth;
260
261     m_call = dt + m_call;
262     m_call = qBound(m_minCall, m_call, m_maxCall - m_callWidth);
263
264     emit viewChanged(m_call, m_callWidth);
265     update();
266 }
267
268
269 void GraphWidget::mouseDoubleClickEvent(QMouseEvent *e)
270 {
271     const Call* call = callAtPosition(e->pos());
272
273     if (call) {
274         emit jumpToCall(call->no);
275     }
276 }
277
278
279 void GraphWidget::resizeEvent(QResizeEvent *e)
280 {
281     m_graphWidth = qMax(0, width() - m_axisWidth);
282     m_graphHeight = qMax(0, height() - m_axisHeight);
283
284     m_graphGradientGpu.setStart(0, m_graphHeight);
285     m_graphGradientCpu.setStart(0, m_graphHeight);
286 }
287
288
289 /**
290  * Draw the vertical axis of time
291  */
292 void GraphWidget::paintVerticalAxis(QPainter& painter)
293 {
294     int height = painter.fontMetrics().height();
295     int ticks  = m_graphHeight / (height * 2);
296
297     double step   = m_maxTime / (double)ticks;
298     double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0)));
299     step = qFloor((step / step10) * 2) * (step10 / 2);
300
301     painter.resetTransform();
302     painter.translate(0, m_axisHeight);
303     painter.setPen(m_axisForeground);
304
305     for (double tick = 0; tick <= m_maxTime; tick += step) {
306         int y = m_graphHeight - ((tick / m_maxTime) * m_graphHeight);
307
308         painter.drawLine(m_axisWidth - 8, y, m_axisWidth - 1, y);
309
310         painter.drawText(0,
311                          qBound(0, y - height / 2, m_graphHeight - height),
312                          m_axisWidth - 10,
313                          height,
314                          Qt::AlignRight | Qt::AlignVCenter,
315                          getTimeString(tick, m_maxTime));
316     }
317 }
318
319
320 /**
321  * Draw horizontal axis of frame numbers
322  */
323 void GraphWidget::paintHorizontalAxis(QPainter& painter)
324 {
325     double dxdc = m_graphWidth / (double)m_callWidth;
326     double scroll = dxdc * m_call;
327     int lastLabel = -9999;
328
329     painter.resetTransform();
330     painter.fillRect(0, 0, width(), m_axisHeight, m_axisBackground);
331     painter.fillRect(0, 0, m_axisWidth, height(), m_axisBackground);
332
333     painter.setPen(m_axisForeground);
334     painter.drawLine(0, m_axisHeight - 1, width(), m_axisHeight - 1);
335     painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, height());
336
337     painter.translate(m_axisWidth, 0);
338
339     for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
340         static const int padding = 4;
341         const Frame& frame = *itr;
342         bool draw = true;
343         int width;
344
345         if (frame.calls.begin > m_call + m_callWidth) {
346             break;
347         }
348
349         if (frame.calls.end < m_call) {
350             draw = false;
351         }
352
353         double left = dxdc * frame.calls.begin;
354         double right = dxdc * frame.calls.end;
355         QString text = QString("%1").arg(frame.no);
356
357         width = painter.fontMetrics().width(text) + padding * 2;
358
359         if (left + width > scroll)
360             draw = true;
361
362         /* Draw a frame number if we have space since the last one */
363         if (left - lastLabel > width) {
364             lastLabel = left + width;
365
366             if (draw) {
367                 int textX;
368
369                 if (left < scroll && right - left > width) {
370                     if (right - scroll > width) {
371                         textX = 0;
372                     } else {
373                         textX = right - scroll - width;
374                     }
375                 } else {
376                     textX = left - scroll;
377                 }
378
379                 /* Draw frame number and major ruler marking */
380                 painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
381                 painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
382             }
383         } else if (draw) {
384             /* Draw a minor ruler marking */
385             painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
386         }
387     }
388 }
389
390
391 void GraphWidget::paintEvent(QPaintEvent *e)
392 {
393     if (!m_profile) {
394         return;
395     }
396
397     QPainter painter(this);
398     QBrush brush;
399
400     /* Draw axes */
401     paintHorizontalAxis(painter);
402     paintVerticalAxis(painter);
403
404     /* Draw the label */
405     painter.resetTransform();
406     painter.fillRect(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::lightGray);
407
408     if (m_type == GraphGpu) {
409         painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "GPU");
410     } else {
411         painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
412     }
413
414     /* Draw graph */
415     if (m_type == GraphGpu) {
416         brush = QBrush(m_graphGradientGpu);
417     } else {
418         brush = QBrush(m_graphGradientCpu);
419     }
420
421     painter.setBrush(brush);
422     painter.setPen(QPen(brush, 1));
423     painter.translate(m_axisWidth, m_axisHeight);
424
425     double x = 0;
426     double dydt = m_graphHeight / (double)m_maxTime;
427     double dxdc = m_graphWidth / (double)m_callWidth;
428
429     if (dxdc < 1.0) {
430         /* Less than 1 pixel per call, draw the longest call in a pixel */
431         int64_t longest = 0;
432         int lastX = 0;
433
434         if (m_type == GraphGpu) {
435             for (int i = m_call; i < m_call + m_callWidth; ++i) {
436                 const Call& call =  m_profile->calls[i];
437
438                 if (call.gpuDuration > longest) {
439                     longest = call.gpuDuration;
440                 }
441
442                 x += dxdc;
443
444                 if (lastX != (int)x) {
445                     painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (longest * dydt));
446                     longest = 0;
447                     lastX = x;
448                 }
449             }
450         } else {
451             for (int i = m_call; i < m_call + m_callWidth; ++i) {
452                 const Call& call =  m_profile->calls[i];
453
454                 if (call.cpuDuration > longest) {
455                     longest = call.cpuDuration;
456                 }
457
458                 x += dxdc;
459
460                 if (lastX != (int)x) {
461                     painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (longest * dydt));
462                     longest = 0;
463                     lastX = x;
464                 }
465             }
466         }
467     } else {
468         /* At least 1 pixel per call, draw rects */
469         if (m_type == GraphGpu) {
470             for (int i = m_call; i < m_call + m_callWidth; ++i) {
471                 const Call& call =  m_profile->calls[i];
472
473                 if (call.pixels >= 0) {
474                     int y = qMax<int>(1, call.gpuDuration * dydt);
475                     painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), brush);
476                 }
477
478                 x += dxdc;
479             }
480         } else {
481             for (int i = m_call; i < m_call + m_callWidth; ++i) {
482                 const Call& call =  m_profile->calls[i];
483
484                 int y = qMax<int>(1, call.cpuDuration * dydt);
485                 painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), brush);
486
487                 x += dxdc;
488             }
489         }
490     }
491 }
492
493 #include "graphwidget.moc"