]> git.cworth.org Git - apitrace/blob - gui/timelinewidget.cpp
Add gui support for trace profiling.
[apitrace] / gui / timelinewidget.cpp
1 #include "timelinewidget.h"
2 #include "trace_profiler.hpp"
3
4 #include <math.h>
5 #include <QColor>
6 #include <QPainter>
7 #include <QToolTip>
8 #include <QMouseEvent>
9 #include <QWheelEvent>
10 #include <QApplication>
11
12 typedef trace::Profile::Call Call;
13 typedef trace::Profile::Frame Frame;
14
15 TimelineWidget::TimelineWidget(QWidget *parent)
16     : QWidget(parent),
17       m_profile(NULL),
18       m_timeSelectionStart(0),
19       m_timeSelectionEnd(0),
20       m_rowHeight(20),
21       m_axisWidth(50),
22       m_axisHeight(20),
23       m_axisBorder(Qt::black),
24       m_axisBackground(Qt::lightGray),
25       m_itemBorder(Qt::red),
26       m_itemForeground(Qt::cyan),
27       m_itemBackground(Qt::red),
28       m_selectionBackground(QColor(245, 245, 255)),
29       m_selectionBorder(QColor(50, 50, 255)),
30       m_zoomBorder(Qt::green),
31       m_zoomBackground(QColor(100, 255, 100, 80))
32 {
33     setBackgroundRole(QPalette::Base);
34     setAutoFillBackground(true);
35     setMouseTracking(true);
36 }
37
38
39 /**
40  * Update horizontal view scroll based on scroll value
41  */
42 void TimelineWidget::setHorizontalScrollValue(int scrollValue)
43 {
44     if (!m_profile) {
45         return;
46     }
47
48     /* Calculate time from scroll value */
49     double time = scrollValue;
50     time /= m_maxScrollX;
51     time *= (m_timeMax - m_timeWidth) - m_timeMin;
52     time += m_timeMin;
53
54     setTimeScroll(time, false);
55 }
56
57
58 /**
59  * Update vertical view scroll based on scroll value
60  */
61 void TimelineWidget::setVerticalScrollValue(int value)
62 {
63     if (!m_profile) {
64         return;
65     }
66
67     setRowScroll(value, false);
68 }
69
70
71 /**
72  * Update the time selection
73  */
74 void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify)
75 {
76     m_timeSelectionStart = start;
77     m_timeSelectionEnd = end;
78
79     if (notify) {
80         emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd);
81     }
82
83     update();
84 }
85
86
87 /**
88  * Convert time to view position
89  */
90 double TimelineWidget::timeToPosition(int64_t time)
91 {
92     double pos = time;
93     pos -= m_time;
94     pos /= m_timeWidth;
95     pos *= m_viewWidth;
96     return pos;
97 }
98
99
100 /**
101  * Convert view position to time
102  */
103 int64_t TimelineWidget::positionToTime(int pos)
104 {
105     double time = pos;
106     time /= m_viewWidth;
107     time *= m_timeWidth;
108     time += m_time;
109     return (int64_t)time;
110 }
111
112
113 /**
114  * Return the item at position
115  */
116 const VisibleItem* TimelineWidget::itemAtPosition(const QPoint& pos)
117 {
118     foreach (const VisibleItem& item, m_visibleItems) {
119         if (pos.x() < item.rect.left() || pos.y() < item.rect.top())
120             continue;
121
122         if (pos.x() > item.rect.right() || pos.y() > item.rect.bottom())
123             continue;
124
125         return &item;
126     }
127
128     return NULL;
129 }
130
131
132 /**
133  * Calculate the row order by total gpu time per shader
134  */
135 void TimelineWidget::calculateRows()
136 {
137     typedef QPair<uint64_t, unsigned> HeatProgram;
138     QList<HeatProgram> heats;
139     int idx;
140
141     m_programRowMap.clear();
142     m_rowCount = 0;
143
144     for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
145         const Frame& frame = *itr;
146
147         for (Call::const_iterator jtr = frame.calls.begin(); jtr != frame.calls.end(); ++jtr) {
148             const Call& call = *jtr;
149
150             while (call.program >= heats.size()) {
151                 heats.append(HeatProgram(0, heats.size()));
152                 m_programRowMap.append(m_programRowMap.size());
153             }
154
155             heats[call.program].first += call.gpuDuration;
156         }
157     }
158
159     qSort(heats);
160     idx = heats.size() - 1;
161
162     for (QList<HeatProgram>::iterator itr = heats.begin(); itr != heats.end(); ++itr, --idx) {
163         HeatProgram& pair = *itr;
164
165         if (pair.first == 0) {
166             m_programRowMap[pair.second] = -1;
167         } else {
168             m_programRowMap[pair.second] = idx;
169             m_rowCount++;
170         }
171     }
172 }
173
174
175 /**
176  * Set the trace profile to use for the timeline
177  */
178 void TimelineWidget::setProfile(trace::Profile* profile)
179 {
180     if (!profile->frames.size())
181         return;
182
183     m_profile = profile;
184     calculateRows();
185
186     m_timeMin = m_profile->frames.front().gpuStart;
187     m_timeMax = m_profile->frames.back().gpuStart + m_profile->frames.back().gpuDuration;
188
189     m_time = m_timeMin;
190     m_timeWidth = m_timeMax - m_timeMin;
191
192     m_timeWidthMin = 1000;
193     m_timeWidthMax = m_timeWidth;
194
195     m_maxScrollX = 0;
196     m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
197
198     setTimeScroll(m_time);
199     setRowScroll(0);
200
201     update();
202 }
203
204
205 /**
206  * Set the horizontal scroll position to time
207  */
208 void TimelineWidget::setTimeScroll(int64_t time, bool notify)
209 {
210     time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
211
212     m_time = time;
213
214     if (m_timeWidth == m_timeWidthMax) {
215         m_maxScrollX = 0;
216     } else {
217         m_maxScrollX = 10000;
218     }
219
220     if (notify) {
221         double value = time - m_timeMin;
222         value /= m_timeMax - m_timeWidth - m_timeMin;
223         value *= m_maxScrollX;
224         m_scrollX = value;
225
226         emit horizontalScrollMaxChanged(m_maxScrollX);
227         emit horizontalScrollValueChanged(m_scrollX);
228     }
229
230     update();
231 }
232
233
234 /**
235  * Set the vertical scroll position to position
236  */
237 void TimelineWidget::setRowScroll(int position, bool notify)
238 {
239     position = qBound(0, position, m_maxScrollY);
240
241     m_scrollY = position;
242     m_row  = m_scrollY / m_rowHeight;
243
244     if (notify) {
245         emit verticalScrollMaxChanged(m_maxScrollY);
246         emit verticalScrollValueChanged(m_scrollY);
247     }
248
249     update();
250 }
251
252
253 void TimelineWidget::resizeEvent(QResizeEvent *e)
254 {
255     /* Update viewport size */
256     m_viewWidth = width() - m_axisWidth;
257     m_viewHeight = height() - m_axisHeight;
258
259     /* Update vertical scroll bar */
260     if (m_profile) {
261         m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
262         emit verticalScrollMaxChanged(m_maxScrollY);
263         setRowScroll(m_scrollY);
264     }
265 }
266
267
268 void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
269 {
270     if (!m_profile) {
271         return;
272     }
273
274     /* Display tooltip if necessary */
275     if (e->buttons() == Qt::NoButton) {
276         const VisibleItem* item = itemAtPosition(e->pos());
277
278         if (item) {
279             const trace::Profile::Call* call = item->call;
280
281             QString text;
282             text  = QString::fromStdString(call->name);
283             text += QString("\nCall: %1").arg(call->no);
284             text += QString("\nGPU Time: %1").arg(call->gpuDuration);
285             text += QString("\nCPU Time: %1").arg(call->cpuDuration);
286             text += QString("\nPixels Drawn: %1").arg(call->pixels);
287
288             QToolTip::showText(e->globalPos(), text);
289         }
290     }
291
292     m_mousePosition = e->pos();
293
294     if (e->buttons().testFlag(Qt::LeftButton)) {
295         QToolTip::hideText();
296
297         if (m_mousePressMode == DragView) {
298             /* Horizontal scroll */
299             double dt = m_timeWidth;
300             dt /= m_viewWidth;
301             dt *= m_mousePressPosition.x() - e->pos().x();
302             setTimeScroll(m_mousePressTime + dt);
303
304             /* Vertical scroll */
305             int dy = m_mousePressPosition.y() - e->pos().y();
306             setRowScroll(m_mousePressRow + dy);
307         } else if (m_mousePressMode == RulerSelect) {
308             int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
309             int64_t up    = positionToTime(e->pos().x() - m_axisWidth);
310
311             setSelection(qMin(down, up), qMax(down, up));
312         }
313
314         update();
315     }
316 }
317
318
319 void TimelineWidget::mousePressEvent(QMouseEvent *e)
320 {
321     if (e->buttons() & Qt::LeftButton) {
322         if (e->pos().y() < m_axisHeight) {
323             if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
324                 m_mousePressMode = RulerZoom;
325             } else {
326                 m_mousePressMode = RulerSelect;
327
328                 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
329                 m_timeSelectionStart = time;
330                 m_timeSelectionEnd = time;
331             }
332         } else {
333             m_mousePressMode = DragView;
334         }
335
336         m_mousePressPosition  = e->pos();
337         m_mousePressTime = m_time;
338         m_mousePressRow   = m_scrollY;
339
340         update();
341     }
342 }
343
344
345 void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
346 {
347     if (!m_profile) {
348         return;
349     }
350
351     /* Calculate new time view based on selected area */
352     int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
353     int64_t up    = positionToTime(e->pos().x() - m_axisWidth);
354
355     int64_t left  = qMin(down, up);
356     int64_t right = qMax(down, up);
357
358     if (m_mousePressMode == RulerZoom) {
359         m_timeWidth = right - left;
360         m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
361
362         m_mousePressMode = NoMousePress;
363         setTimeScroll(left);
364     } else if (m_mousePressMode == RulerSelect) {
365         setSelection(m_timeSelectionStart, m_timeSelectionEnd, true);
366     }
367 }
368
369
370 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
371 {
372     int64_t time = positionToTime(e->pos().x() - m_axisWidth);
373
374     if (e->pos().y() < m_axisHeight) {
375         int64_t time = positionToTime(e->pos().x() - m_axisWidth);
376
377         for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
378             const Frame& frame = *itr;
379
380             if (frame.gpuStart + frame.gpuDuration < time)
381                 continue;
382
383             if (frame.gpuStart > time)
384                 break;
385
386             setSelection(frame.gpuStart, frame.gpuStart + frame.gpuDuration, true);
387             return;
388         }
389     }
390
391     if (const VisibleItem* item = itemAtPosition(e->pos())) {
392         emit jumpToCall(item->call->no);
393     } else if (time < m_timeSelectionStart || time > m_timeSelectionEnd) {
394         setSelection(0, 0, true);
395     }
396 }
397
398
399 void TimelineWidget::wheelEvent(QWheelEvent *e)
400 {
401     if (!m_profile) {
402         return;
403     }
404
405     int zoomPercent = 10;
406
407     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
408         zoomPercent = 20;
409     }
410
411     /* Zoom view by adjusting width */
412     double dt = m_timeWidth;
413     double size = m_timeWidth;
414     size *= -e->delta();
415
416     /* Zoom deltas normally come in increments of 120 */
417     size /= 120 * (100 / zoomPercent);
418
419     m_timeWidth += size;
420     m_timeWidth  = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
421
422     /* Scroll view to zoom around mouse */
423     dt -= m_timeWidth;
424     dt *= e->x() - m_axisWidth;
425     dt /= m_viewWidth;
426     setTimeScroll(dt + m_time);
427
428     update();
429 }
430
431
432 /**
433  * Paints a single pixel column of the heat map
434  */
435 void TimelineWidget::paintHeatmapColumn(int x, QPainter& painter, QVector<uint64_t>& rows)
436 {
437     double timePerPixel = m_timeWidth;
438     timePerPixel /= m_viewWidth;
439
440     for(int i = 0, y = 0; i < rows.size(); ++i, y += m_rowHeight) {
441         if (rows[i] == 0)
442             continue;
443
444         if (y > m_viewHeight)
445             continue;
446
447         double heat = rows[i] / timePerPixel;
448         heat = qBound(0.0, heat, 1.0);
449         heat *= 255.0;
450
451         painter.setPen(QColor(255, 255 - heat, 255 - heat));
452         painter.drawLine(x, y, x, y + m_rowHeight);
453
454         rows[i] = 0;
455     }
456 }
457
458
459 /**
460  * Render the whole widget
461  */
462 void TimelineWidget::paintEvent(QPaintEvent *e)
463 {
464     if (!m_profile)
465         return;
466
467     QVector<uint64_t> heatMap(m_programRowMap.size(), 0);
468     QPainter painter(this);
469     int64_t timeEnd;
470     int selectionRight;
471     int selectionLeft;
472     int rowEnd;
473     int lastX;
474
475
476     /*
477      * Draw the active selection background
478      */
479     if (m_timeSelectionStart != m_timeSelectionEnd) {
480         selectionLeft  = timeToPosition(m_timeSelectionStart) + m_axisWidth;
481         selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth;
482
483         selectionLeft  = qBound(-1, selectionLeft, width() + 1);
484         selectionRight = qBound(-1, selectionRight, width() + 1);
485
486         painter.setPen(Qt::NoPen);
487         painter.setBrush(m_selectionBackground);
488         painter.drawRect(selectionLeft, m_axisHeight, selectionRight - selectionLeft, m_viewHeight);
489     }
490
491
492     /*
493      * Draw profile heatmap
494      */
495     rowEnd = m_row + (m_viewHeight / m_rowHeight) + 1;
496     timeEnd = m_time + m_timeWidth;
497     m_visibleItems.clear();
498     lastX = 0;
499
500     painter.translate(m_axisWidth + 1, m_axisHeight + 1 - (m_scrollY % m_rowHeight));
501     painter.setBrush(m_itemBackground);
502     painter.setPen(m_itemBorder);
503
504     for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
505         const Frame& frame = *itr;
506
507         if (frame.gpuStart > timeEnd)
508             break;
509
510         if (frame.gpuStart + frame.gpuDuration < m_time)
511             continue;
512
513         for (Call::const_iterator jtr = frame.calls.begin(); jtr != frame.calls.end(); ++jtr) {
514             const Call& call = *jtr;
515             int row = m_programRowMap[call.program];
516
517             if (call.gpuStart + call.gpuDuration < m_time || call.gpuStart > timeEnd)
518                 continue;
519
520             if (row < m_row || row > rowEnd)
521                 continue;
522
523             double left = qMax(0.0, timeToPosition(call.gpuStart));
524             double right = timeToPosition(call.gpuStart + call.gpuDuration);
525
526             int leftX = left;
527             int rightX = right;
528
529             if (lastX != leftX) {
530                 paintHeatmapColumn(lastX, painter, heatMap);
531                 lastX = leftX;
532             }
533
534             row -= m_row;
535
536             if (rightX <= lastX + 1) {
537                 if (lastX == rightX) {
538                     /* Fully contained in this X */
539                     heatMap[row] += call.gpuDuration;
540                 } else {
541                     /* Split call time between the two pixels it occupies */
542                     int64_t time = positionToTime(rightX);
543
544                     heatMap[row] += time - call.gpuStart;
545                     paintHeatmapColumn(lastX, painter, heatMap);
546
547                     heatMap[row] += (call.gpuDuration + call.gpuStart) - time;
548                     lastX = rightX;
549                 }
550             } else {
551                 leftX = (left + 0.5);
552                 rightX = (right + 0.5);
553
554                 QRect rect;
555                 rect.setLeft(leftX);
556                 rect.setWidth(rightX - leftX);
557                 rect.setTop(row * m_rowHeight);
558                 rect.setHeight(m_rowHeight);
559
560                 VisibleItem vis;
561                 vis.rect = painter.transform().mapRect(rect);
562                 vis.frame = &frame;
563                 vis.call = &call;
564                 m_visibleItems.push_back(vis);
565
566                 painter.drawRect(rect);
567
568                 if (rect.width() > 6) {
569                     rect.adjust(1, 0, -1, 0);
570                     painter.setPen(m_itemForeground);
571                     painter.drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, QString::fromStdString(call.name));
572                     painter.setPen(m_itemBorder);
573                 }
574             }
575         }
576     }
577
578     /* Paint the last column if needed */
579     paintHeatmapColumn(lastX, painter, heatMap);
580
581
582     /*
583      * Draw the axis border and background
584      */
585     painter.resetTransform();
586     painter.setPen(Qt::NoPen);
587     painter.setBrush(m_axisBackground);
588     painter.drawRect(0, 0, m_viewWidth + m_axisWidth, m_axisHeight);
589     painter.drawRect(0, m_axisHeight, m_axisWidth, m_viewHeight);
590
591     painter.setPen(m_axisBorder);
592     painter.drawLine(0, m_axisHeight, m_axisWidth + m_viewWidth, m_axisHeight);
593     painter.drawLine(m_axisWidth, 0, m_axisWidth, m_viewHeight + m_axisHeight);
594
595
596     /*
597      * Draw horizontal axis
598      */
599     for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
600         const Frame& frame = *itr;
601         int left, right, width;
602         bool drawText = true;
603         QString text;
604
605         if (frame.gpuStart > timeEnd)
606             break;
607
608         if (frame.gpuStart + frame.gpuDuration < m_time)
609             continue;
610
611         left = timeToPosition(frame.gpuStart);
612         right = timeToPosition(frame.gpuStart + frame.gpuDuration) + 0.5;
613
614         left = qBound(0, left, m_viewWidth) + m_axisWidth;
615         right = qBound(0, right, m_viewWidth) + m_axisWidth;
616
617         width = right - left;
618
619         text = QString("Frame %1").arg(frame.no);
620
621         if (painter.fontMetrics().width(text) > width) {
622             text =  QString("%1").arg(frame.no);
623
624             if (painter.fontMetrics().width(text) > width) {
625                 drawText = false;
626             }
627         }
628
629         if (drawText) {
630             painter.drawText(left, 0, width, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, text);
631         }
632
633         if (width > 5) {
634             painter.drawLine(left, 0, left, m_axisHeight);
635             painter.drawLine(right, 0, right, m_axisHeight);
636         }
637     }
638
639
640     /*
641      * Draw vertical axis
642      */
643     painter.translate(0, -(m_scrollY % m_rowHeight));
644
645     for (int i = 0; i < m_programRowMap.size(); ++i) {
646         int y = (m_programRowMap[i] - m_row) * m_rowHeight;
647
648         if (m_programRowMap[i] < 0 || y < -m_rowHeight || y > m_viewHeight)
649             continue;
650
651         y += m_axisHeight + 1;
652         painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(i));
653         painter.drawLine(0, y + m_rowHeight, m_axisWidth, y + m_rowHeight);
654     }
655
656     /* Draw the top left square again to cover up any hanging over text */
657     painter.resetTransform();
658     painter.setPen(Qt::NoPen);
659     painter.setBrush(m_axisBackground);
660     painter.drawRect(0, 0, m_axisWidth, m_axisHeight);
661
662
663     /*
664      * Draw the active selection border
665      */
666     if (m_timeSelectionStart != m_timeSelectionEnd) {
667         painter.setPen(m_selectionBorder);
668         painter.drawLine(selectionLeft, 0, selectionLeft, m_viewHeight + m_axisHeight);
669         painter.drawLine(selectionRight, 0, selectionRight, m_viewHeight + m_axisHeight);
670         painter.drawLine(selectionLeft, m_axisHeight, selectionRight, m_axisHeight);
671     }
672
673
674     /*
675      * Draw the ruler zoom
676      */
677     if (m_mousePressMode == RulerZoom) {
678         int x1 = m_mousePressPosition.x();
679         int x2 = m_mousePosition.x();
680         int y1 = m_axisHeight;
681         int y2 = height();
682
683         painter.setPen(m_zoomBorder);
684         painter.drawLine(x1, 0, x1, y2);
685         painter.drawLine(x2, 0, x2, y2);
686         painter.drawLine(x1, y1, x2, y1);
687
688         painter.setPen(Qt::NoPen);
689         painter.setBrush(m_zoomBackground);
690         painter.drawRect(x1, y1, x2 - x1, y2);
691     }
692 }
693
694 #include "timelinewidget.moc"