1 #include "timelinewidget.h"
2 #include "profiledialog.h"
3 #include "trace_profiler.hpp"
10 #include <QMouseEvent>
11 #include <QWheelEvent>
12 #include <QApplication>
14 typedef trace::Profile::Call Call;
15 typedef trace::Profile::Frame Frame;
16 typedef trace::Profile::Program Program;
18 TimelineWidget::TimelineWidget(QWidget *parent)
21 m_timeSelectionStart(0),
22 m_timeSelectionEnd(0),
26 m_axisLine(QColor(240, 240, 240)),
27 m_axisBorder(Qt::black),
28 m_axisForeground(Qt::black),
29 m_axisBackground(QColor(210, 210, 210)),
30 m_itemBorder(Qt::red),
31 m_itemForeground(Qt::cyan),
32 m_itemBackground(Qt::red),
33 m_selectionBorder(Qt::green),
34 m_selectionBackground(QColor(100, 255, 100, 8)),
35 m_zoomBorder(QColor(255, 0, 255)),
36 m_zoomBackground(QColor(255, 0, 255, 30))
38 setBackgroundRole(QPalette::Base);
39 setAutoFillBackground(true);
40 setMouseTracking(true);
45 * Update horizontal view scroll based on scroll value
47 void TimelineWidget::setHorizontalScrollValue(int scrollValue)
53 /* Calculate time from scroll value */
54 double time = scrollValue;
56 time *= (m_timeMax - m_timeWidth) - m_timeMin;
59 setTimeScroll(time, false);
64 * Update vertical view scroll based on scroll value
66 void TimelineWidget::setVerticalScrollValue(int value)
72 setRowScroll(value, false);
77 * Update the time selection
79 void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify)
81 m_timeSelectionStart = start;
82 m_timeSelectionEnd = end;
85 emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd);
93 * Convert time to view position
95 double TimelineWidget::timeToPosition(int64_t time)
106 * Convert view position to time
108 int64_t TimelineWidget::positionToTime(int pos)
114 return (int64_t)time;
119 * Binary Search for a time in start+durations
121 template<typename val_ty, int64_t val_ty::* mem_ptr_start, int64_t val_ty::* mem_ptr_dura>
122 typename std::vector<val_ty>::const_iterator binarySearchTimespan(
123 typename std::vector<val_ty>::const_iterator begin,
124 typename std::vector<val_ty>::const_iterator end,
128 int upper = end - begin;
129 int pos = (lower + upper) / 2;
130 typename std::vector<val_ty>::const_iterator itr = begin + pos;
132 while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) {
133 if ((*itr).*mem_ptr_start > time) {
139 pos = (lower + upper) / 2;
143 if (lower <= upper) {
152 * Binary Search for a time in start+durations on an array of indices
154 std::vector<unsigned>::const_iterator binarySearchTimespanIndexed(
155 const std::vector<Call>& calls,
156 std::vector<unsigned>::const_iterator begin,
157 std::vector<unsigned>::const_iterator end,
161 int upper = end - begin;
162 int pos = (lower + upper) / 2;
163 std::vector<unsigned>::const_iterator itr = begin + pos;
165 while (lower <= upper) {
166 const Call& call = calls[*itr];
168 if (call.gpuStart <= time && call.gpuStart + call.gpuDuration > time) {
172 if (call.gpuStart > time) {
178 pos = (lower + upper) / 2;
182 if (lower <= upper) {
191 * Find the frame at time
193 const Frame* TimelineWidget::frameAtTime(int64_t time)
199 std::vector<Frame>::const_iterator res
200 = binarySearchTimespan<Frame, &Frame::cpuStart, &Frame::cpuDuration>(
201 m_profile->frames.begin(),
202 m_profile->frames.end(),
205 if (res != m_profile->frames.end()) {
214 * Find the CPU call at time
216 const Call* TimelineWidget::cpuCallAtTime(int64_t time)
222 std::vector<Call>::const_iterator res
223 = binarySearchTimespan<Call, &Call::cpuStart, &Call::cpuDuration>(
224 m_profile->calls.begin(),
225 m_profile->calls.end(),
228 if (res != m_profile->calls.end()) {
237 * Find the draw call at time
239 const Call* TimelineWidget::drawCallAtTime(int64_t time, int program)
245 std::vector<unsigned>::const_iterator res
246 = binarySearchTimespanIndexed(
248 m_profile->programs[program].calls.begin(),
249 m_profile->programs[program].calls.end(),
252 if (res != m_profile->programs[program].calls.end()) {
253 return &m_profile->calls[*res];
261 * Calculate the row order by total gpu time per shader
263 void TimelineWidget::calculateRows()
265 typedef QPair<uint64_t, unsigned> Pair;
266 std::vector<Pair> gpu;
268 /* Map shader to visible row */
269 for (std::vector<Program>::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) {
270 const Program& program = *itr;
271 unsigned no = itr - m_profile->programs.begin();
273 if (program.gpuTotal > 0) {
274 gpu.push_back(Pair(program.gpuTotal, no));
278 /* Sort the shaders by most used gpu */
281 /* Create row order */
282 m_rowPrograms.clear();
284 for (std::vector<Pair>::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) {
285 m_rowPrograms.push_back(itr->second);
288 m_rowCount = m_rowPrograms.size();
293 * Set the trace profile to use for the timeline
295 void TimelineWidget::setProfile(trace::Profile* profile)
297 if (!profile->frames.size())
303 m_timeMin = m_profile->frames.front().cpuStart;
304 m_timeMax = m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration;
307 m_timeWidth = m_timeMax - m_timeMin;
309 m_timeWidthMin = 1000;
310 m_timeWidthMax = m_timeWidth;
313 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
315 setTimeScroll(m_time);
323 * Set the horizontal scroll position to time
325 void TimelineWidget::setTimeScroll(int64_t time, bool notify)
327 time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
331 if (m_timeWidth == m_timeWidthMax) {
334 m_maxScrollX = 10000;
338 double value = time - m_timeMin;
339 value /= m_timeMax - m_timeWidth - m_timeMin;
340 value *= m_maxScrollX;
343 emit horizontalScrollMaxChanged(m_maxScrollX);
344 emit horizontalScrollValueChanged(m_scrollX);
352 * Set the vertical scroll position to position
354 void TimelineWidget::setRowScroll(int position, bool notify)
356 position = qBound(0, position, m_maxScrollY);
358 m_scrollY = position;
359 m_row = m_scrollY / m_rowHeight;
362 emit verticalScrollMaxChanged(m_maxScrollY);
363 emit verticalScrollValueChanged(m_scrollY);
370 void TimelineWidget::resizeEvent(QResizeEvent *e)
372 /* Update viewport size */
373 m_viewWidth = qMax(0, width() - m_axisWidth);
374 m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight);
376 /* Update vertical scroll bar */
378 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
379 emit verticalScrollMaxChanged(m_maxScrollY);
380 setRowScroll(m_scrollY);
385 void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
387 bool tooltip = false;
388 m_mousePosition = e->pos();
394 /* Display tooltip if necessary */
395 if (e->buttons() == Qt::NoButton) {
396 if (m_mousePosition.x() > m_axisWidth && m_mousePosition.y() > m_axisHeight) {
397 int64_t time = positionToTime(m_mousePosition.x() - m_axisWidth);
398 int y = m_mousePosition.y() - m_axisHeight;
400 if (y < m_rowHeight) {
401 const Call* call = cpuCallAtTime(time);
405 text = QString::fromStdString(call->name);
406 text += QString("\nCall: %1").arg(call->no);
407 text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
408 text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
410 QToolTip::showText(e->globalPos(), text);
414 int row = (y - m_rowHeight + m_scrollY) / m_rowHeight;
416 if (row < m_rowPrograms.size()) {
417 const Call* call = drawCallAtTime(time, m_rowPrograms[row]);
421 text = QString::fromStdString(call->name);
422 text += QString("\nCall: %1").arg(call->no);
423 text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
424 text += QString("\nGPU Start: %1").arg(getTimeString(call->gpuStart));
425 text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
426 text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
427 text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
429 QToolTip::showText(e->globalPos(), text);
435 } else if (e->buttons().testFlag(Qt::LeftButton)) {
436 if (m_mousePressMode == DragView) {
437 /* Horizontal scroll */
438 double dt = m_timeWidth;
440 dt *= m_mousePressPosition.x() - e->pos().x();
441 setTimeScroll(m_mousePressTime + dt);
443 /* Vertical scroll */
444 int dy = m_mousePressPosition.y() - e->pos().y();
445 setRowScroll(m_mousePressRow + dy);
446 } else if (m_mousePressMode == RulerSelect) {
447 /* Horizontal selection */
448 int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth);
449 int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
451 setSelection(qMin(down, up), qMax(down, up));
458 QToolTip::hideText();
463 void TimelineWidget::mousePressEvent(QMouseEvent *e)
465 if (e->buttons() & Qt::LeftButton) {
466 if (e->pos().y() < m_axisHeight && e->pos().x() >= m_axisWidth) {
467 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
468 m_mousePressMode = RulerZoom;
470 m_mousePressMode = RulerSelect;
472 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
473 m_timeSelectionStart = time;
474 m_timeSelectionEnd = time;
477 m_mousePressMode = DragView;
480 m_mousePressPosition = e->pos();
481 m_mousePressTime = m_time;
482 m_mousePressRow = m_scrollY;
489 void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
495 /* Calculate new time view based on selected area */
496 int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth);
497 int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
499 int64_t left = qMin(down, up);
500 int64_t right = qMax(down, up);
502 if (m_mousePressMode == RulerZoom) {
503 m_timeWidth = right - left;
504 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
506 m_mousePressMode = NoMousePress;
508 } else if (m_mousePressMode == RulerSelect) {
509 setSelection(m_timeSelectionStart, m_timeSelectionEnd, true);
514 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
516 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
518 if (e->pos().x() > m_axisWidth) {
519 int row = (e->pos().y() - m_axisHeight) / m_rowHeight;
521 if (e->pos().y() < m_axisHeight) {
522 /* Horizontal axis */
523 const Frame* frame = frameAtTime(time);
526 setSelection(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
529 } else if (row == 0) {
531 const Call* call = cpuCallAtTime(time);
534 emit jumpToCall(call->no);
537 } else if (row > 0) {
539 const Call* call = drawCallAtTime(time, 0);
542 emit jumpToCall(call->no);
548 if (time < m_timeSelectionStart || time > m_timeSelectionEnd) {
549 setSelection(0, 0, true);
554 void TimelineWidget::wheelEvent(QWheelEvent *e)
560 int zoomPercent = 10;
562 /* If holding Ctrl key then zoom 2x faster */
563 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
567 /* Zoom view by adjusting width */
568 double dt = m_timeWidth;
569 double size = m_timeWidth;
572 /* Zoom deltas normally come in increments of 120 */
573 size /= 120 * (100 / zoomPercent);
576 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
578 /* Scroll view to zoom around mouse */
580 dt *= e->x() - m_axisWidth;
582 setTimeScroll(dt + m_time);
589 * Paints a single pixel column of the heat map
591 void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool isCpu)
597 double timePerPixel = m_timeWidth;
598 timePerPixel /= m_viewWidth;
600 double colour = heat / timePerPixel;
601 colour = qBound(0.0, colour * 255.0, 255.0);
604 painter.setPen(QColor(255 - colour, 255 - colour, 255));
606 painter.setPen(QColor(255, 255 - colour, 255 - colour));
609 painter.drawLine(x, 0, x, m_rowHeight - 1);
614 * Render the whole widget
616 void TimelineWidget::paintEvent(QPaintEvent *e)
621 QPainter painter(this);
623 int rowEnd = qMin(m_row + (m_viewHeight / m_rowHeight) + 1, m_rowCount);
624 int64_t timeEnd = m_time + m_timeWidth;
627 int widgetHeight = height();
628 int widgetWidth = width();
631 painter.translate(m_axisWidth, m_axisHeight + m_rowHeight - (m_scrollY % m_rowHeight));
633 for (int row = m_row; row < rowEnd; ++row) {
634 Program& program = m_profile->programs[m_rowPrograms[row]];
638 for (std::vector<unsigned>::const_iterator itr = program.calls.begin(); itr != program.calls.end(); ++itr) {
639 const Call& call = m_profile->calls[*itr];
640 int64_t gpuEnd = call.gpuStart + call.gpuDuration;
642 if (call.gpuStart > timeEnd) {
646 if (gpuEnd < m_time) {
650 double left = timeToPosition(call.gpuStart);
651 double right = timeToPosition(gpuEnd);
656 /* Draw last heat if needed */
657 if (leftX != lastX) {
658 drawHeat(painter, lastX, heat, false);
663 if (rightX <= leftX + 1) {
664 if (rightX == lastX) {
665 /* Fully contained in this X */
666 heat += call.gpuDuration;
668 /* Split call time between the two pixels it occupies */
669 int64_t time = positionToTime(rightX);
671 heat += time - call.gpuStart;
672 drawHeat(painter, lastX, heat, false);
674 heat = gpuEnd - time;
679 rect.setLeft(left + 0.5);
680 rect.setWidth(right - left);
682 rect.setHeight(m_rowHeight);
684 painter.fillRect(rect, m_itemBackground);
686 if (rect.width() > 6) {
687 rect.adjust(1, 0, -1, -2);
688 painter.setPen(m_itemForeground);
690 painter.drawText(rect,
691 Qt::AlignLeft | Qt::AlignVCenter,
692 painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
697 painter.translate(0, m_rowHeight);
701 painter.resetTransform();
702 painter.translate(m_axisWidth, m_axisHeight);
703 painter.fillRect(0, 0, m_viewWidth, m_rowHeight, Qt::white);
705 for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
706 const Call& call = *itr;
707 int64_t cpuEnd = call.cpuStart + call.cpuDuration;
709 if (call.cpuStart > timeEnd) {
713 if (cpuEnd < m_time) {
717 double left = timeToPosition(call.cpuStart);
718 double right = timeToPosition(cpuEnd);
723 /* Draw last heat if needed */
724 if (leftX != lastX) {
725 drawHeat(painter, lastX, heat, true);
730 if (rightX <= leftX + 1) {
731 if (rightX == lastX) {
732 /* Fully contained in this X */
733 heat += call.cpuDuration;
735 /* Split call time between the two pixels it occupies */
736 int64_t time = positionToTime(rightX);
738 heat += time - call.cpuStart;
739 drawHeat(painter, lastX, heat, true);
741 heat = cpuEnd - time;
746 rect.setLeft(left + 0.5);
747 rect.setWidth(right - left);
749 rect.setHeight(m_rowHeight);
751 painter.fillRect(rect, QColor(0, 0, 255));
753 if (rect.width() > 6) {
754 rect.adjust(1, 0, -1, -2);
755 painter.setPen(QColor(255, 255, 0));
757 painter.drawText(rect,
758 Qt::AlignLeft | Qt::AlignVCenter,
759 painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
765 painter.resetTransform();
766 painter.setPen(m_axisBorder);
769 painter.fillRect(m_axisWidth - 1, 0, widgetWidth, m_axisHeight - 1, m_axisBackground);
770 painter.drawLine(0, m_axisHeight - 1, widgetWidth, m_axisHeight - 1);
773 painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground);
774 painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight);
776 /* Draw the program numbers */
777 painter.translate(0, m_axisHeight + m_rowHeight);
779 for (int row = m_row; row < rowEnd; ++row) {
780 int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight);
782 painter.setPen(m_axisForeground);
783 painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row]));
785 painter.setPen(m_axisBorder);
786 painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
788 painter.setPen(m_axisLine);
789 painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
792 /* Draw the "CPU" axis label */
793 painter.resetTransform();
794 painter.translate(0, m_axisHeight);
796 painter.setPen(m_axisBorder);
797 painter.setBrush(m_axisBackground);
798 painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
800 painter.setPen(m_axisForeground);
801 painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
803 painter.setPen(m_axisBorder);
804 painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
807 /* Draw the frame numbers */
808 painter.resetTransform();
810 painter.setPen(m_axisForeground);
811 painter.translate(m_axisWidth, 0);
813 int lastLabel = -9999;
815 double scroll = m_time;
816 scroll /= m_timeWidth;
817 scroll *= m_viewWidth;
819 for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
820 static const int padding = 4;
821 const Frame& frame = *itr;
825 if (frame.cpuStart > timeEnd) {
829 if (frame.cpuStart + frame.cpuDuration < m_time) {
833 double left = frame.cpuStart;
837 double right = frame.cpuStart + frame.cpuDuration;
838 right /= m_timeWidth;
839 right *= m_viewWidth;
841 QString text = QString("%1").arg(frame.no);
843 width = painter.fontMetrics().width(text) + padding * 2;
845 if (left + width > scroll)
848 /* Draw a frame number if we have space since the last one */
849 if (left - lastLabel > width) {
850 lastLabel = left + width;
854 painter.setPen(m_axisForeground);
856 if (left < scroll && right - left > width) {
857 if (right - scroll > width) {
860 textX = right - scroll - width;
863 textX = left - scroll;
866 /* Draw frame number and major ruler marking */
867 painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
868 painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
871 /* Draw a minor ruler marking */
872 painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
876 /* Draw "Frame" axis label */
877 painter.resetTransform();
879 painter.setPen(m_axisBorder);
880 painter.setBrush(m_axisBackground);
881 painter.drawRect(-1, -1, m_axisWidth, m_axisHeight);
883 painter.setPen(m_axisForeground);
884 painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame");
886 /* Draw the active selection border */
887 if (m_timeSelectionStart != m_timeSelectionEnd) {
888 int selectionLeft = timeToPosition(m_timeSelectionStart) + m_axisWidth;
889 int selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth;
891 painter.setPen(m_selectionBorder);
893 if (selectionLeft >= m_axisWidth && selectionLeft < widgetWidth) {
894 painter.drawLine(selectionLeft, 0, selectionLeft, widgetHeight);
897 if (selectionRight >= m_axisWidth && selectionRight < widgetWidth) {
898 painter.drawLine(selectionRight, 0, selectionRight, widgetHeight);
901 selectionLeft = qBound(m_axisWidth, selectionLeft, widgetWidth);
902 selectionRight = qBound(m_axisWidth, selectionRight, widgetWidth);
904 painter.drawLine(selectionLeft, m_axisHeight - 1, selectionRight, m_axisHeight - 1);
905 painter.fillRect(selectionLeft, 0, selectionRight - selectionLeft, widgetHeight, m_selectionBackground);
908 /* Draw the ruler zoom */
909 if (m_mousePressMode == RulerZoom) {
910 int x1 = m_mousePressPosition.x();
911 int x2 = qMax(m_mousePosition.x(), m_axisWidth);
913 painter.setPen(m_zoomBorder);
914 painter.drawLine(x1, 0, x1, widgetHeight);
915 painter.drawLine(x2, 0, x2, widgetHeight);
916 painter.drawLine(x1, m_axisHeight - 1, x2, m_axisHeight - 1);
917 painter.fillRect(x1, m_axisHeight, x2 - x1, widgetHeight, m_zoomBackground);
921 #include "timelinewidget.moc"