1 #include "timelinewidget.h"
2 #include "trace_profiler.hpp"
10 #include <QApplication>
12 typedef trace::Profile::Call Call;
13 typedef trace::Profile::Frame Frame;
15 TimelineWidget::TimelineWidget(QWidget *parent)
18 m_timeSelectionStart(0),
19 m_timeSelectionEnd(0),
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_selectionBorder(QColor(50, 50, 255)),
29 m_selectionBackground(QColor(245, 245, 255)),
30 m_zoomBorder(Qt::green),
31 m_zoomBackground(QColor(100, 255, 100, 80))
33 setBackgroundRole(QPalette::Base);
34 setAutoFillBackground(true);
35 setMouseTracking(true);
40 * Update horizontal view scroll based on scroll value
42 void TimelineWidget::setHorizontalScrollValue(int scrollValue)
48 /* Calculate time from scroll value */
49 double time = scrollValue;
51 time *= (m_timeMax - m_timeWidth) - m_timeMin;
54 setTimeScroll(time, false);
59 * Update vertical view scroll based on scroll value
61 void TimelineWidget::setVerticalScrollValue(int value)
67 setRowScroll(value, false);
72 * Update the time selection
74 void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify)
76 m_timeSelectionStart = start;
77 m_timeSelectionEnd = end;
80 emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd);
88 * Convert time to view position
90 double TimelineWidget::timeToPosition(int64_t time)
101 * Convert view position to time
103 int64_t TimelineWidget::positionToTime(int pos)
109 return (int64_t)time;
114 * Return the item at position
116 const VisibleItem* TimelineWidget::itemAtPosition(const QPoint& pos)
118 foreach (const VisibleItem& item, m_visibleItems) {
119 if (pos.x() < item.rect.left() || pos.y() < item.rect.top())
122 if (pos.x() > item.rect.right() || pos.y() > item.rect.bottom())
133 * Calculate the row order by total gpu time per shader
135 void TimelineWidget::calculateRows()
137 typedef QPair<uint64_t, unsigned> HeatProgram;
138 QList<HeatProgram> heats;
141 m_programRowMap.clear();
144 for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
145 const Frame& frame = *itr;
147 for (Call::const_iterator jtr = frame.calls.begin(); jtr != frame.calls.end(); ++jtr) {
148 const Call& call = *jtr;
150 while (call.program >= heats.size()) {
151 heats.append(HeatProgram(0, heats.size()));
152 m_programRowMap.append(m_programRowMap.size());
155 heats[call.program].first += call.gpuDuration;
160 idx = heats.size() - 1;
162 for (QList<HeatProgram>::iterator itr = heats.begin(); itr != heats.end(); ++itr, --idx) {
163 HeatProgram& pair = *itr;
165 if (pair.first == 0) {
166 m_programRowMap[pair.second] = -1;
168 m_programRowMap[pair.second] = idx;
176 * Set the trace profile to use for the timeline
178 void TimelineWidget::setProfile(trace::Profile* profile)
180 if (!profile->frames.size())
186 m_timeMin = m_profile->frames.front().gpuStart;
187 m_timeMax = m_profile->frames.back().gpuStart + m_profile->frames.back().gpuDuration;
190 m_timeWidth = m_timeMax - m_timeMin;
192 m_timeWidthMin = 1000;
193 m_timeWidthMax = m_timeWidth;
196 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
198 setTimeScroll(m_time);
206 * Set the horizontal scroll position to time
208 void TimelineWidget::setTimeScroll(int64_t time, bool notify)
210 time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
214 if (m_timeWidth == m_timeWidthMax) {
217 m_maxScrollX = 10000;
221 double value = time - m_timeMin;
222 value /= m_timeMax - m_timeWidth - m_timeMin;
223 value *= m_maxScrollX;
226 emit horizontalScrollMaxChanged(m_maxScrollX);
227 emit horizontalScrollValueChanged(m_scrollX);
235 * Set the vertical scroll position to position
237 void TimelineWidget::setRowScroll(int position, bool notify)
239 position = qBound(0, position, m_maxScrollY);
241 m_scrollY = position;
242 m_row = m_scrollY / m_rowHeight;
245 emit verticalScrollMaxChanged(m_maxScrollY);
246 emit verticalScrollValueChanged(m_scrollY);
253 void TimelineWidget::resizeEvent(QResizeEvent *e)
255 /* Update viewport size */
256 m_viewWidth = width() - m_axisWidth;
257 m_viewHeight = height() - m_axisHeight;
259 /* Update vertical scroll bar */
261 m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
262 emit verticalScrollMaxChanged(m_maxScrollY);
263 setRowScroll(m_scrollY);
268 void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
274 /* Display tooltip if necessary */
275 if (e->buttons() == Qt::NoButton) {
276 const VisibleItem* item = itemAtPosition(e->pos());
279 const trace::Profile::Call* call = item->call;
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);
288 QToolTip::showText(e->globalPos(), text);
292 m_mousePosition = e->pos();
294 if (e->buttons().testFlag(Qt::LeftButton)) {
295 QToolTip::hideText();
297 if (m_mousePressMode == DragView) {
298 /* Horizontal scroll */
299 double dt = m_timeWidth;
301 dt *= m_mousePressPosition.x() - e->pos().x();
302 setTimeScroll(m_mousePressTime + dt);
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);
311 setSelection(qMin(down, up), qMax(down, up));
319 void TimelineWidget::mousePressEvent(QMouseEvent *e)
321 if (e->buttons() & Qt::LeftButton) {
322 if (e->pos().y() < m_axisHeight) {
323 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
324 m_mousePressMode = RulerZoom;
326 m_mousePressMode = RulerSelect;
328 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
329 m_timeSelectionStart = time;
330 m_timeSelectionEnd = time;
333 m_mousePressMode = DragView;
336 m_mousePressPosition = e->pos();
337 m_mousePressTime = m_time;
338 m_mousePressRow = m_scrollY;
345 void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
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);
355 int64_t left = qMin(down, up);
356 int64_t right = qMax(down, up);
358 if (m_mousePressMode == RulerZoom) {
359 m_timeWidth = right - left;
360 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
362 m_mousePressMode = NoMousePress;
364 } else if (m_mousePressMode == RulerSelect) {
365 setSelection(m_timeSelectionStart, m_timeSelectionEnd, true);
370 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
372 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
374 if (e->pos().y() < m_axisHeight) {
375 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
377 for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
378 const Frame& frame = *itr;
380 if (frame.gpuStart + frame.gpuDuration < time)
383 if (frame.gpuStart > time)
386 setSelection(frame.gpuStart, frame.gpuStart + frame.gpuDuration, true);
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);
399 void TimelineWidget::wheelEvent(QWheelEvent *e)
405 int zoomPercent = 10;
407 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
411 /* Zoom view by adjusting width */
412 double dt = m_timeWidth;
413 double size = m_timeWidth;
416 /* Zoom deltas normally come in increments of 120 */
417 size /= 120 * (100 / zoomPercent);
420 m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
422 /* Scroll view to zoom around mouse */
424 dt *= e->x() - m_axisWidth;
426 setTimeScroll(dt + m_time);
433 * Paints a single pixel column of the heat map
435 void TimelineWidget::paintHeatmapColumn(int x, QPainter& painter, QVector<uint64_t>& rows)
437 double timePerPixel = m_timeWidth;
438 timePerPixel /= m_viewWidth;
440 for(int i = 0, y = 0; i < rows.size(); ++i, y += m_rowHeight) {
444 if (y > m_viewHeight)
447 double heat = rows[i] / timePerPixel;
448 heat = qBound(0.0, heat, 1.0);
451 painter.setPen(QColor(255, 255 - heat, 255 - heat));
452 painter.drawLine(x, y, x, y + m_rowHeight);
460 * Render the whole widget
462 void TimelineWidget::paintEvent(QPaintEvent *e)
467 QVector<uint64_t> heatMap(m_programRowMap.size(), 0);
468 QPainter painter(this);
477 * Draw the active selection background
479 if (m_timeSelectionStart != m_timeSelectionEnd) {
480 selectionLeft = timeToPosition(m_timeSelectionStart) + m_axisWidth;
481 selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth;
483 selectionLeft = qBound(-1, selectionLeft, width() + 1);
484 selectionRight = qBound(-1, selectionRight, width() + 1);
486 painter.setPen(Qt::NoPen);
487 painter.setBrush(m_selectionBackground);
488 painter.drawRect(selectionLeft, m_axisHeight, selectionRight - selectionLeft, m_viewHeight);
493 * Draw profile heatmap
495 rowEnd = m_row + (m_viewHeight / m_rowHeight) + 1;
496 timeEnd = m_time + m_timeWidth;
497 m_visibleItems.clear();
500 painter.translate(m_axisWidth + 1, m_axisHeight + 1 - (m_scrollY % m_rowHeight));
501 painter.setBrush(m_itemBackground);
502 painter.setPen(m_itemBorder);
504 for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
505 const Frame& frame = *itr;
507 if (frame.gpuStart > timeEnd)
510 if (frame.gpuStart + frame.gpuDuration < m_time)
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];
517 if (call.gpuStart + call.gpuDuration < m_time || call.gpuStart > timeEnd)
520 if (row < m_row || row > rowEnd)
523 double left = qMax(0.0, timeToPosition(call.gpuStart));
524 double right = timeToPosition(call.gpuStart + call.gpuDuration);
529 if (lastX != leftX) {
530 paintHeatmapColumn(lastX, painter, heatMap);
536 if (rightX <= lastX + 1) {
537 if (lastX == rightX) {
538 /* Fully contained in this X */
539 heatMap[row] += call.gpuDuration;
541 /* Split call time between the two pixels it occupies */
542 int64_t time = positionToTime(rightX);
544 heatMap[row] += time - call.gpuStart;
545 paintHeatmapColumn(lastX, painter, heatMap);
547 heatMap[row] += (call.gpuDuration + call.gpuStart) - time;
551 leftX = (left + 0.5);
552 rightX = (right + 0.5);
556 rect.setWidth(rightX - leftX);
557 rect.setTop(row * m_rowHeight);
558 rect.setHeight(m_rowHeight);
561 vis.rect = painter.transform().mapRect(rect);
564 m_visibleItems.push_back(vis);
566 painter.drawRect(rect);
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);
578 /* Paint the last column if needed */
579 paintHeatmapColumn(lastX, painter, heatMap);
583 * Draw the axis border and background
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);
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);
597 * Draw horizontal axis
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;
605 if (frame.gpuStart > timeEnd)
608 if (frame.gpuStart + frame.gpuDuration < m_time)
611 left = timeToPosition(frame.gpuStart);
612 right = timeToPosition(frame.gpuStart + frame.gpuDuration) + 0.5;
614 left = qBound(0, left, m_viewWidth) + m_axisWidth;
615 right = qBound(0, right, m_viewWidth) + m_axisWidth;
617 width = right - left;
619 text = QString("Frame %1").arg(frame.no);
621 if (painter.fontMetrics().width(text) > width) {
622 text = QString("%1").arg(frame.no);
624 if (painter.fontMetrics().width(text) > width) {
630 painter.drawText(left, 0, width, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, text);
634 painter.drawLine(left, 0, left, m_axisHeight);
635 painter.drawLine(right, 0, right, m_axisHeight);
643 painter.translate(0, -(m_scrollY % m_rowHeight));
645 for (int i = 0; i < m_programRowMap.size(); ++i) {
646 int y = (m_programRowMap[i] - m_row) * m_rowHeight;
648 if (m_programRowMap[i] < 0 || y < -m_rowHeight || y > m_viewHeight)
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);
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);
664 * Draw the active selection border
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);
675 * Draw the ruler zoom
677 if (m_mousePressMode == RulerZoom) {
678 int x1 = m_mousePressPosition.x();
679 int x2 = m_mousePosition.x();
680 int y1 = m_axisHeight;
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);
688 painter.setPen(Qt::NoPen);
689 painter.setBrush(m_zoomBackground);
690 painter.drawRect(x1, y1, x2 - x1, y2);
694 #include "timelinewidget.moc"