1 #include "timelinewidget.h"
2 #include "trace_profiler.hpp"
10 #include <QApplication>
12 typedef trace::Profile::Frame Frame;
13 typedef trace::Profile::Program Program;
14 typedef trace::Profile::CpuCall CpuCall;
15 typedef trace::Profile::DrawCall DrawCall;
17 TimelineWidget::TimelineWidget(QWidget *parent)
20 m_timeSelectionStart(0),
21 m_timeSelectionEnd(0),
25 m_axisLine(QColor(240, 240, 240)),
26 m_axisBorder(Qt::black),
27 m_axisForeground(Qt::black),
28 m_axisBackground(QColor(210, 210, 210)),
29 m_itemBorder(Qt::red),
30 m_itemForeground(Qt::cyan),
31 m_itemBackground(Qt::red),
32 m_selectionBorder(Qt::green),
33 m_selectionBackground(QColor(100, 255, 100, 8)),
34 m_zoomBorder(QColor(255, 0, 255)),
35 m_zoomBackground(QColor(255, 0, 255, 30))
37 setBackgroundRole(QPalette::Base);
38 setAutoFillBackground(true);
39 setMouseTracking(true);
44 * Update horizontal view scroll based on scroll value
46 void TimelineWidget::setHorizontalScrollValue(int scrollValue)
52 /* Calculate time from scroll value */
53 double time = scrollValue;
55 time *= (m_timeMax - m_timeWidth) - m_timeMin;
58 setTimeScroll(time, false);
63 * Update vertical view scroll based on scroll value
65 void TimelineWidget::setVerticalScrollValue(int value)
71 setRowScroll(value, false);
76 * Update the time selection
78 void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify)
80 m_timeSelectionStart = start;
81 m_timeSelectionEnd = end;
84 emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd);
92 * Convert time to view position
94 double TimelineWidget::timeToPosition(int64_t time)
105 * Convert view position to time
107 int64_t TimelineWidget::positionToTime(int pos)
113 return (int64_t)time;
118 * Binary Search for a time in start+durations
120 template<typename val_ty, int64_t val_ty::* mem_ptr_start, int64_t val_ty::* mem_ptr_dura>
121 typename std::vector<val_ty>::const_iterator binarySearchTimespan(
122 typename std::vector<val_ty>::const_iterator begin,
123 typename std::vector<val_ty>::const_iterator end,
127 int upper = end - begin;
128 int pos = (lower + upper) / 2;
129 typename std::vector<val_ty>::const_iterator itr = begin + pos;
131 while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) {
132 if ((*itr).*mem_ptr_start > time) {
138 pos = (lower + upper) / 2;
150 * Find the frame at time
152 const Frame* TimelineWidget::frameAtTime(int64_t time) {
157 std::vector<Frame>::const_iterator res
158 = binarySearchTimespan<Frame, &Frame::cpuStart, &Frame::cpuDuration>(
159 m_profile->frames.begin(),
160 m_profile->frames.end(),
163 if (res != m_profile->frames.end()) {
164 const Frame& frame = *res;
173 * Find the CPU call at time
175 const CpuCall* TimelineWidget::cpuCallAtTime(int64_t time) {
180 std::vector<CpuCall>::const_iterator res
181 = binarySearchTimespan<CpuCall, &CpuCall::cpuStart, &CpuCall::cpuDuration>(
182 m_profile->cpuCalls.begin(),
183 m_profile->cpuCalls.end(),
186 if (res != m_profile->cpuCalls.end()) {
187 const CpuCall& call = *res;
196 * Find the draw call at time
198 const DrawCall* TimelineWidget::drawCallAtTime(int program, int64_t time) {
203 std::vector<DrawCall>& drawCalls = m_profile->programs[program].drawCalls;
205 std::vector<DrawCall>::const_iterator res
206 = binarySearchTimespan<DrawCall, &DrawCall::gpuStart, &DrawCall::gpuDuration>(
211 if (res != drawCalls.end()) {
212 const DrawCall& call = *res;
221 * Calculate the row order by total gpu time per shader
223 void TimelineWidget::calculateRows()
225 typedef QPair<uint64_t, unsigned> Pair;
226 std::vector<Pair> gpu;
228 /* Map shader to visible row */
229 for (std::vector<Program>::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) {
230 const Program& program = *itr;
231 unsigned no = itr - m_profile->programs.begin();
233 if (program.gpuTotal > 0) {
234 gpu.push_back(Pair(program.gpuTotal, no));
238 /* Sort the shaders by most used gpu */
241 /* Create row order */
242 m_rowPrograms.clear();
244 for (std::vector<Pair>::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) {
245 m_rowPrograms.push_back(itr->second);
248 m_rowCount = m_rowPrograms.size();
253 * Set the trace profile to use for the timeline
255 void TimelineWidget::setProfile(trace::Profile* profile)
257 if (!profile->frames.size())
263 m_timeMin = m_profile->frames.front().cpuStart;
264 m_timeMax = m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration;
267 m_timeWidth = m_timeMax - m_timeMin;
269 m_timeWidthMin = 1000;
270 m_timeWidthMax = m_timeWidth;
273 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
275 setTimeScroll(m_time);
283 * Set the horizontal scroll position to time
285 void TimelineWidget::setTimeScroll(int64_t time, bool notify)
287 time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
291 if (m_timeWidth == m_timeWidthMax) {
294 m_maxScrollX = 10000;
298 double value = time - m_timeMin;
299 value /= m_timeMax - m_timeWidth - m_timeMin;
300 value *= m_maxScrollX;
303 emit horizontalScrollMaxChanged(m_maxScrollX);
304 emit horizontalScrollValueChanged(m_scrollX);
312 * Set the vertical scroll position to position
314 void TimelineWidget::setRowScroll(int position, bool notify)
316 position = qBound(0, position, m_maxScrollY);
318 m_scrollY = position;
319 m_row = m_scrollY / m_rowHeight;
322 emit verticalScrollMaxChanged(m_maxScrollY);
323 emit verticalScrollValueChanged(m_scrollY);
330 void TimelineWidget::resizeEvent(QResizeEvent *e)
332 /* Update viewport size */
333 m_viewWidth = qMax(0, width() - m_axisWidth);
334 m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight);
336 /* Update vertical scroll bar */
338 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
339 emit verticalScrollMaxChanged(m_maxScrollY);
340 setRowScroll(m_scrollY);
345 void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
347 bool tooltip = false;
348 m_mousePosition = e->pos();
354 /* Display tooltip if necessary */
355 if (e->buttons() == Qt::NoButton) {
356 if (m_mousePosition.x() > m_axisWidth && m_mousePosition.y() > m_axisHeight) {
357 int64_t time = positionToTime(m_mousePosition.x() - m_axisWidth);
358 int y = m_mousePosition.y() - m_axisHeight;
360 if (y < m_rowHeight) {
361 const CpuCall* call = cpuCallAtTime(time);
365 text = QString::fromStdString(call->name);
366 text += QString("\nCall: %1").arg(call->no);
367 text += QString("\nCPU Start: %1").arg(call->cpuStart);
368 text += QString("\nCPU Duration: %1").arg(call->cpuDuration);
370 QToolTip::showText(e->globalPos(), text);
374 int row = (y - m_rowHeight + m_scrollY) / m_rowHeight;
376 if (row < m_rowPrograms.size()) {
377 int program = m_rowPrograms[row];
378 const DrawCall* call = drawCallAtTime(program, time);
382 text = QString::fromStdString(call->name);
383 text += QString("\nCall: %1").arg(call->no);
384 text += QString("\nGPU Start: %1").arg(call->gpuStart);
385 text += QString("\nCPU Start: %1").arg(call->cpuStart);
386 text += QString("\nGPU Duration: %1").arg(call->gpuDuration);
387 text += QString("\nCPU Duration: %1").arg(call->cpuDuration);
388 text += QString("\nPixels Drawn: %1").arg(call->pixels);
390 QToolTip::showText(e->globalPos(), text);
396 } else if (e->buttons().testFlag(Qt::LeftButton)) {
397 if (m_mousePressMode == DragView) {
398 /* Horizontal scroll */
399 double dt = m_timeWidth;
401 dt *= m_mousePressPosition.x() - e->pos().x();
402 setTimeScroll(m_mousePressTime + dt);
404 /* Vertical scroll */
405 int dy = m_mousePressPosition.y() - e->pos().y();
406 setRowScroll(m_mousePressRow + dy);
407 } else if (m_mousePressMode == RulerSelect) {
408 /* Horizontal selection */
409 int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth);
410 int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
412 setSelection(qMin(down, up), qMax(down, up));
419 QToolTip::hideText();
424 void TimelineWidget::mousePressEvent(QMouseEvent *e)
426 if (e->buttons() & Qt::LeftButton) {
427 if (e->pos().y() < m_axisHeight && e->pos().x() >= m_axisWidth) {
428 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
429 m_mousePressMode = RulerZoom;
431 m_mousePressMode = RulerSelect;
433 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
434 m_timeSelectionStart = time;
435 m_timeSelectionEnd = time;
438 m_mousePressMode = DragView;
441 m_mousePressPosition = e->pos();
442 m_mousePressTime = m_time;
443 m_mousePressRow = m_scrollY;
450 void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
456 /* Calculate new time view based on selected area */
457 int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth);
458 int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
460 int64_t left = qMin(down, up);
461 int64_t right = qMax(down, up);
463 if (m_mousePressMode == RulerZoom) {
464 m_timeWidth = right - left;
465 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
467 m_mousePressMode = NoMousePress;
469 } else if (m_mousePressMode == RulerSelect) {
470 setSelection(m_timeSelectionStart, m_timeSelectionEnd, true);
475 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
477 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
479 if (e->pos().x() > m_axisWidth) {
480 int row = (e->pos().y() - m_axisHeight) / m_rowHeight;
482 if (e->pos().y() < m_axisHeight) {
483 /* Horizontal axis */
484 const Frame* frame = frameAtTime(time);
487 setSelection(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
490 } else if (row == 0) {
492 const CpuCall* call = cpuCallAtTime(time);
495 emit jumpToCall(call->no);
498 } else if (row > 0) {
500 int program = m_rowPrograms[row - 1 + m_row];
501 const DrawCall* call = drawCallAtTime(program, time);
504 emit jumpToCall(call->no);
510 if (time < m_timeSelectionStart || time > m_timeSelectionEnd) {
511 setSelection(0, 0, true);
516 void TimelineWidget::wheelEvent(QWheelEvent *e)
522 int zoomPercent = 10;
524 /* If holding Ctrl key then zoom 2x faster */
525 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
529 /* Zoom view by adjusting width */
530 double dt = m_timeWidth;
531 double size = m_timeWidth;
534 /* Zoom deltas normally come in increments of 120 */
535 size /= 120 * (100 / zoomPercent);
538 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
540 /* Scroll view to zoom around mouse */
542 dt *= e->x() - m_axisWidth;
544 setTimeScroll(dt + m_time);
551 * Paints a single pixel column of the heat map
553 void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool isCpu)
559 double timePerPixel = m_timeWidth;
560 timePerPixel /= m_viewWidth;
562 double colour = heat / timePerPixel;
563 colour = qBound(0.0, colour * 255.0, 255.0);
566 painter.setPen(QColor(255 - colour, 255 - colour, 255));
568 painter.setPen(QColor(255, 255 - colour, 255 - colour));
571 painter.drawLine(x, 0, x, m_rowHeight - 1);
576 * Render the whole widget
578 void TimelineWidget::paintEvent(QPaintEvent *e)
583 QPainter painter(this);
585 int rowEnd = qMin(m_row + (m_viewHeight / m_rowHeight) + 1, m_rowCount);
586 int64_t timeEnd = m_time + m_timeWidth;
589 int widgetHeight = height();
590 int widgetWidth = width();
593 painter.translate(m_axisWidth, m_axisHeight + m_rowHeight - (m_scrollY % m_rowHeight));
595 for (int row = m_row; row < rowEnd; ++row) {
596 Program& program = m_profile->programs[m_rowPrograms[row]];
600 for (std::vector<DrawCall>::const_iterator itr = program.drawCalls.begin(); itr != program.drawCalls.end(); ++itr) {
601 const DrawCall& call = *itr;
602 int64_t gpuEnd = call.gpuStart + call.gpuDuration;
604 if (call.gpuStart > timeEnd) {
608 if (gpuEnd < m_time) {
612 double left = timeToPosition(call.gpuStart);
613 double right = timeToPosition(gpuEnd);
618 /* Draw last heat if needed */
619 if (leftX != lastX) {
620 drawHeat(painter, lastX, heat, false);
625 if (rightX <= leftX + 1) {
626 if (rightX == lastX) {
627 /* Fully contained in this X */
628 heat += call.gpuDuration;
630 /* Split call time between the two pixels it occupies */
631 int64_t time = positionToTime(rightX);
633 heat += time - call.gpuStart;
634 drawHeat(painter, lastX, heat, false);
636 heat = gpuEnd - time;
641 rect.setLeft(left + 0.5);
642 rect.setWidth(right - left);
644 rect.setHeight(m_rowHeight);
646 painter.fillRect(rect, m_itemBackground);
648 if (rect.width() > 6) {
649 rect.adjust(1, 0, -1, -2);
650 painter.setPen(m_itemForeground);
652 painter.drawText(rect,
653 Qt::AlignLeft | Qt::AlignVCenter,
654 painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
659 painter.translate(0, m_rowHeight);
663 painter.resetTransform();
664 painter.translate(m_axisWidth, m_axisHeight);
665 painter.fillRect(0, 0, m_viewWidth, m_rowHeight, Qt::white);
667 for (std::vector<CpuCall>::const_iterator itr = m_profile->cpuCalls.begin(); itr != m_profile->cpuCalls.end(); ++itr) {
668 const CpuCall& call = *itr;
669 int64_t cpuEnd = call.cpuStart + call.cpuDuration;
671 if (call.cpuStart > timeEnd) {
675 if (cpuEnd < m_time) {
679 double left = timeToPosition(call.cpuStart);
680 double right = timeToPosition(cpuEnd);
685 /* Draw last heat if needed */
686 if (leftX != lastX) {
687 drawHeat(painter, lastX, heat, true);
692 if (rightX <= leftX + 1) {
693 if (rightX == lastX) {
694 /* Fully contained in this X */
695 heat += call.cpuDuration;
697 /* Split call time between the two pixels it occupies */
698 int64_t time = positionToTime(rightX);
700 heat += time - call.cpuStart;
701 drawHeat(painter, lastX, heat, true);
703 heat = cpuEnd - time;
708 rect.setLeft(left + 0.5);
709 rect.setWidth(right - left);
711 rect.setHeight(m_rowHeight);
713 painter.fillRect(rect, QColor(0, 0, 255));
715 if (rect.width() > 6) {
716 rect.adjust(1, 0, -1, -2);
717 painter.setPen(QColor(255, 255, 0));
719 painter.drawText(rect,
720 Qt::AlignLeft | Qt::AlignVCenter,
721 painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
727 painter.resetTransform();
728 painter.setPen(m_axisBorder);
731 painter.fillRect(m_axisWidth - 1, 0, widgetWidth, m_axisHeight - 1, m_axisBackground);
732 painter.drawLine(0, m_axisHeight - 1, widgetWidth, m_axisHeight - 1);
735 painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground);
736 painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight);
738 /* Draw the program numbers */
739 painter.translate(0, m_axisHeight + m_rowHeight);
741 for (int row = m_row; row < rowEnd; ++row) {
742 int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight);
744 painter.setPen(m_axisForeground);
745 painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row]));
747 painter.setPen(m_axisBorder);
748 painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
750 painter.setPen(m_axisLine);
751 painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
754 /* Draw the "CPU" axis label */
755 painter.resetTransform();
756 painter.translate(0, m_axisHeight);
758 painter.setPen(m_axisBorder);
759 painter.setBrush(m_axisBackground);
760 painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
762 painter.setPen(m_axisForeground);
763 painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
765 painter.setPen(m_axisBorder);
766 painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
769 /* Draw the frame numbers */
770 painter.resetTransform();
772 painter.setPen(m_axisForeground);
773 painter.translate(m_axisWidth, 0);
775 int lastLabel = -9999;
777 double scroll = m_time;
778 scroll /= m_timeWidth;
779 scroll *= m_viewWidth;
781 for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
782 static const int padding = 4;
783 const Frame& frame = *itr;
787 if (frame.cpuStart > timeEnd) {
791 if (frame.cpuStart + frame.cpuDuration < m_time) {
795 double left = frame.cpuStart;
799 double right = frame.cpuStart + frame.cpuDuration;
800 right /= m_timeWidth;
801 right *= m_viewWidth;
803 QString text = QString("%1").arg(frame.no);
805 width = painter.fontMetrics().width(text) + padding * 2;
807 if (left + width > scroll)
810 /* Draw a frame number if we have space since the last one */
811 if (left - lastLabel > width) {
812 lastLabel = left + width;
816 painter.setPen(m_axisForeground);
818 if (left < scroll && right - left > width) {
819 if (right - scroll > width) {
822 textX = right - scroll - width;
825 textX = left - scroll;
828 /* Draw frame number and major ruler marking */
829 painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
830 painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
833 /* Draw a minor ruler marking */
834 painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
838 /* Draw "Frame" axis label */
839 painter.resetTransform();
841 painter.setPen(m_axisBorder);
842 painter.setBrush(m_axisBackground);
843 painter.drawRect(-1, -1, m_axisWidth, m_axisHeight);
845 painter.setPen(m_axisForeground);
846 painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame");
848 /* Draw the active selection border */
849 if (m_timeSelectionStart != m_timeSelectionEnd) {
850 int selectionLeft = timeToPosition(m_timeSelectionStart) + m_axisWidth;
851 int selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth;
853 painter.setPen(m_selectionBorder);
855 if (selectionLeft >= m_axisWidth && selectionLeft < widgetWidth) {
856 painter.drawLine(selectionLeft, 0, selectionLeft, widgetHeight);
859 if (selectionRight >= m_axisWidth && selectionRight < widgetWidth) {
860 painter.drawLine(selectionRight, 0, selectionRight, widgetHeight);
863 selectionLeft = qBound(m_axisWidth, selectionLeft, widgetWidth);
864 selectionRight = qBound(m_axisWidth, selectionRight, widgetWidth);
866 painter.drawLine(selectionLeft, m_axisHeight - 1, selectionRight, m_axisHeight - 1);
867 painter.fillRect(selectionLeft, 0, selectionRight - selectionLeft, widgetHeight, m_selectionBackground);
870 /* Draw the ruler zoom */
871 if (m_mousePressMode == RulerZoom) {
872 int x1 = m_mousePressPosition.x();
873 int x2 = qMax(m_mousePosition.x(), m_axisWidth);
875 painter.setPen(m_zoomBorder);
876 painter.drawLine(x1, 0, x1, widgetHeight);
877 painter.drawLine(x2, 0, x2, widgetHeight);
878 painter.drawLine(x1, m_axisHeight - 1, x2, m_axisHeight - 1);
879 painter.fillRect(x1, m_axisHeight, x2 - x1, widgetHeight, m_zoomBackground);
883 #include "timelinewidget.moc"