From 0b65a2bc99ea926111c0ff19723bd5e2ac84827d Mon Sep 17 00:00:00 2001 From: James Benton Date: Fri, 7 Sep 2012 18:38:15 +0100 Subject: [PATCH] Rewrote profile graph drawing code. Graphing is now reasonably generic to allow for future graphs to be fairly easily added without too much replication of code. --- gui/CMakeLists.txt | 10 +- gui/calldurationgraph.h | 173 ++++ gui/graphing/frameaxiswidget.cpp | 99 ++ gui/graphing/frameaxiswidget.h | 32 + gui/graphing/graphaxiswidget.cpp | 141 +++ gui/graphing/graphaxiswidget.h | 73 ++ gui/graphing/graphing.h | 48 + gui/graphing/graphlabelwidget.h | 42 + gui/graphing/graphview.cpp | 187 ++++ gui/graphing/graphview.h | 93 ++ gui/graphing/graphwidget.cpp | 583 +++++++++++ gui/graphing/graphwidget.h | 112 ++ gui/graphing/heatmapverticalaxiswidget.cpp | 82 ++ gui/graphing/heatmapverticalaxiswidget.h | 24 + gui/graphing/heatmapview.cpp | 276 +++++ gui/graphing/heatmapview.h | 124 +++ gui/graphing/histogramview.cpp | 258 +++++ gui/graphing/histogramview.h | 41 + gui/graphing/timeaxiswidget.cpp | 80 ++ gui/graphing/timeaxiswidget.h | 16 + gui/graphwidget.cpp | 666 ------------ gui/graphwidget.h | 105 -- gui/profiledialog.cpp | 350 ++++--- gui/profiledialog.h | 11 +- gui/profileheatmap.h | 470 +++++++++ gui/profiletablemodel.cpp | 9 +- gui/profiling.h | 128 +++ gui/timelinewidget.cpp | 1078 -------------------- gui/timelinewidget.h | 148 --- gui/ui/profiledialog.ui | 354 +------ 30 files changed, 3306 insertions(+), 2507 deletions(-) create mode 100644 gui/calldurationgraph.h create mode 100644 gui/graphing/frameaxiswidget.cpp create mode 100644 gui/graphing/frameaxiswidget.h create mode 100644 gui/graphing/graphaxiswidget.cpp create mode 100644 gui/graphing/graphaxiswidget.h create mode 100644 gui/graphing/graphing.h create mode 100644 gui/graphing/graphlabelwidget.h create mode 100644 gui/graphing/graphview.cpp create mode 100644 gui/graphing/graphview.h create mode 100644 gui/graphing/graphwidget.cpp create mode 100644 gui/graphing/graphwidget.h create mode 100644 gui/graphing/heatmapverticalaxiswidget.cpp create mode 100644 gui/graphing/heatmapverticalaxiswidget.h create mode 100644 gui/graphing/heatmapview.cpp create mode 100644 gui/graphing/heatmapview.h create mode 100644 gui/graphing/histogramview.cpp create mode 100644 gui/graphing/histogramview.h create mode 100644 gui/graphing/timeaxiswidget.cpp create mode 100644 gui/graphing/timeaxiswidget.h delete mode 100644 gui/graphwidget.cpp delete mode 100644 gui/graphwidget.h create mode 100644 gui/profileheatmap.h create mode 100644 gui/profiling.h delete mode 100644 gui/timelinewidget.cpp delete mode 100644 gui/timelinewidget.h diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 89075da..ece6643 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -10,7 +10,6 @@ set(qapitrace_SRCS apitracemodel.cpp argumentseditor.cpp glsledit.cpp - graphwidget.cpp imageviewer.cpp jumpwidget.cpp mainwindow.cpp @@ -26,8 +25,15 @@ set(qapitrace_SRCS traceloader.cpp traceprocess.cpp trimprocess.cpp - timelinewidget.cpp vertexdatainterpreter.cpp + graphing/frameaxiswidget.cpp + graphing/graphwidget.cpp + graphing/graphaxiswidget.cpp + graphing/graphview.cpp + graphing/heatmapview.cpp + graphing/heatmapverticalaxiswidget.cpp + graphing/histogramview.cpp + graphing/timeaxiswidget.cpp ) qt4_automoc(${qapitrace_SRCS}) diff --git a/gui/calldurationgraph.h b/gui/calldurationgraph.h new file mode 100644 index 0000000..0c7e8c9 --- /dev/null +++ b/gui/calldurationgraph.h @@ -0,0 +1,173 @@ +#ifndef CALLDURATIONGRAPH_H +#define CALLDURATIONGRAPH_H + +#include "graphing/graphwidget.h" +#include "trace_profiler.hpp" +#include "profiling.h" + +/** + * Wrapper for call duration graphs. + * + * This implements the transformSelectionIn and transformSelectionOut to + * allow sharing the selection between the graphs and the heatmap as they + * are using different scales. The duration graphs have call.no on the X-axis + * whereas the heatmap has time on the X axis. + */ +class CallDurationGraph : public GraphWidget { +public: + CallDurationGraph(QWidget* parent = 0) : + GraphWidget(parent), + m_profile(NULL) + { + } + + void setProfile(const trace::Profile* profile) + { + m_profile = profile; + } + +protected: + /* Transform from time-based horizontal selection to call no based. */ + virtual SelectionState transformSelectionIn(SelectionState state) + { + if (!m_profile || state.type != SelectionState::Horizontal) { + return state; + } + + qint64 timeStart = state.start; + qint64 timeEnd = state.end; + + std::vector::const_iterator itr; + + itr = + Profiling::binarySearchTimespan< + trace::Profile::Call, + &trace::Profile::Call::cpuStart, + &trace::Profile::Call::cpuDuration> + (m_profile->calls.begin(), m_profile->calls.end(), timeStart, true); + + state.start = itr - m_profile->calls.begin(); + + itr = + Profiling::binarySearchTimespan< + trace::Profile::Call, + &trace::Profile::Call::cpuStart, + &trace::Profile::Call::cpuDuration> + (m_profile->calls.begin(), m_profile->calls.end(), timeEnd, true); + + state.end = itr - m_profile->calls.begin(); + + return state; + } + + virtual SelectionState transformSelectionOut(SelectionState state) + { + if (!m_profile || state.type != SelectionState::Horizontal) { + return state; + } + + qint64 start = qMax(0, state.start); + qint64 end = qMin(state.end, m_profile->calls.size()); + + /* Call based -> time based */ + state.start = m_profile->calls[start].cpuStart; + state.end = m_profile->calls[end].cpuStart + m_profile->calls[end].cpuDuration; + + return state; + } + +private: + const trace::Profile* m_profile; +}; + +/* Data provider for call duration graphs */ +class CallDurationDataProvider : public GraphDataProvider { +public: + CallDurationDataProvider(const trace::Profile* profile, bool gpu) : + m_gpu(gpu), + m_profile(profile), + m_selectionState(NULL) + { + } + + virtual qint64 size() const + { + return m_profile ? m_profile->calls.size() : 0; + } + + virtual bool selected(qint64 index) const + { + if (m_selectionState) { + if (m_selectionState->type == SelectionState::Horizontal) { + if (m_selectionState->start <= index && index < m_selectionState->end) { + return true; + } + } else if (m_selectionState->type == SelectionState::Vertical) { + return m_profile->calls[index].program == m_selectionState->start; + } + } + + return false; + } + + virtual void setSelectionState(SelectionState* state) + { + m_selectionState = state; + } + + virtual qint64 value(qint64 index) const + { + if (m_gpu) { + return m_profile->calls[index].gpuDuration; + } else { + return m_profile->calls[index].cpuDuration; + } + } + + virtual void itemDoubleClicked(qint64 index) const + { + if (!m_profile) { + return; + } + + if (index < 0 || index >= m_profile->calls.size()) { + return; + } + + const trace::Profile::Call& call = m_profile->calls[index]; + Profiling::jumpToCall(call.no); + } + + virtual QString itemTooltip(qint64 index) const + { + if (!m_profile) { + return QString(); + } + + if (index < 0 || index >= m_profile->calls.size()) { + return QString(); + } + + const trace::Profile::Call& call = m_profile->calls[index]; + + QString text; + text = QString::fromStdString(call.name); + text += QString("\nCall: %1").arg(call.no); + text += QString("\nCPU Duration: %1").arg(Profiling::getTimeString(call.cpuDuration)); + + if (call.pixels >= 0) { + text += QString("\nGPU Duration: %1").arg(Profiling::getTimeString(call.gpuDuration)); + text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call.pixels)); + text += QString("\nProgram: %1").arg(call.program); + } + + return text; + } + +private: + bool m_gpu; + const trace::Profile* m_profile; + SelectionState* m_selectionState; +}; + +#endif diff --git a/gui/graphing/frameaxiswidget.cpp b/gui/graphing/frameaxiswidget.cpp new file mode 100644 index 0000000..ad5d868 --- /dev/null +++ b/gui/graphing/frameaxiswidget.cpp @@ -0,0 +1,99 @@ +#include "frameaxiswidget.h" + +#include + +FrameAxisWidget::FrameAxisWidget(QWidget* parent) : + GraphAxisWidget(parent), + m_data(NULL) +{ + setSelectable(GraphAxisWidget::Range); +} + +void FrameAxisWidget::setDataProvider(FrameDataProvider* data) +{ + delete m_data; + m_data = data; +} + +void FrameAxisWidget::paintEvent(QPaintEvent *) +{ + if (!m_data || m_orientation != GraphAxisWidget::Horizontal) { + /* TODO: Vertical axis support */ + return; + } + + QPainter painter(this); + painter.setPen(Qt::black); + painter.setBrush(Qt::lightGray); + painter.drawRect(0, 0, width() - 1, height() - 1); + + qint64 range = m_valueEnd - m_valueBegin; + double dxdv = width() / (double)range; + double scroll = dxdv * m_valueBegin; + int lastLabel = -9999; + + /* Iterate over frames, drawing a label when there is space to do so */ + for (unsigned i = 0; i < m_data->size(); ++i) { + static const int padding = 4; + qint64 start = m_data->frameStart(i); + qint64 end = m_data->frameEnd(i); + bool visible = false; + + if (start > m_valueEnd) { + break; + } + + if (end < m_valueBegin) { + visible = false; + } + + double left = dxdv * start; + double right = dxdv * end; + QString text = QString("%1").arg(i); + + int width = painter.fontMetrics().width(text) + padding * 2; + + if (right > scroll) { + visible = true; + } + + if (left - lastLabel > width) { + lastLabel = left + width; + + if (visible) { + int textX; + + if (left < scroll && right - left > width) { + if (right - scroll > width) { + textX = 0; + } else { + textX = right - scroll - width; + } + } else { + textX = left - scroll; + } + + painter.drawText(textX + padding, 0, width - padding, height() - 5, Qt::AlignLeft | Qt::AlignVCenter, text); + painter.drawLine(left - scroll, height() / 2, left - scroll, height() - 1); + } + } else if (visible) { + painter.drawLine(left - scroll, height() * 3/4.0, left - scroll, height() - 1); + } + } + + /* Draw selection */ + if (hasSelection()) { + double left = (dxdv * m_selectionState->start) - scroll; + double right = (dxdv * m_selectionState->end) - scroll; + + painter.setPen(Qt::green); + + if (left >= 0 && left <= width()) { + painter.drawLine(left, 0, left, height()); + } + + if (right >= 0 && right <= width()) { + painter.drawLine(right, 0, right, height()); + } + } +} diff --git a/gui/graphing/frameaxiswidget.h b/gui/graphing/frameaxiswidget.h new file mode 100644 index 0000000..db091d9 --- /dev/null +++ b/gui/graphing/frameaxiswidget.h @@ -0,0 +1,32 @@ +#ifndef FRAMEAXISWIDGET_H +#define FRAMEAXISWIDGET_H + +#include "graphaxiswidget.h" + +class FrameDataProvider { +public: + /* Number of frames */ + virtual unsigned size() const = 0; + + /* Start and end values of frame */ + virtual qint64 frameStart(unsigned frame) const = 0; + virtual qint64 frameEnd(unsigned frame) const = 0; +}; + +/** + * A generic axis which will draw frame numbers over a period of values. + * Frames designated by start / end values. + */ +class FrameAxisWidget : public GraphAxisWidget { +public: + FrameAxisWidget(QWidget* parent = 0); + + void setDataProvider(FrameDataProvider* data); + + virtual void paintEvent(QPaintEvent *e); + +protected: + FrameDataProvider* m_data; +}; + +#endif diff --git a/gui/graphing/graphaxiswidget.cpp b/gui/graphing/graphaxiswidget.cpp new file mode 100644 index 0000000..0adeadd --- /dev/null +++ b/gui/graphing/graphaxiswidget.cpp @@ -0,0 +1,141 @@ +#include "graphaxiswidget.h" + +#include + +GraphAxisWidget::GraphAxisWidget(QWidget* parent) : + QWidget(parent), + m_selectable(None), + m_selectionState(NULL) +{ +} + + +bool GraphAxisWidget::hasSelection() +{ + if (!m_selectionState) { + return false; + } + + if (m_selectionState->type == SelectionState::Horizontal && m_orientation == GraphAxisWidget::Horizontal) { + return true; + } + + if (m_selectionState->type == SelectionState::Vertical && m_orientation == GraphAxisWidget::Vertical) { + return true; + } + + return false; +} + + +void GraphAxisWidget::setSelectable(SelectionStyle selectable) +{ + m_selectable = selectable; +} + + +void GraphAxisWidget::setSelectionState(SelectionState* state) +{ + m_selectionState = state; +} + + +void GraphAxisWidget::setOrientation(Orientation v) +{ + m_orientation = v; + + if (m_orientation == Horizontal) { + setMinimumWidth(60); + } else { + setMinimumHeight(60); + } +} + + +void GraphAxisWidget::mouseMoveEvent(QMouseEvent *e) +{ + if (m_selectable == None) { + return; + } + + int pos, max; + + if (m_orientation == Horizontal) { + pos = e->x(); + max = width(); + } else { + pos = e->y(); + max = height(); + } + + double value = m_valueEnd - m_valueBegin; + value *= pos / (double)max; + value += m_valueBegin; + + if (e->buttons().testFlag(Qt::LeftButton)) { + m_selectionState->start = qMin(m_mousePressValue, value); + m_selectionState->end = qMax(m_mousePressValue, value); + m_selectionState->type = m_orientation == Horizontal ? SelectionState::Horizontal : SelectionState::Vertical; + emit selectionChanged(); + update(); + } +} + + +void GraphAxisWidget::mousePressEvent(QMouseEvent *e) +{ + if (m_selectable == None) { + return; + } + + int pos, max; + + if (m_orientation == Horizontal) { + pos = e->x(); + max = width(); + } else { + pos = e->y(); + max = height(); + } + + double value = m_valueEnd - m_valueBegin; + value *= pos / (double)max; + value += m_valueBegin; + + m_mousePressPosition = e->pos(); + m_mousePressValue = value; +} + + +void GraphAxisWidget::mouseReleaseEvent(QMouseEvent *e) +{ + if (m_selectable == None) { + return; + } + + int dx = qAbs(m_mousePressPosition.x() - e->x()); + int dy = qAbs(m_mousePressPosition.y() - e->y()); + + if (dx + dy < 2) { + m_selectionState->type = SelectionState::None; + emit selectionChanged(); + } +} + + +void GraphAxisWidget::setRange(qint64 min, qint64 max) +{ + m_valueMin = min; + m_valueMax = max; + update(); +} + + +void GraphAxisWidget::setView(qint64 start, qint64 end) +{ + m_valueBegin = start; + m_valueEnd = end; + update(); +} + +#include "graphaxiswidget.moc" diff --git a/gui/graphing/graphaxiswidget.h b/gui/graphing/graphaxiswidget.h new file mode 100644 index 0000000..6ae0080 --- /dev/null +++ b/gui/graphing/graphaxiswidget.h @@ -0,0 +1,73 @@ +#ifndef GRAPHAXISWIDGET_H +#define GRAPHAXISWIDGET_H + +#include "graphing.h" + +#include + +/** + * The generic base class of all graph axes. + * + * Handles orientation, simple selections, and view area. + */ +class GraphAxisWidget : public QWidget { + Q_OBJECT +public: + enum Orientation { + Horizontal, + Vertical + }; + + enum SelectionStyle { + None, + Single, + Range + }; + +public: + GraphAxisWidget(QWidget* parent = 0); + virtual ~GraphAxisWidget(){} + + /* Is this axis part of the active selection */ + bool hasSelection(); + + void setSelectable(SelectionStyle selectable); + void setSelectionState(SelectionState* state); + + void setOrientation(Orientation v); + + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + +public slots: + /* The minimum and maximum values of this axis */ + void setRange(qint64 min, qint64 max); + + /* The currently visible range of values */ + void setView(qint64 start, qint64 end); + +signals: + void selectionChanged(); + +protected: + Orientation m_orientation; + + /* The min/max value of this axis */ + qint64 m_valueMin; + qint64 m_valueMax; + + /* The highest and lowest currently visible value */ + qint64 m_valueBegin; + qint64 m_valueEnd; + + /* Selection */ + SelectionStyle m_selectable; + SelectionState* m_selectionState; + + /* Mouse tracking */ + QPoint m_mousePressPosition; + qint64 m_mousePressValue; +}; + +#endif diff --git a/gui/graphing/graphing.h b/gui/graphing/graphing.h new file mode 100644 index 0000000..35b4a91 --- /dev/null +++ b/gui/graphing/graphing.h @@ -0,0 +1,48 @@ +#ifndef GRAPHING_H +#define GRAPHING_H + +#include + +/** + * A simple struct to hold a horizontal or vertical selection + */ +struct SelectionState { + enum SelectionType { + None, + Horizontal, + Vertical + }; + + SelectionType type; + qint64 start; + qint64 end; +}; + + +/** + * Fairly generic data provider for graphs + */ +class GraphDataProvider { +public: + virtual ~GraphDataProvider(){} + + /* Number of elements in graph */ + virtual qint64 size() const = 0; + + /* Returns value for index */ + virtual qint64 value(qint64 index) const = 0; + + /* Is the item at index selected */ + virtual bool selected(qint64 index) const = 0; + + /* Get mouse hover tooltip for item */ + virtual QString itemTooltip(qint64 index) const = 0; + + /* Called on item double click */ + virtual void itemDoubleClicked(qint64 index) const = 0; + + /* Set pointer to selection state */ + virtual void setSelectionState(SelectionState* state) = 0; +}; + +#endif diff --git a/gui/graphing/graphlabelwidget.h b/gui/graphing/graphlabelwidget.h new file mode 100644 index 0000000..49c0cd4 --- /dev/null +++ b/gui/graphing/graphlabelwidget.h @@ -0,0 +1,42 @@ +#ifndef GRAPHLABELWIDGET_H +#define GRAPHLABELWIDGET_H + +#include +#include + +/** + * A very simple label widget, basically a box with text in. + */ +class GraphLabelWidget : public QWidget { +public: + GraphLabelWidget(QString text = QString(), QWidget* parent = 0) : + QWidget(parent), + m_flags(Qt::AlignHCenter | Qt::AlignVCenter), + m_text(text) + { + } + + void setText(const QString& text) + { + m_text = text; + } + + void setFlags(int flags) + { + m_flags = flags; + } + + virtual void paintEvent(QPaintEvent *) + { + QPainter painter(this); + painter.setPen(Qt::black); + painter.fillRect(rect(), Qt::lightGray); + painter.drawText(rect(), m_flags, m_text); + } + +protected: + int m_flags; + QString m_text; +}; + +#endif diff --git a/gui/graphing/graphview.cpp b/gui/graphing/graphview.cpp new file mode 100644 index 0000000..6ecac57 --- /dev/null +++ b/gui/graphing/graphview.cpp @@ -0,0 +1,187 @@ +#include "graphview.h" + +#include +#include + +GraphView::GraphView(QWidget* parent) : + QWidget(parent), + m_selectionState(NULL), + m_viewLeft(0), + m_viewRight(0), + m_viewBottom(0), + m_viewTop(0), + m_graphLeft(0), + m_graphRight(0), + m_graphBottom(0), + m_graphTop(0), + m_viewWidth(0), + m_viewWidthMin(0), + m_viewWidthMax(0), + m_viewHeight(0), + m_viewHeightMin(0), + m_viewHeightMax(0) +{ + memset(&m_previous, -1, sizeof(m_previous)); +} + +void GraphView::update() +{ + if (m_graphLeft != m_previous.m_graphLeft || m_graphRight != m_previous.m_graphRight) { + m_previous.m_graphLeft = m_graphLeft; + m_previous.m_graphRight = m_graphRight; + + emit horizontalRangeChanged(m_graphLeft, m_graphRight); + } + + if (m_viewLeft != m_previous.m_viewLeft || m_viewRight != m_previous.m_viewRight) { + m_previous.m_viewLeft = m_viewLeft; + m_previous.m_viewRight = m_viewRight; + + emit horizontalViewChanged(m_viewLeft, m_viewRight); + } + + if (m_graphBottom != m_previous.m_graphBottom || m_graphTop != m_previous.m_graphTop) { + m_previous.m_graphBottom = m_graphBottom; + m_previous.m_graphTop = m_graphTop; + + emit verticalRangeChanged(m_graphBottom, m_graphTop); + } + + if (m_viewBottom != m_previous.m_viewBottom || m_viewTop != m_previous.m_viewTop) { + m_previous.m_viewBottom = m_viewBottom; + m_previous.m_viewTop = m_viewTop; + + emit verticalViewChanged(m_viewBottom, m_viewTop); + } + + QWidget::update(); +} + +void GraphView::resizeEvent(QResizeEvent *) +{ + m_viewHeight = height(); + m_viewHeightMin = m_viewHeight; + m_viewHeightMax = m_viewHeight; + + m_viewTop = m_viewBottom + m_viewHeight; + + update(); +} + +void GraphView::wheelEvent(QWheelEvent *e) +{ + int zoomPercent = 10; + + /* If holding Ctrl key then zoom 2x faster */ + if (QApplication::keyboardModifiers() & Qt::ControlModifier) { + zoomPercent = 20; + } + + /* Zoom view by adjusting width */ + double dt = m_viewWidth; + double size = dt; + size *= -e->delta(); + + /* Zoom deltas normally come in increments of 120 */ + size /= 120 * (100 / zoomPercent); + + m_viewWidth += size; + m_viewWidth = qBound(m_viewWidthMin, m_viewWidth, m_viewWidthMax); + + /* Scroll view to zoom around mouse */ + dt -= m_viewWidth; + dt *= e->x(); + dt /= width(); + + m_viewLeft = dt + m_viewLeft; + m_viewLeft = qBound(m_graphLeft, m_viewLeft, m_graphRight - m_viewWidth); + m_viewRight = m_viewLeft + m_viewWidth; + + update(); +} + +void GraphView::mouseMoveEvent(QMouseEvent *e) +{ + if (e->buttons().testFlag(Qt::LeftButton)) { + /* Horizontal scroll */ + double dvdx = m_viewWidth / (double)width(); + dvdx *= m_mousePressPosition.x() - e->pos().x(); + + m_viewLeft = m_mousePressViewLeft + dvdx; + m_viewLeft = qBound(m_graphLeft, m_viewLeft, m_graphRight - m_viewWidth); + m_viewRight = m_viewLeft + m_viewWidth; + + /* Vertical scroll */ + double dvdy = m_viewHeight / (double)height(); + dvdy *= m_mousePressPosition.y() - e->pos().y(); + + m_viewBottom = m_mousePressViewBottom + dvdy; + m_viewBottom = qBound(m_graphBottom, m_viewBottom, m_graphTop - m_viewHeight); + m_viewTop = m_viewBottom + m_viewHeight; + + update(); + } +} + +void GraphView::mousePressEvent(QMouseEvent *e) +{ + m_mousePressPosition = e->pos(); + m_mousePressViewLeft = m_viewLeft; + m_mousePressViewBottom = m_viewBottom; +} + +void GraphView::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (m_selectionState) { + m_selectionState->type = SelectionState::None; + emit selectionChanged(); + } +} + +void GraphView::setSelectionState(SelectionState* state) +{ + m_selectionState = state; +} + +void GraphView::setHorizontalView(qint64 start, qint64 end) +{ + m_viewLeft = qBound(m_graphLeft, start, m_graphRight - (end - start)); + m_viewRight = qBound(m_graphLeft, end, m_graphRight); + m_viewWidth = m_viewRight - m_viewLeft; + update(); +} + +void GraphView::setVerticalView(qint64 start, qint64 end) +{ + m_viewBottom = qBound(m_graphBottom, start, m_graphTop - (end - start)); + m_viewTop = qBound(m_graphBottom, end, m_graphTop); + m_viewHeight = m_viewTop - m_viewBottom; + update(); +} + +void GraphView::setDefaultView(qint64 min, qint64 max) +{ + m_graphLeft = min; + m_graphRight = max; + m_viewWidth = max - min; + + m_viewWidthMin = 1; + m_viewWidthMax = m_viewWidth; + + m_viewLeft = min; + m_viewRight = max; + + m_viewHeight = height(); + m_viewHeightMin = m_viewHeight; + m_viewHeightMax = m_viewHeight; + + m_viewBottom = 0; + m_viewTop = m_viewHeight; + + m_graphBottom = 0; + m_graphTop = m_viewHeight; + + update(); +} + +#include "graphview.moc" diff --git a/gui/graphing/graphview.h b/gui/graphing/graphview.h new file mode 100644 index 0000000..6b881d4 --- /dev/null +++ b/gui/graphing/graphview.h @@ -0,0 +1,93 @@ +#ifndef GRAPHVIEW_H +#define GRAPHVIEW_H + +#include "graphing.h" + +#include + +/** + * The generic base class for a graph's view, this is the component that + * displays the actual data for the graph. + * + * - Stores the view area within the graph + * - Simple user interaction such as translating and zooming with mouse + * - Selection tracking synchronised with axis + */ +class GraphView : public QWidget { + Q_OBJECT +public: + GraphView(QWidget* parent = 0); + virtual ~GraphView(){} + + virtual void update(); + + virtual void resizeEvent(QResizeEvent *); + + virtual void wheelEvent(QWheelEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + + virtual void setSelectionState(SelectionState* state); + + void setHorizontalView(qint64 start, qint64 end); + void setVerticalView(qint64 start, qint64 end); + +protected: + void setDefaultView(qint64 min, qint64 max); + +signals: + void selectionChanged(); + + void verticalViewChanged(qint64 start, qint64 end); + void verticalRangeChanged(qint64 min, qint64 max); + + void horizontalRangeChanged(qint64 min, qint64 max); + void horizontalViewChanged(qint64 start, qint64 end); + +protected: + /* Viewport area */ + qint64 m_viewLeft; + qint64 m_viewRight; + qint64 m_viewBottom; + qint64 m_viewTop; + + /* Graph limits */ + qint64 m_graphLeft; + qint64 m_graphRight; + qint64 m_graphBottom; + qint64 m_graphTop; + + /* Viewport width (m_viewRight - m_viewLeft), used for zoom */ + qint64 m_viewWidth; + qint64 m_viewWidthMin; + qint64 m_viewWidthMax; + + /* Viewport height (m_viewTop - m_viewBottom), used for zoom */ + qint64 m_viewHeight; + qint64 m_viewHeightMin; + qint64 m_viewHeightMax; + + /* Mouse tracking */ + QPoint m_mousePressPosition; + qint64 m_mousePressViewLeft; + qint64 m_mousePressViewBottom; + + /* Selection */ + SelectionState* m_selectionState; + + /* State from the last update() call */ + struct PreviousUpdate { + qint64 m_viewLeft; + qint64 m_viewRight; + qint64 m_viewBottom; + qint64 m_viewTop; + + qint64 m_graphLeft; + qint64 m_graphRight; + qint64 m_graphBottom; + qint64 m_graphTop; + } m_previous; +}; + +#endif diff --git a/gui/graphing/graphwidget.cpp b/gui/graphing/graphwidget.cpp new file mode 100644 index 0000000..88a86cc --- /dev/null +++ b/gui/graphing/graphwidget.cpp @@ -0,0 +1,583 @@ +#include "graphwidget.h" + +#include + +GraphWidget::GraphWidget(QWidget* parent) : + QWidget(parent), + m_view(NULL), + m_label(NULL), + m_axisTop(NULL), + m_axisLeft(NULL), + m_axisRight(NULL), + m_axisBottom(NULL), + m_horizontalScrollbar(NULL), + m_horizontalMin(0), + m_horizontalMax(0), + m_horizontalStart(0), + m_horizontalEnd(0), + m_horizontalScrollbarPolicy(Qt::ScrollBarAlwaysOff), + m_verticalScrollbar(NULL), + m_verticalMin(0), + m_verticalMax(0), + m_verticalStart(0), + m_verticalEnd(0), + m_verticalScrollbarPolicy(Qt::ScrollBarAlwaysOff) +{ + m_selection.type = SelectionState::None; + + m_verticalScrollbar = new QScrollBar(this); + m_verticalScrollbar->setOrientation(Qt::Vertical); + m_verticalScrollbar->hide(); + m_verticalScrollbar->resize(m_verticalScrollbar->sizeHint()); + + m_horizontalScrollbar = new QScrollBar(this); + m_horizontalScrollbar->setOrientation(Qt::Horizontal); + m_horizontalScrollbar->hide(); + m_horizontalScrollbar->resize(m_horizontalScrollbar->sizeHint()); + + updateLayout(); + setAutoFillBackground(true); +} + + +GraphView* GraphWidget::view() +{ + return m_view; +} + + +GraphLabelWidget* GraphWidget::label() +{ + return m_label; +} + + +GraphAxisWidget* GraphWidget::axis(AxisPosition pos) +{ + switch(pos) { + case AxisTop: + return m_axisTop; + + case AxisLeft: + return m_axisLeft; + + case AxisRight: + return m_axisRight; + + case AxisBottom: + return m_axisBottom; + + default: + return NULL; + } +} + + +void GraphWidget::setView(GraphView* view) +{ + delete m_view; + m_view = view; + + updateConnections(); + + m_view->setSelectionState(&m_selection); + m_view->show(); + m_view->update(); +} + + +void GraphWidget::setLabel(GraphLabelWidget* label) +{ + delete m_label; + m_label = label; +} + + +void GraphWidget::setAxis(AxisPosition pos, GraphAxisWidget* axis) +{ + switch(pos) { + case AxisTop: + delete m_axisTop; + m_axisTop = axis; + m_axisTop->setOrientation(GraphAxisWidget::Horizontal); + m_axisTop->setSelectionState(&m_selection); + break; + + case AxisLeft: + delete m_axisLeft; + m_axisLeft = axis; + m_axisLeft->setOrientation(GraphAxisWidget::Vertical); + m_axisLeft->setSelectionState(&m_selection); + break; + + case AxisRight: + delete m_axisRight; + m_axisRight = axis; + m_axisRight->setOrientation(GraphAxisWidget::Vertical); + m_axisRight->setSelectionState(&m_selection); + break; + + case AxisBottom: + delete m_axisBottom; + m_axisBottom = axis; + m_axisBottom->setOrientation(GraphAxisWidget::Horizontal); + m_axisBottom->setSelectionState(&m_selection); + break; + } + + updateConnections(); + updateSelection(); + axis->show(); +} + + +void GraphWidget::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy) +{ + m_horizontalScrollbarPolicy = policy; + updateScrollbars(); +} + + +void GraphWidget::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) +{ + m_verticalScrollbarPolicy = policy; + updateScrollbars(); +} + + +void GraphWidget::resizeEvent(QResizeEvent *e) +{ + updateLayout(); + update(); +} + + +/* Used if a selection would be shared between graphs with different axis */ +SelectionState GraphWidget::transformSelectionIn(SelectionState state) +{ + return state; +} + + +/* Used if a selection would be shared between graphs with different axis */ +SelectionState GraphWidget::transformSelectionOut(SelectionState state) +{ + return state; +} + + +/* Update the scrollbars based on current view */ +void GraphWidget::updateScrollbars() +{ + /* Vertical scroll bar */ + qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart); + + if (size <= INT_MAX) { + m_verticalScrollbar->setValue(m_verticalStart - m_verticalMin); + m_verticalScrollbar->setPageStep(m_verticalEnd - m_verticalStart); + m_verticalScrollbar->setRange(0, size); + } else { + /* QScrollBar only supports up to INT_MAX values, + * here we must scale our values to match this */ + double curSize = m_verticalEnd - m_verticalStart; + double pages = (m_verticalMax - m_verticalMin) / curSize; + double value = (m_verticalStart - m_verticalMin) / curSize; + + m_verticalScrollbar->setValue(value); + m_verticalScrollbar->setPageStep(1); + m_verticalScrollbar->setRange(0, pages); + } + + /* Adhere to scrollbar policy */ + bool visible = false; + + if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOn) { + visible = true; + } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOff) { + visible = false; + } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAsNeeded) { + visible = m_verticalMin != m_verticalStart || m_verticalMax != m_verticalEnd; + } + + if (visible != m_verticalScrollbar->isVisible()) { + m_verticalScrollbar->setVisible(visible); + updateLayout(); + } + + /* Horizontal scroll bar */ + size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart); + + if (size <= INT_MAX) { + m_horizontalScrollbar->setValue(m_horizontalStart - m_horizontalMin); + m_horizontalScrollbar->setPageStep(m_horizontalEnd - m_horizontalStart); + m_horizontalScrollbar->setRange(0, size); + } else { + /* QScrollBar only supports up to INT_MAX values, + * here we must scale our values to match this */ + double dxdv = INT_MAX / (double)size; + double value = (m_horizontalStart - m_horizontalMin) * dxdv; + double pageStep = (m_horizontalEnd - m_horizontalStart) * dxdv; + + m_horizontalScrollbar->setValue((int)value); + m_horizontalScrollbar->setPageStep((int)pageStep); + m_horizontalScrollbar->setRange(0, INT_MAX); + } + + /* Adhere to scrollbar policy */ + visible = false; + + if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOn) { + visible = true; + } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOff) { + visible = false; + } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAsNeeded) { + visible = m_horizontalMin != m_horizontalStart || m_horizontalMax != m_horizontalEnd; + } + + if (visible != m_horizontalScrollbar->isVisible()) { + m_horizontalScrollbar->setVisible(visible); + updateLayout(); + } +} + + +/* Update all signal / slot connections */ +void GraphWidget::updateConnections() +{ + if (m_view) { + connect(m_view, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection); + + connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), this, SLOT(horizontalViewChange(qint64,qint64)), Qt::UniqueConnection); + connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), this, SLOT(horizontalRangeChange(qint64,qint64)), Qt::UniqueConnection); + + connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), this, SLOT(verticalViewChange(qint64,qint64)), Qt::UniqueConnection); + connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), this, SLOT(verticalRangeChange(qint64,qint64)), Qt::UniqueConnection); + } + + if (m_axisTop) { + if (m_view) { + connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisTop, SLOT(setView(qint64,qint64)), Qt::UniqueConnection); + connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisTop, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection); + } + + connect(m_axisTop, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection); + } + + if (m_axisLeft) { + if (m_view) { + connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisLeft, SLOT(setView(qint64,qint64)), Qt::UniqueConnection); + connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisLeft, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection); + } + + connect(m_axisLeft, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection); + } + + if (m_axisRight) { + if (m_view) { + connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisRight, SLOT(setView(qint64,qint64)), Qt::UniqueConnection); + connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisRight, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection); + } + + connect(m_axisRight, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection); + } + + if (m_axisBottom) { + if (m_view) { + connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisBottom, SLOT(setView(qint64,qint64)), Qt::UniqueConnection); + connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisBottom, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection); + } + + connect(m_axisBottom, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection); + } + + if (m_horizontalScrollbar) { + connect(m_horizontalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(horizontalScrollAction(int))); + } + + if (m_verticalScrollbar) { + connect(m_verticalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(verticalScrollAction(int))); + } +} + + +/* Recalculate the layout */ +void GraphWidget::updateLayout() +{ + int x, y; + int padX = 0, padY = 0; + + if (m_axisTop) { + padY += m_axisTop->height(); + } + + if (m_axisBottom) { + padY += m_axisBottom->height(); + } + + if (m_axisLeft) { + padX += m_axisLeft->width(); + } + + if (m_axisRight) { + padX += m_axisRight->width(); + } + + if (m_horizontalScrollbar->isVisible()) { + padY += m_horizontalScrollbar->height(); + } + + if (m_verticalScrollbar->isVisible()) { + padX += m_verticalScrollbar->width(); + } + + if (m_axisTop) { + x = m_axisLeft ? m_axisLeft->width() : 0; + y = 0; + + m_axisTop->move(x, y); + m_axisTop->resize(width() - padX, m_axisTop->height()); + } + + if (m_axisBottom) { + x = m_axisLeft ? m_axisLeft->width() : 0; + y = height() - m_axisBottom->height(); + + if (m_horizontalScrollbar->isVisible()) { + y -= m_horizontalScrollbar->height(); + } + + m_axisBottom->move(x, y); + m_axisBottom->resize(width() - padX, m_axisBottom->height()); + } + + if (m_axisLeft) { + x = 0; + y = m_axisTop ? m_axisTop->height() : 0; + + m_axisLeft->move(x, y); + m_axisLeft->resize(m_axisLeft->width(), height() - padY); + } + + if (m_axisRight) { + x = width() - m_axisRight->width(); + y = m_axisTop ? m_axisTop->height() : 0; + + if (m_verticalScrollbar->isVisible()) { + x -= m_verticalScrollbar->width(); + } + + m_axisRight->move(x, y); + m_axisRight->resize(m_axisRight->width(), height() - padY); + } + + if (m_view) { + x = m_axisLeft ? m_axisLeft->width() : 0; + y = m_axisTop ? m_axisTop->height() : 0; + + m_view->move(x, y); + m_view->resize(width() - padX, height() - padY); + } + + if (m_label) { + if (m_axisTop && m_axisLeft) { + m_label->move(0, 0); + m_label->resize(m_axisLeft->width(), m_axisTop->height()); + } + } + + if (m_verticalScrollbar) { + m_verticalScrollbar->move(width() - m_verticalScrollbar->width(), 0); + + if (m_horizontalScrollbar) { + m_verticalScrollbar->resize(m_verticalScrollbar->width(), height() - m_horizontalScrollbar->height()); + } else { + m_verticalScrollbar->resize(m_verticalScrollbar->width(), height()); + } + } + + if (m_horizontalScrollbar) { + m_horizontalScrollbar->move(0, height() - m_horizontalScrollbar->height()); + + if (m_verticalScrollbar) { + m_horizontalScrollbar->resize(width() - m_verticalScrollbar->width(), m_horizontalScrollbar->height()); + } else { + m_horizontalScrollbar->resize(width(), m_horizontalScrollbar->height()); + } + } +} + + +void GraphWidget::setSelection(SelectionState state) +{ + m_selection = transformSelectionIn(state); + updateSelection(false); +} + + +void GraphWidget::setHorizontalView(qint64 start, qint64 end) +{ + if (m_view) { + m_view->setHorizontalView(start, end); + } +} + + +void GraphWidget::setVerticalView(qint64 start, qint64 end) +{ + if (m_view) { + m_view->setVerticalView(start, end); + } +} + + +/* Called when the view is translated / zoomed */ +void GraphWidget::verticalViewChange(qint64 start, qint64 end) +{ + m_verticalStart = start; + m_verticalEnd = end; + updateScrollbars(); + + emit verticalViewChanged(start, end); +} + + +void GraphWidget::verticalRangeChange(qint64 start, qint64 end) +{ + m_verticalMin = start; + m_verticalMax = end; + updateScrollbars(); + + emit verticalRangeChanged(start, end); +} + + +void GraphWidget::horizontalViewChange(qint64 start, qint64 end) +{ + m_horizontalStart = start; + m_horizontalEnd = end; + updateScrollbars(); + + emit horizontalViewChanged(start, end); +} + + +void GraphWidget::horizontalRangeChange(qint64 start, qint64 end) +{ + m_horizontalMin = start; + m_horizontalMax = end; + updateScrollbars(); + + emit horizontalRangeChanged(start, end); +} + + +/* User interaction with horizontal scroll bar */ +void GraphWidget::horizontalScrollAction(int /*action*/) +{ + int value = m_horizontalScrollbar->sliderPosition(); + qint64 size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart); + + /* Calculate the new scroll values */ + if (size <= INT_MAX) { + m_horizontalEnd -= m_horizontalStart; + m_horizontalStart = value + m_horizontalMin; + m_horizontalEnd += value; + } else { + /* QScrollBar only supports up to INT_MAX values, here we must scale + * our values to match this */ + double dxdv = INT_MAX / (double)size; + + size = m_horizontalEnd - m_horizontalStart; + m_horizontalStart = value / dxdv + m_horizontalMin; + m_horizontalEnd = m_horizontalStart + size; + } + + /* Update view */ + if (m_view) { + m_view->setHorizontalView(m_horizontalStart, m_horizontalEnd); + } + + /* Update horizontal axes */ + if (m_axisTop) { + m_axisTop->setView(m_horizontalStart, m_horizontalEnd); + } + + if (m_axisBottom) { + m_axisBottom->setView(m_horizontalStart, m_horizontalEnd); + } + + /* Inform the world of our changes! */ + emit horizontalViewChanged(m_horizontalStart, m_horizontalEnd); +} + + +/* User interaction with vertical scroll bar */ +void GraphWidget::verticalScrollAction(int /*action*/) +{ + int value = m_verticalScrollbar->sliderPosition(); + qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart); + + /* Calculate the new scroll values */ + if (size <= INT_MAX) { + m_verticalEnd -= m_verticalStart; + m_verticalStart = value + m_verticalMin; + m_verticalEnd += value; + } else { + /* QScrollBar only supports up to INT_MAX values, here we must scale + * our values to match this */ + double dxdv = INT_MAX / (double)size; + + size = m_verticalEnd - m_verticalStart; + m_verticalStart = value / dxdv + m_verticalMin; + m_verticalEnd = m_verticalStart + size; + } + + /* Update view */ + if (m_view) { + m_view->setVerticalView(m_verticalStart, m_verticalEnd); + } + + /* Update vertical axes */ + if (m_axisLeft) { + m_axisLeft->setView(m_verticalStart, m_verticalEnd); + } + + if (m_axisRight) { + m_axisRight->setView(m_verticalStart, m_verticalEnd); + } + + /* Inform the world of our changes! */ + emit verticalViewChanged(m_verticalStart, m_verticalEnd); +} + + +/* Update child elements when selection changes */ +void GraphWidget::updateSelection(bool emitSignal) +{ + if (m_view) { + m_view->update(); + } + + if (m_axisTop) { + m_axisTop->update(); + } + + if (m_axisLeft) { + m_axisLeft->update(); + } + + if (m_axisRight) { + m_axisRight->update(); + } + + if (m_axisBottom) { + m_axisBottom->update(); + } + + if (emitSignal) { + emit selectionChanged(transformSelectionOut(m_selection)); + } +} + + +#include "graphwidget.moc" diff --git a/gui/graphing/graphwidget.h b/gui/graphing/graphwidget.h new file mode 100644 index 0000000..16b7786 --- /dev/null +++ b/gui/graphing/graphwidget.h @@ -0,0 +1,112 @@ +#ifndef GRAPHWIDGET_H +#define GRAPHWIDGET_H + +#include "graphview.h" +#include "graphaxiswidget.h" +#include "graphlabelwidget.h" + +class QScrollBar; + +/** + * The generic GraphWidget class which combines the elements of a graph, + * the axis, view, scrollbars and label. + */ +class GraphWidget : public QWidget { + Q_OBJECT +public: + enum AxisPosition { + AxisTop, + AxisLeft, + AxisRight, + AxisBottom + }; + +public: + GraphWidget(QWidget* parent = 0); + virtual ~GraphWidget(){} + + GraphView* view(); + GraphLabelWidget* label(); + GraphAxisWidget* axis(AxisPosition pos); + + void setView(GraphView* view); + void setLabel(GraphLabelWidget* label); + void setAxis(AxisPosition pos, GraphAxisWidget* axis); + + void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy); + void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy); + + virtual void resizeEvent(QResizeEvent *e); + +protected: + /* Used if a selection would be shared between graphs with different axis */ + virtual SelectionState transformSelectionIn(SelectionState state); + virtual SelectionState transformSelectionOut(SelectionState state); + + /* Update the scrollbars based on current view */ + void updateScrollbars(); + + /* Update all signal / slot connections */ + void updateConnections(); + + /* Recalculate child widget layout */ + void updateLayout(); + +public slots: + void setSelection(SelectionState state); + + /* Set view areas */ + void setHorizontalView(qint64 start, qint64 end); + void setVerticalView(qint64 start, qint64 end); + +protected slots: + /* View changed by translation / zooming */ + void verticalViewChange(qint64 start, qint64 end); + void verticalRangeChange(qint64 start, qint64 end); + void horizontalViewChange(qint64 start, qint64 end); + void horizontalRangeChange(qint64 start, qint64 end); + + /* User interaction with scroll bars */ + void horizontalScrollAction(int action); + void verticalScrollAction(int action); + + /* Update child elements when selection changes */ + void updateSelection(bool emitSignal = true); + +signals: + void selectionChanged(SelectionState state); + + void verticalViewChanged(qint64 start, qint64 end); + void verticalRangeChanged(qint64 start, qint64 end); + + void horizontalViewChanged(qint64 start, qint64 end); + void horizontalRangeChanged(qint64 start, qint64 end); + +protected: + SelectionState m_selection; + + GraphView* m_view; + + GraphLabelWidget* m_label; + + GraphAxisWidget* m_axisTop; + GraphAxisWidget* m_axisLeft; + GraphAxisWidget* m_axisRight; + GraphAxisWidget* m_axisBottom; + + QScrollBar* m_horizontalScrollbar; + qint64 m_horizontalMin; + qint64 m_horizontalMax; + qint64 m_horizontalStart; + qint64 m_horizontalEnd; + Qt::ScrollBarPolicy m_horizontalScrollbarPolicy; + + QScrollBar* m_verticalScrollbar; + qint64 m_verticalMin; + qint64 m_verticalMax; + qint64 m_verticalStart; + qint64 m_verticalEnd; + Qt::ScrollBarPolicy m_verticalScrollbarPolicy; +}; + +#endif diff --git a/gui/graphing/heatmapverticalaxiswidget.cpp b/gui/graphing/heatmapverticalaxiswidget.cpp new file mode 100644 index 0000000..ae127c0 --- /dev/null +++ b/gui/graphing/heatmapverticalaxiswidget.cpp @@ -0,0 +1,82 @@ +#include "heatmapverticalaxiswidget.h" + +#include +#include +#include + +HeatmapVerticalAxisWidget::HeatmapVerticalAxisWidget(QWidget* parent) : + GraphAxisWidget(parent), + m_data(NULL) +{ + m_rowHeight = 20; +} + +void HeatmapVerticalAxisWidget::setDataProvider(HeatmapDataProvider* data) +{ + delete m_data; + m_data = data; + + m_valueMin = 0; + m_valueMax = (data->dataRows() + data->headerRows()) * m_rowHeight; + + m_valueBegin = m_valueMin; + m_valueEnd = m_valueMax; + + update(); +} + +void HeatmapVerticalAxisWidget::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->pos().y() < m_data->headerRows() * m_rowHeight) { + m_selectionState->type = SelectionState::None; + emit selectionChanged(); + } else { + int row = e->pos().y(); + row -= m_data->headerRows() * m_rowHeight; + row += m_valueBegin; + row /= m_rowHeight; + + if (row >= m_data->dataRows()) { + m_selectionState->type = SelectionState::None; + emit selectionChanged(); + } else { + m_selectionState->type = SelectionState::Vertical; + m_selectionState->start = m_data->dataRowAt(row); + m_selectionState->end = m_selectionState->start; + emit selectionChanged(); + } + } +} + +void HeatmapVerticalAxisWidget::paintEvent(QPaintEvent *) +{ + if (!m_data) { + return; + } + + QPainter painter(this); + painter.setPen(Qt::black); + painter.setBrush(Qt::lightGray); + painter.drawRect(0, 0, width() - 1, height() - 1); + + /* Draw scrollable rows */ + painter.translate(0, m_data->headerRows() * m_rowHeight - m_valueBegin % m_rowHeight); + int rowStart = m_valueBegin / m_rowHeight; + int rowEnd = qMin(qCeil(m_valueEnd / (double)m_rowHeight), m_data->dataRows()); + + for (unsigned i = rowStart; i < rowEnd; ++i) { + painter.drawText(0, 0, width(), m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_data->dataLabel(i)); + painter.drawLine(0, m_rowHeight, width(), m_rowHeight); + painter.translate(0, m_rowHeight); + } + + /* Draw fixed position headers */ + painter.resetTransform(); + painter.drawRect(0, 0, width() - 1, m_data->headerRows() * m_rowHeight); + + for (unsigned i = 0; i < m_data->headerRows(); ++i) { + painter.drawText(0, 0, width(), m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_data->headerLabel(i)); + painter.drawLine(0, m_rowHeight, width(), m_rowHeight); + painter.translate(0, m_rowHeight); + } +} diff --git a/gui/graphing/heatmapverticalaxiswidget.h b/gui/graphing/heatmapverticalaxiswidget.h new file mode 100644 index 0000000..ca64adb --- /dev/null +++ b/gui/graphing/heatmapverticalaxiswidget.h @@ -0,0 +1,24 @@ +#ifndef HEATMAPVERTICALAXISWIDGET_H +#define HEATMAPVERTICALAXISWIDGET_H + +#include "heatmapview.h" +#include "graphaxiswidget.h" + +/** + * Vertical axis specifically for heatmap displaying header and data rows + */ +class HeatmapVerticalAxisWidget : public GraphAxisWidget { +public: + HeatmapVerticalAxisWidget(QWidget* parent); + + void setDataProvider(HeatmapDataProvider* data); + + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void paintEvent(QPaintEvent *); + +protected: + int m_rowHeight; + HeatmapDataProvider* m_data; +}; + +#endif diff --git a/gui/graphing/heatmapview.cpp b/gui/graphing/heatmapview.cpp new file mode 100644 index 0000000..9bb485c --- /dev/null +++ b/gui/graphing/heatmapview.cpp @@ -0,0 +1,276 @@ +#include "heatmapview.h" + +#include +#include +#include +#include + +HeatmapView::HeatmapView(QWidget* parent) : + GraphView(parent), + m_data(NULL) +{ + m_rowHeight = 20; + setMouseTracking(true); +} + + +void HeatmapView::setDataProvider(HeatmapDataProvider* data) +{ + delete m_data; + m_data = data; + + if (m_data) { + m_data->setSelectionState(m_selectionState); + + setDefaultView(m_data->start(), m_data->end()); + + m_viewWidthMin = 1000; + + m_graphBottom = 0; + m_graphTop = (m_data->headerRows() + m_data->dataRows()) * m_rowHeight; + } else { + setDefaultView(0, 0); + m_graphBottom = m_graphTop = 0; + } + + update(); +} + + +void HeatmapView::setSelectionState(SelectionState* state) +{ + if (m_data) { + m_data->setSelectionState(state); + } + + GraphView::setSelectionState(state); +} + + +void HeatmapView::mouseMoveEvent(QMouseEvent *e) +{ + GraphView::mouseMoveEvent(e); + + if (e->buttons() || !m_data) { + QToolTip::hideText(); + return; + } + + qint64 index = itemAtPosition(e->pos()); + + if (index >= 0) { + QToolTip::showText(e->globalPos(), m_data->itemTooltip(index)); + } else { + QToolTip::hideText(); + } +} + + +void HeatmapView::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (m_data && e->button() == Qt::LeftButton) { + qint64 index = itemAtPosition(e->pos()); + + if (index >= 0) { + m_data->itemDoubleClicked(index); + return; + } + } + + GraphView::mouseDoubleClickEvent(e); +} + + +void HeatmapView::paintEvent(QPaintEvent *) +{ + if (!m_data) { + return; + } + + QPainter painter(this); + painter.fillRect(0, m_data->headerRows() * m_rowHeight, width(), height(), Qt::white); + + /* Draw data rows */ + painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight); + int rowStart = m_viewBottom / m_rowHeight; + int rowEnd = qMin(qCeil(m_viewTop / (double)m_rowHeight), m_data->dataRows()); + + for (unsigned i = rowStart; i < rowEnd; ++i) { + HeatmapRowIterator* itr = m_data->dataRowIterator(i, m_viewLeft, m_viewRight, width()); + paintRow(painter, itr); + painter.translate(0, m_rowHeight); + delete itr; + } + + /* Draw Header */ + painter.resetTransform(); + painter.fillRect(0, 0, width(), m_data->headerRows() * m_rowHeight, Qt::white); + + for (unsigned i = 0; i < m_data->headerRows(); ++i) { + HeatmapRowIterator* itr = m_data->headerRowIterator(i, m_viewLeft, m_viewRight, width()); + paintRow(painter, itr); + painter.translate(0, m_rowHeight); + delete itr; + } + + /* Draw Axis Lines */ + painter.resetTransform(); + painter.setPen(Qt::black); + painter.drawLine(0, m_rowHeight, width(), m_rowHeight); + painter.drawLine(0, m_rowHeight * 2, width(), m_rowHeight * 2); + + painter.setPen(QColor(240, 240, 240)); + painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight); + + for (unsigned i = rowStart; i < rowEnd; ++i) { + painter.drawLine(0, m_rowHeight, width(), m_rowHeight); + painter.translate(0, m_rowHeight); + } + + /* Draw selection borders */ + painter.resetTransform(); + painter.setPen(Qt::green); + + if (m_selectionState->type == SelectionState::Horizontal) { + double dxdt = width() / (double)m_viewWidth; + double scroll = m_viewLeft * dxdt; + double left = (m_selectionState->start * dxdt) - scroll; + double right = (m_selectionState->end * dxdt) - scroll; + + /* Highlight time */ + if (left >= 0 && left <= width()) { + painter.drawLine(left, 0, left, height()); + } + + if (right >= 0 && right <= width()) { + painter.drawLine(right, 0, right, height()); + } + } else if (m_selectionState->type == SelectionState::Vertical) { + /* Highlight row */ + int row = m_selectionState->start; + + for (unsigned i = rowStart; i < rowEnd; ++i) { + if (m_data->dataRowAt(i) == row) { + row = i - rowStart; + + painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight); + painter.drawLine(0, (row + 1) * m_rowHeight, width(), (row + 1) * m_rowHeight); + + if (row > 0) { + painter.drawLine(0, row * m_rowHeight, width(), row * m_rowHeight); + } + + break; + } + } + } +} + + +void HeatmapView::paintRow(QPainter& painter, HeatmapRowIterator* itr) +{ + bool selection = m_selectionState ? m_selectionState->type != SelectionState::None : false; + + while (itr->next()) + { + double heat = itr->heat(); + int width = itr->width(); + int x = itr->step(); + + /* Gamma correction */ + heat = qPow(heat, 1.0 / 2.0); + + if (width == 1) { + /* Draw single line */ + if (selection) { + double selectedHeat = itr->selectedHeat(); + + if (selectedHeat >= 0.999) { + heat = 255.0 - qBound(0.0, heat * 255.0, 255.0); + + if (itr->isGpu()) { + painter.setPen(QColor(255, heat, heat)); + } else { + painter.setPen(QColor(heat, heat, 255)); + } + + painter.drawLine(x, 0, x, m_rowHeight - 1); + } else { + heat = 255.0 - qBound(0.0, heat * 100.0, 100.0); + painter.setPen(QColor(heat, heat, heat)); + painter.drawLine(x, 0, x, m_rowHeight - 1); + + if (selectedHeat > 0.001) { + selectedHeat = qPow(selectedHeat, 1.0 / 2.0); + selectedHeat = qBound(0.0, selectedHeat * 255.0, 255.0); + + if (itr->isGpu()) { + painter.setPen(QColor(255, 0, 0, selectedHeat)); + } else { + painter.setPen(QColor(0, 0, 255, selectedHeat)); + } + + painter.drawLine(x, 0, x, m_rowHeight - 1); + } + } + } else { + heat = qBound(0.0, heat * 255.0, 255.0); + + if (itr->isGpu()) { + painter.setPen(QColor(255, 255 - heat, 255 - heat)); + } else { + painter.setPen(QColor(255 - heat, 255 - heat, 255)); + } + + painter.drawLine(x, 0, x, m_rowHeight - 1); + } + } else { + double selectedHeat = itr->selectedHeat(); + + if (selection && selectedHeat < 0.9) { + painter.fillRect(x, 0, width, m_rowHeight, QColor(255 - 100, 255 - 100, 255 - 100)); + } else if (itr->isGpu()) { + painter.fillRect(x, 0, width, m_rowHeight, QColor(255, 0, 0)); + } else { + painter.fillRect(x, 0, width, m_rowHeight, QColor(0, 0, 255)); + } + + if (width > 6) { + painter.setPen(Qt::white); + QString elided = painter.fontMetrics().elidedText(itr->label(), Qt::ElideRight, width - 1); + + painter.drawText(x + 1, 0, width - 1, m_rowHeight - 1, + Qt::AlignLeft | Qt::AlignVCenter, + elided); + } + } + } +} + + +qint64 HeatmapView::itemAtPosition(QPoint pos) +{ + if (!m_data) { + return -1; + } + + double t = m_viewWidth / (double)width(); + t *= pos.x(); + t += m_viewLeft; + + qint64 time = (qint64)t; + qint64 index; + + if (pos.y() < m_data->headerRows() * m_rowHeight) { + int row = pos.y() / m_rowHeight; + index = m_data->headerItemAt(row, time); + } else { + int row = pos.y(); + row -= m_data->headerRows() * m_rowHeight; + row += m_viewBottom; + row /= m_rowHeight; + index = m_data->dataItemAt(row, time); + } + + return index; +} diff --git a/gui/graphing/heatmapview.h b/gui/graphing/heatmapview.h new file mode 100644 index 0000000..781c9a7 --- /dev/null +++ b/gui/graphing/heatmapview.h @@ -0,0 +1,124 @@ +#ifndef HEATMAPVIEW_H +#define HEATMAPVIEW_H + +#include "graphview.h" + +/** + * The heatmap iterator will only return data when there is something to draw, + * this allows much faster access to the data in the case where the view is + * zoomed out to the point of where there is multiple calls in one pixel, + * it automagically calculates the heat for that pixel. + */ +class HeatmapRowIterator { +public: + virtual ~HeatmapRowIterator(){} + + /* Go to the next visible heat map */ + virtual bool next() = 0; + + /* Is the current value GPU or CPU heat */ + virtual bool isGpu() const = 0; + + /* Current step (normally x coordinate) */ + virtual int step() const = 0; + + /* Current width (in steps) */ + virtual int width() const = 0; + + /* Current heat */ + virtual float heat() const = 0; + + /* Heat value for selected calls */ + virtual float selectedHeat() const = 0; + + /* Label only used when there is a single call in this heat */ + virtual QString label() const = 0; +}; + + +/** + * Data provider for the whole heatmap + */ +class HeatmapDataProvider { +public: + virtual ~HeatmapDataProvider(){} + + /* The start and end values (time on x-axis) for the heatmap */ + virtual qint64 start() const = 0; + virtual qint64 end() const = 0; + + /* + * Header rows (fixed at top of heatmap view) + */ + + /* Header row count */ + virtual unsigned headerRows() const = 0; + + /* Label to be used in the vertical axis */ + virtual QString headerLabel(unsigned row) const = 0; + + /* Get identifier (program no) for row */ + virtual qint64 headerRowAt(unsigned row) const = 0; + + /* Get item at row and time */ + virtual qint64 headerItemAt(unsigned row, qint64 time) const = 0; + + /* Get iterator for a row between start and end time for steps */ + virtual HeatmapRowIterator* headerRowIterator(int row, qint64 start, qint64 end, int steps) const = 0; + + /* + * Data rows (scrollable in heatmap view) + */ + + /* Data row count */ + virtual unsigned dataRows() const = 0; + + /* Label to be used in the vertical axis */ + virtual QString dataLabel(unsigned row) const = 0; + + /* Get identifier (program no) for row */ + virtual qint64 dataRowAt(unsigned row) const = 0; + + /* Get item at row and time */ + virtual qint64 dataItemAt(unsigned row, qint64 time) const = 0; + + /* Get iterator for a row between start and end time for steps */ + virtual HeatmapRowIterator* dataRowIterator(int row, qint64 start, qint64 end, int steps) const = 0; + + /* Handle double click on item */ + virtual void itemDoubleClicked(qint64 index) const = 0; + + /* Get mouse over tooltip for item */ + virtual QString itemTooltip(qint64 index) const = 0; + + /* Set the selection */ + virtual void setSelectionState(SelectionState* state) = 0; +}; + + +/** + * A not very generic heatmap for row based data + */ +class HeatmapView : public GraphView { +public: + HeatmapView(QWidget* parent); + + void setDataProvider(HeatmapDataProvider* data); + void setSelectionState(SelectionState* state); + + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + + virtual void paintEvent(QPaintEvent *e); + virtual void paintRow(QPainter& painter, HeatmapRowIterator* itr); + + +protected: + qint64 itemAtPosition(QPoint pos); + +protected: + int m_rowHeight; + HeatmapDataProvider* m_data; +}; + +#endif diff --git a/gui/graphing/histogramview.cpp b/gui/graphing/histogramview.cpp new file mode 100644 index 0000000..0b94577 --- /dev/null +++ b/gui/graphing/histogramview.cpp @@ -0,0 +1,258 @@ +#include "histogramview.h" + +#include +#include +#include +#include +#include +#include + +HistogramView::HistogramView(QWidget* parent) : + GraphView(parent), + m_data(NULL) +{ + setMouseTracking(true); + + m_gradientUnselected.setColorAt(0.9, QColor(200, 200, 200)); + m_gradientUnselected.setColorAt(0.0, QColor(220, 220, 220)); + + m_gradientSelected.setColorAt(0.9, QColor(0, 0, 210)); + m_gradientSelected.setColorAt(0.0, QColor(130, 130, 255)); +} + + +void HistogramView::setDataProvider(GraphDataProvider* data) +{ + delete m_data; + m_data = data; + + if (m_data) { + m_data->setSelectionState(m_selectionState); + setDefaultView(0, m_data->size()); + m_viewWidthMin = 10; + } else { + setDefaultView(0, 0); + } +} + + +void HistogramView::setSelectionState(SelectionState* state) +{ + if (m_data) { + m_data->setSelectionState(state); + } + + GraphView::setSelectionState(state); +} + + +void HistogramView::setSelectedGradient(const QLinearGradient& gradient) +{ + m_gradientSelected = gradient; +} + + +void HistogramView::setUnelectedGradient(const QLinearGradient& gradient) +{ + m_gradientUnselected = gradient; +} + + +void HistogramView::mouseMoveEvent(QMouseEvent *e) +{ + GraphView::mouseMoveEvent(e); + + if (e->buttons() || !m_data) { + QToolTip::hideText(); + return; + } + + qint64 index = itemAtPosition(e->pos()); + qint64 time = valueAtPosition(e->pos()); + + if (m_data->value(index) >= time) { + QToolTip::showText(e->globalPos(), m_data->itemTooltip(index)); + } else { + QToolTip::hideText(); + } +} + + +void HistogramView::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + qint64 index = itemAtPosition(e->pos()); + qint64 time = valueAtPosition(e->pos()); + + if (m_data->value(index) >= time) { + m_data->itemDoubleClicked(index); + return; + } + } + + GraphView::mouseDoubleClickEvent(e); +} + + +void HistogramView::update() +{ + m_graphBottom = 0; + m_graphTop = 0; + + if (m_data) { + for (qint64 i = m_viewLeft; i < m_viewRight; ++i) { + qint64 value = m_data->value(i); + + if (value > m_graphTop) { + m_graphTop = value; + } + } + } + + GraphView::update(); +} + + +void HistogramView::resizeEvent(QResizeEvent *) +{ + m_gradientSelected.setStart(0, height()); + m_gradientUnselected.setStart(0, height()); +} + + +/* Draw the histogram + * + * When the view is zoomed such that there is more than one item occupying a single pixel + * the one with the highest value will be displayed. + */ +void HistogramView::paintEvent(QPaintEvent *) +{ + if (!m_data) { + return; + } + + QBrush selectedBrush = QBrush(m_gradientSelected); + QPen selectedPen = QPen(selectedBrush, 1); + + QBrush unselectedBrush = QBrush(m_gradientUnselected); + QPen unselectedPen = QPen(unselectedBrush, 1); + + QPainter painter(this); + painter.fillRect(0, 0, width(), height(), Qt::white); + + double dydv = height() / (double)m_graphTop; + double dxdv = width() / (double)(m_viewRight - m_viewLeft); + bool selection = m_selectionState && m_selectionState->type != SelectionState::None; + + if (dxdv < 1.0) { + /* Less than one pixel per item */ + qint64 longestValue = m_graphBottom; + qint64 longestSelected = m_graphBottom; + int lastX = 0; + double x = 0; + + if (selection) { + painter.setPen(unselectedPen); + } else { + painter.setPen(selectedPen); + } + + for (qint64 i = m_viewLeft; i < m_viewRight; ++i) { + qint64 value = m_data->value(i); + int ix; + + if (value > longestValue) { + longestValue = value; + } + + if (selection && m_data->selected(i) && value > longestSelected) { + longestSelected = value; + } + + x += dxdv; + ix = (int)x; + + if (lastX != ix) { + painter.drawLine(lastX, height(), lastX, height() - (longestValue * dydv)); + + if (selection && longestSelected > m_graphBottom) { + painter.setPen(selectedPen); + painter.drawLine(lastX, height(), lastX, height() - (longestSelected * dydv)); + painter.setPen(unselectedPen); + longestSelected = m_graphBottom; + } + + longestValue = m_graphBottom; + lastX = ix; + } + } + } else { + /* Draw rectangles for graph */ + double x = 0; + + for (qint64 i = m_viewLeft; i < m_viewRight; ++i, x += dxdv) { + qint64 value = m_data->value(i); + int y = qMax(1, value * dydv); + + if (!selection || m_data->selected(i)) { + painter.fillRect(x, height() - y, dxdv, y, selectedBrush); + } else { + painter.fillRect(x, height() - y, dxdv, y, unselectedBrush); + } + } + } + + /* Draw the borders for the selection */ + if (m_selectionState && m_selectionState->type == SelectionState::Horizontal) { + double dxdt = width() / (double)m_viewWidth; + double scroll = m_viewLeft * dxdt; + double left = (m_selectionState->start * dxdt) - scroll; + double right = (m_selectionState->end * dxdt) - scroll; + + painter.setPen(Qt::green); + + if (left >= 0 && left <= width()) { + painter.drawLine(left, 0, left, height()); + } + + if (right >= 0 && right <= width()) { + painter.drawLine(right, 0, right, height()); + } + } +} + + +/* Find the item with the highest value at pos.x() +/- 1, + * the mouse must be within the bar height-wise. + */ +qint64 HistogramView::itemAtPosition(QPoint pos) { + double dvdx = m_viewWidth / (double)width(); + + qint64 left = qFloor(dvdx) * (pos.x() - 1) + m_viewLeft; + qint64 right = qCeil(dvdx) * (pos.x() + 1) + m_viewLeft; + + qint64 longestIndex = 0; + qint64 longestValue = 0; + + left = qBound(0, left, m_data->size() - 1); + right = qBound(0, right, m_data->size() - 1); + + for (qint64 i = left; i <= right; ++i) { + if (m_data->value(i) > longestValue) { + longestValue = m_data->value(i); + longestIndex = i; + } + } + + return longestIndex; +} + + +/* Return the value at position */ +qint64 HistogramView::valueAtPosition(QPoint pos) { + double value = m_graphTop / (double)height(); + value *= height() - pos.y(); + value += m_graphBottom; + return (qint64)value; +} + diff --git a/gui/graphing/histogramview.h b/gui/graphing/histogramview.h new file mode 100644 index 0000000..563d664 --- /dev/null +++ b/gui/graphing/histogramview.h @@ -0,0 +1,41 @@ +#ifndef HISTOGRAMVIEW_H +#define HISTOGRAMVIEW_H + +#include "graphview.h" + +/** + * Histogram graph view. + * + * When the view is zoomed such that there is more than one item occupying + * a single pixel the one with the highest value will be displayed. + */ +class HistogramView : public GraphView { +public: + HistogramView(QWidget* parent); + + void setDataProvider(GraphDataProvider* data); + void setSelectionState(SelectionState* state); + + /* Gradient colours for selected and unselected items */ + void setSelectedGradient(const QLinearGradient& gradient); + void setUnelectedGradient(const QLinearGradient& gradient); + + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + + virtual void update(); + virtual void resizeEvent(QResizeEvent *e); + virtual void paintEvent(QPaintEvent *e); + +protected: + qint64 itemAtPosition(QPoint pos); + qint64 valueAtPosition(QPoint pos); + +protected: + QLinearGradient m_gradientSelected; + QLinearGradient m_gradientUnselected; + + GraphDataProvider* m_data; +}; + +#endif diff --git a/gui/graphing/timeaxiswidget.cpp b/gui/graphing/timeaxiswidget.cpp new file mode 100644 index 0000000..cebabf7 --- /dev/null +++ b/gui/graphing/timeaxiswidget.cpp @@ -0,0 +1,80 @@ +#include "timeaxiswidget.h" +#include "profiling.h" + +#include +#include + +TimeAxisWidget::TimeAxisWidget(QWidget* parent) : + GraphAxisWidget(parent) +{ +} + +void TimeAxisWidget::paintEvent(QPaintEvent *) +{ + /* TODO: Support selections? */ + /* TODO: Vertical scrolling? */ + + QPainter painter(this); + painter.setPen(Qt::black); + painter.setBrush(Qt::lightGray); + painter.drawRect(0, 0, width() - 1, height() - 1); + + if (m_orientation == GraphAxisWidget::Vertical) { + int axisHeight = height() - 1; + int fontHeight = painter.fontMetrics().height(); + int ticks = axisHeight / (fontHeight * 2); + + double range = m_valueMax - m_valueMin; + double step = range / (double)ticks; + double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0))); + step = qFloor((step / step10) * 2) * (step10 / 2); + + if (step <= 0) { + return; + } + + painter.resetTransform(); + + for (double tick = 0; tick <= range; tick += step) { + int y = axisHeight - ((tick / range) * axisHeight); + + painter.drawLine(width() - 8, y, width(), y); + + painter.drawText(0, + qBound(0, y - fontHeight / 2, axisHeight - fontHeight), + width() - 10, + fontHeight, + Qt::AlignRight | Qt::AlignVCenter, + Profiling::getTimeString(tick, m_valueMax)); + } + } else { + int axisWidth = width() - 1; + int fontWidth = painter.fontMetrics().width("0.000 ns"); + int fontHeight= painter.fontMetrics().height(); + int ticks = axisWidth / (fontWidth * 2); + + double range = m_valueMax - m_valueMin; + double step = range / (double)ticks; + double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0))); + step = qFloor((step / step10) * 2) * (step10 / 2); + + if (step <= 0) { + return; + } + + painter.resetTransform(); + + for (double tick = 0; tick <= range; tick += step) { + int x = (tick / range) * axisWidth; + + painter.drawLine(x, 0, x, 8); + + painter.drawText(qBound(0, x - fontWidth / 2, axisWidth - fontWidth), + 8, + fontWidth, + fontHeight, + Qt::AlignHCenter | Qt::AlignTop, + Profiling::getTimeString(tick, m_valueMax)); + } + } +} diff --git a/gui/graphing/timeaxiswidget.h b/gui/graphing/timeaxiswidget.h new file mode 100644 index 0000000..836651c --- /dev/null +++ b/gui/graphing/timeaxiswidget.h @@ -0,0 +1,16 @@ +#ifndef TIMEAXISWIDGET_H +#define TIMEAXISWIDGET_H + +#include "graphaxiswidget.h" + +/** + * A simple axis that draws text using getTimeString to nicely format time values + */ +class TimeAxisWidget : public GraphAxisWidget { +public: + TimeAxisWidget(QWidget* parent = 0); + + virtual void paintEvent(QPaintEvent *e); +}; + +#endif diff --git a/gui/graphwidget.cpp b/gui/graphwidget.cpp deleted file mode 100644 index ea0f6a7..0000000 --- a/gui/graphwidget.cpp +++ /dev/null @@ -1,666 +0,0 @@ -#include "graphwidget.h" -#include "timelinewidget.h" -#include "profiledialog.h" - -#include -#include -#include -#include -#include -#include - -typedef trace::Profile::Call Call; -typedef trace::Profile::Frame Frame; -typedef trace::Profile::Program Program; - -GraphWidget::GraphWidget(QWidget *parent) - : QWidget(parent), - m_profile(0), - m_axisWidth(80), - m_axisHeight(30), - m_axisForeground(Qt::black), - m_axisBackground(Qt::lightGray) -{ - m_selection.type = SelectNone; - - m_graphGradientGpu.setColorAt(0.9, QColor(210, 0, 0)); - m_graphGradientGpu.setColorAt(0.0, QColor(255, 130, 130)); - - m_graphGradientCpu.setColorAt(0.9, QColor(0, 0, 210)); - m_graphGradientCpu.setColorAt(0.0, QColor(130, 130, 255)); - - m_graphGradientDeselected.setColorAt(0.9, QColor(200, 200, 200)); - m_graphGradientDeselected.setColorAt(0.0, QColor(220, 220, 220)); - - setMouseTracking(true); - setAutoFillBackground(true); - setBackgroundRole(QPalette::Base); -} - - -/** - * Setup graph data from profile results - */ -void GraphWidget::setProfile(trace::Profile* profile, GraphType type) -{ - m_type = type; - m_profile = profile; - m_timeMax = 0; - - /* Find longest call to use as y axis */ - if (m_type == GraphGpu) { - for (std::vector::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) { - const Call& call = *itr; - - if (call.gpuDuration > m_timeMax) { - m_timeMax = call.gpuDuration; - } - } - } else { - for (std::vector::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) { - const Call& call = *itr; - - if (call.cpuDuration > m_timeMax) { - m_timeMax = call.cpuDuration; - } - } - } - - m_callMin = 0; - m_callMax = m_profile->calls.size(); - - m_callWidthMin = 10; - m_callWidthMax = m_callMax - m_callMin; - - m_call = m_callMin; - m_callWidth = m_callWidthMax; - - selectNone(); - update(); -} - - -/** - * Set selection to nothing - */ -void GraphWidget::selectNone(bool notify) -{ - m_selection.type = SelectNone; - - if (notify) { - emit selectedNone(); - } - - update(); -} - - -/** - * Set selection to a time period - */ -void GraphWidget::selectTime(int64_t start, int64_t end, bool notify) -{ - m_selection.timeStart = start; - m_selection.timeEnd = end; - m_selection.type = (start == end) ? SelectNone : SelectTime; - - if (notify) { - emit selectedTime(start, end); - } - - update(); -} - - -/** - * Set selection to a program - */ -void GraphWidget::selectProgram(unsigned program, bool notify) -{ - m_selection.program = program; - m_selection.type = SelectProgram; - - if (notify) { - emit selectedProgram(program); - } - - update(); -} - - -/** - * Slot to synchronise with other graph views - */ -void GraphWidget::changeView(int call, int width) -{ - m_call = call; - m_callWidth = width; - update(); -} - - -/** - * Calculate the maxTime variable when model is updated - */ -void GraphWidget::update() -{ - m_timeMax = 0; - - if (m_type == GraphGpu) { - for (int i = m_call; i < m_call + m_callWidth; ++i) { - const Call& call = m_profile->calls[i]; - - if (call.gpuDuration > m_timeMax) { - m_timeMax = call.gpuDuration; - } - } - } else { - for (int i = m_call; i < m_call + m_callWidth; ++i) { - const Call& call = m_profile->calls[i]; - - if (call.cpuDuration > m_timeMax) { - m_timeMax = call.cpuDuration; - } - } - } - - QWidget::update(); -} - - -/** - * Find the call at (x, y) position - */ -const Call* GraphWidget::callAtPosition(const QPoint& pos) -{ - int left, right, size; - int64_t time; - - if (!m_profile) { - return NULL; - } - - int posX = qMax(0, pos.x() - m_axisWidth); - int posY = qMax(0, pos.y() - m_axisHeight); - - time = ((m_graphHeight - posY) / (double)m_graphHeight) * m_timeMax; - time -= (2 * m_timeMax) / m_graphHeight; - - size = m_callWidth / (double)m_graphWidth; - - left = m_call + (posX / (double)m_graphWidth) * m_callWidth; - left = qMax(m_callMin, left - size); - - right = qMin(m_callMax - 1, left + size * 2); - - if (m_type == GraphGpu) { - const Call* longest = NULL; - - for (int i = left; i <= right; ++i) { - const Call& call = m_profile->calls[i]; - - if (call.pixels >= 0) { - if (!longest || call.gpuDuration > longest->gpuDuration) { - longest = &call; - } - } - } - - if (longest && time < longest->gpuDuration) { - return longest; - } - } else { - const Call* longest = NULL; - - for (int i = left; i <= right; ++i) { - const Call& call = m_profile->calls[i]; - - if (!longest || call.cpuDuration > longest->cpuDuration) { - longest = &call; - } - } - - if (longest && time < longest->cpuDuration) { - return longest; - } - } - - return NULL; -} - - -void GraphWidget::mousePressEvent(QMouseEvent *e) -{ - if (e->button() == Qt::LeftButton) { - m_mousePressPosition = e->pos(); - m_mousePressCall = m_call; - } -} - - -void GraphWidget::mouseReleaseEvent(QMouseEvent *e) -{ - if (!m_profile) { - return; - } - - if (e->button() == Qt::LeftButton) { - int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y()); - - if (dxy <= 2) { - int x = qMax(m_axisWidth, e->pos().x()); - double dcdx = m_callWidth / (double)m_graphWidth; - - int call = m_mousePressCall + dcdx * (x - m_axisWidth); - - int64_t start = m_profile->calls[call].cpuStart; - int64_t end = m_profile->calls[call].cpuStart + m_profile->calls[call].cpuDuration; - - if (start < m_selection.timeStart || end > m_selection.timeEnd) { - selectNone(true); - } - } - } -} - - -void GraphWidget::mouseDoubleClickEvent(QMouseEvent *e) -{ - const Call* call = callAtPosition(e->pos()); - - if (call) { - emit jumpToCall(call->no); - } -} - - -void GraphWidget::mouseMoveEvent(QMouseEvent *e) -{ - if (!m_profile) { - return; - } - - if (e->buttons().testFlag(Qt::LeftButton)) { - double dcdx = m_callWidth / (double)m_graphWidth; - - if (m_mousePressPosition.y() > m_axisHeight) { - dcdx *= m_mousePressPosition.x() - e->pos().x(); - - /* Horizontal scroll */ - m_call = m_mousePressCall + dcdx; - m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth); - - emit viewChanged(m_call, m_callWidth); - update(); - } else { - int x = qMax(m_axisWidth, e->pos().x()); - - int down = m_mousePressCall + dcdx * (m_mousePressPosition.x() - m_axisWidth); - int up = m_mousePressCall + dcdx * (x - m_axisWidth); - - int left = qMax(qMin(down, up), 0); - int right = qMin(qMax(down, up), m_profile->calls.size() - 1); - - selectTime(m_profile->calls[left].cpuStart, m_profile->calls[right].cpuStart + m_profile->calls[right].cpuDuration, true); - } - } - - const Call* call = callAtPosition(e->pos()); - - if (e->button() == Qt::NoButton && call) { - QString text; - text = QString::fromStdString(call->name); - text += QString("\nCall: %1").arg(call->no); - text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration)); - - if (call->pixels >= 0) { - text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration)); - text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels)); - text += QString("\nProgram: %1").arg(call->program); - } - - QToolTip::showText(e->globalPos(), text); - } else { - QToolTip::hideText(); - } -} - - -void GraphWidget::wheelEvent(QWheelEvent *e) -{ - if (!m_profile) { - return; - } - - if (e->pos().x() < m_axisWidth) { - return; - } - - int zoomPercent = 10; - - /* If holding Ctrl key then zoom 2x faster */ - if (QApplication::keyboardModifiers() & Qt::ControlModifier) { - zoomPercent = 20; - } - - /* Zoom view by adjusting width */ - double dt = m_callWidth; - double size = dt * -e->delta(); - - /* Zoom deltas normally come in increments of 120 */ - size /= 120 * (100 / zoomPercent); - - m_callWidth += size; - m_callWidth = qBound(m_callWidthMin, m_callWidth, m_callWidthMax); - - /* Scroll view to zoom around mouse */ - dt -= m_callWidth; - dt *= e->x() - m_axisWidth; - dt /= m_graphWidth; - - m_call = dt + m_call; - m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth); - - emit viewChanged(m_call, m_callWidth); - update(); -} - - -void GraphWidget::resizeEvent(QResizeEvent *e) -{ - m_graphWidth = qMax(0, width() - m_axisWidth); - m_graphHeight = qMax(0, height() - m_axisHeight); - - m_graphGradientGpu.setStart(0, m_graphHeight); - m_graphGradientCpu.setStart(0, m_graphHeight); - m_graphGradientDeselected.setStart(0, m_graphHeight); -} - - -/** - * Draw the vertical axis of time - */ -void GraphWidget::paintVerticalAxis(QPainter& painter) -{ - int height = painter.fontMetrics().height(); - int ticks = m_graphHeight / (height * 2); - - double step = m_timeMax / (double)ticks; - double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0))); - step = qFloor((step / step10) * 2) * (step10 / 2); - - if (step <= 0) { - return; - } - - painter.resetTransform(); - painter.translate(0, m_axisHeight); - painter.setPen(m_axisForeground); - - for (double tick = 0; tick <= m_timeMax; tick += step) { - int y = m_graphHeight - ((tick / m_timeMax) * m_graphHeight); - - painter.drawLine(m_axisWidth - 8, y, m_axisWidth - 1, y); - - painter.drawText(0, - qBound(0, y - height / 2, m_graphHeight - height), - m_axisWidth - 10, - height, - Qt::AlignRight | Qt::AlignVCenter, - getTimeString(tick, m_timeMax)); - } -} - - -/** - * Draw horizontal axis of frame numbers - */ -void GraphWidget::paintHorizontalAxis(QPainter& painter) -{ - double dxdc = m_graphWidth / (double)m_callWidth; - double scroll = dxdc * m_call; - int lastLabel = -9999; - - painter.resetTransform(); - painter.fillRect(0, 0, width(), m_axisHeight, m_axisBackground); - painter.fillRect(0, 0, m_axisWidth, height(), m_axisBackground); - - painter.setPen(m_axisForeground); - painter.drawLine(0, m_axisHeight - 1, width(), m_axisHeight - 1); - painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, height()); - - painter.translate(m_axisWidth, 0); - - for (std::vector::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) { - static const int padding = 4; - const Frame& frame = *itr; - bool draw = true; - int width; - - if (frame.calls.begin > m_call + m_callWidth) { - break; - } - - if (frame.calls.end < m_call) { - draw = false; - } - - double left = dxdc * frame.calls.begin; - double right = dxdc * frame.calls.end; - QString text = QString("%1").arg(frame.no); - - width = painter.fontMetrics().width(text) + padding * 2; - - if (left + width > scroll) - draw = true; - - /* Draw a frame number if we have space since the last one */ - if (left - lastLabel > width) { - lastLabel = left + width; - - if (draw) { - int textX; - - if (left < scroll && right - left > width) { - if (right - scroll > width) { - textX = 0; - } else { - textX = right - scroll - width; - } - } else { - textX = left - scroll; - } - - /* Draw frame number and major ruler marking */ - painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text); - painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1); - } - } else if (draw) { - /* Draw a minor ruler marking */ - painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1); - } - } -} - - -void GraphWidget::paintEvent(QPaintEvent *e) -{ - if (!m_profile) { - return; - } - - QPainter painter(this); - QBrush deselectBrush; - QPen deselectPen; - QBrush brush; - QPen pen; - - /* Draw axes */ - paintHorizontalAxis(painter); - paintVerticalAxis(painter); - - /* Draw the label */ - painter.resetTransform(); - painter.fillRect(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::lightGray); - - if (m_type == GraphGpu) { - painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "GPU"); - } else { - painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "CPU"); - } - - /* Draw graph */ - deselectBrush = QBrush(m_graphGradientDeselected); - - if (m_type == GraphGpu) { - brush = QBrush(m_graphGradientGpu); - } else { - brush = QBrush(m_graphGradientCpu); - } - - pen = QPen(brush, 1); - deselectPen = QPen(deselectBrush, 1); - - painter.setBrush(brush); - painter.setPen(pen); - painter.translate(m_axisWidth, m_axisHeight); - - double x = 0; - double dydt = m_graphHeight / (double)m_timeMax; - double dxdc = m_graphWidth / (double)m_callWidth; - - int selectLeft = m_call + m_callWidth; - int selectRight = -1; - - if (m_selection.type == SelectProgram) { - painter.setPen(deselectPen); - } - - if (dxdc < 1.0) { - /* Draw the longest call in a pixel */ - int64_t selectedLongest = 0; - int64_t pixelLongest = 0; - int lastX = 0; - - for (int i = m_call; i < m_call + m_callWidth; ++i) { - const Call& call = m_profile->calls[i]; - int ix; - - if (m_type == GraphGpu) { - if (call.gpuDuration > pixelLongest) { - pixelLongest = call.gpuDuration; - } - - if (m_selection.type == SelectProgram && call.program == m_selection.program) { - if (call.gpuDuration > selectedLongest) { - selectedLongest = call.gpuDuration; - } - } - } else { - if (call.cpuDuration > pixelLongest) { - pixelLongest = call.cpuDuration; - } - - if (m_selection.type == SelectProgram && call.program == m_selection.program) { - if (call.cpuDuration > selectedLongest) { - selectedLongest = call.cpuDuration; - } - } - } - - x += dxdc; - ix = (int)x; - - if (lastX != ix) { - if (m_selection.type == SelectTime) { - if (call.cpuStart < m_selection.timeStart || call.cpuStart > m_selection.timeEnd) { - painter.setPen(deselectPen); - } else { - if (ix < selectLeft) { - selectLeft = ix; - } - - if (ix > selectRight) { - selectRight = ix; - } - - painter.setPen(pen); - } - } - - painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (pixelLongest * dydt)); - pixelLongest = 0; - - if (selectedLongest > 0) { - painter.setPen(pen); - painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (selectedLongest * dydt)); - painter.setPen(deselectPen); - selectedLongest = 0; - } - - lastX = ix; - } - } - } else { - /* Draw rectangles for graph */ - for (int i = m_call; i < m_call + m_callWidth; ++i, x += dxdc) { - const Call& call = m_profile->calls[i]; - int y; - - if (m_type == GraphGpu) { - y = qMax(1, call.gpuDuration * dydt); - } else { - y = qMax(1, call.cpuDuration * dydt); - } - - if (m_selection.type == SelectTime) { - if (call.cpuStart < m_selection.timeStart || call.cpuStart > m_selection.timeEnd) { - if (m_type == GraphCpu || call.pixels >= 0) { - painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), deselectBrush); - } - - continue; - } else { - if (x < selectLeft) { - selectLeft = x; - } - - if (x + dxdc > selectRight) { - selectRight = x + dxdc; - } - } - } - - if (m_type == GraphCpu || call.pixels >= 0) { - if (m_selection.type == SelectProgram && call.program != m_selection.program) { - painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), deselectBrush); - } else { - painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), brush); - } - } - } - } - - /* Draw the selection borders */ - if (m_selection.type == SelectTime && selectLeft < selectRight) { - selectLeft += m_axisWidth; - selectRight += m_axisWidth; - - painter.resetTransform(); - painter.setPen(Qt::green); - - if (m_profile->calls[m_call].cpuStart <= m_selection.timeStart) { - painter.drawLine(selectLeft, 0, selectLeft, height()); - } - - if (m_profile->calls[m_call + m_callWidth - 1].cpuStart >= m_selection.timeEnd) { - painter.drawLine(selectRight, 0, selectRight, height()); - } - - selectLeft = qBound(m_axisWidth, selectLeft, width()); - selectRight = qBound(m_axisWidth, selectRight, width()); - painter.drawLine(selectLeft, m_axisHeight - 1, selectRight, m_axisHeight - 1); - } -} - -#include "graphwidget.moc" diff --git a/gui/graphwidget.h b/gui/graphwidget.h deleted file mode 100644 index 7ab17c6..0000000 --- a/gui/graphwidget.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef GRAPHWIDGET_H -#define GRAPHWIDGET_H - -#include -#include -#include -#include -#include "trace_profiler.hpp" - -enum GraphType { - GraphGpu, - GraphCpu -}; - -enum SelectType { - SelectNone, - SelectTime, - SelectProgram -}; - -class GraphWidget : public QWidget -{ - Q_OBJECT - -public: - GraphWidget(QWidget *parent = 0); - - void setProfile(trace::Profile* profile, GraphType type); - const trace::Profile::Call* callAtPosition(const QPoint& pos); - - void selectNone(bool notify = false); - void selectTime(int64_t start, int64_t end, bool notify = false); - void selectProgram(unsigned program, bool notify = false); - -protected: - virtual void paintEvent(QPaintEvent *e); - virtual void resizeEvent(QResizeEvent *e); - - virtual void wheelEvent(QWheelEvent *e); - virtual void mouseMoveEvent(QMouseEvent *e); - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - virtual void mouseDoubleClickEvent(QMouseEvent *e); - -signals: - void jumpToCall(int no); - void viewChanged(int call, int width); - - void selectedNone(); - void selectedProgram(unsigned program); - void selectedTime(int64_t start, int64_t end); - -public slots: - void changeView(int call, int width); - -private: - void update(); - - void paintVerticalAxis(QPainter& painter); - void paintHorizontalAxis(QPainter& painter); - -private: - /* Data */ - trace::Profile* m_profile; - GraphType m_type; - - /* Vertical Axis */ - int64_t m_timeMax; - - /* Horizontal axis */ - int m_call; - int m_callMin; - int m_callMax; - int m_callWidth; - int m_callWidthMin; - int m_callWidthMax; - - /* Viewport */ - int m_graphWidth; - int m_graphHeight; - - /* Mouse track data */ - int m_mousePressCall; - QPoint m_mousePressPosition; - - /* Style */ - int m_axisWidth; - int m_axisHeight; - QPen m_axisForeground; - QBrush m_axisBackground; - QLinearGradient m_graphGradientGpu; - QLinearGradient m_graphGradientCpu; - QLinearGradient m_graphGradientDeselected; - - struct { - SelectType type; - - unsigned program; - - int64_t timeStart; - int64_t timeEnd; - } m_selection; -}; - -#endif // GRAPHWIDGET_H diff --git a/gui/profiledialog.cpp b/gui/profiledialog.cpp index f330203..a9892cf 100644 --- a/gui/profiledialog.cpp +++ b/gui/profiledialog.cpp @@ -2,210 +2,264 @@ #include "profiletablemodel.h" #include -ProfileDialog::ProfileDialog(QWidget *parent) - : QDialog(parent), - m_profile(0) -{ - setupUi(this); - - connect(m_timeline, SIGNAL(jumpToCall(int)), SIGNAL(jumpToCall(int))); - connect(m_gpuGraph, SIGNAL(jumpToCall(int)), SIGNAL(jumpToCall(int))); - connect(m_cpuGraph, SIGNAL(jumpToCall(int)), SIGNAL(jumpToCall(int))); -} +#include "graphing/histogramview.h" +#include "graphing/timeaxiswidget.h" +#include "graphing/frameaxiswidget.h" +#include "graphing/heatmapverticalaxiswidget.h" +#include "profileheatmap.h" +/* Handy function to allow selection of a call in main window */ +ProfileDialog* g_profileDialog = 0; -ProfileDialog::~ProfileDialog() +void Profiling::jumpToCall(int index) { - delete m_profile; + if (g_profileDialog) { + g_profileDialog->showCall(index); + } } +/* Provides frame numbers based off call index */ +class FrameCallDataProvider : public FrameDataProvider { +public: + FrameCallDataProvider(const trace::Profile* profile) + { + m_profile = profile; + } -void ProfileDialog::tableDoubleClicked(const QModelIndex& index) -{ - ProfileTableModel* model = (ProfileTableModel*)m_table->model(); - const trace::Profile::Call* call = model->getJumpCall(index); + unsigned size() const { + return m_profile->frames.size(); + } - if (call) { - emit jumpToCall(call->no); - } else { - unsigned program = model->getProgram(index); - m_timeline->selectProgram(program); - m_cpuGraph->selectProgram(program); - m_gpuGraph->selectProgram(program); + qint64 frameStart(unsigned index) const { + return m_profile->frames[index].calls.begin; } -} + qint64 frameEnd(unsigned index) const { + return m_profile->frames[index].calls.end; + } -void ProfileDialog::setProfile(trace::Profile* profile) -{ - delete m_profile; +private: + const trace::Profile* m_profile; +}; - if (profile->frames.size() == 0) { - m_profile = NULL; - } else { +/* Provides frame numbers based off time */ +class FrameTimeDataProvider : public FrameDataProvider { +public: + FrameTimeDataProvider(const trace::Profile* profile) + { m_profile = profile; - m_timeline->setProfile(m_profile); - m_gpuGraph->setProfile(m_profile, GraphGpu); - m_cpuGraph->setProfile(m_profile, GraphCpu); + } - ProfileTableModel* model = new ProfileTableModel(m_table); - model->setProfile(m_profile); + unsigned size() const { + return m_profile->frames.size(); + } - delete m_table->model(); - m_table->setModel(model); - m_table->update(QModelIndex()); - m_table->sortByColumn(1, Qt::DescendingOrder); - m_table->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents); - m_table->resizeColumnsToContents(); + qint64 frameStart(unsigned index) const { + return m_profile->frames[index].cpuStart; } -} + qint64 frameEnd(unsigned index) const { + return m_profile->frames[index].cpuStart + m_profile->frames[index].cpuDuration; + } + +private: + const trace::Profile* m_profile; +}; -void ProfileDialog::selectNone() +ProfileDialog::ProfileDialog(QWidget *parent) + : QDialog(parent), + m_profile(0) { - QObject* src = QObject::sender(); + setupUi(this); + g_profileDialog = this; - /* Update table model */ - ProfileTableModel* model = (ProfileTableModel*)m_table->model(); - model->selectNone(); - m_table->reset(); + /* Gradients for call duration histograms */ + QLinearGradient cpuGradient; + cpuGradient.setColorAt(0.9, QColor(0, 0, 210)); + cpuGradient.setColorAt(0.0, QColor(130, 130, 255)); - /* Update graphs */ - if (src != m_gpuGraph) { - m_gpuGraph->selectNone(); - } + QLinearGradient gpuGradient; + gpuGradient.setColorAt(0.9, QColor(210, 0, 0)); + gpuGradient.setColorAt(0.0, QColor(255, 130, 130)); - if (src != m_cpuGraph) { - m_cpuGraph->selectNone(); - } - /* Update timeline */ - if (src != m_timeline) { - m_timeline->selectNone(); - } -} + /* Setup heatmap timeline */ + m_timeline->setLabel(new GraphLabelWidget("Frames ", m_timeline)); + m_timeline->label()->setFlags(Qt::AlignVCenter | Qt::AlignRight); + m_timeline->setView(new HeatmapView(m_timeline)); -void ProfileDialog::selectProgram(unsigned program) -{ - QObject* src = QObject::sender(); + m_timeline->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_timeline)); + m_timeline->setAxis(GraphWidget::AxisLeft, new HeatmapVerticalAxisWidget(m_timeline)); + m_timeline->axis(GraphWidget::AxisLeft)->resize(80, 0); - /* Update table model */ - ProfileTableModel* model = (ProfileTableModel*)m_table->model(); - model->selectNone(); - m_table->reset(); - m_table->selectRow(model->getRowIndex(program)); + m_timeline->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_timeline->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - /* Update graphs */ - if (src != m_gpuGraph) { - m_gpuGraph->selectProgram(program); - } - if (src != m_cpuGraph) { - m_cpuGraph->selectProgram(program); - } + /* Setup Cpu call duration histogram */ + m_cpuGraph->setLabel(new GraphLabelWidget("CPU", m_cpuGraph)); - /* Update timeline */ - if (src != m_timeline) { - m_timeline->selectProgram(program); - } + m_cpuGraph->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_cpuGraph)); + m_cpuGraph->setAxis(GraphWidget::AxisLeft, new TimeAxisWidget(m_cpuGraph)); + m_cpuGraph->axis(GraphWidget::AxisLeft)->resize(80, 0); + + HistogramView* cpuView = new HistogramView(m_cpuGraph); + cpuView->setSelectedGradient(cpuGradient); + m_cpuGraph->setView(cpuView); + + + /* Setup Gpu call duration histogram */ + m_gpuGraph->setLabel(new GraphLabelWidget("GPU", m_gpuGraph)); + + m_gpuGraph->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_gpuGraph)); + m_gpuGraph->setAxis(GraphWidget::AxisLeft, new TimeAxisWidget(m_gpuGraph)); + m_gpuGraph->axis(GraphWidget::AxisLeft)->resize(80, 0); + + HistogramView* gpuView = new HistogramView(m_gpuGraph); + gpuView->setSelectedGradient(gpuGradient); + m_gpuGraph->setView(gpuView); + + + /* Synchronise selections */ + connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), m_cpuGraph, SLOT(setSelection(SelectionState))); + connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), m_gpuGraph, SLOT(setSelection(SelectionState))); + + connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), m_timeline, SLOT(setSelection(SelectionState))); + connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), m_gpuGraph, SLOT(setSelection(SelectionState))); + + connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), m_timeline, SLOT(setSelection(SelectionState))); + connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), m_cpuGraph, SLOT(setSelection(SelectionState))); + + connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState))); + connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState))); + connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState))); + + + /* Synchronise views between cpuGraph and gpuGraph */ + connect(m_cpuGraph, SIGNAL(horizontalViewChanged(qint64,qint64)), m_gpuGraph, SLOT(setHorizontalView(qint64,qint64))); + connect(m_gpuGraph, SIGNAL(horizontalViewChanged(qint64,qint64)), m_cpuGraph, SLOT(setHorizontalView(qint64,qint64))); } -void ProfileDialog::selectTime(int64_t start, int64_t end) +ProfileDialog::~ProfileDialog() { - QObject* src = QObject::sender(); + delete m_profile; +} - /* Update table model */ - ProfileTableModel* model = (ProfileTableModel*)m_table->model(); - model->selectTime(start, end); - m_table->reset(); - /* Update graphs */ - if (src != m_gpuGraph) { - m_gpuGraph->selectTime(start, end); - } +void ProfileDialog::showCall(int call) +{ + emit jumpToCall(call); +} - if (src != m_cpuGraph) { - m_cpuGraph->selectTime(start, end); - } - /* Update timeline */ - if (src != m_timeline) { - m_timeline->selectTime(start, end); +void ProfileDialog::tableDoubleClicked(const QModelIndex& index) +{ + ProfileTableModel* model = (ProfileTableModel*)m_table->model(); + + if (!model) { + return; } -} + const trace::Profile::Call* call = model->getJumpCall(index); -void ProfileDialog::setVerticalScrollMax(int max) -{ - if (max <= 0) { - m_verticalScrollBar->hide(); + if (call) { + emit jumpToCall(call->no); } else { - m_verticalScrollBar->show(); - m_verticalScrollBar->setMinimum(0); - m_verticalScrollBar->setMaximum(max); + unsigned program = model->getProgram(index); + + SelectionState state; + state.type = SelectionState::Vertical; + state.end = state.start = program; + + m_timeline->setSelection(state); + m_cpuGraph->setSelection(state); + m_gpuGraph->setSelection(state); } } -void ProfileDialog::setHorizontalScrollMax(int max) +void ProfileDialog::setProfile(trace::Profile* profile) { - if (max <= 0) { - m_horizontalScrollBar->hide(); - } else { - m_horizontalScrollBar->show(); - m_horizontalScrollBar->setMinimum(0); - m_horizontalScrollBar->setMaximum(max); + + if (profile && profile->frames.size()) { + HeatmapVerticalAxisWidget* programAxis; + FrameAxisWidget* frameAxis; + HistogramView* histogram; + HeatmapView* heatmap; + + + /* Setup data providers for Cpu graph */ + m_cpuGraph->setProfile(profile); + histogram = (HistogramView*)m_cpuGraph->view(); + frameAxis = (FrameAxisWidget*)m_cpuGraph->axis(GraphWidget::AxisTop); + + histogram->setDataProvider(new CallDurationDataProvider(profile, false)); + frameAxis->setDataProvider(new FrameCallDataProvider(profile)); + + /* Setup data provider for Gpu graph */ + m_gpuGraph->setProfile(profile); + histogram = (HistogramView*)m_gpuGraph->view(); + frameAxis = (FrameAxisWidget*)m_gpuGraph->axis(GraphWidget::AxisTop); + + histogram->setDataProvider(new CallDurationDataProvider(profile, true)); + frameAxis->setDataProvider(new FrameCallDataProvider(profile)); + + /* Setup data provider for heatmap timeline */ + heatmap = (HeatmapView*)m_timeline->view(); + frameAxis = (FrameAxisWidget*)m_timeline->axis(GraphWidget::AxisTop); + programAxis = (HeatmapVerticalAxisWidget*)m_timeline->axis(GraphWidget::AxisLeft); + + heatmap->setDataProvider(new ProfileHeatmapDataProvider(profile)); + frameAxis->setDataProvider(new FrameTimeDataProvider(profile)); + programAxis->setDataProvider(new ProfileHeatmapDataProvider(profile)); + + /* Setup data model for table view */ + ProfileTableModel* model = new ProfileTableModel(m_table); + model->setProfile(profile); + + delete m_table->model(); + m_table->setModel(model); + m_table->update(QModelIndex()); + m_table->sortByColumn(2, Qt::DescendingOrder); + m_table->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents); + m_table->resizeColumnsToContents(); + + /* Reset selection */ + SelectionState emptySelection; + emptySelection.type = SelectionState::None; + m_cpuGraph->setSelection(emptySelection); + m_gpuGraph->setSelection(emptySelection); + m_timeline->setSelection(emptySelection); } + + delete m_profile; + m_profile = profile; } -/** - * Convert a CPU / GPU time to a textual representation. - * This includes automatic unit selection. - */ -QString getTimeString(int64_t time, int64_t unitTime) +void ProfileDialog::graphSelectionChanged(SelectionState state) { - QString text; - QString unit = " ns"; - double unitScale = 1; + ProfileTableModel* model = (ProfileTableModel*)m_table->model(); - if (unitTime == 0) { - unitTime = time; + if (!model) { + return; } - if (unitTime >= 60e9) { - int64_t mins = time / 60e9; - text += QString("%1 m ").arg(mins); - - time -= mins * 60e9; - unit = " s"; - unitScale = 1e9; - } else if (unitTime >= 1e9) { - unit = " s"; - unitScale = 1e9; - } else if (unitTime >= 1e6) { - unit = " ms"; - unitScale = 1e6; - } else if (unitTime >= 1e3) { - unit = QString::fromUtf8(" µs"); - unitScale = 1e3; + if (state.type == SelectionState::None) { + model->selectNone(); + } else if (state.type == SelectionState::Horizontal) { + model->selectTime(state.start, state.end); + } else if (state.type == SelectionState::Vertical) { + model->selectProgram(state.start); } - /* 3 decimal places */ - text += QString("%1").arg(time / unitScale, 0, 'f', 3); - - /* Remove trailing 0 */ - while(text.endsWith('0')) - text.truncate(text.length() - 1); - - /* Remove trailing decimal point */ - if (text.endsWith(QLocale::system().decimalPoint())) - text.truncate(text.length() - 1); + m_table->reset(); - return text + unit; + if (state.type == SelectionState::Vertical) { + m_table->selectRow(model->getRowIndex(state.start)); + } } - #include "profiledialog.moc" diff --git a/gui/profiledialog.h b/gui/profiledialog.h index 22ef066..862d068 100644 --- a/gui/profiledialog.h +++ b/gui/profiledialog.h @@ -15,16 +15,11 @@ public: ~ProfileDialog(); void setProfile(trace::Profile* profile); + void showCall(int call); public slots: - void setVerticalScrollMax(int max); - void setHorizontalScrollMax(int max); - void tableDoubleClicked(const QModelIndex& index); - - void selectNone(); - void selectProgram(unsigned program); - void selectTime(int64_t start, int64_t end); + void graphSelectionChanged(SelectionState state); signals: void jumpToCall(int call); @@ -33,6 +28,4 @@ private: trace::Profile *m_profile; }; -QString getTimeString(int64_t time, int64_t unitTime = 0); - #endif diff --git a/gui/profileheatmap.h b/gui/profileheatmap.h new file mode 100644 index 0000000..e50ea26 --- /dev/null +++ b/gui/profileheatmap.h @@ -0,0 +1,470 @@ +#ifndef PROFILEHEATMAP_H +#define PROFILEHEATMAP_H + +#include "graphing/heatmapview.h" +#include "profiling.h" + +/** + * Data providers for a heatmap based off the trace::Profile call data + */ + +class ProfileHeatmapRowIterator : public HeatmapRowIterator { +public: + ProfileHeatmapRowIterator(const trace::Profile* profile, qint64 start, qint64 end, int steps, bool gpu, int program = -1) : + m_profile(profile), + m_step(-1), + m_stepWidth(1), + m_stepCount(steps), + m_index(0), + m_timeStart(start), + m_timeEnd(end), + m_useGpu(gpu), + m_program(program), + m_selected(false), + m_timeSelection(false), + m_programSelection(false) + { + m_timeWidth = m_timeEnd - m_timeStart; + } + + virtual bool next() + { + unsigned maxIndex = m_program == -1 ? m_profile->calls.size() : m_profile->programs[m_program].calls.size(); + + if (m_index >= maxIndex) { + return false; + } + + double dtds = m_timeWidth / (double)m_stepCount; + + qint64 heatDuration = 0; + qint64 programHeatDuration = 0; + m_heat = 0.0f; + m_step += m_stepWidth; + m_stepWidth = 1; + + m_selected = false; + + /* Iterator through calls until step != lastStep */ + for (; m_index < maxIndex; ++m_index) + { + const trace::Profile::Call* call; + + if (m_program == -1) { + call = &m_profile->calls[m_index]; + } else { + call = &m_profile->calls[ m_profile->programs[m_program].calls[m_index] ]; + } + + qint64 start, duration, end; + + if (m_useGpu) { + start = call->gpuStart; + duration = call->gpuDuration; + + if (call->pixels < 0) { + continue; + } + } else { + start = call->cpuStart; + duration = call->cpuDuration; + } + + end = start + duration; + + if (end < m_timeStart) { + continue; + } + + if (start > m_timeEnd) { + m_index = m_profile->calls.size(); + break; + } + + double left = timeToStep(start); + double right = timeToStep(end); + + int leftStep = left; + int rightStep = right; + + if (leftStep > m_step) { + break; + } + + if (m_programSelection && call->program == m_programSel) { + m_selected = true; + } + + if (rightStep - leftStep > 1) { + m_label = QString::fromStdString(call->name); + m_step = left; + m_stepWidth = rightStep - leftStep; + heatDuration = dtds; + ++m_index; + break; + } + + if (leftStep < m_step) { + qint64 rightTime = stepToTime(rightStep); + heatDuration += end - rightTime; + + if (m_programSelection && call->program == m_programSel) { + programHeatDuration += end - rightTime; + } + } else if (leftStep == rightStep) { + heatDuration += duration; + + if (m_programSelection && call->program == m_programSel) { + programHeatDuration += duration; + } + } else if (rightStep - leftStep == 1) { + qint64 rightTime = stepToTime(rightStep); + heatDuration += rightTime - start; + + if (m_programSelection && call->program == m_programSel) { + programHeatDuration += rightTime - start; + } + + break; + } + } + + m_heat = heatDuration / dtds; + m_programHeat = programHeatDuration / dtds; + + if (m_timeSelection) { + qint64 time = stepToTime(m_step); + + if (time >= m_timeSelStart && time <= m_timeSelEnd) { + m_programHeat = 1.0; + } + } + + if (m_programSelection && (m_program == m_programSel || (m_selected && m_stepWidth > 1))) { + m_programHeat = 1.0; + } + + if (m_programHeat > 0) { + m_selected = true; + } + + return true; + } + + virtual bool isGpu() const + { + return m_useGpu; + } + + virtual float heat() const + { + return m_heat; + } + + virtual float selectedHeat() const + { + return m_programHeat; + } + + virtual int step() const + { + return m_step; + } + + virtual int width() const + { + return m_stepWidth; + } + + virtual QString label() const + { + return m_label; + } + + void setProgramSelection(int program) + { + m_programSelection = true; + m_programSel = program; + } + + void setTimeSelection(qint64 start, qint64 end) + { + m_timeSelection = true; + m_timeSelStart = start; + m_timeSelEnd = end; + } + +private: + double timeToStep(qint64 time) const + { + double pos = time; + pos -= m_timeStart; + pos /= m_timeWidth; + pos *= m_stepCount; + return pos; + } + + qint64 stepToTime(int pos) const + { + double time = pos; + time /= m_stepCount; + time *= m_timeWidth; + time += m_timeStart; + return (qint64)time; + } + +private: + const trace::Profile* m_profile; + + int m_step; + int m_stepWidth; + int m_stepCount; + + unsigned m_index; + + float m_heat; + + qint64 m_timeStart; + qint64 m_timeEnd; + qint64 m_timeWidth; + + bool m_useGpu; + int m_program; + + QString m_label; + + bool m_selected; + + bool m_timeSelection; + qint64 m_timeSelStart; + qint64 m_timeSelEnd; + + bool m_programSelection; + int m_programSel; + + float m_programHeat; +}; + +class ProfileHeatmapDataProvider : public HeatmapDataProvider { +protected: + enum SelectionType { + None, + Time, + Program + }; + +public: + ProfileHeatmapDataProvider(trace::Profile* profile) : + m_profile(profile), + m_selectionState(NULL) + { + sortRows(); + } + + virtual qint64 start() const + { + return m_profile->frames.front().cpuStart; + } + + virtual qint64 end() const + { + return m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration; + } + + virtual unsigned dataRows() const + { + return m_rowPrograms.size(); + } + + virtual QString dataLabel(unsigned row) const + { + if (row >= m_rowPrograms.size()) { + return QString(); + } else { + return QString("%1").arg(m_rowPrograms[row]); + } + } + + virtual qint64 dataRowAt(unsigned row) const + { + if (row >= m_rowPrograms.size()) { + return 0; + } else { + return m_rowPrograms[row]; + } + } + + virtual HeatmapRowIterator* dataRowIterator(int row, qint64 start, qint64 end, int steps) const + { + ProfileHeatmapRowIterator* itr = new ProfileHeatmapRowIterator(m_profile, start, end, steps, true, m_rowPrograms[row]); + + if (m_selectionState) { + if (m_selectionState->type == SelectionState::Horizontal) { + itr->setTimeSelection(m_selectionState->start, m_selectionState->end); + } else if (m_selectionState->type == SelectionState::Vertical) { + itr->setProgramSelection(m_selectionState->start); + } + } + + return itr; + } + + virtual unsigned headerRows() const + { + return 2; + } + + virtual qint64 headerRowAt(unsigned row) const + { + return row; + } + + virtual QString headerLabel(unsigned row) const + { + if (row == 0) { + return "CPU"; + } else if (row == 1) { + return "GPU"; + } else { + return QString(); + } + } + + virtual HeatmapRowIterator* headerRowIterator(int row, qint64 start, qint64 end, int steps) const + { + ProfileHeatmapRowIterator* itr = new ProfileHeatmapRowIterator(m_profile, start, end, steps, row != 0); + + if (m_selectionState) { + if (m_selectionState->type == SelectionState::Horizontal) { + itr->setTimeSelection(m_selectionState->start, m_selectionState->end); + } else if (m_selectionState->type == SelectionState::Vertical) { + itr->setProgramSelection(m_selectionState->start); + } + } + + return itr; + } + + virtual qint64 dataItemAt(unsigned row, qint64 time) const + { + if (row >= m_rowPrograms.size()) { + return -1; + } + + unsigned program = m_rowPrograms[row]; + + std::vector::const_iterator item = + Profiling::binarySearchTimespanIndexed + (m_profile->calls, m_profile->programs[program].calls.begin(), m_profile->programs[program].calls.end(), time); + + if (item == m_profile->programs[program].calls.end()) { + return -1; + } + + return *item; + } + + virtual qint64 headerItemAt(unsigned row, qint64 time) const + { + if (row >= m_rowPrograms.size()) { + return -1; + } + + if (row == 0) { + /* CPU */ + std::vector::const_iterator item = + Profiling::binarySearchTimespan + (m_profile->calls.begin(), m_profile->calls.end(), time); + + if (item != m_profile->calls.end()) { + return item - m_profile->calls.begin(); + } + } else if (row == 1) { + /* GPU */ + for (unsigned i = 0; i < m_rowPrograms.size(); ++i) { + qint64 index = dataItemAt(i, time); + + if (index != -1) { + return index; + } + } + } + + return -1; + } + + virtual void itemDoubleClicked(qint64 index) const + { + if (index < 0 || index >= m_profile->calls.size()) { + return; + } + + const trace::Profile::Call& call = m_profile->calls[index]; + Profiling::jumpToCall(call.no); + } + + virtual QString itemTooltip(qint64 index) const + { + if (index >= m_profile->calls.size()) { + return QString(); + } + + const trace::Profile::Call& call = m_profile->calls[index]; + + QString text; + text = QString::fromStdString(call.name); + + text += QString("\nCall: %1").arg(call.no); + text += QString("\nCPU Start: %1").arg(Profiling::getTimeString(call.cpuStart)); + text += QString("\nCPU Duration: %1").arg(Profiling::getTimeString(call.cpuDuration)); + + if (call.pixels >= 0) { + text += QString("\nGPU Start: %1").arg(Profiling::getTimeString(call.gpuStart)); + text += QString("\nGPU Duration: %1").arg(Profiling::getTimeString(call.gpuDuration)); + text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call.pixels)); + } + + return text; + } + + virtual void setSelectionState(SelectionState* state) + { + m_selectionState = state; + } + +private: + void sortRows() + { + typedef QPair Pair; + std::vector gpu; + + /* Map shader to visible row */ + for (std::vector::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) { + const trace::Profile::Program& program = *itr; + unsigned no = itr - m_profile->programs.begin(); + + if (program.gpuTotal > 0) { + gpu.push_back(Pair(program.gpuTotal, no)); + } + } + + /* Sort the shaders by most used gpu */ + qSort(gpu); + + /* Create row order */ + m_rowPrograms.clear(); + + for (std::vector::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) { + m_rowPrograms.push_back(itr->second); + } + } + +protected: + trace::Profile* m_profile; + std::vector m_rowPrograms; + SelectionState* m_selectionState; +}; + +#endif diff --git a/gui/profiletablemodel.cpp b/gui/profiletablemodel.cpp index cbe2eea..11a0d0c 100644 --- a/gui/profiletablemodel.cpp +++ b/gui/profiletablemodel.cpp @@ -1,5 +1,6 @@ #include "profiletablemodel.h" #include "profiledialog.h" +#include "profiling.h" #include @@ -235,15 +236,15 @@ QVariant ProfileTableModel::data(const QModelIndex &index, int role) const case COLUMN_USAGES: return QLocale::system().toString(row.uses); case COLUMN_GPU_TIME: - return getTimeString(row.gpuTime); + return Profiling::getTimeString(row.gpuTime); case COLUMN_CPU_TIME: - return getTimeString(row.cpuTime); + return Profiling::getTimeString(row.cpuTime); case COLUMN_PIXELS_DRAWN: return QLocale::system().toString((qlonglong)row.pixels); case COLUMN_GPU_AVERAGE: - return getTimeString((row.uses <= 0) ? 0 : (row.gpuTime / row.uses)); + return Profiling::getTimeString((row.uses <= 0) ? 0 : (row.gpuTime / row.uses)); case COLUMN_CPU_AVERAGE: - return getTimeString((row.uses <= 0) ? 0 : (row.cpuTime / row.uses)); + return Profiling::getTimeString((row.uses <= 0) ? 0 : (row.cpuTime / row.uses)); case COLUMN_PIXELS_AVERAGE: return QLocale::system().toString((row.uses <= 0) ? 0 : (row.pixels / row.uses)); } diff --git a/gui/profiling.h b/gui/profiling.h new file mode 100644 index 0000000..a5b1266 --- /dev/null +++ b/gui/profiling.h @@ -0,0 +1,128 @@ +#ifndef PROFILING_H +#define PROFILING_H + +#include +#include +#include "trace_profiler.hpp" + +class Profiling { +public: + /** + * Select and show the call in main window. + */ + static void jumpToCall(int index); + + /** + * Convert a CPU / GPU time to a textual representation. + * This includes automatic unit selection. + */ + static QString getTimeString(int64_t time, int64_t unitTime = 0) + { + QString text; + QString unit = " ns"; + double unitScale = 1; + + if (unitTime == 0) { + unitTime = time; + } + + if (unitTime >= 60e9) { + int64_t mins = time / 60e9; + text += QString("%1 m ").arg(mins); + + time -= mins * 60e9; + unit = " s"; + unitScale = 1e9; + } else if (unitTime >= 1e9) { + unit = " s"; + unitScale = 1e9; + } else if (unitTime >= 1e6) { + unit = " ms"; + unitScale = 1e6; + } else if (unitTime >= 1e3) { + unit = QString::fromUtf8(" µs"); + unitScale = 1e3; + } + + /* 3 decimal places */ + text += QString("%1").arg(time / unitScale, 0, 'f', 3); + + /* Remove trailing 0 */ + while(text.endsWith('0')) + text.truncate(text.length() - 1); + + /* Remove trailing decimal point */ + if (text.endsWith(QLocale::system().decimalPoint())) + text.truncate(text.length() - 1); + + return text + unit; + } + + template + static typename std::vector::const_iterator binarySearchTimespan( + typename std::vector::const_iterator begin, + typename std::vector::const_iterator end, + int64_t time, + bool nearest = false) + { + int lower = 0; + int upper = end - begin; + int pos = (lower + upper) / 2; + typename std::vector::const_iterator itr = begin + pos; + + while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) { + if ((*itr).*mem_ptr_start > time) { + upper = pos - 1; + } else { + lower = pos + 1; + } + + pos = (lower + upper) / 2; + itr = begin + pos; + } + + if (nearest || lower <= upper) { + return itr; + } else { + return end; + } + } + + static std::vector::const_iterator binarySearchTimespanIndexed( + const std::vector& calls, + std::vector::const_iterator begin, + std::vector::const_iterator end, + int64_t time, + bool nearest = false) + { + int lower = 0; + int upper = end - begin - 1; + int pos = (lower + upper) / 2; + std::vector::const_iterator itr = begin + pos; + + while (lower <= upper) { + const trace::Profile::Call& call = calls[*itr]; + + if (call.gpuStart <= time && call.gpuStart + call.gpuDuration > time) { + break; + } + + if (call.gpuStart > time) { + upper = pos - 1; + } else { + lower = pos + 1; + } + + pos = (lower + upper) / 2; + itr = begin + pos; + } + + if (nearest || lower <= upper) { + return itr; + } else { + return end; + } + } +}; + +#endif diff --git a/gui/timelinewidget.cpp b/gui/timelinewidget.cpp deleted file mode 100644 index 0f30225..0000000 --- a/gui/timelinewidget.cpp +++ /dev/null @@ -1,1078 +0,0 @@ -#include "timelinewidget.h" -#include "profiledialog.h" -#include "trace_profiler.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -typedef trace::Profile::Call Call; -typedef trace::Profile::Frame Frame; -typedef trace::Profile::Program Program; - -TimelineWidget::TimelineWidget(QWidget *parent) - : QWidget(parent), - m_profile(NULL), - m_rowHeight(20), - m_axisWidth(50), - m_axisHeight(30), - m_axisLine(QColor(240, 240, 240)), - m_axisBorder(Qt::black), - m_axisForeground(Qt::black), - m_axisBackground(QColor(210, 210, 210)), - m_itemBorder(Qt::red), - m_itemGpuForeground(Qt::cyan), - m_itemGpuBackground(Qt::red), - m_itemCpuForeground(QColor(255, 255, 0)), - m_itemCpuBackground(QColor(0, 0, 255)), - m_itemDeselectedForeground(Qt::white), - m_itemDeselectedBackground(QColor(155, 155, 155)), - m_selectionBorder(Qt::green), - m_selectionBackground(QColor(100, 255, 100, 8)), - m_zoomBorder(QColor(255, 0, 255)), - m_zoomBackground(QColor(255, 0, 255, 30)) -{ - setBackgroundRole(QPalette::Base); - setAutoFillBackground(true); - setMouseTracking(true); - - m_selection.type = SelectNone; -} - - -/** - * Update horizontal view scroll based on scroll value - */ -void TimelineWidget::setHorizontalScrollValue(int scrollValue) -{ - if (!m_profile) { - return; - } - - /* Calculate time from scroll value */ - double time = scrollValue; - time /= m_maxScrollX; - time *= (m_timeMax - m_timeWidth) - m_timeMin; - time += m_timeMin; - - setTimeScroll(time, false); -} - - -/** - * Update vertical view scroll based on scroll value - */ -void TimelineWidget::setVerticalScrollValue(int value) -{ - if (!m_profile) { - return; - } - - setRowScroll(value, false); -} - - -/** - * Set selection to nothing - */ -void TimelineWidget::selectNone(bool notify) -{ - m_selection.type = SelectNone; - - if (notify) { - emit selectedNone(); - } - - update(); -} - - -/** - * Set selection to a program - */ -void TimelineWidget::selectProgram(unsigned program, bool notify) -{ - m_selection.program = program; - m_selection.type = SelectProgram; - - if (notify) { - emit selectedProgram(program); - } - - update(); -} - - -/** - * Set selection to a period of time - */ -void TimelineWidget::selectTime(int64_t start, int64_t end, bool notify) -{ - m_selection.timeStart = start; - m_selection.timeEnd = end; - m_selection.type = SelectTime; - - if (notify) { - emit selectedTime(start, end); - } - - update(); -} - - -/** - * Convert time to view position - */ -double TimelineWidget::timeToPosition(int64_t time) -{ - double pos = time; - pos -= m_time; - pos /= m_timeWidth; - pos *= m_viewWidth; - return pos; -} - - -/** - * Convert view position to time - */ -int64_t TimelineWidget::positionToTime(int pos) -{ - double time = pos; - time /= m_viewWidth; - time *= m_timeWidth; - time += m_time; - return (int64_t)time; -} - - -/** - * Binary Search for a time in start+durations - */ -template -typename std::vector::const_iterator binarySearchTimespan( - typename std::vector::const_iterator begin, - typename std::vector::const_iterator end, - int64_t time) -{ - int lower = 0; - int upper = end - begin; - int pos = (lower + upper) / 2; - typename std::vector::const_iterator itr = begin + pos; - - while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) { - if ((*itr).*mem_ptr_start > time) { - upper = pos - 1; - } else { - lower = pos + 1; - } - - pos = (lower + upper) / 2; - itr = begin + pos; - } - - if (lower <= upper) { - return itr; - } else { - return end; - } -} - - -/** - * Binary Search for a time in start+durations on an array of indices - */ -std::vector::const_iterator binarySearchTimespanIndexed( - const std::vector& calls, - std::vector::const_iterator begin, - std::vector::const_iterator end, - int64_t time) -{ - int lower = 0; - int upper = end - begin - 1; - int pos = (lower + upper) / 2; - std::vector::const_iterator itr = begin + pos; - - while (lower <= upper) { - const Call& call = calls[*itr]; - - if (call.gpuStart <= time && call.gpuStart + call.gpuDuration > time) { - break; - } - - if (call.gpuStart > time) { - upper = pos - 1; - } else { - lower = pos + 1; - } - - pos = (lower + upper) / 2; - itr = begin + pos; - } - - if (lower <= upper) { - return itr; - } else { - return end; - } -} - - -/** - * Find the frame at time - */ -const Frame* TimelineWidget::frameAtTime(int64_t time) -{ - if (!m_profile) { - return NULL; - } - - std::vector::const_iterator res - = binarySearchTimespan( - m_profile->frames.begin(), - m_profile->frames.end(), - time); - - if (res != m_profile->frames.end()) { - return &*res; - } - - return NULL; -} - - -/** - * Find the CPU call at time - */ -const Call* TimelineWidget::cpuCallAtTime(int64_t time) -{ - if (!m_profile) { - return NULL; - } - - std::vector::const_iterator res - = binarySearchTimespan( - m_profile->calls.begin(), - m_profile->calls.end(), - time); - - if (res != m_profile->calls.end()) { - return &*res; - } - - return NULL; -} - - -/** - * Find the draw call at time - */ -const Call* TimelineWidget::drawCallAtTime(int64_t time) -{ - if (!m_profile) { - return NULL; - } - - for (int i = 0; i < m_rowPrograms.size(); ++i) { - const Call* call = drawCallAtTime(time, m_rowPrograms[i]); - - if (call) { - return call; - } - } - - return NULL; -} - - -/** - * Find the draw call at time for a selected program - */ -const Call* TimelineWidget::drawCallAtTime(int64_t time, int program) -{ - if (!m_profile) { - return NULL; - } - - std::vector::const_iterator res - = binarySearchTimespanIndexed( - m_profile->calls, - m_profile->programs[program].calls.begin(), - m_profile->programs[program].calls.end(), - time); - - if (res != m_profile->programs[program].calls.end()) { - return &m_profile->calls[*res]; - } - - return NULL; -} - - -/** - * Calculate the row order by total gpu time per shader - */ -void TimelineWidget::calculateRows() -{ - typedef QPair Pair; - std::vector gpu; - - /* Map shader to visible row */ - for (std::vector::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) { - const Program& program = *itr; - unsigned no = itr - m_profile->programs.begin(); - - if (program.gpuTotal > 0) { - gpu.push_back(Pair(program.gpuTotal, no)); - } - } - - /* Sort the shaders by most used gpu */ - qSort(gpu); - - /* Create row order */ - m_rowPrograms.clear(); - - for (std::vector::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) { - m_rowPrograms.push_back(itr->second); - } - - m_rowCount = m_rowPrograms.size(); -} - - -/** - * Set the trace profile to use for the timeline - */ -void TimelineWidget::setProfile(trace::Profile* profile) -{ - if (!profile->frames.size()) - return; - - m_profile = profile; - calculateRows(); - - m_timeMin = m_profile->frames.front().cpuStart; - m_timeMax = m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration; - - m_time = m_timeMin; - m_timeWidth = m_timeMax - m_timeMin; - - m_timeWidthMin = 1000; - m_timeWidthMax = m_timeWidth; - - m_maxScrollX = 0; - m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight); - - setTimeScroll(m_time); - setRowScroll(0); - selectNone(); - update(); -} - - -/** - * Set the horizontal scroll position to time - */ -void TimelineWidget::setTimeScroll(int64_t time, bool notify) -{ - time = qBound(m_timeMin, time, m_timeMax - m_timeWidth); - - m_time = time; - - if (m_timeWidth == m_timeWidthMax) { - m_maxScrollX = 0; - } else { - m_maxScrollX = 10000; - } - - if (notify) { - double value = time - m_timeMin; - value /= m_timeMax - m_timeWidth - m_timeMin; - value *= m_maxScrollX; - m_scrollX = value; - - emit horizontalScrollMaxChanged(m_maxScrollX); - emit horizontalScrollValueChanged(m_scrollX); - } - - update(); -} - - -/** - * Set the vertical scroll position to position - */ -void TimelineWidget::setRowScroll(int position, bool notify) -{ - position = qBound(0, position, m_maxScrollY); - - m_scrollY = position; - m_row = m_scrollY / m_rowHeight; - - if (notify) { - emit verticalScrollMaxChanged(m_maxScrollY); - emit verticalScrollValueChanged(m_scrollY); - } - - update(); -} - - -void TimelineWidget::resizeEvent(QResizeEvent *e) -{ - /* Update viewport size */ - m_viewWidth = qMax(0, width() - m_axisWidth); - m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight * 2); - - /* Update vertical scroll bar */ - if (m_profile) { - m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight); - emit verticalScrollMaxChanged(m_maxScrollY); - setRowScroll(m_scrollY); - } -} - - -void TimelineWidget::mouseMoveEvent(QMouseEvent *e) -{ - bool tooltip = false; - m_mousePosition = e->pos(); - - if (!m_profile) { - return; - } - - /* Display tooltip if necessary */ - if (e->buttons() == Qt::NoButton) { - if (m_mousePosition.x() > m_axisWidth && m_mousePosition.y() > m_axisHeight) { - int64_t time = positionToTime(m_mousePosition.x() - m_axisWidth); - int y = m_mousePosition.y() - m_axisHeight; - - if (y < m_rowHeight) { - const Call* call = cpuCallAtTime(time); - - if (call) { - QString text; - text = QString::fromStdString(call->name); - text += QString("\nCall: %1").arg(call->no); - text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart)); - text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration)); - - QToolTip::showText(e->globalPos(), text); - tooltip = true; - } - } else { - const Call* call = NULL; - - if (y < m_rowHeight * 2) { - call = drawCallAtTime(time); - } else { - int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight; - - if (row < m_rowPrograms.size()) { - call = drawCallAtTime(time, m_rowPrograms[row]); - } - } - - if (call) { - QString text; - text = QString::fromStdString(call->name); - text += QString("\nCall: %1").arg(call->no); - text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart)); - text += QString("\nGPU Start: %1").arg(getTimeString(call->gpuStart)); - text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration)); - text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration)); - text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels)); - - QToolTip::showText(e->globalPos(), text); - tooltip = true; - } - } - } else if (m_mousePosition.x() < m_axisWidth && m_mousePosition.y() > m_axisHeight) { - int y = m_mousePosition.y() - m_axisHeight; - - if (y < m_rowHeight) { - QToolTip::showText(e->globalPos(), "All CPU calls"); - tooltip = true; - } else if (y < m_rowHeight * 2) { - QToolTip::showText(e->globalPos(), "All GPU calls"); - tooltip = true; - } else { - int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight; - - if (row < m_rowPrograms.size()) { - QToolTip::showText(e->globalPos(), QString("All calls in Shader Program %1").arg(m_rowPrograms[row])); - tooltip = true; - } - } - } - } else if (e->buttons().testFlag(Qt::LeftButton)) { - if (m_mousePressMode == DragView) { - /* Horizontal scroll */ - double dt = m_timeWidth; - dt /= m_viewWidth; - dt *= m_mousePressPosition.x() - e->pos().x(); - setTimeScroll(m_mousePressTime + dt); - - /* Vertical scroll */ - int dy = m_mousePressPosition.y() - e->pos().y(); - setRowScroll(m_mousePressRow + dy); - } else if (m_mousePressMode == RulerSelect) { - /* Horizontal selection */ - int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth); - int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0)); - - selectTime(qMin(down, up), qMax(down, up), true); - } - - update(); - } - - if (!tooltip) { - QToolTip::hideText(); - } -} - - -void TimelineWidget::mousePressEvent(QMouseEvent *e) -{ - if (e->buttons() & Qt::LeftButton) { - if (e->pos().y() < m_axisHeight && e->pos().x() >= m_axisWidth) { - if (QApplication::keyboardModifiers() & Qt::ControlModifier) { - m_mousePressMode = RulerZoom; - } else { - m_mousePressMode = RulerSelect; - } - } else if (e->pos().x() >= m_axisWidth) { - m_mousePressMode = DragView; - } else { - m_mousePressMode = NoMousePress; - } - - m_mousePressPosition = e->pos(); - m_mousePressTime = m_time; - m_mousePressRow = m_scrollY; - - update(); - } -} - - -void TimelineWidget::mouseReleaseEvent(QMouseEvent *e) -{ - if (!m_profile) { - return; - } - - /* Calculate new time view based on selected area */ - int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y()); - - int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth); - int64_t up = positionToTime(qMax(e->pos().x() - m_axisWidth, 0)); - - int64_t left = qMin(down, up); - int64_t right = qMax(down, up); - - if (m_mousePressMode == RulerZoom) { - m_timeWidth = right - left; - m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax); - - m_mousePressMode = NoMousePress; - setTimeScroll(left); - } else { - if (dxy <= 2) { - if (m_selection.type == SelectTime) { - if (left < m_selection.timeStart || right > m_selection.timeEnd || e->pos().x() < m_axisWidth) { - selectNone(true); - } - } else if (m_selection.type == SelectProgram) { - int y = e->pos().y() - m_axisHeight; - int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight; - - if (row < 0 || m_rowPrograms[row] != m_selection.program) { - selectNone(true); - } - } - } else if (m_mousePressMode == RulerSelect) { - selectTime(left, right, true); - } - } -} - - -void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e) -{ - int64_t time = positionToTime(e->pos().x() - m_axisWidth); - - if (e->pos().x() > m_axisWidth) { - int row = (e->pos().y() - m_axisHeight) / m_rowHeight; - - if (e->pos().y() < m_axisHeight) { - /* Horizontal axis */ - const Frame* frame = frameAtTime(time); - - if (frame) { - selectTime(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true); - return; - } - } else if (row == 0) { - /* CPU Calls */ - const Call* call = cpuCallAtTime(time); - - if (call) { - emit jumpToCall(call->no); - return; - } - } else if (row > 0) { - /* Draw Calls */ - const Call* call = drawCallAtTime(time, 0); - - if (call) { - emit jumpToCall(call->no); - return; - } - } - } else { - int y = e->pos().y() - m_axisHeight; - int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight; - - if (row >= 0 && row < m_rowPrograms.size()) { - selectProgram(m_rowPrograms[row], true); - } - } -} - - -void TimelineWidget::wheelEvent(QWheelEvent *e) -{ - if (!m_profile) { - return; - } - - if (e->pos().x() < m_axisWidth) { - return; - } - - int zoomPercent = 10; - - /* If holding Ctrl key then zoom 2x faster */ - if (QApplication::keyboardModifiers() & Qt::ControlModifier) { - zoomPercent = 20; - } - - /* Zoom view by adjusting width */ - double dt = m_timeWidth; - double size = m_timeWidth; - size *= -e->delta(); - - /* Zoom deltas normally come in increments of 120 */ - size /= 120 * (100 / zoomPercent); - - m_timeWidth += size; - m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax); - - /* Scroll view to zoom around mouse */ - dt -= m_timeWidth; - dt *= e->x() - m_axisWidth; - dt /= m_viewWidth; - setTimeScroll(dt + m_time); - - update(); -} - - -/** - * Paints a single pixel column of the heat map - */ -void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool gpu, bool selected) -{ - if (heat == 0) { - return; - } - - if (m_selection.type == SelectTime) { - selected = x >= m_selectionLeft && x <= m_selectionRight; - } - - double timePerPixel = m_timeWidth / (double)m_viewWidth; - double colour = heat / timePerPixel; - - /* Gamma correction */ - colour = qPow(colour, 1.0 / 2.0); - - if (!selected) { - colour = qBound(0.0, colour * 100.0, 100.0); - painter.setPen(QColor(255 - colour, 255 - colour, 255 - colour)); - } else if (gpu) { - colour = qBound(0.0, colour * 255.0, 255.0); - painter.setPen(QColor(255, 255 - colour, 255 - colour)); - } else { - colour = qBound(0.0, colour * 255.0, 255.0); - painter.setPen(QColor(255 - colour, 255 - colour, 255)); - } - - painter.drawLine(x, 0, x, m_rowHeight - 1); -} - - -/** - * Draws a call on the heatmap - */ -bool TimelineWidget::drawCall(QPainter& painter, const trace::Profile::Call& call, int& lastX, int64_t& heat, bool gpu) -{ - int64_t start, duration, end; - - if (gpu) { - start = call.gpuStart; - duration = call.gpuDuration; - } else { - start = call.cpuStart; - duration = call.cpuDuration; - } - - end = start + duration; - - if (start > m_timeEnd) { - return false; - } - - if (end < m_time) { - return true; - } - - double left = timeToPosition(start); - double right = timeToPosition(end); - - int leftX = left; - int rightX = right; - - bool selected = true; - - if (m_selection.type == SelectProgram) { - selected = call.program == m_selection.program; - } - - /* Draw last heat if needed */ - if (leftX != lastX) { - drawHeat(painter, lastX, heat, gpu, selected); - lastX = leftX; - heat = 0; - } - - if (rightX <= leftX + 1) { - if (rightX == lastX) { - /* Fully contained in this X */ - heat += duration; - } else { - /* Split call time between the two pixels it occupies */ - int64_t time = positionToTime(rightX); - heat += time - start; - - drawHeat(painter, lastX, heat, gpu, selected); - - heat = end - time; - lastX = rightX; - } - } else { - QRect rect; - rect.setLeft(left + 0.5); - rect.setWidth(right - left); - rect.setTop(0); - rect.setHeight(m_rowHeight); - - if (m_selection.type == SelectTime) { - selected = (start >= m_selection.timeStart && start <= m_selection.timeEnd) - || (end >= m_selection.timeStart && end <= m_selection.timeEnd); - } - - /* Draw background rect */ - if (selected) { - if (gpu) { - painter.fillRect(rect, m_itemGpuBackground); - } else { - painter.fillRect(rect, m_itemCpuBackground); - } - } else { - painter.fillRect(rect, m_itemDeselectedBackground); - } - - /* If wide enough, draw text */ - if (rect.width() > 6) { - rect.adjust(1, 0, -1, -2); - - if (selected) { - if (gpu) { - painter.setPen(m_itemGpuForeground); - } else { - painter.setPen(m_itemCpuForeground); - } - } else { - painter.setPen(m_itemDeselectedForeground); - } - - painter.drawText(rect, - Qt::AlignLeft | Qt::AlignVCenter, - painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width())); - } - } - - return true; -} - - -/** - * Render the whole widget - */ -void TimelineWidget::paintEvent(QPaintEvent *e) -{ - if (!m_profile) - return; - - QPainter painter(this); - - int rowEnd = qMin(m_row + qCeil(m_viewHeight / (double)m_rowHeight) + 1, m_rowCount); - int64_t heatGPU = 0, heatCPU = 0; - int lastCpuX = 0, lastGpuX = 0; - int widgetHeight = height(); - int widgetWidth = width(); - - m_timeEnd = m_time + m_timeWidth; - m_selectionLeft = timeToPosition(m_selection.timeStart); - m_selectionRight = (timeToPosition(m_selection.timeEnd) + 0.5); - - - /* Draw program rows */ - painter.translate(m_axisWidth, m_axisHeight + m_rowHeight * 2 - (m_scrollY % m_rowHeight)); - - for (int row = m_row; row < rowEnd; ++row) { - Program& program = m_profile->programs[m_rowPrograms[row]]; - lastGpuX = 0; - heatGPU = 0; - - for (std::vector::const_iterator itr = program.calls.begin(); itr != program.calls.end(); ++itr) { - const Call& call = m_profile->calls[*itr]; - - if (!drawCall(painter, call, lastGpuX, heatGPU, true)) { - break; - } - } - - painter.translate(0, m_rowHeight); - } - - - /* Draw CPU/GPU rows */ - painter.resetTransform(); - painter.translate(m_axisWidth, m_axisHeight); - painter.fillRect(0, 0, m_viewWidth, m_rowHeight * 2, Qt::white); - - lastCpuX = lastGpuX = 0; - heatCPU = heatGPU = 0; - - for (std::vector::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) { - const Call& call = *itr; - - /* Draw gpu row */ - if (call.pixels >= 0) { - painter.translate(0, m_rowHeight); - drawCall(painter, call, lastGpuX, heatGPU, true); - painter.translate(0, -m_rowHeight); - } - - /* Draw cpu row */ - if (!drawCall(painter, call, lastCpuX, heatCPU, false)) { - break; - } - } - - - /* Draw axis */ - painter.resetTransform(); - painter.setPen(m_axisBorder); - - /* Top Rect */ - painter.fillRect(m_axisWidth - 1, 0, widgetWidth, m_axisHeight - 1, m_axisBackground); - painter.drawLine(0, m_axisHeight - 1, widgetWidth, m_axisHeight - 1); - - /* Left Rect */ - painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground); - painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight); - - - /* Draw the program numbers */ - painter.translate(0, m_axisHeight + m_rowHeight * 2); - - for (int row = m_row; row < rowEnd; ++row) { - int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight); - - painter.setPen(m_axisForeground); - painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row])); - - if (m_selection.type == SelectProgram && m_selection.program == m_rowPrograms[row]) { - painter.setPen(m_selectionBorder); - painter.drawLine(0, qMax(0, y - 1), widgetWidth, qMax(0, y - 1)); - painter.drawLine(0, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1); - painter.drawLine(m_axisWidth - 1, y - 1, m_axisWidth - 1, y + m_rowHeight - 1); - } else { - painter.setPen(m_axisBorder); - painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1); - - painter.setPen(m_axisLine); - painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1); - } - } - - - /* Draw the "CPU" axis label */ - painter.resetTransform(); - painter.translate(0, m_axisHeight); - - painter.setPen(m_axisBorder); - painter.setBrush(m_axisBackground); - painter.drawRect(-1, -1, m_axisWidth, m_rowHeight); - - painter.setPen(m_axisForeground); - painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "CPU"); - - painter.setPen(m_axisBorder); - painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1); - - - /* Draw the "GPU" axis label */ - painter.translate(0, m_rowHeight); - - painter.setPen(m_axisBorder); - painter.setBrush(m_axisBackground); - painter.drawRect(-1, -1, m_axisWidth, m_rowHeight); - - painter.setPen(m_axisForeground); - painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "GPU"); - - painter.setPen(m_axisBorder); - painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1); - - - /* Draw the frame numbers */ - painter.resetTransform(); - - painter.setPen(m_axisForeground); - painter.translate(m_axisWidth, 0); - - int lastLabel = -999; /* Ensure first label gets drawn */ - - double scroll = m_time; - scroll /= m_timeWidth; - scroll *= m_viewWidth; - - for (std::vector::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) { - static const int padding = 4; - const Frame& frame = *itr; - bool draw = true; - int width; - - if (frame.cpuStart > m_timeEnd) { - break; - } - - if (frame.cpuStart + frame.cpuDuration < m_time) { - draw = false; - } - - double left = frame.cpuStart; - left /= m_timeWidth; - left *= m_viewWidth; - - double right = frame.cpuStart + frame.cpuDuration; - right /= m_timeWidth; - right *= m_viewWidth; - - QString text = QString("%1").arg(frame.no); - - width = painter.fontMetrics().width(text) + padding * 2; - - if (left + width > scroll) - draw = true; - - /* Draw a frame number if we have space since the last one */ - if (left - lastLabel > width) { - lastLabel = left + width; - - if (draw) { - int textX; - painter.setPen(m_axisForeground); - - if (left < scroll && right - left > width) { - if (right - scroll > width) { - textX = 0; - } else { - textX = right - scroll - width; - } - } else { - textX = left - scroll; - } - - /* Draw frame number and major ruler marking */ - painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text); - painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1); - } - } else if (draw) { - /* Draw a minor ruler marking */ - painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1); - } - } - - - /* Draw "Frame" axis label */ - painter.resetTransform(); - - painter.setPen(m_axisBorder); - painter.setBrush(m_axisBackground); - painter.drawRect(-1, -1, m_axisWidth, m_axisHeight); - - painter.setPen(m_axisForeground); - painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame"); - - - /* Draw the active selection border */ - if (m_selection.type == SelectTime) { - painter.setPen(m_selectionBorder); - - m_selectionLeft += m_axisWidth; - m_selectionRight += m_axisWidth; - - if (m_selectionLeft >= m_axisWidth && m_selectionLeft < widgetWidth) { - painter.drawLine(m_selectionLeft, 0, m_selectionLeft, widgetHeight); - } - - if (m_selectionRight >= m_axisWidth && m_selectionRight < widgetWidth) { - painter.drawLine(m_selectionRight, 0, m_selectionRight, widgetHeight); - } - - m_selectionLeft = qBound(m_axisWidth, m_selectionLeft, widgetWidth); - m_selectionRight = qBound(m_axisWidth, m_selectionRight, widgetWidth); - - painter.drawLine(m_selectionLeft, m_axisHeight - 1, m_selectionRight, m_axisHeight - 1); - painter.fillRect(m_selectionLeft, 0, m_selectionRight - m_selectionLeft, widgetHeight, m_selectionBackground); - } - - - /* Draw the ruler zoom */ - if (m_mousePressMode == RulerZoom) { - int x1 = m_mousePressPosition.x(); - int x2 = qMax(m_mousePosition.x(), m_axisWidth); - - painter.setPen(m_zoomBorder); - painter.drawLine(x1, 0, x1, widgetHeight); - painter.drawLine(x2, 0, x2, widgetHeight); - painter.drawLine(x1, m_axisHeight - 1, x2, m_axisHeight - 1); - painter.fillRect(x1, m_axisHeight, x2 - x1, widgetHeight, m_zoomBackground); - } -} - -#include "timelinewidget.moc" diff --git a/gui/timelinewidget.h b/gui/timelinewidget.h deleted file mode 100644 index ba42a10..0000000 --- a/gui/timelinewidget.h +++ /dev/null @@ -1,148 +0,0 @@ -#ifndef TIMELINEWIDGET_H -#define TIMELINEWIDGET_H - -#include -#include -#include -#include "trace_profiler.hpp" - -class TimelineWidget : public QWidget -{ - Q_OBJECT - - enum MousePressMode { - NoMousePress, - DragView, - RulerZoom, - RulerSelect - }; - - enum SelectType { - SelectNone, - SelectTime, - SelectProgram - }; - -public: - TimelineWidget(QWidget *parent = 0); - - void setProfile(trace::Profile* profile); - - void selectNone(bool notify = false); - void selectProgram(unsigned program, bool notify = false); - void selectTime(int64_t start, int64_t end, bool notify = false); - -protected: - virtual void wheelEvent(QWheelEvent *e); - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseMoveEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - virtual void mouseDoubleClickEvent(QMouseEvent *e); - - virtual void paintEvent(QPaintEvent *e); - virtual void resizeEvent(QResizeEvent *e); - -public slots: - void setHorizontalScrollValue(int value); - void setVerticalScrollValue(int value); - -signals: - void verticalScrollMaxChanged(int max); - void verticalScrollValueChanged(int value); - - void horizontalScrollMaxChanged(int max); - void horizontalScrollValueChanged(int value); - - void jumpToCall(int call); - - void selectedNone(); - void selectedProgram(unsigned program); - void selectedTime(int64_t start, int64_t end); - -private: - void setRowScroll(int position, bool notify = true); - void setTimeScroll(int64_t time, bool notify = true); - - bool drawCall(QPainter& painter, const trace::Profile::Call& call, int &lastX, int64_t &heat, bool gpu); - void drawHeat(QPainter& painter, int x, int64_t heat, bool gpu, bool selected); - - double timeToPosition(int64_t time); - int64_t positionToTime(int pos); - - void calculateRows(); - - const trace::Profile::Frame* frameAtTime(int64_t time); - const trace::Profile::Call* cpuCallAtTime(int64_t time); - const trace::Profile::Call* drawCallAtTime(int64_t time); - const trace::Profile::Call* drawCallAtTime(int64_t time, int program); - -private: - /* Data */ - trace::Profile* m_profile; - std::vector m_rowPrograms; - - /* Scrollbars */ - int m_scrollX; - int m_scrollY; - int m_maxScrollX; - int m_maxScrollY; - - /* Viewport */ - int m_viewWidth; - int m_viewHeight; - - /* Visible Times */ - int64_t m_time; - int64_t m_timeEnd; - int64_t m_timeMin; - int64_t m_timeMax; - int64_t m_timeWidth; - int64_t m_timeWidthMin; - int64_t m_timeWidthMax; - - int m_selectionLeft; - int m_selectionRight; - - /* Visible Rows */ - int m_row; - int m_rowCount; - - /* Mouse data */ - int m_mousePressRow; - int64_t m_mousePressTime; - QPoint m_mousePosition; - QPoint m_mousePressPosition; - MousePressMode m_mousePressMode; - - /* Style */ - int m_rowHeight; - int m_axisWidth; - int m_axisHeight; - QPen m_axisLine; - QPen m_axisBorder; - QPen m_axisForeground; - QBrush m_axisBackground; - QPen m_itemBorder; - QPen m_itemGpuForeground; - QBrush m_itemGpuBackground; - QPen m_itemCpuForeground; - QBrush m_itemCpuBackground; - QPen m_itemDeselectedForeground; - QBrush m_itemDeselectedBackground; - QPen m_selectionBorder; - QBrush m_selectionBackground; - QPen m_zoomBorder; - QBrush m_zoomBackground; - - /* Selection */ - struct { - SelectType type; - - unsigned program; - - int64_t timeStart; - int64_t timeEnd; - } m_selection; -}; - -#endif // TIMELINEWIDGET_H diff --git a/gui/ui/profiledialog.ui b/gui/ui/profiledialog.ui index 7a54ef7..bbcfbe9 100644 --- a/gui/ui/profiledialog.ui +++ b/gui/ui/profiledialog.ui @@ -31,7 +31,7 @@ false - + 0 @@ -48,55 +48,6 @@ 0 - - - - 2 - - - - - - 0 - 0 - - - - Qt::WheelFocus - - - - - - - - 0 - 0 - - - - Qt::Vertical - - - - - - - - 0 - 0 - - - - 10000 - - - Qt::Horizontal - - - - - @@ -120,7 +71,7 @@ 0 - + 0 @@ -139,7 +90,7 @@ - + 0 @@ -194,167 +145,20 @@ - TimelineWidget + GraphWidget QWidget -
timelinewidget.h
+
graphing/graphwidget.h
1 - - horizontalScrollValueChanged(int) - verticalScrollValueChanged(int) - horizontalScrollMaxChanged(int) - verticalScrollMaxChanged(int) - jumpToCall(int) - selectedTime(int64_t,int64_t) - selectedProgram(unsigned) - selectedNone() - setHorizontalScrollValue(int) - setVerticalScrollValue(int) -
- GraphWidget + CallDurationGraph QWidget -
graphwidget.h
+
calldurationgraph.h
1 - - viewChanged(int,int) - selectedTime(int64_t,int64_t) - selectedProgram(unsigned) - selectedNone() - changeView(int,int) -
- - m_horizontalScrollBar - sliderMoved(int) - m_timeline - setHorizontalScrollValue(int) - - - 373 - 434 - - - 373 - 213 - - - - - m_verticalScrollBar - sliderMoved(int) - m_timeline - setVerticalScrollValue(int) - - - 754 - 213 - - - 373 - 213 - - - - - m_timeline - horizontalScrollValueChanged(int) - m_horizontalScrollBar - setValue(int) - - - 373 - 213 - - - 373 - 434 - - - - - m_timeline - verticalScrollValueChanged(int) - m_verticalScrollBar - setValue(int) - - - 373 - 213 - - - 754 - 213 - - - - - m_timeline - horizontalScrollMaxChanged(int) - ProfileDialog - setHorizontalScrollMax(int) - - - 373 - 213 - - - 511 - 383 - - - - - m_timeline - verticalScrollMaxChanged(int) - ProfileDialog - setVerticalScrollMax(int) - - - 373 - 213 - - - 511 - 383 - - - - - m_cpuGraph - viewChanged(int,int) - m_gpuGraph - changeView(int,int) - - - 511 - 687 - - - 511 - 527 - - - - - m_gpuGraph - viewChanged(int,int) - m_cpuGraph - changeView(int,int) - - - 511 - 527 - - - 511 - 687 - - - m_table doubleClicked(QModelIndex) @@ -371,150 +175,6 @@ - - m_cpuGraph - selectedTime(int64_t,int64_t) - ProfileDialog - selectTime(int64_t,int64_t) - - - 552 - 555 - - - 552 - 401 - - - - - m_gpuGraph - selectedTime(int64_t,int64_t) - ProfileDialog - selectTime(int64_t,int64_t) - - - 552 - 455 - - - 552 - 401 - - - - - m_timeline - selectedTime(int64_t,int64_t) - ProfileDialog - selectTime(int64_t,int64_t) - - - 544 - 192 - - - 552 - 401 - - - - - m_cpuGraph - selectedProgram(unsigned) - ProfileDialog - selectProgram(unsigned) - - - 552 - 555 - - - 552 - 401 - - - - - m_gpuGraph - selectedProgram(unsigned) - ProfileDialog - selectProgram(unsigned) - - - 552 - 455 - - - 552 - 401 - - - - - m_timeline - selectedProgram(unsigned) - ProfileDialog - selectProgram(unsigned) - - - 544 - 192 - - - 552 - 401 - - - - - m_cpuGraph - selectedNone() - ProfileDialog - selectNone() - - - 552 - 555 - - - 552 - 401 - - - - - m_gpuGraph - selectedNone() - ProfileDialog - selectNone() - - - 552 - 455 - - - 552 - 401 - - - - - m_timeline - selectedNone() - ProfileDialog - selectNone() - - - 544 - 192 - - - 552 - 401 - - - jumpToCall(int) -- 2.43.0