1 #include "timelinewidget.h"
2 #include "trace_profiler.hpp"
10 #include <QApplication>
12 typedef trace::Profile::Call Call;
13 typedef trace::Profile::Frame Frame;
14 typedef trace::Profile::Program Program;
16 TimelineWidget::TimelineWidget(QWidget *parent)
19 m_timeSelectionStart(0),
20 m_timeSelectionEnd(0),
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_itemForeground(Qt::cyan),
30 m_itemBackground(Qt::red),
31 m_selectionBorder(Qt::green),
32 m_selectionBackground(QColor(100, 255, 100, 8)),
33 m_zoomBorder(QColor(255, 0, 255)),
34 m_zoomBackground(QColor(255, 0, 255, 30))
36 setBackgroundRole(QPalette::Base);
37 setAutoFillBackground(true);
38 setMouseTracking(true);
43 * Update horizontal view scroll based on scroll value
45 void TimelineWidget::setHorizontalScrollValue(int scrollValue)
51 /* Calculate time from scroll value */
52 double time = scrollValue;
54 time *= (m_timeMax - m_timeWidth) - m_timeMin;
57 setTimeScroll(time, false);
62 * Update vertical view scroll based on scroll value
64 void TimelineWidget::setVerticalScrollValue(int value)
70 setRowScroll(value, false);
75 * Update the time selection
77 void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify)
79 m_timeSelectionStart = start;
80 m_timeSelectionEnd = end;
83 emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd);
91 * Convert time to view position
93 double TimelineWidget::timeToPosition(int64_t time)
104 * Convert view position to time
106 int64_t TimelineWidget::positionToTime(int pos)
112 return (int64_t)time;
117 * Binary Search for a time in start+durations
119 template<typename val_ty, int64_t val_ty::* mem_ptr_start, int64_t val_ty::* mem_ptr_dura>
120 typename std::vector<val_ty>::const_iterator binarySearchTimespan(
121 typename std::vector<val_ty>::const_iterator begin,
122 typename std::vector<val_ty>::const_iterator end,
126 int upper = end - begin;
127 int pos = (lower + upper) / 2;
128 typename std::vector<val_ty>::const_iterator itr = begin + pos;
130 while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) {
131 if ((*itr).*mem_ptr_start > time) {
137 pos = (lower + upper) / 2;
141 if (lower <= upper) {
150 * Binary Search for a time in start+durations on an array of indices
152 std::vector<unsigned>::const_iterator binarySearchTimespanIndexed(
153 const std::vector<Call>& calls,
154 std::vector<unsigned>::const_iterator begin,
155 std::vector<unsigned>::const_iterator end,
159 int upper = end - begin;
160 int pos = (lower + upper) / 2;
161 std::vector<unsigned>::const_iterator itr = begin + pos;
163 while (lower <= upper) {
164 const Call& call = calls[*itr];
166 if (call.gpuStart <= time && call.gpuStart + call.gpuDuration > time) {
170 if (call.gpuStart > time) {
176 pos = (lower + upper) / 2;
180 if (lower <= upper) {
189 * Find the frame at time
191 const Frame* TimelineWidget::frameAtTime(int64_t time)
197 std::vector<Frame>::const_iterator res
198 = binarySearchTimespan<Frame, &Frame::cpuStart, &Frame::cpuDuration>(
199 m_profile->frames.begin(),
200 m_profile->frames.end(),
203 if (res != m_profile->frames.end()) {
212 * Find the CPU call at time
214 const Call* TimelineWidget::cpuCallAtTime(int64_t time)
220 std::vector<Call>::const_iterator res
221 = binarySearchTimespan<Call, &Call::cpuStart, &Call::cpuDuration>(
222 m_profile->calls.begin(),
223 m_profile->calls.end(),
226 if (res != m_profile->calls.end()) {
235 * Find the draw call at time
237 const Call* TimelineWidget::drawCallAtTime(int64_t time, int program)
243 std::vector<unsigned>::const_iterator res
244 = binarySearchTimespanIndexed(
246 m_profile->programs[program].calls.begin(),
247 m_profile->programs[program].calls.end(),
250 if (res != m_profile->programs[program].calls.end()) {
251 return &m_profile->calls[*res];
259 * Calculate the row order by total gpu time per shader
261 void TimelineWidget::calculateRows()
263 typedef QPair<uint64_t, unsigned> Pair;
264 std::vector<Pair> gpu;
266 /* Map shader to visible row */
267 for (std::vector<Program>::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) {
268 const Program& program = *itr;
269 unsigned no = itr - m_profile->programs.begin();
271 if (program.gpuTotal > 0) {
272 gpu.push_back(Pair(program.gpuTotal, no));
276 /* Sort the shaders by most used gpu */
279 /* Create row order */
280 m_rowPrograms.clear();
282 for (std::vector<Pair>::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) {
283 m_rowPrograms.push_back(itr->second);
286 m_rowCount = m_rowPrograms.size();
291 * Set the trace profile to use for the timeline
293 void TimelineWidget::setProfile(trace::Profile* profile)
295 if (!profile->frames.size())
301 m_timeMin = m_profile->frames.front().cpuStart;
302 m_timeMax = m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration;
305 m_timeWidth = m_timeMax - m_timeMin;
307 m_timeWidthMin = 1000;
308 m_timeWidthMax = m_timeWidth;
311 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
313 setTimeScroll(m_time);
321 * Set the horizontal scroll position to time
323 void TimelineWidget::setTimeScroll(int64_t time, bool notify)
325 time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
329 if (m_timeWidth == m_timeWidthMax) {
332 m_maxScrollX = 10000;
336 double value = time - m_timeMin;
337 value /= m_timeMax - m_timeWidth - m_timeMin;
338 value *= m_maxScrollX;
341 emit horizontalScrollMaxChanged(m_maxScrollX);
342 emit horizontalScrollValueChanged(m_scrollX);
350 * Set the vertical scroll position to position
352 void TimelineWidget::setRowScroll(int position, bool notify)
354 position = qBound(0, position, m_maxScrollY);
356 m_scrollY = position;
357 m_row = m_scrollY / m_rowHeight;
360 emit verticalScrollMaxChanged(m_maxScrollY);
361 emit verticalScrollValueChanged(m_scrollY);
368 void TimelineWidget::resizeEvent(QResizeEvent *e)
370 /* Update viewport size */
371 m_viewWidth = qMax(0, width() - m_axisWidth);
372 m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight);
374 /* Update vertical scroll bar */
376 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
377 emit verticalScrollMaxChanged(m_maxScrollY);
378 setRowScroll(m_scrollY);
383 void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
385 bool tooltip = false;
386 m_mousePosition = e->pos();
392 /* Display tooltip if necessary */
393 if (e->buttons() == Qt::NoButton) {
394 if (m_mousePosition.x() > m_axisWidth && m_mousePosition.y() > m_axisHeight) {
395 int64_t time = positionToTime(m_mousePosition.x() - m_axisWidth);
396 int y = m_mousePosition.y() - m_axisHeight;
398 if (y < m_rowHeight) {
399 const Call* call = cpuCallAtTime(time);
403 text = QString::fromStdString(call->name);
404 text += QString("\nCall: %1").arg(call->no);
405 text += QString("\nCPU Start: %1").arg(call->cpuStart);
406 text += QString("\nCPU Duration: %1").arg(call->cpuDuration);
408 QToolTip::showText(e->globalPos(), text);
412 int row = (y - m_rowHeight + m_scrollY) / m_rowHeight;
414 if (row < m_rowPrograms.size()) {
415 const Call* call = drawCallAtTime(time, m_rowPrograms[row]);
419 text = QString::fromStdString(call->name);
420 text += QString("\nCall: %1").arg(call->no);
421 text += QString("\nGPU Start: %1").arg(call->gpuStart);
422 text += QString("\nCPU Start: %1").arg(call->cpuStart);
423 text += QString("\nGPU Duration: %1").arg(call->gpuDuration);
424 text += QString("\nCPU Duration: %1").arg(call->cpuDuration);
425 text += QString("\nPixels Drawn: %1").arg(call->pixels);
427 QToolTip::showText(e->globalPos(), text);
433 } else if (e->buttons().testFlag(Qt::LeftButton)) {
434 if (m_mousePressMode == DragView) {
435 /* Horizontal scroll */
436 double dt = m_timeWidth;
438 dt *= m_mousePressPosition.x() - e->pos().x();
439 setTimeScroll(m_mousePressTime + dt);
441 /* Vertical scroll */
442 int dy = m_mousePressPosition.y() - e->pos().y();
443 setRowScroll(m_mousePressRow + dy);
444 } else if (m_mousePressMode == RulerSelect) {
445 /* Horizontal selection */
446 int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth);
447 int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
449 setSelection(qMin(down, up), qMax(down, up));
456 QToolTip::hideText();
461 void TimelineWidget::mousePressEvent(QMouseEvent *e)
463 if (e->buttons() & Qt::LeftButton) {
464 if (e->pos().y() < m_axisHeight && e->pos().x() >= m_axisWidth) {
465 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
466 m_mousePressMode = RulerZoom;
468 m_mousePressMode = RulerSelect;
470 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
471 m_timeSelectionStart = time;
472 m_timeSelectionEnd = time;
475 m_mousePressMode = DragView;
478 m_mousePressPosition = e->pos();
479 m_mousePressTime = m_time;
480 m_mousePressRow = m_scrollY;
487 void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
493 /* Calculate new time view based on selected area */
494 int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth);
495 int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
497 int64_t left = qMin(down, up);
498 int64_t right = qMax(down, up);
500 if (m_mousePressMode == RulerZoom) {
501 m_timeWidth = right - left;
502 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
504 m_mousePressMode = NoMousePress;
506 } else if (m_mousePressMode == RulerSelect) {
507 setSelection(m_timeSelectionStart, m_timeSelectionEnd, true);
512 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
514 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
516 if (e->pos().x() > m_axisWidth) {
517 int row = (e->pos().y() - m_axisHeight) / m_rowHeight;
519 if (e->pos().y() < m_axisHeight) {
520 /* Horizontal axis */
521 const Frame* frame = frameAtTime(time);
524 setSelection(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
527 } else if (row == 0) {
529 const Call* call = cpuCallAtTime(time);
532 emit jumpToCall(call->no);
535 } else if (row > 0) {
537 const Call* call = drawCallAtTime(time, 0);
540 emit jumpToCall(call->no);
546 if (time < m_timeSelectionStart || time > m_timeSelectionEnd) {
547 setSelection(0, 0, true);
552 void TimelineWidget::wheelEvent(QWheelEvent *e)
558 int zoomPercent = 10;
560 /* If holding Ctrl key then zoom 2x faster */
561 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
565 /* Zoom view by adjusting width */
566 double dt = m_timeWidth;
567 double size = m_timeWidth;
570 /* Zoom deltas normally come in increments of 120 */
571 size /= 120 * (100 / zoomPercent);
574 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
576 /* Scroll view to zoom around mouse */
578 dt *= e->x() - m_axisWidth;
580 setTimeScroll(dt + m_time);
587 * Paints a single pixel column of the heat map
589 void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool isCpu)
595 double timePerPixel = m_timeWidth;
596 timePerPixel /= m_viewWidth;
598 double colour = heat / timePerPixel;
599 colour = qBound(0.0, colour * 255.0, 255.0);
602 painter.setPen(QColor(255 - colour, 255 - colour, 255));
604 painter.setPen(QColor(255, 255 - colour, 255 - colour));
607 painter.drawLine(x, 0, x, m_rowHeight - 1);
612 * Render the whole widget
614 void TimelineWidget::paintEvent(QPaintEvent *e)
619 QPainter painter(this);
621 int rowEnd = qMin(m_row + (m_viewHeight / m_rowHeight) + 1, m_rowCount);
622 int64_t timeEnd = m_time + m_timeWidth;
625 int widgetHeight = height();
626 int widgetWidth = width();
629 painter.translate(m_axisWidth, m_axisHeight + m_rowHeight - (m_scrollY % m_rowHeight));
631 for (int row = m_row; row < rowEnd; ++row) {
632 Program& program = m_profile->programs[m_rowPrograms[row]];
636 for (std::vector<unsigned>::const_iterator itr = program.calls.begin(); itr != program.calls.end(); ++itr) {
637 const Call& call = m_profile->calls[*itr];
638 int64_t gpuEnd = call.gpuStart + call.gpuDuration;
640 if (call.gpuStart > timeEnd) {
644 if (gpuEnd < m_time) {
648 double left = timeToPosition(call.gpuStart);
649 double right = timeToPosition(gpuEnd);
654 /* Draw last heat if needed */
655 if (leftX != lastX) {
656 drawHeat(painter, lastX, heat, false);
661 if (rightX <= leftX + 1) {
662 if (rightX == lastX) {
663 /* Fully contained in this X */
664 heat += call.gpuDuration;
666 /* Split call time between the two pixels it occupies */
667 int64_t time = positionToTime(rightX);
669 heat += time - call.gpuStart;
670 drawHeat(painter, lastX, heat, false);
672 heat = gpuEnd - time;
677 rect.setLeft(left + 0.5);
678 rect.setWidth(right - left);
680 rect.setHeight(m_rowHeight);
682 painter.fillRect(rect, m_itemBackground);
684 if (rect.width() > 6) {
685 rect.adjust(1, 0, -1, -2);
686 painter.setPen(m_itemForeground);
688 painter.drawText(rect,
689 Qt::AlignLeft | Qt::AlignVCenter,
690 painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
695 painter.translate(0, m_rowHeight);
699 painter.resetTransform();
700 painter.translate(m_axisWidth, m_axisHeight);
701 painter.fillRect(0, 0, m_viewWidth, m_rowHeight, Qt::white);
703 for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
704 const Call& call = *itr;
705 int64_t cpuEnd = call.cpuStart + call.cpuDuration;
707 if (call.cpuStart > timeEnd) {
711 if (cpuEnd < m_time) {
715 double left = timeToPosition(call.cpuStart);
716 double right = timeToPosition(cpuEnd);
721 /* Draw last heat if needed */
722 if (leftX != lastX) {
723 drawHeat(painter, lastX, heat, true);
728 if (rightX <= leftX + 1) {
729 if (rightX == lastX) {
730 /* Fully contained in this X */
731 heat += call.cpuDuration;
733 /* Split call time between the two pixels it occupies */
734 int64_t time = positionToTime(rightX);
736 heat += time - call.cpuStart;
737 drawHeat(painter, lastX, heat, true);
739 heat = cpuEnd - time;
744 rect.setLeft(left + 0.5);
745 rect.setWidth(right - left);
747 rect.setHeight(m_rowHeight);
749 painter.fillRect(rect, QColor(0, 0, 255));
751 if (rect.width() > 6) {
752 rect.adjust(1, 0, -1, -2);
753 painter.setPen(QColor(255, 255, 0));
755 painter.drawText(rect,
756 Qt::AlignLeft | Qt::AlignVCenter,
757 painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
763 painter.resetTransform();
764 painter.setPen(m_axisBorder);
767 painter.fillRect(m_axisWidth - 1, 0, widgetWidth, m_axisHeight - 1, m_axisBackground);
768 painter.drawLine(0, m_axisHeight - 1, widgetWidth, m_axisHeight - 1);
771 painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground);
772 painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight);
774 /* Draw the program numbers */
775 painter.translate(0, m_axisHeight + m_rowHeight);
777 for (int row = m_row; row < rowEnd; ++row) {
778 int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight);
780 painter.setPen(m_axisForeground);
781 painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row]));
783 painter.setPen(m_axisBorder);
784 painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
786 painter.setPen(m_axisLine);
787 painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
790 /* Draw the "CPU" axis label */
791 painter.resetTransform();
792 painter.translate(0, m_axisHeight);
794 painter.setPen(m_axisBorder);
795 painter.setBrush(m_axisBackground);
796 painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
798 painter.setPen(m_axisForeground);
799 painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
801 painter.setPen(m_axisBorder);
802 painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
805 /* Draw the frame numbers */
806 painter.resetTransform();
808 painter.setPen(m_axisForeground);
809 painter.translate(m_axisWidth, 0);
811 int lastLabel = -9999;
813 double scroll = m_time;
814 scroll /= m_timeWidth;
815 scroll *= m_viewWidth;
817 for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
818 static const int padding = 4;
819 const Frame& frame = *itr;
823 if (frame.cpuStart > timeEnd) {
827 if (frame.cpuStart + frame.cpuDuration < m_time) {
831 double left = frame.cpuStart;
835 double right = frame.cpuStart + frame.cpuDuration;
836 right /= m_timeWidth;
837 right *= m_viewWidth;
839 QString text = QString("%1").arg(frame.no);
841 width = painter.fontMetrics().width(text) + padding * 2;
843 if (left + width > scroll)
846 /* Draw a frame number if we have space since the last one */
847 if (left - lastLabel > width) {
848 lastLabel = left + width;
852 painter.setPen(m_axisForeground);
854 if (left < scroll && right - left > width) {
855 if (right - scroll > width) {
858 textX = right - scroll - width;
861 textX = left - scroll;
864 /* Draw frame number and major ruler marking */
865 painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
866 painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
869 /* Draw a minor ruler marking */
870 painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
874 /* Draw "Frame" axis label */
875 painter.resetTransform();
877 painter.setPen(m_axisBorder);
878 painter.setBrush(m_axisBackground);
879 painter.drawRect(-1, -1, m_axisWidth, m_axisHeight);
881 painter.setPen(m_axisForeground);
882 painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame");
884 /* Draw the active selection border */
885 if (m_timeSelectionStart != m_timeSelectionEnd) {
886 int selectionLeft = timeToPosition(m_timeSelectionStart) + m_axisWidth;
887 int selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth;
889 painter.setPen(m_selectionBorder);
891 if (selectionLeft >= m_axisWidth && selectionLeft < widgetWidth) {
892 painter.drawLine(selectionLeft, 0, selectionLeft, widgetHeight);
895 if (selectionRight >= m_axisWidth && selectionRight < widgetWidth) {
896 painter.drawLine(selectionRight, 0, selectionRight, widgetHeight);
899 selectionLeft = qBound(m_axisWidth, selectionLeft, widgetWidth);
900 selectionRight = qBound(m_axisWidth, selectionRight, widgetWidth);
902 painter.drawLine(selectionLeft, m_axisHeight - 1, selectionRight, m_axisHeight - 1);
903 painter.fillRect(selectionLeft, 0, selectionRight - selectionLeft, widgetHeight, m_selectionBackground);
906 /* Draw the ruler zoom */
907 if (m_mousePressMode == RulerZoom) {
908 int x1 = m_mousePressPosition.x();
909 int x2 = qMax(m_mousePosition.x(), m_axisWidth);
911 painter.setPen(m_zoomBorder);
912 painter.drawLine(x1, 0, x1, widgetHeight);
913 painter.drawLine(x2, 0, x2, widgetHeight);
914 painter.drawLine(x1, m_axisHeight - 1, x2, m_axisHeight - 1);
915 painter.fillRect(x1, m_axisHeight, x2 - x1, widgetHeight, m_zoomBackground);
919 #include "timelinewidget.moc"