]> git.cworth.org Git - apitrace/blob - gui/timelinewidget.cpp
Combine timeline and histogram tabs.
[apitrace] / gui / timelinewidget.cpp
1 #include "timelinewidget.h"
2 #include "profiledialog.h"
3 #include "trace_profiler.hpp"
4
5 #include <qmath.h>
6 #include <QColor>
7 #include <QLocale>
8 #include <QPainter>
9 #include <QToolTip>
10 #include <QMouseEvent>
11 #include <QWheelEvent>
12 #include <QApplication>
13
14 typedef trace::Profile::Call Call;
15 typedef trace::Profile::Frame Frame;
16 typedef trace::Profile::Program Program;
17
18 TimelineWidget::TimelineWidget(QWidget *parent)
19     : QWidget(parent),
20       m_profile(NULL),
21       m_rowHeight(20),
22       m_axisWidth(50),
23       m_axisHeight(30),
24       m_axisLine(QColor(240, 240, 240)),
25       m_axisBorder(Qt::black),
26       m_axisForeground(Qt::black),
27       m_axisBackground(QColor(210, 210, 210)),
28       m_itemBorder(Qt::red),
29       m_itemGpuForeground(Qt::cyan),
30       m_itemGpuBackground(Qt::red),
31       m_itemCpuForeground(QColor(255, 255, 0)),
32       m_itemCpuBackground(QColor(0, 0, 255)),
33       m_itemDeselectedForeground(Qt::white),
34       m_itemDeselectedBackground(QColor(155, 155, 155)),
35       m_selectionBorder(Qt::green),
36       m_selectionBackground(QColor(100, 255, 100, 8)),
37       m_zoomBorder(QColor(255, 0, 255)),
38       m_zoomBackground(QColor(255, 0, 255, 30))
39 {
40     setBackgroundRole(QPalette::Base);
41     setAutoFillBackground(true);
42     setMouseTracking(true);
43
44     m_selection.type = SelectNone;
45 }
46
47
48 /**
49  * Update horizontal view scroll based on scroll value
50  */
51 void TimelineWidget::setHorizontalScrollValue(int scrollValue)
52 {
53     if (!m_profile) {
54         return;
55     }
56
57     /* Calculate time from scroll value */
58     double time = scrollValue;
59     time /= m_maxScrollX;
60     time *= (m_timeMax - m_timeWidth) - m_timeMin;
61     time += m_timeMin;
62
63     setTimeScroll(time, false);
64 }
65
66
67 /**
68  * Update vertical view scroll based on scroll value
69  */
70 void TimelineWidget::setVerticalScrollValue(int value)
71 {
72     if (!m_profile) {
73         return;
74     }
75
76     setRowScroll(value, false);
77 }
78
79
80 /**
81  * Set selection to nothing
82  */
83 void TimelineWidget::selectNone(bool notify)
84 {
85     m_selection.type = SelectNone;
86
87     if (notify) {
88         emit selectedNone();
89     }
90
91     update();
92 }
93
94
95 /**
96  * Set selection to a program
97  */
98 void TimelineWidget::selectProgram(unsigned program, bool notify)
99 {
100     m_selection.program = program;
101     m_selection.type = SelectProgram;
102
103     if (notify) {
104         emit selectedProgram(program);
105     }
106
107     update();
108 }
109
110
111 /**
112  * Set selection to a period of time
113  */
114 void TimelineWidget::selectTime(int64_t start, int64_t end, bool notify)
115 {
116     m_selection.timeStart = start;
117     m_selection.timeEnd = end;
118     m_selection.type = SelectTime;
119
120     if (notify) {
121         emit selectedTime(start, end);
122     }
123
124     update();
125 }
126
127
128 /**
129  * Convert time to view position
130  */
131 double TimelineWidget::timeToPosition(int64_t time)
132 {
133     double pos = time;
134     pos -= m_time;
135     pos /= m_timeWidth;
136     pos *= m_viewWidth;
137     return pos;
138 }
139
140
141 /**
142  * Convert view position to time
143  */
144 int64_t TimelineWidget::positionToTime(int pos)
145 {
146     double time = pos;
147     time /= m_viewWidth;
148     time *= m_timeWidth;
149     time += m_time;
150     return (int64_t)time;
151 }
152
153
154 /**
155  * Binary Search for a time in start+durations
156  */
157 template<typename val_ty, int64_t val_ty::* mem_ptr_start, int64_t val_ty::* mem_ptr_dura>
158 typename std::vector<val_ty>::const_iterator binarySearchTimespan(
159         typename std::vector<val_ty>::const_iterator begin,
160         typename std::vector<val_ty>::const_iterator end,
161         int64_t time)
162 {
163     int lower = 0;
164     int upper = end - begin;
165     int pos = (lower + upper) / 2;
166     typename std::vector<val_ty>::const_iterator itr = begin + pos;
167
168     while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) {
169         if ((*itr).*mem_ptr_start > time) {
170             upper = pos - 1;
171         } else {
172             lower = pos + 1;
173         }
174
175         pos = (lower + upper) / 2;
176         itr = begin + pos;
177     }
178
179     if (lower <= upper) {
180         return itr;
181     } else {
182         return end;
183     }
184 }
185
186
187 /**
188  * Binary Search for a time in start+durations on an array of indices
189  */
190 std::vector<unsigned>::const_iterator binarySearchTimespanIndexed(
191         const std::vector<Call>& calls,
192         std::vector<unsigned>::const_iterator begin,
193         std::vector<unsigned>::const_iterator end,
194         int64_t time)
195 {
196     int lower = 0;
197     int upper = end - begin - 1;
198     int pos = (lower + upper) / 2;
199     std::vector<unsigned>::const_iterator itr = begin + pos;
200
201     while (lower <= upper) {
202         const Call& call = calls[*itr];
203
204         if (call.gpuStart <= time && call.gpuStart + call.gpuDuration > time) {
205             break;
206         }
207
208         if (call.gpuStart > time) {
209             upper = pos - 1;
210         } else {
211             lower = pos + 1;
212         }
213
214         pos = (lower + upper) / 2;
215         itr = begin + pos;
216     }
217
218     if (lower <= upper) {
219         return itr;
220     } else {
221         return end;
222     }
223 }
224
225
226 /**
227  * Find the frame at time
228  */
229 const Frame* TimelineWidget::frameAtTime(int64_t time)
230 {
231     if (!m_profile) {
232         return NULL;
233     }
234
235     std::vector<Frame>::const_iterator res
236             = binarySearchTimespan<Frame, &Frame::cpuStart, &Frame::cpuDuration>(
237                 m_profile->frames.begin(),
238                 m_profile->frames.end(),
239                 time);
240
241     if (res != m_profile->frames.end()) {
242         return &*res;
243     }
244
245     return NULL;
246 }
247
248
249 /**
250  * Find the CPU call at time
251  */
252 const Call* TimelineWidget::cpuCallAtTime(int64_t time)
253 {
254     if (!m_profile) {
255         return NULL;
256     }
257
258     std::vector<Call>::const_iterator res
259             = binarySearchTimespan<Call, &Call::cpuStart, &Call::cpuDuration>(
260                 m_profile->calls.begin(),
261                 m_profile->calls.end(),
262                 time);
263
264     if (res != m_profile->calls.end()) {
265         return &*res;
266     }
267
268     return NULL;
269 }
270
271
272 /**
273  * Find the draw call at time
274  */
275 const Call* TimelineWidget::drawCallAtTime(int64_t time)
276 {
277     if (!m_profile) {
278         return NULL;
279     }
280
281     for (int i = 0; i < m_rowPrograms.size(); ++i) {
282         const Call* call = drawCallAtTime(time, m_rowPrograms[i]);
283
284         if (call) {
285             return call;
286         }
287     }
288
289     return NULL;
290 }
291
292
293 /**
294  * Find the draw call at time for a selected program
295  */
296 const Call* TimelineWidget::drawCallAtTime(int64_t time, int program)
297 {
298     if (!m_profile) {
299         return NULL;
300     }
301
302     std::vector<unsigned>::const_iterator res
303             = binarySearchTimespanIndexed(
304                 m_profile->calls,
305                 m_profile->programs[program].calls.begin(),
306                 m_profile->programs[program].calls.end(),
307                 time);
308
309     if (res != m_profile->programs[program].calls.end()) {
310         return &m_profile->calls[*res];
311     }
312
313     return NULL;
314 }
315
316
317 /**
318  * Calculate the row order by total gpu time per shader
319  */
320 void TimelineWidget::calculateRows()
321 {
322     typedef QPair<uint64_t, unsigned> Pair;
323     std::vector<Pair> gpu;
324
325     /* Map shader to visible row */
326     for (std::vector<Program>::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) {
327         const Program& program = *itr;
328         unsigned no = itr -  m_profile->programs.begin();
329
330         if (program.gpuTotal > 0) {
331             gpu.push_back(Pair(program.gpuTotal, no));
332         }
333     }
334
335     /* Sort the shaders by most used gpu */
336     qSort(gpu);
337
338     /* Create row order */
339     m_rowPrograms.clear();
340
341     for (std::vector<Pair>::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) {
342         m_rowPrograms.push_back(itr->second);
343     }
344
345     m_rowCount = m_rowPrograms.size();
346 }
347
348
349 /**
350  * Set the trace profile to use for the timeline
351  */
352 void TimelineWidget::setProfile(trace::Profile* profile)
353 {
354     if (!profile->frames.size())
355         return;
356
357     m_profile = profile;
358     calculateRows();
359
360     m_timeMin = m_profile->frames.front().cpuStart;
361     m_timeMax = m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration;
362
363     m_time = m_timeMin;
364     m_timeWidth = m_timeMax - m_timeMin;
365
366     m_timeWidthMin = 1000;
367     m_timeWidthMax = m_timeWidth;
368
369     m_maxScrollX = 0;
370     m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
371
372     setTimeScroll(m_time);
373     setRowScroll(0);
374     selectNone();
375     update();
376 }
377
378
379 /**
380  * Set the horizontal scroll position to time
381  */
382 void TimelineWidget::setTimeScroll(int64_t time, bool notify)
383 {
384     time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
385
386     m_time = time;
387
388     if (m_timeWidth == m_timeWidthMax) {
389         m_maxScrollX = 0;
390     } else {
391         m_maxScrollX = 10000;
392     }
393
394     if (notify) {
395         double value = time - m_timeMin;
396         value /= m_timeMax - m_timeWidth - m_timeMin;
397         value *= m_maxScrollX;
398         m_scrollX = value;
399
400         emit horizontalScrollMaxChanged(m_maxScrollX);
401         emit horizontalScrollValueChanged(m_scrollX);
402     }
403
404     update();
405 }
406
407
408 /**
409  * Set the vertical scroll position to position
410  */
411 void TimelineWidget::setRowScroll(int position, bool notify)
412 {
413     position = qBound(0, position, m_maxScrollY);
414
415     m_scrollY = position;
416     m_row  = m_scrollY / m_rowHeight;
417
418     if (notify) {
419         emit verticalScrollMaxChanged(m_maxScrollY);
420         emit verticalScrollValueChanged(m_scrollY);
421     }
422
423     update();
424 }
425
426
427 void TimelineWidget::resizeEvent(QResizeEvent *e)
428 {
429     /* Update viewport size */
430     m_viewWidth = qMax(0, width() - m_axisWidth);
431     m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight * 2);
432
433     /* Update vertical scroll bar */
434     if (m_profile) {
435         m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
436         emit verticalScrollMaxChanged(m_maxScrollY);
437         setRowScroll(m_scrollY);
438     }
439 }
440
441
442 void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
443 {
444     bool tooltip = false;
445     m_mousePosition = e->pos();
446
447     if (!m_profile) {
448         return;
449     }
450
451     /* Display tooltip if necessary */
452     if (e->buttons() == Qt::NoButton) {
453         if (m_mousePosition.x() > m_axisWidth && m_mousePosition.y() > m_axisHeight) {
454             int64_t time = positionToTime(m_mousePosition.x() - m_axisWidth);
455             int y = m_mousePosition.y() - m_axisHeight;
456
457             if (y < m_rowHeight) {
458                 const Call* call = cpuCallAtTime(time);
459
460                 if (call) {
461                     QString text;
462                     text  = QString::fromStdString(call->name);
463                     text += QString("\nCall: %1").arg(call->no);
464                     text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
465                     text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
466
467                     QToolTip::showText(e->globalPos(), text);
468                     tooltip = true;
469                 }
470             } else {
471                 const Call* call = NULL;
472
473                 if (y < m_rowHeight * 2) {
474                     call = drawCallAtTime(time);
475                 } else {
476                     int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
477
478                     if (row < m_rowPrograms.size()) {
479                         call = drawCallAtTime(time, m_rowPrograms[row]);
480                     }
481                 }
482
483                 if (call) {
484                     QString text;
485                     text  = QString::fromStdString(call->name);
486                     text += QString("\nCall: %1").arg(call->no);
487                     text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
488                     text += QString("\nGPU Start: %1").arg(getTimeString(call->gpuStart));
489                     text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
490                     text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
491                     text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
492
493                     QToolTip::showText(e->globalPos(), text);
494                     tooltip = true;
495                 }
496             }
497         } else if (m_mousePosition.x() < m_axisWidth && m_mousePosition.y() > m_axisHeight) {
498             int y = m_mousePosition.y() - m_axisHeight;
499
500             if (y < m_rowHeight) {
501                 QToolTip::showText(e->globalPos(), "All CPU calls");
502                 tooltip = true;
503             } else if (y < m_rowHeight * 2) {
504                 QToolTip::showText(e->globalPos(), "All GPU calls");
505                 tooltip = true;
506             } else {
507                 int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
508
509                 if (row < m_rowPrograms.size()) {
510                     QToolTip::showText(e->globalPos(), QString("All calls in Shader Program %1").arg(m_rowPrograms[row]));
511                     tooltip = true;
512                 }
513             }
514         }
515     } else if (e->buttons().testFlag(Qt::LeftButton)) {
516         if (m_mousePressMode == DragView) {
517             /* Horizontal scroll */
518             double dt = m_timeWidth;
519             dt /= m_viewWidth;
520             dt *= m_mousePressPosition.x() - e->pos().x();
521             setTimeScroll(m_mousePressTime + dt);
522
523             /* Vertical scroll */
524             int dy = m_mousePressPosition.y() - e->pos().y();
525             setRowScroll(m_mousePressRow + dy);
526         } else if (m_mousePressMode == RulerSelect) {
527             /* Horizontal selection */
528             int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
529             int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
530
531             selectTime(qMin(down, up), qMax(down, up), true);
532         }
533
534         update();
535     }
536
537     if (!tooltip) {
538         QToolTip::hideText();
539     }
540 }
541
542
543 void TimelineWidget::mousePressEvent(QMouseEvent *e)
544 {
545     if (e->buttons() & Qt::LeftButton) {
546         if (e->pos().y() < m_axisHeight && e->pos().x() >= m_axisWidth) {
547             if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
548                 m_mousePressMode = RulerZoom;
549             } else {
550                 m_mousePressMode = RulerSelect;
551             }
552         } else if (e->pos().x() >= m_axisWidth) {
553             m_mousePressMode = DragView;
554         } else {
555             m_mousePressMode = NoMousePress;
556         }
557
558         m_mousePressPosition  = e->pos();
559         m_mousePressTime = m_time;
560         m_mousePressRow  = m_scrollY;
561
562         update();
563     }
564 }
565
566
567 void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
568 {
569     if (!m_profile) {
570         return;
571     }
572
573     /* Calculate new time view based on selected area */
574     int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y());
575
576     int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
577     int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
578
579     int64_t left  = qMin(down, up);
580     int64_t right = qMax(down, up);
581
582     if (m_mousePressMode == RulerZoom) {
583         m_timeWidth = right - left;
584         m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
585
586         m_mousePressMode = NoMousePress;
587         setTimeScroll(left);
588     } else {
589         if (dxy <= 2) {
590             if (m_selection.type == SelectTime) {
591                 if (left < m_selection.timeStart || right > m_selection.timeEnd || e->pos().x() < m_axisWidth) {
592                     selectNone(true);
593                 }
594             } else if (m_selection.type == SelectProgram) {
595                 int y = e->pos().y() - m_axisHeight;
596                 int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
597
598                 if (row < 0 || m_rowPrograms[row] != m_selection.program) {
599                     selectNone(true);
600                 }
601             }
602         } else if (m_mousePressMode == RulerSelect) {
603             selectTime(left, right, true);
604         }
605     }
606 }
607
608
609 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
610 {
611     int64_t time = positionToTime(e->pos().x() - m_axisWidth);
612
613     if (e->pos().x() > m_axisWidth) {
614         int row = (e->pos().y() - m_axisHeight) / m_rowHeight;
615
616         if (e->pos().y() < m_axisHeight) {
617             /* Horizontal axis */
618             const Frame* frame = frameAtTime(time);
619
620             if (frame) {
621                 selectTime(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
622                 return;
623             }
624         } else if (row == 0) {
625             /* CPU Calls */
626             const Call* call = cpuCallAtTime(time);
627
628             if (call) {
629                 emit jumpToCall(call->no);
630                 return;
631             }
632         } else if (row > 0) {
633             /* Draw Calls */
634             const Call* call = drawCallAtTime(time, 0);
635
636             if (call) {
637                 emit jumpToCall(call->no);
638                 return;
639             }
640         }
641     } else {
642         int y = e->pos().y() - m_axisHeight;
643         int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
644
645         if (row >= 0 && row < m_rowPrograms.size()) {
646             selectProgram(m_rowPrograms[row], true);
647         }
648     }
649 }
650
651
652 void TimelineWidget::wheelEvent(QWheelEvent *e)
653 {
654     if (!m_profile) {
655         return;
656     }
657
658     if (e->pos().x() < m_axisWidth) {
659         return;
660     }
661
662     int zoomPercent = 10;
663
664     /* If holding Ctrl key then zoom 2x faster */
665     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
666         zoomPercent = 20;
667     }
668
669     /* Zoom view by adjusting width */
670     double dt = m_timeWidth;
671     double size = m_timeWidth;
672     size *= -e->delta();
673
674     /* Zoom deltas normally come in increments of 120 */
675     size /= 120 * (100 / zoomPercent);
676
677     m_timeWidth += size;
678     m_timeWidth  = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
679
680     /* Scroll view to zoom around mouse */
681     dt -= m_timeWidth;
682     dt *= e->x() - m_axisWidth;
683     dt /= m_viewWidth;
684     setTimeScroll(dt + m_time);
685
686     update();
687 }
688
689
690 /**
691  * Paints a single pixel column of the heat map
692  */
693 void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool gpu, bool selected)
694 {
695     if (heat == 0) {
696         return;
697     }
698
699     if (m_selection.type == SelectTime) {
700         selected = x >= m_selectionLeft && x <= m_selectionRight;
701     }
702
703     double timePerPixel = m_timeWidth / (double)m_viewWidth;
704     double colour = heat / timePerPixel;
705
706     /* Gamma correction */
707     colour = qPow(colour, 1.0 / 2.0);
708
709     if (!selected) {
710         colour = qBound(0.0, colour * 100.0, 100.0);
711         painter.setPen(QColor(255 - colour, 255 - colour, 255 - colour));
712     } else if (gpu) {
713         colour = qBound(0.0, colour * 255.0, 255.0);
714         painter.setPen(QColor(255, 255 - colour, 255 - colour));
715     } else {
716         colour = qBound(0.0, colour * 255.0, 255.0);
717         painter.setPen(QColor(255 - colour, 255 - colour, 255));
718     }
719
720     painter.drawLine(x, 0, x, m_rowHeight - 1);
721 }
722
723
724 /**
725  * Draws a call on the heatmap
726  */
727 bool TimelineWidget::drawCall(QPainter& painter, const trace::Profile::Call& call, int& lastX, int64_t& heat, bool gpu)
728 {
729     int64_t start, duration, end;
730
731     if (gpu) {
732         start = call.gpuStart;
733         duration = call.gpuDuration;
734     } else {
735         start = call.cpuStart;
736         duration = call.cpuDuration;
737     }
738
739     end = start + duration;
740
741     if (start > m_timeEnd) {
742         return false;
743     }
744
745     if (end < m_time) {
746         return true;
747     }
748
749     double left  = timeToPosition(start);
750     double right = timeToPosition(end);
751
752     int leftX  = left;
753     int rightX = right;
754
755     bool selected = true;
756
757     if (m_selection.type == SelectProgram) {
758         selected = call.program == m_selection.program;
759     }
760
761     /* Draw last heat if needed */
762     if (leftX != lastX) {
763         drawHeat(painter, lastX, heat, gpu, selected);
764         lastX = leftX;
765         heat = 0;
766     }
767
768     if (rightX <= leftX + 1) {
769         if (rightX == lastX) {
770             /* Fully contained in this X */
771             heat += duration;
772         } else {
773             /* Split call time between the two pixels it occupies */
774             int64_t time = positionToTime(rightX);
775             heat += time - start;
776
777             drawHeat(painter, lastX, heat, gpu, selected);
778
779             heat = end - time;
780             lastX = rightX;
781         }
782     } else {
783         QRect rect;
784         rect.setLeft(left + 0.5);
785         rect.setWidth(right - left);
786         rect.setTop(0);
787         rect.setHeight(m_rowHeight);
788
789         if (m_selection.type == SelectTime) {
790             selected = (start >= m_selection.timeStart && start <= m_selection.timeEnd)
791                     || (end >= m_selection.timeStart && end <= m_selection.timeEnd);
792         }
793
794         /* Draw background rect */
795         if (selected) {
796             if (gpu) {
797                 painter.fillRect(rect, m_itemGpuBackground);
798             } else {
799                 painter.fillRect(rect, m_itemCpuBackground);
800             }
801         } else {
802             painter.fillRect(rect, m_itemDeselectedBackground);
803         }
804
805         /* If wide enough, draw text */
806         if (rect.width() > 6) {
807             rect.adjust(1, 0, -1, -2);
808
809             if (selected) {
810                 if (gpu) {
811                     painter.setPen(m_itemGpuForeground);
812                 } else {
813                     painter.setPen(m_itemCpuForeground);
814                 }
815             } else {
816                 painter.setPen(m_itemDeselectedForeground);
817             }
818
819             painter.drawText(rect,
820                              Qt::AlignLeft | Qt::AlignVCenter,
821                              painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
822         }
823     }
824
825     return true;
826 }
827
828
829 /**
830  * Render the whole widget
831  */
832 void TimelineWidget::paintEvent(QPaintEvent *e)
833 {
834     if (!m_profile)
835         return;
836
837     QPainter painter(this);
838
839     int rowEnd = qMin(m_row + qCeil(m_viewHeight / (double)m_rowHeight) + 1, m_rowCount);
840     int64_t heatGPU = 0, heatCPU = 0;
841     int lastCpuX = 0, lastGpuX = 0;
842     int widgetHeight = height();
843     int widgetWidth = width();
844
845     m_timeEnd = m_time + m_timeWidth;
846     m_selectionLeft  = timeToPosition(m_selection.timeStart);
847     m_selectionRight = (timeToPosition(m_selection.timeEnd) + 0.5);
848
849
850     /* Draw program rows */
851     painter.translate(m_axisWidth, m_axisHeight + m_rowHeight * 2 - (m_scrollY % m_rowHeight));
852
853     for (int row = m_row; row < rowEnd; ++row) {
854         Program& program = m_profile->programs[m_rowPrograms[row]];
855         lastGpuX = 0;
856         heatGPU = 0;
857
858         for (std::vector<unsigned>::const_iterator itr = program.calls.begin(); itr != program.calls.end(); ++itr) {
859             const Call& call = m_profile->calls[*itr];
860
861             if (!drawCall(painter, call, lastGpuX, heatGPU, true)) {
862                 break;
863             }
864         }
865
866         painter.translate(0, m_rowHeight);
867     }
868
869
870     /* Draw CPU/GPU rows */
871     painter.resetTransform();
872     painter.translate(m_axisWidth, m_axisHeight);
873     painter.fillRect(0, 0, m_viewWidth, m_rowHeight * 2, Qt::white);
874
875     lastCpuX = lastGpuX = 0;
876     heatCPU = heatGPU = 0;
877
878     for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
879         const Call& call = *itr;
880
881         /* Draw gpu row */
882         if (call.pixels >= 0) {
883             painter.translate(0, m_rowHeight);
884             drawCall(painter, call, lastGpuX, heatGPU, true);
885             painter.translate(0, -m_rowHeight);
886         }
887
888         /* Draw cpu row */
889         if (!drawCall(painter, call, lastCpuX, heatCPU, false)) {
890             break;
891         }
892     }
893
894
895     /* Draw axis */
896     painter.resetTransform();
897     painter.setPen(m_axisBorder);
898
899     /* Top Rect */
900     painter.fillRect(m_axisWidth - 1, 0, widgetWidth, m_axisHeight - 1, m_axisBackground);
901     painter.drawLine(0, m_axisHeight - 1, widgetWidth, m_axisHeight - 1);
902
903     /* Left Rect */
904     painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground);
905     painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight);
906
907
908     /* Draw the program numbers */
909     painter.translate(0, m_axisHeight + m_rowHeight * 2);
910
911     for (int row = m_row; row < rowEnd; ++row) {
912         int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight);
913
914         painter.setPen(m_axisForeground);
915         painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row]));
916
917         if (m_selection.type == SelectProgram && m_selection.program == m_rowPrograms[row]) {
918             painter.setPen(m_selectionBorder);
919             painter.drawLine(0, qMax(0, y - 1), widgetWidth, qMax(0, y - 1));
920             painter.drawLine(0, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
921             painter.drawLine(m_axisWidth - 1, y - 1, m_axisWidth - 1, y + m_rowHeight - 1);
922         } else {
923             painter.setPen(m_axisBorder);
924             painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
925
926             painter.setPen(m_axisLine);
927             painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
928         }
929     }
930
931
932     /* Draw the "CPU" axis label */
933     painter.resetTransform();
934     painter.translate(0, m_axisHeight);
935
936     painter.setPen(m_axisBorder);
937     painter.setBrush(m_axisBackground);
938     painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
939
940     painter.setPen(m_axisForeground);
941     painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
942
943     painter.setPen(m_axisBorder);
944     painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
945
946
947     /* Draw the "GPU" axis label */
948     painter.translate(0, m_rowHeight);
949
950     painter.setPen(m_axisBorder);
951     painter.setBrush(m_axisBackground);
952     painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
953
954     painter.setPen(m_axisForeground);
955     painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "GPU");
956
957     painter.setPen(m_axisBorder);
958     painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
959
960
961     /* Draw the frame numbers */
962     painter.resetTransform();
963
964     painter.setPen(m_axisForeground);
965     painter.translate(m_axisWidth, 0);
966
967     int lastLabel = -999; /* Ensure first label gets drawn */
968
969     double scroll = m_time;
970     scroll /= m_timeWidth;
971     scroll *= m_viewWidth;
972
973     for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
974         static const int padding = 4;
975         const Frame& frame = *itr;
976         bool draw = true;
977         int width;
978
979         if (frame.cpuStart > m_timeEnd) {
980             break;
981         }
982
983         if (frame.cpuStart + frame.cpuDuration < m_time) {
984             draw = false;
985         }
986
987         double left = frame.cpuStart;
988         left /= m_timeWidth;
989         left *= m_viewWidth;
990
991         double right = frame.cpuStart + frame.cpuDuration;
992         right /= m_timeWidth;
993         right *= m_viewWidth;
994
995         QString text = QString("%1").arg(frame.no);
996
997         width = painter.fontMetrics().width(text) + padding * 2;
998
999         if (left + width > scroll)
1000             draw = true;
1001
1002         /* Draw a frame number if we have space since the last one */
1003         if (left - lastLabel > width) {
1004             lastLabel = left + width;
1005
1006             if (draw) {
1007                 int textX;
1008                 painter.setPen(m_axisForeground);
1009
1010                 if (left < scroll && right - left > width) {
1011                     if (right - scroll > width) {
1012                         textX = 0;
1013                     } else {
1014                         textX = right - scroll - width;
1015                     }
1016                 } else {
1017                     textX = left - scroll;
1018                 }
1019
1020                 /* Draw frame number and major ruler marking */
1021                 painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
1022                 painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
1023             }
1024         } else if (draw) {
1025             /* Draw a minor ruler marking */
1026             painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
1027         }
1028     }
1029
1030
1031     /* Draw "Frame" axis label */
1032     painter.resetTransform();
1033
1034     painter.setPen(m_axisBorder);
1035     painter.setBrush(m_axisBackground);
1036     painter.drawRect(-1, -1, m_axisWidth, m_axisHeight);
1037
1038     painter.setPen(m_axisForeground);
1039     painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame");
1040
1041
1042     /* Draw the active selection border */
1043     if (m_selection.type == SelectTime) {
1044         painter.setPen(m_selectionBorder);
1045
1046         m_selectionLeft += m_axisWidth;
1047         m_selectionRight += m_axisWidth;
1048
1049         if (m_selectionLeft >= m_axisWidth && m_selectionLeft < widgetWidth) {
1050             painter.drawLine(m_selectionLeft, 0, m_selectionLeft, widgetHeight);
1051         }
1052
1053         if (m_selectionRight >= m_axisWidth && m_selectionRight < widgetWidth) {
1054             painter.drawLine(m_selectionRight, 0, m_selectionRight, widgetHeight);
1055         }
1056
1057         m_selectionLeft = qBound(m_axisWidth, m_selectionLeft, widgetWidth);
1058         m_selectionRight = qBound(m_axisWidth, m_selectionRight, widgetWidth);
1059
1060         painter.drawLine(m_selectionLeft, m_axisHeight - 1, m_selectionRight, m_axisHeight - 1);
1061         painter.fillRect(m_selectionLeft, 0, m_selectionRight - m_selectionLeft, widgetHeight, m_selectionBackground);
1062     }
1063
1064
1065     /* Draw the ruler zoom */
1066     if (m_mousePressMode == RulerZoom) {
1067         int x1 = m_mousePressPosition.x();
1068         int x2 = qMax(m_mousePosition.x(), m_axisWidth);
1069
1070         painter.setPen(m_zoomBorder);
1071         painter.drawLine(x1, 0, x1, widgetHeight);
1072         painter.drawLine(x2, 0, x2, widgetHeight);
1073         painter.drawLine(x1, m_axisHeight - 1, x2, m_axisHeight - 1);
1074         painter.fillRect(x1, m_axisHeight, x2 - x1, widgetHeight, m_zoomBackground);
1075     }
1076 }
1077
1078 #include "timelinewidget.moc"