]> git.cworth.org Git - apitrace/blob - gui/graphwidget.cpp
Change the default graphic system to raster.
[apitrace] / gui / graphwidget.cpp
1 #include "graphwidget.h"
2 #include "timelinewidget.h"
3 #include "profiledialog.h"
4
5 #include <qmath.h>
6 #include <QLocale>
7 #include <QPainter>
8 #include <QToolTip>
9 #include <QMouseEvent>
10 #include <QApplication>
11
12 typedef trace::Profile::Call Call;
13 typedef trace::Profile::Frame Frame;
14 typedef trace::Profile::Program Program;
15
16 GraphWidget::GraphWidget(QWidget *parent)
17     : QWidget(parent),
18       m_profile(0),
19       m_axisWidth(80),
20       m_axisHeight(30),
21       m_axisForeground(Qt::black),
22       m_axisBackground(Qt::lightGray)
23 {
24     m_selection.type = SelectNone;
25
26     m_graphGradientGpu.setColorAt(0.9, QColor(210, 0, 0));
27     m_graphGradientGpu.setColorAt(0.0, QColor(255, 130, 130));
28
29     m_graphGradientCpu.setColorAt(0.9, QColor(0, 0, 210));
30     m_graphGradientCpu.setColorAt(0.0, QColor(130, 130, 255));
31
32     m_graphGradientDeselected.setColorAt(0.9, QColor(200, 200, 200));
33     m_graphGradientDeselected.setColorAt(0.0, QColor(220, 220, 220));
34
35     setMouseTracking(true);
36     setAutoFillBackground(true);
37     setBackgroundRole(QPalette::Base);
38 }
39
40
41 /**
42  * Setup graph data from profile results
43  */
44 void GraphWidget::setProfile(trace::Profile* profile, GraphType type)
45 {
46     m_type = type;
47     m_profile = profile;
48     m_timeMax = 0;
49
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;
54
55             if (call.gpuDuration > m_timeMax) {
56                 m_timeMax = call.gpuDuration;
57             }
58         }
59     } else {
60         for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
61             const Call& call = *itr;
62
63             if (call.cpuDuration > m_timeMax) {
64                 m_timeMax = call.cpuDuration;
65             }
66         }
67     }
68
69     m_callMin = 0;
70     m_callMax = m_profile->calls.size();
71
72     m_callWidthMin = 10;
73     m_callWidthMax = m_callMax - m_callMin;
74
75     m_call = m_callMin;
76     m_callWidth = m_callWidthMax;
77
78     selectNone();
79     update();
80 }
81
82
83 /**
84  * Set selection to nothing
85  */
86 void GraphWidget::selectNone(bool notify)
87 {
88     m_selection.type = SelectNone;
89
90     if (notify) {
91         emit selectedNone();
92     }
93
94     update();
95 }
96
97
98 /**
99  * Set selection to a time period
100  */
101 void GraphWidget::selectTime(int64_t start, int64_t end, bool notify)
102 {
103     m_selection.timeStart = start;
104     m_selection.timeEnd = end;
105     m_selection.type = (start == end) ? SelectNone : SelectTime;
106
107     if (notify) {
108         emit selectedTime(start, end);
109     }
110
111     update();
112 }
113
114
115 /**
116  * Set selection to a program
117  */
118 void GraphWidget::selectProgram(unsigned program, bool notify)
119 {
120     m_selection.program = program;
121     m_selection.type = SelectProgram;
122
123     if (notify) {
124         emit selectedProgram(program);
125     }
126
127     update();
128 }
129
130
131 /**
132  * Slot to synchronise with other graph views
133  */
134 void GraphWidget::changeView(int call, int width)
135 {
136     m_call = call;
137     m_callWidth = width;
138     update();
139 }
140
141
142 /**
143  * Calculate the maxTime variable when model is updated
144  */
145 void GraphWidget::update()
146 {
147     m_timeMax = 0;
148
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];
152
153             if (call.gpuDuration > m_timeMax) {
154                 m_timeMax = call.gpuDuration;
155             }
156         }
157     } else {
158         for (int i = m_call; i < m_call + m_callWidth; ++i) {
159             const Call& call =  m_profile->calls[i];
160
161             if (call.cpuDuration > m_timeMax) {
162                 m_timeMax = call.cpuDuration;
163             }
164         }
165     }
166
167     QWidget::update();
168 }
169
170
171 /**
172  * Find the call at (x, y) position
173  */
174 const Call* GraphWidget::callAtPosition(const QPoint& pos)
175 {
176     int left, right, size;
177     int64_t time;
178
179     if (!m_profile) {
180         return NULL;
181     }
182
183     int posX = qMax(0, pos.x() - m_axisWidth);
184     int posY = qMax(0, pos.y() - m_axisHeight);
185
186     time  = ((m_graphHeight - posY) / (double)m_graphHeight) * m_timeMax;
187     time -= (2 * m_timeMax) / m_graphHeight;
188
189     size  = m_callWidth / (double)m_graphWidth;
190
191     left  = m_call + (posX / (double)m_graphWidth) * m_callWidth;
192     left  = qMax(m_callMin, left - size);
193
194     right = qMin(m_callMax - 1, left + size * 2);
195
196     if (m_type == GraphGpu) {
197         const Call* longest = NULL;
198
199         for (int i = left; i <= right; ++i) {
200             const Call& call = m_profile->calls[i];
201
202             if (call.pixels >= 0) {
203                 if (!longest || call.gpuDuration > longest->gpuDuration) {
204                     longest = &call;
205                 }
206             }
207         }
208
209         if (longest && time < longest->gpuDuration) {
210             return longest;
211         }
212     } else {
213         const Call* longest = NULL;
214
215         for (int i = left; i <= right; ++i) {
216             const Call& call = m_profile->calls[i];
217
218             if (!longest || call.cpuDuration > longest->cpuDuration) {
219                 longest = &call;
220             }
221         }
222
223         if (longest && time < longest->cpuDuration) {
224             return longest;
225         }
226     }
227
228     return NULL;
229 }
230
231
232 void GraphWidget::mousePressEvent(QMouseEvent *e)
233 {
234     if (e->button() == Qt::LeftButton) {
235         m_mousePressPosition = e->pos();
236         m_mousePressCall = m_call;
237     }
238 }
239
240
241 void GraphWidget::mouseReleaseEvent(QMouseEvent *e)
242 {
243     if (!m_profile) {
244         return;
245     }
246
247     if (e->button() == Qt::LeftButton) {
248         int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y());
249
250         if (dxy <= 2) {
251             int x = qMax(m_axisWidth, e->pos().x());
252             double dcdx = m_callWidth / (double)m_graphWidth;
253
254             int call = m_mousePressCall + dcdx * (x - m_axisWidth);
255
256             int64_t start = m_profile->calls[call].cpuStart;
257             int64_t end = m_profile->calls[call].cpuStart + m_profile->calls[call].cpuDuration;
258
259             if (start < m_selection.timeStart || end > m_selection.timeEnd) {
260                 selectNone(true);
261             }
262         }
263     }
264 }
265
266
267 void GraphWidget::mouseDoubleClickEvent(QMouseEvent *e)
268 {
269     const Call* call = callAtPosition(e->pos());
270
271     if (call) {
272         emit jumpToCall(call->no);
273     }
274 }
275
276
277 void GraphWidget::mouseMoveEvent(QMouseEvent *e)
278 {
279     if (!m_profile) {
280         return;
281     }
282
283     if (e->buttons().testFlag(Qt::LeftButton)) {
284         double dcdx = m_callWidth / (double)m_graphWidth;
285
286         if (m_mousePressPosition.y() > m_axisHeight) {
287             dcdx *= m_mousePressPosition.x() - e->pos().x();
288
289             /* Horizontal scroll */
290             m_call = m_mousePressCall + dcdx;
291             m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth);
292
293             emit viewChanged(m_call, m_callWidth);
294             update();
295         } else {
296             int x = qMax(m_axisWidth, e->pos().x());
297
298             int down = m_mousePressCall + dcdx * (m_mousePressPosition.x() - m_axisWidth);
299             int up = m_mousePressCall + dcdx * (x - m_axisWidth);
300
301             int left = qMax(qMin(down, up), 0);
302             int right = qMin<int>(qMax(down, up), m_profile->calls.size() - 1);
303
304             selectTime(m_profile->calls[left].cpuStart, m_profile->calls[right].cpuStart + m_profile->calls[right].cpuDuration, true);
305         }
306     }
307
308     const Call* call = callAtPosition(e->pos());
309
310     if (e->button() == Qt::NoButton && call) {
311         QString text;
312         text  = QString::fromStdString(call->name);
313         text += QString("\nCall: %1").arg(call->no);
314         text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
315
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);
320         }
321
322         QToolTip::showText(e->globalPos(), text);
323     } else {
324         QToolTip::hideText();
325     }
326 }
327
328
329 void GraphWidget::wheelEvent(QWheelEvent *e)
330 {
331     if (!m_profile) {
332         return;
333     }
334
335     if (e->pos().x() < m_axisWidth) {
336         return;
337     }
338
339     int zoomPercent = 10;
340
341     /* If holding Ctrl key then zoom 2x faster */
342     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
343         zoomPercent = 20;
344     }
345
346     /* Zoom view by adjusting width */
347     double dt = m_callWidth;
348     double size = dt * -e->delta();
349
350     /* Zoom deltas normally come in increments of 120 */
351     size /= 120 * (100 / zoomPercent);
352
353     m_callWidth += size;
354     m_callWidth = qBound(m_callWidthMin, m_callWidth, m_callWidthMax);
355
356     /* Scroll view to zoom around mouse */
357     dt -= m_callWidth;
358     dt *= e->x() - m_axisWidth;
359     dt /= m_graphWidth;
360
361     m_call = dt + m_call;
362     m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth);
363
364     emit viewChanged(m_call, m_callWidth);
365     update();
366 }
367
368
369 void GraphWidget::resizeEvent(QResizeEvent *e)
370 {
371     m_graphWidth = qMax(0, width() - m_axisWidth);
372     m_graphHeight = qMax(0, height() - m_axisHeight);
373
374     m_graphGradientGpu.setStart(0, m_graphHeight);
375     m_graphGradientCpu.setStart(0, m_graphHeight);
376     m_graphGradientDeselected.setStart(0, m_graphHeight);
377 }
378
379
380 /**
381  * Draw the vertical axis of time
382  */
383 void GraphWidget::paintVerticalAxis(QPainter& painter)
384 {
385     int height = painter.fontMetrics().height();
386     int ticks  = m_graphHeight / (height * 2);
387
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);
391
392     if (step <= 0) {
393         return;
394     }
395
396     painter.resetTransform();
397     painter.translate(0, m_axisHeight);
398     painter.setPen(m_axisForeground);
399
400     for (double tick = 0; tick <= m_timeMax; tick += step) {
401         int y = m_graphHeight - ((tick / m_timeMax) * m_graphHeight);
402
403         painter.drawLine(m_axisWidth - 8, y, m_axisWidth - 1, y);
404
405         painter.drawText(0,
406                          qBound(0, y - height / 2, m_graphHeight - height),
407                          m_axisWidth - 10,
408                          height,
409                          Qt::AlignRight | Qt::AlignVCenter,
410                          getTimeString(tick, m_timeMax));
411     }
412 }
413
414
415 /**
416  * Draw horizontal axis of frame numbers
417  */
418 void GraphWidget::paintHorizontalAxis(QPainter& painter)
419 {
420     double dxdc = m_graphWidth / (double)m_callWidth;
421     double scroll = dxdc * m_call;
422     int lastLabel = -9999;
423
424     painter.resetTransform();
425     painter.fillRect(0, 0, width(), m_axisHeight, m_axisBackground);
426     painter.fillRect(0, 0, m_axisWidth, height(), m_axisBackground);
427
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());
431
432     painter.translate(m_axisWidth, 0);
433
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;
437         bool draw = true;
438         int width;
439
440         if (frame.calls.begin > m_call + m_callWidth) {
441             break;
442         }
443
444         if (frame.calls.end < m_call) {
445             draw = false;
446         }
447
448         double left = dxdc * frame.calls.begin;
449         double right = dxdc * frame.calls.end;
450         QString text = QString("%1").arg(frame.no);
451
452         width = painter.fontMetrics().width(text) + padding * 2;
453
454         if (left + width > scroll)
455             draw = true;
456
457         /* Draw a frame number if we have space since the last one */
458         if (left - lastLabel > width) {
459             lastLabel = left + width;
460
461             if (draw) {
462                 int textX;
463
464                 if (left < scroll && right - left > width) {
465                     if (right - scroll > width) {
466                         textX = 0;
467                     } else {
468                         textX = right - scroll - width;
469                     }
470                 } else {
471                     textX = left - scroll;
472                 }
473
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);
477             }
478         } else if (draw) {
479             /* Draw a minor ruler marking */
480             painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
481         }
482     }
483 }
484
485
486 void GraphWidget::paintEvent(QPaintEvent *e)
487 {
488     if (!m_profile) {
489         return;
490     }
491
492     QPainter painter(this);
493     QBrush deselectBrush;
494     QPen deselectPen;
495     QBrush brush;
496     QPen pen;
497
498     /* Draw axes */
499     paintHorizontalAxis(painter);
500     paintVerticalAxis(painter);
501
502     /* Draw the label */
503     painter.resetTransform();
504     painter.fillRect(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::lightGray);
505
506     if (m_type == GraphGpu) {
507         painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "GPU");
508     } else {
509         painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
510     }
511
512     /* Draw graph */
513     deselectBrush = QBrush(m_graphGradientDeselected);
514
515     if (m_type == GraphGpu) {
516         brush = QBrush(m_graphGradientGpu);
517     } else {
518         brush = QBrush(m_graphGradientCpu);
519     }
520
521     pen = QPen(brush, 1);
522     deselectPen = QPen(deselectBrush, 1);
523
524     painter.setBrush(brush);
525     painter.setPen(pen);
526     painter.translate(m_axisWidth, m_axisHeight);
527
528     double x = 0;
529     double dydt = m_graphHeight / (double)m_timeMax;
530     double dxdc = m_graphWidth / (double)m_callWidth;
531
532     int selectLeft = m_call + m_callWidth;
533     int selectRight = -1;
534
535     if (m_selection.type == SelectProgram) {
536         painter.setPen(deselectPen);
537     }
538
539     if (dxdc < 1.0) {
540         /* Draw the longest call in a pixel */
541         int64_t selectedLongest = 0;
542         int64_t pixelLongest = 0;
543         int lastX = 0;
544
545         for (int i = m_call; i < m_call + m_callWidth; ++i) {
546             const Call& call =  m_profile->calls[i];
547             int ix;
548
549             if (m_type == GraphGpu) {
550                 if (call.gpuDuration > pixelLongest) {
551                     pixelLongest = call.gpuDuration;
552                 }
553
554                 if (m_selection.type == SelectProgram && call.program == m_selection.program) {
555                     if (call.gpuDuration > selectedLongest) {
556                         selectedLongest = call.gpuDuration;
557                     }
558                 }
559             } else {
560                 if (call.cpuDuration > pixelLongest) {
561                     pixelLongest = call.cpuDuration;
562                 }
563
564                 if (m_selection.type == SelectProgram && call.program == m_selection.program) {
565                     if (call.cpuDuration > selectedLongest) {
566                         selectedLongest = call.cpuDuration;
567                     }
568                 }
569             }
570
571             x += dxdc;
572             ix = (int)x;
573
574             if (lastX != ix) {
575                 if (m_selection.type == SelectTime) {
576                     if (call.cpuStart < m_selection.timeStart || call.cpuStart > m_selection.timeEnd) {
577                         painter.setPen(deselectPen);
578                     } else {
579                         if (ix < selectLeft) {
580                             selectLeft = ix;
581                         }
582
583                         if (ix > selectRight) {
584                             selectRight = ix;
585                         }
586
587                         painter.setPen(pen);
588                     }
589                 }
590
591                 painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (pixelLongest * dydt));
592                 pixelLongest = 0;
593
594                 if (selectedLongest > 0) {
595                     painter.setPen(pen);
596                     painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (selectedLongest * dydt));
597                     painter.setPen(deselectPen);
598                     selectedLongest = 0;
599                 }
600
601                 lastX = ix;
602             }
603         }
604     } else {
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];
608             int y;
609
610             if (m_type == GraphGpu) {
611                 y = qMax<int>(1, call.gpuDuration * dydt);
612             } else {
613                 y = qMax<int>(1, call.cpuDuration * dydt);
614             }
615
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);
620                     }
621
622                     continue;
623                 } else {
624                     if (x < selectLeft) {
625                         selectLeft = x;
626                     }
627
628                     if (x + dxdc > selectRight) {
629                         selectRight = x + dxdc;
630                     }
631                 }
632             }
633
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);
637                 } else {
638                     painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), brush);
639                 }
640             }
641         }
642     }
643
644     /* Draw the selection borders */
645     if (m_selection.type == SelectTime && selectLeft < selectRight) {
646         selectLeft += m_axisWidth;
647         selectRight += m_axisWidth;
648
649         painter.resetTransform();
650         painter.setPen(Qt::green);
651
652         if (m_profile->calls[m_call].cpuStart <= m_selection.timeStart) {
653             painter.drawLine(selectLeft, 0, selectLeft, height());
654         }
655
656         if (m_profile->calls[m_call + m_callWidth - 1].cpuStart >= m_selection.timeEnd) {
657             painter.drawLine(selectRight, 0, selectRight, height());
658         }
659
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);
663     }
664 }
665
666 #include "graphwidget.moc"