1 #include "graphwidget.h"
2 #include "timelinewidget.h"
3 #include "profiledialog.h"
10 #include <QApplication>
12 typedef trace::Profile::Call Call;
13 typedef trace::Profile::Frame Frame;
14 typedef trace::Profile::Program Program;
16 GraphWidget::GraphWidget(QWidget *parent)
21 m_axisForeground(Qt::black),
22 m_axisBackground(Qt::lightGray)
24 m_selection.type = SelectNone;
26 m_graphGradientGpu.setColorAt(0.9, QColor(210, 0, 0));
27 m_graphGradientGpu.setColorAt(0.0, QColor(255, 130, 130));
29 m_graphGradientCpu.setColorAt(0.9, QColor(0, 0, 210));
30 m_graphGradientCpu.setColorAt(0.0, QColor(130, 130, 255));
32 m_graphGradientDeselected.setColorAt(0.9, QColor(200, 200, 200));
33 m_graphGradientDeselected.setColorAt(0.0, QColor(220, 220, 220));
35 setMouseTracking(true);
36 setAutoFillBackground(true);
37 setBackgroundRole(QPalette::Base);
42 * Setup graph data from profile results
44 void GraphWidget::setProfile(trace::Profile* profile, GraphType type)
50 /* Find longest call to use as y axis */
51 if (m_type == GraphGpu) {
52 for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
53 const Call& call = *itr;
55 if (call.gpuDuration > m_timeMax) {
56 m_timeMax = call.gpuDuration;
60 for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
61 const Call& call = *itr;
63 if (call.cpuDuration > m_timeMax) {
64 m_timeMax = call.cpuDuration;
70 m_callMax = m_profile->calls.size();
73 m_callWidthMax = m_callMax - m_callMin;
76 m_callWidth = m_callWidthMax;
84 * Set selection to nothing
86 void GraphWidget::selectNone(bool notify)
88 m_selection.type = SelectNone;
99 * Set selection to a time period
101 void GraphWidget::selectTime(int64_t start, int64_t end, bool notify)
103 m_selection.timeStart = start;
104 m_selection.timeEnd = end;
105 m_selection.type = (start == end) ? SelectNone : SelectTime;
108 emit selectedTime(start, end);
116 * Set selection to a program
118 void GraphWidget::selectProgram(unsigned program, bool notify)
120 m_selection.program = program;
121 m_selection.type = SelectProgram;
124 emit selectedProgram(program);
132 * Slot to synchronise with other graph views
134 void GraphWidget::changeView(int call, int width)
143 * Calculate the maxTime variable when model is updated
145 void GraphWidget::update()
149 if (m_type == GraphGpu) {
150 for (int i = m_call; i < m_call + m_callWidth; ++i) {
151 const Call& call = m_profile->calls[i];
153 if (call.gpuDuration > m_timeMax) {
154 m_timeMax = call.gpuDuration;
158 for (int i = m_call; i < m_call + m_callWidth; ++i) {
159 const Call& call = m_profile->calls[i];
161 if (call.cpuDuration > m_timeMax) {
162 m_timeMax = call.cpuDuration;
172 * Find the call at (x, y) position
174 const Call* GraphWidget::callAtPosition(const QPoint& pos)
176 int left, right, size;
183 int posX = qMax(0, pos.x() - m_axisWidth);
184 int posY = qMax(0, pos.y() - m_axisHeight);
186 time = ((m_graphHeight - posY) / (double)m_graphHeight) * m_timeMax;
187 time -= (2 * m_timeMax) / m_graphHeight;
189 size = m_callWidth / (double)m_graphWidth;
191 left = m_call + (posX / (double)m_graphWidth) * m_callWidth;
192 left = qMax(m_callMin, left - size);
194 right = qMin(m_callMax - 1, left + size * 2);
196 if (m_type == GraphGpu) {
197 const Call* longest = NULL;
199 for (int i = left; i <= right; ++i) {
200 const Call& call = m_profile->calls[i];
202 if (call.pixels >= 0) {
203 if (!longest || call.gpuDuration > longest->gpuDuration) {
209 if (longest && time < longest->gpuDuration) {
213 const Call* longest = NULL;
215 for (int i = left; i <= right; ++i) {
216 const Call& call = m_profile->calls[i];
218 if (!longest || call.cpuDuration > longest->cpuDuration) {
223 if (longest && time < longest->cpuDuration) {
232 void GraphWidget::mousePressEvent(QMouseEvent *e)
234 if (e->button() == Qt::LeftButton) {
235 m_mousePressPosition = e->pos();
236 m_mousePressCall = m_call;
241 void GraphWidget::mouseReleaseEvent(QMouseEvent *e)
247 if (e->button() == Qt::LeftButton) {
248 int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y());
251 int x = qMax(m_axisWidth, e->pos().x());
252 double dcdx = m_callWidth / (double)m_graphWidth;
254 int call = m_mousePressCall + dcdx * (x - m_axisWidth);
256 int64_t start = m_profile->calls[call].cpuStart;
257 int64_t end = m_profile->calls[call].cpuStart + m_profile->calls[call].cpuDuration;
259 if (start < m_selection.timeStart || end > m_selection.timeEnd) {
267 void GraphWidget::mouseDoubleClickEvent(QMouseEvent *e)
269 const Call* call = callAtPosition(e->pos());
272 emit jumpToCall(call->no);
277 void GraphWidget::mouseMoveEvent(QMouseEvent *e)
283 if (e->buttons().testFlag(Qt::LeftButton)) {
284 double dcdx = m_callWidth / (double)m_graphWidth;
286 if (m_mousePressPosition.y() > m_axisHeight) {
287 dcdx *= m_mousePressPosition.x() - e->pos().x();
289 /* Horizontal scroll */
290 m_call = m_mousePressCall + dcdx;
291 m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth);
293 emit viewChanged(m_call, m_callWidth);
296 int x = qMax(m_axisWidth, e->pos().x());
298 int down = m_mousePressCall + dcdx * (m_mousePressPosition.x() - m_axisWidth);
299 int up = m_mousePressCall + dcdx * (x - m_axisWidth);
301 int left = qMax(qMin(down, up), 0);
302 int right = qMin<int>(qMax(down, up), m_profile->calls.size() - 1);
304 selectTime(m_profile->calls[left].cpuStart, m_profile->calls[right].cpuStart + m_profile->calls[right].cpuDuration, true);
308 const Call* call = callAtPosition(e->pos());
310 if (e->button() == Qt::NoButton && call) {
312 text = QString::fromStdString(call->name);
313 text += QString("\nCall: %1").arg(call->no);
314 text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
316 if (call->pixels >= 0) {
317 text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
318 text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
319 text += QString("\nProgram: %1").arg(call->program);
322 QToolTip::showText(e->globalPos(), text);
324 QToolTip::hideText();
329 void GraphWidget::wheelEvent(QWheelEvent *e)
335 if (e->pos().x() < m_axisWidth) {
339 int zoomPercent = 10;
341 /* If holding Ctrl key then zoom 2x faster */
342 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
346 /* Zoom view by adjusting width */
347 double dt = m_callWidth;
348 double size = dt * -e->delta();
350 /* Zoom deltas normally come in increments of 120 */
351 size /= 120 * (100 / zoomPercent);
354 m_callWidth = qBound(m_callWidthMin, m_callWidth, m_callWidthMax);
356 /* Scroll view to zoom around mouse */
358 dt *= e->x() - m_axisWidth;
361 m_call = dt + m_call;
362 m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth);
364 emit viewChanged(m_call, m_callWidth);
369 void GraphWidget::resizeEvent(QResizeEvent *e)
371 m_graphWidth = qMax(0, width() - m_axisWidth);
372 m_graphHeight = qMax(0, height() - m_axisHeight);
374 m_graphGradientGpu.setStart(0, m_graphHeight);
375 m_graphGradientCpu.setStart(0, m_graphHeight);
376 m_graphGradientDeselected.setStart(0, m_graphHeight);
381 * Draw the vertical axis of time
383 void GraphWidget::paintVerticalAxis(QPainter& painter)
385 int height = painter.fontMetrics().height();
386 int ticks = m_graphHeight / (height * 2);
388 double step = m_timeMax / (double)ticks;
389 double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0)));
390 step = qFloor((step / step10) * 2) * (step10 / 2);
396 painter.resetTransform();
397 painter.translate(0, m_axisHeight);
398 painter.setPen(m_axisForeground);
400 for (double tick = 0; tick <= m_timeMax; tick += step) {
401 int y = m_graphHeight - ((tick / m_timeMax) * m_graphHeight);
403 painter.drawLine(m_axisWidth - 8, y, m_axisWidth - 1, y);
406 qBound(0, y - height / 2, m_graphHeight - height),
409 Qt::AlignRight | Qt::AlignVCenter,
410 getTimeString(tick, m_timeMax));
416 * Draw horizontal axis of frame numbers
418 void GraphWidget::paintHorizontalAxis(QPainter& painter)
420 double dxdc = m_graphWidth / (double)m_callWidth;
421 double scroll = dxdc * m_call;
422 int lastLabel = -9999;
424 painter.resetTransform();
425 painter.fillRect(0, 0, width(), m_axisHeight, m_axisBackground);
426 painter.fillRect(0, 0, m_axisWidth, height(), m_axisBackground);
428 painter.setPen(m_axisForeground);
429 painter.drawLine(0, m_axisHeight - 1, width(), m_axisHeight - 1);
430 painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, height());
432 painter.translate(m_axisWidth, 0);
434 for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
435 static const int padding = 4;
436 const Frame& frame = *itr;
440 if (frame.calls.begin > m_call + m_callWidth) {
444 if (frame.calls.end < m_call) {
448 double left = dxdc * frame.calls.begin;
449 double right = dxdc * frame.calls.end;
450 QString text = QString("%1").arg(frame.no);
452 width = painter.fontMetrics().width(text) + padding * 2;
454 if (left + width > scroll)
457 /* Draw a frame number if we have space since the last one */
458 if (left - lastLabel > width) {
459 lastLabel = left + width;
464 if (left < scroll && right - left > width) {
465 if (right - scroll > width) {
468 textX = right - scroll - width;
471 textX = left - scroll;
474 /* Draw frame number and major ruler marking */
475 painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
476 painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
479 /* Draw a minor ruler marking */
480 painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
486 void GraphWidget::paintEvent(QPaintEvent *e)
492 QPainter painter(this);
493 QBrush deselectBrush;
499 paintHorizontalAxis(painter);
500 paintVerticalAxis(painter);
503 painter.resetTransform();
504 painter.fillRect(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::lightGray);
506 if (m_type == GraphGpu) {
507 painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "GPU");
509 painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
513 deselectBrush = QBrush(m_graphGradientDeselected);
515 if (m_type == GraphGpu) {
516 brush = QBrush(m_graphGradientGpu);
518 brush = QBrush(m_graphGradientCpu);
521 pen = QPen(brush, 1);
522 deselectPen = QPen(deselectBrush, 1);
524 painter.setBrush(brush);
526 painter.translate(m_axisWidth, m_axisHeight);
529 double dydt = m_graphHeight / (double)m_timeMax;
530 double dxdc = m_graphWidth / (double)m_callWidth;
532 int selectLeft = m_call + m_callWidth;
533 int selectRight = -1;
535 if (m_selection.type == SelectProgram) {
536 painter.setPen(deselectPen);
540 /* Draw the longest call in a pixel */
541 int64_t selectedLongest = 0;
542 int64_t pixelLongest = 0;
545 for (int i = m_call; i < m_call + m_callWidth; ++i) {
546 const Call& call = m_profile->calls[i];
549 if (m_type == GraphGpu) {
550 if (call.gpuDuration > pixelLongest) {
551 pixelLongest = call.gpuDuration;
554 if (m_selection.type == SelectProgram && call.program == m_selection.program) {
555 if (call.gpuDuration > selectedLongest) {
556 selectedLongest = call.gpuDuration;
560 if (call.cpuDuration > pixelLongest) {
561 pixelLongest = call.cpuDuration;
564 if (m_selection.type == SelectProgram && call.program == m_selection.program) {
565 if (call.cpuDuration > selectedLongest) {
566 selectedLongest = call.cpuDuration;
575 if (m_selection.type == SelectTime) {
576 if (call.cpuStart < m_selection.timeStart || call.cpuStart > m_selection.timeEnd) {
577 painter.setPen(deselectPen);
579 if (ix < selectLeft) {
583 if (ix > selectRight) {
591 painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (pixelLongest * dydt));
594 if (selectedLongest > 0) {
596 painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (selectedLongest * dydt));
597 painter.setPen(deselectPen);
605 /* Draw rectangles for graph */
606 for (int i = m_call; i < m_call + m_callWidth; ++i, x += dxdc) {
607 const Call& call = m_profile->calls[i];
610 if (m_type == GraphGpu) {
611 y = qMax<int>(1, call.gpuDuration * dydt);
613 y = qMax<int>(1, call.cpuDuration * dydt);
616 if (m_selection.type == SelectTime) {
617 if (call.cpuStart < m_selection.timeStart || call.cpuStart > m_selection.timeEnd) {
618 if (m_type == GraphCpu || call.pixels >= 0) {
619 painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), deselectBrush);
624 if (x < selectLeft) {
628 if (x + dxdc > selectRight) {
629 selectRight = x + dxdc;
634 if (m_type == GraphCpu || call.pixels >= 0) {
635 if (m_selection.type == SelectProgram && call.program != m_selection.program) {
636 painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), deselectBrush);
638 painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), brush);
644 /* Draw the selection borders */
645 if (m_selection.type == SelectTime && selectLeft < selectRight) {
646 selectLeft += m_axisWidth;
647 selectRight += m_axisWidth;
649 painter.resetTransform();
650 painter.setPen(Qt::green);
652 if (m_profile->calls[m_call].cpuStart <= m_selection.timeStart) {
653 painter.drawLine(selectLeft, 0, selectLeft, height());
656 if (m_profile->calls[m_call + m_callWidth - 1].cpuStart >= m_selection.timeEnd) {
657 painter.drawLine(selectRight, 0, selectRight, height());
660 selectLeft = qBound(m_axisWidth, selectLeft, width());
661 selectRight = qBound(m_axisWidth, selectRight, width());
662 painter.drawLine(selectLeft, m_axisHeight - 1, selectRight, m_axisHeight - 1);
666 #include "graphwidget.moc"