From fc4f55a3193269f86c142219f5593dd8b8e9b3c8 Mon Sep 17 00:00:00 2001 From: James Benton Date: Wed, 8 Aug 2012 17:09:07 +0100 Subject: [PATCH] Add gui support for trace profiling. --- common/trace_profiler.cpp | 33 ++ common/trace_profiler.hpp | 35 ++ gui/CMakeLists.txt | 5 + gui/mainwindow.cpp | 99 ++++- gui/mainwindow.h | 9 + gui/profiledialog.cpp | 83 ++++ gui/profiledialog.h | 34 ++ gui/profiletablemodel.cpp | 283 ++++++++++++++ gui/profiletablemodel.h | 65 ++++ gui/retracer.cpp | 84 +++- gui/retracer.h | 12 + gui/timelinewidget.cpp | 694 ++++++++++++++++++++++++++++++++++ gui/timelinewidget.h | 122 ++++++ gui/ui/mainwindow.ui | 27 +- gui/ui/profiledialog.ui | 276 ++++++++++++++ gui/ui/profilereplaydialog.ui | 112 ++++++ 16 files changed, 1948 insertions(+), 25 deletions(-) create mode 100644 gui/profiledialog.cpp create mode 100644 gui/profiledialog.h create mode 100644 gui/profiletablemodel.cpp create mode 100644 gui/profiletablemodel.h create mode 100644 gui/timelinewidget.cpp create mode 100644 gui/timelinewidget.h create mode 100644 gui/ui/profiledialog.ui create mode 100644 gui/ui/profilereplaydialog.ui diff --git a/common/trace_profiler.cpp b/common/trace_profiler.cpp index 02d88dc..8a3b137 100644 --- a/common/trace_profiler.cpp +++ b/common/trace_profiler.cpp @@ -25,6 +25,9 @@ #include "trace_profiler.hpp" #include +#include +#include +#include namespace trace { Profiler::Profiler() @@ -155,4 +158,34 @@ void Profiler::addFrameEnd(uint64_t gpuEnd, uint64_t cpuEnd) << " " << cpuDuration << std::endl; } + +void Profiler::parseLine(const char* line, Profile* profile) +{ + char name[64]; + + if (line[0] == '#' || strlen(line) < 12) + return; + + if (strncmp(line, "call ", 5) == 0) { + assert(profile->frames.size()); + + Profile::Call call; + sscanf(line, "call %u %li %li %li %li %li %u %s", &call.no, &call.gpuStart, &call.gpuDuration, &call.cpuStart, &call.cpuDuration, &call.pixels, &call.program, name); + call.name = name; + profile->frames.back().calls.push_back(call); + } else if (strncmp(line, "frame_begin ", 12) == 0) { + Profile::Frame frame; + frame.gpuDuration = 0; + frame.gpuDuration = 0; + sscanf(line, "frame_begin %u %li %li", &frame.no, &frame.gpuStart, &frame.cpuStart); + profile->frames.push_back(frame); + } else if (strncmp(line, "frame_end ", 10) == 0) { + assert(profile->frames.size()); + + Profile::Frame& frame = profile->frames.back(); + unsigned no; + sscanf(line, "frame_end %u %*li %li %*li %li", &no, &frame.gpuDuration, &frame.cpuDuration); + assert(no == frame.no); + } +} } diff --git a/common/trace_profiler.hpp b/common/trace_profiler.hpp index c36fd77..c8b38b9 100644 --- a/common/trace_profiler.hpp +++ b/common/trace_profiler.hpp @@ -27,10 +27,43 @@ #define TRACE_PROFILER_H #include +#include #include namespace trace { + +struct Profile { + struct Call { + unsigned no; + int64_t gpuStart; + int64_t gpuDuration; + int64_t cpuStart; + int64_t cpuDuration; + int64_t pixels; + unsigned program; + std::string name; + + typedef std::vector::iterator iterator; + typedef std::vector::const_iterator const_iterator; + }; + + struct Frame { + unsigned no; + int64_t gpuStart; + int64_t gpuDuration; + int64_t cpuStart; + int64_t cpuDuration; + + std::vector calls; + + typedef std::vector::iterator iterator; + typedef std::vector::const_iterator const_iterator; + }; + + std::vector frames; +}; + class Profiler { public: @@ -49,6 +82,8 @@ public: uint64_t gpuStart, uint64_t gpuDuration, uint64_t cpuStart, uint64_t cpuDuration); + static void parseLine(const char* line, Profile* profile); + private: uint64_t baseGpuTime; uint64_t baseCpuTime; diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 070aca7..6037b7d 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -14,6 +14,8 @@ set(qapitrace_SRCS jumpwidget.cpp mainwindow.cpp main.cpp + profiledialog.cpp + profiletablemodel.cpp retracer.cpp saverthread.cpp searchwidget.cpp @@ -23,6 +25,7 @@ set(qapitrace_SRCS traceloader.cpp traceprocess.cpp trimprocess.cpp + timelinewidget.cpp vertexdatainterpreter.cpp ) @@ -35,6 +38,8 @@ set(qapitrace_UIS ui/imageviewer.ui ui/jumpwidget.ui ui/mainwindow.ui + ui/profiledialog.ui + ui/profilereplaydialog.ui ui/retracerdialog.ui ui/settings.ui ui/tracedialog.ui diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 50f7d91..caf9e37 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -8,6 +8,7 @@ #include "argumentseditor.h" #include "imageviewer.h" #include "jumpwidget.h" +#include "profiledialog.h" #include "retracer.h" #include "searchwidget.h" #include "settingsdialog.h" @@ -17,7 +18,9 @@ #include "trimprocess.h" #include "thumbnail.h" #include "ui_retracerdialog.h" +#include "ui_profilereplaydialog.h" #include "vertexdatainterpreter.h" +#include "trace_profiler.hpp" #include #include @@ -49,6 +52,15 @@ MainWindow::MainWindow() initConnections(); } +MainWindow::~MainWindow() +{ + delete m_trace; + m_trace = 0; + + delete m_proxyModel; + delete m_model; +} + void MainWindow::createTrace() { if (!m_traceProcess->canTrace()) { @@ -164,21 +176,52 @@ void MainWindow::replayStart() "Please wait until it finishes and try again.")); return; } + QDialog dlg; Ui_RetracerDialog dlgUi; dlgUi.setupUi(&dlg); dlgUi.doubleBufferingCB->setChecked( m_retracer->isDoubleBuffered()); + dlgUi.errorCheckCB->setChecked( !m_retracer->isBenchmarking()); if (dlg.exec() == QDialog::Accepted) { m_retracer->setDoubleBuffered( dlgUi.doubleBufferingCB->isChecked()); + m_retracer->setBenchmarking( !dlgUi.errorCheckCB->isChecked()); - replayTrace(false, true); + + m_retracer->setProfiling(false, false, false); + + replayTrace(false, false); + } +} + +void MainWindow::replayProfile() +{ + if (m_trace->isSaving()) { + QMessageBox::warning( + this, + tr("Trace Saving"), + tr("QApiTrace is currently saving the edited trace file. " + "Please wait until it finishes and try again.")); + return; + } + + QDialog dlg; + Ui_ProfileReplayDialog dlgUi; + dlgUi.setupUi(&dlg); + + if (dlg.exec() == QDialog::Accepted) { + m_retracer->setProfiling( + dlgUi.gpuTimesCB->isChecked(), + dlgUi.cpuTimesCB->isChecked(), + dlgUi.pixelsDrawnCB->isChecked()); + + replayTrace(false, false); } } @@ -187,6 +230,7 @@ void MainWindow::replayStop() m_retracer->quit(); m_ui.actionStop->setEnabled(false); m_ui.actionReplay->setEnabled(true); + m_ui.actionProfile->setEnabled(true); m_ui.actionLookupState->setEnabled(true); m_ui.actionShowThumbnails->setEnabled(true); } @@ -200,12 +244,14 @@ void MainWindow::newTraceFile(const QString &fileName) if (fileName.isEmpty()) { m_ui.actionReplay->setEnabled(false); + m_ui.actionProfile->setEnabled(false); m_ui.actionLookupState->setEnabled(false); m_ui.actionShowThumbnails->setEnabled(false); setWindowTitle(tr("QApiTrace")); } else { QFileInfo info(fileName); m_ui.actionReplay->setEnabled(true); + m_ui.actionProfile->setEnabled(true); m_ui.actionLookupState->setEnabled(true); m_ui.actionShowThumbnails->setEnabled(true); m_ui.actionTrim->setEnabled(true); @@ -218,6 +264,7 @@ void MainWindow::replayFinished(const QString &message) { m_ui.actionStop->setEnabled(false); m_ui.actionReplay->setEnabled(true); + m_ui.actionProfile->setEnabled(true); m_ui.actionLookupState->setEnabled(true); m_ui.actionShowThumbnails->setEnabled(true); @@ -235,6 +282,7 @@ void MainWindow::replayError(const QString &message) { m_ui.actionStop->setEnabled(false); m_ui.actionReplay->setEnabled(true); + m_ui.actionProfile->setEnabled(true); m_ui.actionLookupState->setEnabled(true); m_ui.actionShowThumbnails->setEnabled(true); m_stateEvent = 0; @@ -316,6 +364,9 @@ void MainWindow::replayTrace(bool dumpState, bool dumpThumbnails) statusBar()->showMessage( tr("Capturing thumbnails...")); } + } else if (m_retracer->isProfiling()) { + statusBar()->showMessage( + tr("Profiling draw calls in trace file...")); } else { statusBar()->showMessage( tr("Replaying the trace file...")); @@ -378,15 +429,6 @@ void MainWindow::trim() trimEvent(); } -MainWindow::~MainWindow() -{ - delete m_trace; - m_trace = 0; - - delete m_proxyModel; - delete m_model; -} - static void variantToString(const QVariant &var, QString &str) { @@ -757,6 +799,8 @@ void MainWindow::initObjects() m_traceProcess = new TraceProcess(this); m_trimProcess = new TrimProcess(this); + + m_profileDialog = new ProfileDialog(); } void MainWindow::initConnections() @@ -788,6 +832,8 @@ void MainWindow::initConnections() this, SLOT(replayError(const QString&))); connect(m_retracer, SIGNAL(foundState(ApiTraceState*)), this, SLOT(replayStateFound(ApiTraceState*))); + connect(m_retracer, SIGNAL(foundProfile(trace::Profile*)), + this, SLOT(replayProfileFound(trace::Profile*))); connect(m_retracer, SIGNAL(foundThumbnails(const QList&)), this, SLOT(replayThumbnailsFound(const QList&))); connect(m_retracer, SIGNAL(retraceErrors(const QList&)), @@ -823,6 +869,8 @@ void MainWindow::initConnections() connect(m_ui.actionReplay, SIGNAL(triggered()), this, SLOT(replayStart())); + connect(m_ui.actionProfile, SIGNAL(triggered()), + this, SLOT(replayProfile())); connect(m_ui.actionStop, SIGNAL(triggered()), this, SLOT(replayStop())); connect(m_ui.actionLookupState, SIGNAL(triggered()), @@ -881,6 +929,26 @@ void MainWindow::initConnections() connect(m_ui.errorsTreeWidget, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(slotErrorSelected(QTreeWidgetItem*))); + + connect(m_ui.actionShowProfileDialog, SIGNAL(triggered(bool)), + m_profileDialog, SLOT(show())); + connect(m_profileDialog, SIGNAL(jumpToCall(int)), + this, SLOT(slotJumpTo(int))); +} + +void MainWindow::closeEvent(QCloseEvent * event) +{ + m_profileDialog->close(); + QMainWindow::closeEvent(event); +} + +void MainWindow::replayProfileFound(trace::Profile *profile) +{ + m_ui.actionShowProfileDialog->setEnabled(true); + m_profileDialog->setProfile(profile); + m_profileDialog->show(); + m_profileDialog->activateWindow(); + m_profileDialog->setFocus(); } void MainWindow::replayStateFound(ApiTraceState *state) @@ -1308,6 +1376,7 @@ void MainWindow::slotFoundFrameStart(ApiTraceFrame *frame) QModelIndex idx = m_proxyModel->indexForCall(call); if (idx.isValid()) { m_ui.callView->setCurrentIndex(idx); + m_ui.callView->scrollTo(idx, QAbstractItemView::PositionAtTop); break; } ++itr; @@ -1330,6 +1399,7 @@ void MainWindow::slotFoundFrameEnd(ApiTraceFrame *frame) QModelIndex idx = m_proxyModel->indexForCall(call); if (idx.isValid()) { m_ui.callView->setCurrentIndex(idx); + m_ui.callView->scrollTo(idx, QAbstractItemView::PositionAtBottom); break; } } while (itr != calls.constBegin()); @@ -1337,9 +1407,12 @@ void MainWindow::slotFoundFrameEnd(ApiTraceFrame *frame) void MainWindow::slotJumpToResult(ApiTraceCall *call) { - QModelIndex index = m_proxyModel->indexForCall(call); - if (index.isValid()) { - m_ui.callView->setCurrentIndex(index); + QModelIndex idx = m_proxyModel->indexForCall(call); + if (idx.isValid()) { + activateWindow(); + m_ui.callView->setFocus(); + m_ui.callView->setCurrentIndex(idx); + m_ui.callView->scrollTo(idx, QAbstractItemView::PositionAtCenter); } else { statusBar()->showMessage(tr("Call has been filtered out.")); } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 1346b86..2248127 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -30,8 +30,11 @@ class SearchWidget; class ShadersSourceWidget; class TraceProcess; class TrimProcess; +class ProfileDialog; class VertexDataInterpreter; +namespace trace { struct Profile; } + class MainWindow : public QMainWindow { Q_OBJECT @@ -48,9 +51,11 @@ private slots: void createTrace(); void openTrace(); void replayStart(); + void replayProfile(); void replayStop(); void replayFinished(const QString &message); void replayStateFound(ApiTraceState *state); + void replayProfileFound(trace::Profile *state); void replayThumbnailsFound(const QList &thumbnails); void replayError(const QString &msg); void startedLoadingTrace(); @@ -107,6 +112,8 @@ private: ApiTraceFrame *currentFrame() const; ApiTraceCall *currentCall() const; +protected: + virtual void closeEvent(QCloseEvent * event); private: Ui_MainWindow m_ui; @@ -141,6 +148,8 @@ private: ArgumentsEditor *m_argsEditor; ApiTraceEvent *m_nonDefaultsLookupEvent; + + ProfileDialog* m_profileDialog; }; diff --git a/gui/profiledialog.cpp b/gui/profiledialog.cpp new file mode 100644 index 0000000..5568b54 --- /dev/null +++ b/gui/profiledialog.cpp @@ -0,0 +1,83 @@ +#include "profiledialog.h" +#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_timeline, SIGNAL(selectionChanged(int64_t,int64_t)), SLOT(selectionChanged(int64_t,int64_t))); +} + + +ProfileDialog::~ProfileDialog() +{ + delete m_profile; +} + + +void ProfileDialog::tableDoubleClicked(const QModelIndex& index) +{ + ProfileTableModel* model = (ProfileTableModel*)m_table->model(); + const trace::Profile::Call* call = model->getJumpCall(index); + + if (call) { + emit jumpToCall(call->no); + } +} + + +void ProfileDialog::setProfile(trace::Profile* profile) +{ + if (m_profile) { + delete m_profile; + } + + m_profile = profile; + m_timeline->setProfile(m_profile); + + ProfileTableModel* model = new ProfileTableModel(m_table); + model->setProfile(m_profile); + + delete m_table->model(); + m_table->setModel(model); + m_table->resizeColumnsToContents(); + m_table->sortByColumn(2, Qt::DescendingOrder); +} + + +void ProfileDialog::selectionChanged(int64_t start, int64_t end) +{ + ProfileTableModel* model = (ProfileTableModel*)m_table->model(); + model->setTimeSelection(start, end); + m_table->reset(); +} + + +void ProfileDialog::setVerticalScrollMax(int max) +{ + if (max <= 0) { + m_verticalScrollBar->hide(); + } else { + m_verticalScrollBar->show(); + m_verticalScrollBar->setMinimum(0); + m_verticalScrollBar->setMaximum(max); + } +} + + +void ProfileDialog::setHorizontalScrollMax(int max) +{ + if (max <= 0) { + m_horizontalScrollBar->hide(); + } else { + m_horizontalScrollBar->show(); + m_horizontalScrollBar->setMinimum(0); + m_horizontalScrollBar->setMaximum(max); + } +} + +#include "profiledialog.moc" diff --git a/gui/profiledialog.h b/gui/profiledialog.h new file mode 100644 index 0000000..9946efe --- /dev/null +++ b/gui/profiledialog.h @@ -0,0 +1,34 @@ +#ifndef PROFILEDIALOG_H +#define PROFILEDIALOG_H + +#include "ui_profiledialog.h" +#include + +namespace trace { class Profile; } + +class ProfileDialog : public QDialog, public Ui_ProfileDialog +{ + Q_OBJECT + +public: + ProfileDialog(QWidget *parent = 0); + ~ProfileDialog(); + + void setProfile(trace::Profile* profile); + +public slots: + void setVerticalScrollMax(int max); + void setHorizontalScrollMax(int max); + + void tableDoubleClicked(const QModelIndex& index); + + void selectionChanged(int64_t start, int64_t end); + +signals: + void jumpToCall(int call); + +private: + trace::Profile *m_profile; +}; + +#endif diff --git a/gui/profiletablemodel.cpp b/gui/profiletablemodel.cpp new file mode 100644 index 0000000..845ff14 --- /dev/null +++ b/gui/profiletablemodel.cpp @@ -0,0 +1,283 @@ +#include "profiletablemodel.h" + +typedef trace::Profile::Call Call; +typedef trace::Profile::Frame Frame; + +enum { + COLUMN_PROGRAM, + COLUMN_USAGES, + COLUMN_GPU_TIME, + COLUMN_CPU_TIME, + COLUMN_PIXELS_DRAWN, + COLUMN_GPU_AVERAGE, + COLUMN_CPU_AVERAGE, + COLUMN_PIXELS_AVERAGE, + MAX_COLUMN +}; + +static QString columnNames[] = { + QString("Program"), + QString("Calls"), + QString("Total GPU Time"), + QString("Total CPU Time"), + QString("Total Pixels Drawn"), + QString("Avg GPU Time"), + QString("Avg CPU Time"), + QString("Avg Pixels Drawn") +}; + +ProfileTableModel::ProfileTableModel(QObject *parent) + : QAbstractTableModel(parent), + m_profile(0), + m_sortColumn(COLUMN_GPU_TIME), + m_sortOrder(Qt::DescendingOrder) +{ +} + + +void ProfileTableModel::setProfile(trace::Profile* profile) +{ + m_profile = profile; + m_timeMin = m_profile->frames.front().gpuStart; + m_timeMax = m_profile->frames.back().gpuStart + m_profile->frames.back().gpuDuration; + updateModel(); +} + + +void ProfileTableModel::setTimeSelection(int64_t start, int64_t end) +{ + m_timeMin = start; + m_timeMax = end; + updateModel(); + sort(m_sortColumn, m_sortOrder); +} + + +/** + * Creates the row data from trace profile + */ +void ProfileTableModel::updateModel() +{ + if (m_timeMin == m_timeMax) { + m_timeMin = m_profile->frames.front().gpuStart; + m_timeMax = m_profile->frames.back().gpuStart + m_profile->frames.back().gpuDuration; + } + + for (QList::iterator itr = m_rowData.begin(); itr != m_rowData.end(); ++itr) { + ProfileTableRow& row = *itr; + + row.uses = 0; + row.pixels = 0; + row.gpuTime = 0; + row.cpuTime = 0; + row.longestCpu = NULL; + row.longestGpu = NULL; + row.longestPixel = NULL; + } + + for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) { + const Frame& frame = *itr; + + if (frame.gpuStart > m_timeMax) { + break; + } + + if ((frame.gpuStart + frame.gpuDuration) < m_timeMin) { + continue; + } + + for (Call::const_iterator jtr = frame.calls.begin(); jtr != frame.calls.end(); ++jtr) { + const Call& call = *jtr; + + if (call.gpuStart > m_timeMax) { + break; + } + + if ((call.gpuStart + call.gpuDuration) < m_timeMin) { + continue; + } + + ProfileTableRow* row = getRow(call.program); + if (!row) { + m_rowData.append(ProfileTableRow()); + row = &m_rowData.back(); + } + + row->uses++; + row->program = call.program; + row->gpuTime += call.gpuDuration; + row->cpuTime += call.cpuDuration; + row->pixels += call.pixels; + + if (!row->longestGpu || row->longestGpu->gpuDuration < call.gpuDuration) { + row->longestGpu = &call; + } + + if (!row->longestCpu || row->longestCpu->cpuDuration < call.cpuDuration) { + row->longestCpu = &call; + } + + if (!row->longestPixel || row->longestPixel->pixels < call.pixels) { + row->longestPixel = &call; + } + } + } +} + + +/** + * Get the appropriate call associated with an item in the table + */ +const Call* ProfileTableModel::getJumpCall(const QModelIndex & index) const { + const ProfileTableRow& row = m_rowData[index.row()]; + + switch(index.column()) { + case COLUMN_GPU_TIME: + case COLUMN_GPU_AVERAGE: + return row.longestGpu; + case COLUMN_CPU_TIME: + case COLUMN_CPU_AVERAGE: + return row.longestCpu; + case COLUMN_PIXELS_DRAWN: + case COLUMN_PIXELS_AVERAGE: + return row.longestPixel; + } + + return NULL; +} + + +ProfileTableRow* ProfileTableModel::getRow(unsigned program) { + for (QList::iterator itr = m_rowData.begin(); itr != m_rowData.end(); ++itr) { + if (itr->program == program) + return &*itr; + } + + return NULL; +} + + +int ProfileTableModel::rowCount(const QModelIndex & parent) const +{ + if (!parent.isValid()) { + return m_rowData.size(); + } else { + return 0; + } +} + + +int ProfileTableModel::columnCount(const QModelIndex & /*parent*/) const +{ + return MAX_COLUMN; +} + + +QVariant ProfileTableModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + const ProfileTableRow& row = m_rowData[index.row()]; + + switch(index.column()) { + case COLUMN_PROGRAM: + return row.program; + case COLUMN_USAGES: + return row.uses; + case COLUMN_GPU_TIME: + return row.gpuTime; + case COLUMN_CPU_TIME: + return row.cpuTime; + case COLUMN_PIXELS_DRAWN: + return row.pixels; + case COLUMN_GPU_AVERAGE: + return (row.uses <= 0) ? 0 : (row.gpuTime / row.uses); + case COLUMN_CPU_AVERAGE: + return (row.uses <= 0) ? 0 : (row.cpuTime / row.uses); + case COLUMN_PIXELS_AVERAGE: + return (row.uses <= 0) ? 0 : (row.pixels / row.uses); + } + } else if (role == Qt::TextAlignmentRole) { + return Qt::AlignRight; + } + + return QVariant(); +} + + +QVariant ProfileTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + if (section >= 0 && section < MAX_COLUMN) { + return columnNames[section]; + } + } + + return QVariant(); +} + + +class ProgramSorter { +public: + ProgramSorter(int column, Qt::SortOrder order) + : mSortColumn(column), + mSortOrder(order) + { + } + + bool operator()(const ProfileTableRow &p1, const ProfileTableRow &p2) const + { + bool result = true; + + switch(mSortColumn) { + case COLUMN_PROGRAM: + result = p1.program < p2.program; + break; + case COLUMN_USAGES: + result = p1.uses < p2.uses; + break; + case COLUMN_GPU_TIME: + result = p1.gpuTime < p2.gpuTime; + break; + case COLUMN_CPU_TIME: + result = p1.cpuTime < p2.cpuTime; + break; + case COLUMN_PIXELS_DRAWN: + result = p1.pixels < p2.pixels; + break; + case COLUMN_GPU_AVERAGE: + result = (p1.gpuTime / p1.uses) < (p2.gpuTime / p2.uses); + break; + case COLUMN_CPU_AVERAGE: + result = (p1.cpuTime / p1.uses) < (p2.cpuTime / p2.uses); + break; + case COLUMN_PIXELS_AVERAGE: + result = (p1.pixels / p1.uses) < (p2.pixels / p2.uses); + break; + } + + if (mSortOrder == Qt::DescendingOrder) { + return !result; + } else { + return result; + } + } + +private: + int mSortColumn; + Qt::SortOrder mSortOrder; +}; + + +void ProfileTableModel::sort(int column, Qt::SortOrder order) { + m_sortColumn = column; + m_sortOrder = order; + qSort(m_rowData.begin(), m_rowData.end(), ProgramSorter(column, order)); + emit dataChanged(createIndex(0, 0), createIndex(m_rowData.size(), MAX_COLUMN)); +} + + +#include "profiletablemodel.moc" diff --git a/gui/profiletablemodel.h b/gui/profiletablemodel.h new file mode 100644 index 0000000..fe7979a --- /dev/null +++ b/gui/profiletablemodel.h @@ -0,0 +1,65 @@ +#ifndef PROFILETABLEMODEL_H +#define PROFILETABLEMODEL_H + +#include +#include "trace_profiler.hpp" + +struct ProfileTableRow +{ + ProfileTableRow() + : program(0), + uses(0), + gpuTime(0), + cpuTime(0), + pixels(0), + longestGpu(0), + longestCpu(0), + longestPixel(0) + { + } + + unsigned program; + qulonglong uses; + qulonglong gpuTime; + qulonglong cpuTime; + qulonglong pixels; + + const trace::Profile::Call* longestGpu; + const trace::Profile::Call* longestCpu; + const trace::Profile::Call* longestPixel; +}; + +class ProfileTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + ProfileTableModel(QObject *parent = NULL); + + void setProfile(trace::Profile* profile); + void setTimeSelection(int64_t start, int64_t end); + + const trace::Profile::Call* getJumpCall(const QModelIndex & index) const; + + virtual int rowCount(const QModelIndex & parent) const; + virtual int columnCount(const QModelIndex & parent) const; + + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + +private: + void updateModel(); + ProfileTableRow* getRow(unsigned program); + +private: + QList m_rowData; + trace::Profile *m_profile; + int64_t m_timeMin; + int64_t m_timeMax; + int m_sortColumn; + Qt::SortOrder m_sortOrder; +}; + +#endif // PROFILEMODEL_H diff --git a/gui/retracer.cpp b/gui/retracer.cpp index bbe638c..87d9f37 100644 --- a/gui/retracer.cpp +++ b/gui/retracer.cpp @@ -5,6 +5,8 @@ #include "image.hpp" +#include "trace_profiler.hpp" + #include #include #include @@ -129,7 +131,10 @@ Retracer::Retracer(QObject *parent) m_benchmarking(false), m_doubleBuffered(true), m_captureState(false), - m_captureCall(0) + m_captureCall(0), + m_profileGpu(false), + m_profileCpu(false), + m_profilePixels(false) { qRegisterMetaType >(); @@ -182,6 +187,33 @@ void Retracer::setDoubleBuffered(bool db) m_doubleBuffered = db; } +bool Retracer::isProfilingGpu() const +{ + return m_profileGpu; +} + +bool Retracer::isProfilingCpu() const +{ + return m_profileCpu; +} + +bool Retracer::isProfilingPixels() const +{ + return m_profilePixels; +} + +bool Retracer::isProfiling() const +{ + return m_profileGpu || m_profileCpu || m_profilePixels; +} + +void Retracer::setProfiling(bool gpu, bool cpu, bool pixels) +{ + m_profileGpu = gpu; + m_profileCpu = cpu; + m_profilePixels = pixels; +} + void Retracer::setCaptureAtCallNumber(qlonglong num) { m_captureCall = num; @@ -212,7 +244,6 @@ void Retracer::setCaptureThumbnails(bool enable) m_captureThumbnails = enable; } - /** * Starting point for the retracing thread. * @@ -255,20 +286,34 @@ void Retracer::run() return; } - if (m_doubleBuffered) { - arguments << QLatin1String("-db"); - } else { - arguments << QLatin1String("-sb"); - } - if (m_captureState) { arguments << QLatin1String("-D"); arguments << QString::number(m_captureCall); } else if (m_captureThumbnails) { arguments << QLatin1String("-s"); // emit snapshots arguments << QLatin1String("-"); // emit to stdout - } else if (m_benchmarking) { - arguments << QLatin1String("-b"); + } else if (isProfiling()) { + if (m_profileGpu) { + arguments << QLatin1String("-pgpu"); + } + + if (m_profileCpu) { + arguments << QLatin1String("-pcpu"); + } + + if (m_profilePixels) { + arguments << QLatin1String("-ppd"); + } + } else { + if (m_doubleBuffered) { + arguments << QLatin1String("-db"); + } else { + arguments << QLatin1String("-sb"); + } + + if (m_benchmarking) { + arguments << QLatin1String("-b"); + } } arguments << m_fileName; @@ -291,6 +336,7 @@ void Retracer::run() QList thumbnails; QVariantMap parsedJson; + trace::Profile* profile = NULL; process.setReadChannel(QProcess::StandardOutput); if (process.waitForReadyRead(-1)) { @@ -369,7 +415,21 @@ void Retracer::run() } Q_ASSERT(process.state() != QProcess::Running); + } else if (isProfiling()) { + profile = new trace::Profile(); + process.waitForFinished(-1); + + while (!io.atEnd()) { + char line[256]; + qint64 lineLength; + lineLength = io.readLine(line, 256); + + if (lineLength == -1) + break; + + trace::Profiler::parseLine(line, profile); + } } else { QByteArray output; output = process.readAllStandardOutput(); @@ -431,6 +491,10 @@ void Retracer::run() emit foundThumbnails(thumbnails); } + if (isProfiling() && profile) { + emit foundProfile(profile); + } + if (!errors.isEmpty()) { emit retraceErrors(errors); } diff --git a/gui/retracer.h b/gui/retracer.h index d6da7ac..e889d88 100644 --- a/gui/retracer.h +++ b/gui/retracer.h @@ -9,6 +9,8 @@ class ApiTraceState; +namespace trace { struct Profile; } + class Retracer : public QThread { Q_OBJECT @@ -26,6 +28,12 @@ public: bool isDoubleBuffered() const; void setDoubleBuffered(bool db); + bool isProfilingGpu() const; + bool isProfilingCpu() const; + bool isProfilingPixels() const; + bool isProfiling() const; + void setProfiling(bool gpu, bool cpu, bool pixels); + void setCaptureAtCallNumber(qlonglong num); qlonglong captureAtCallNumber() const; @@ -38,6 +46,7 @@ public: signals: void finished(const QString &output); void foundState(ApiTraceState *state); + void foundProfile(trace::Profile *profile); void foundThumbnails(const QList &thumbnails); void error(const QString &msg); void retraceErrors(const QList &errors); @@ -53,6 +62,9 @@ private: bool m_captureState; bool m_captureThumbnails; qlonglong m_captureCall; + bool m_profileGpu; + bool m_profileCpu; + bool m_profilePixels; QProcessEnvironment m_processEnvironment; }; diff --git a/gui/timelinewidget.cpp b/gui/timelinewidget.cpp new file mode 100644 index 0000000..e6d2712 --- /dev/null +++ b/gui/timelinewidget.cpp @@ -0,0 +1,694 @@ +#include "timelinewidget.h" +#include "trace_profiler.hpp" + +#include +#include +#include +#include +#include +#include +#include + +typedef trace::Profile::Call Call; +typedef trace::Profile::Frame Frame; + +TimelineWidget::TimelineWidget(QWidget *parent) + : QWidget(parent), + m_profile(NULL), + m_timeSelectionStart(0), + m_timeSelectionEnd(0), + m_rowHeight(20), + m_axisWidth(50), + m_axisHeight(20), + m_axisBorder(Qt::black), + m_axisBackground(Qt::lightGray), + m_itemBorder(Qt::red), + m_itemForeground(Qt::cyan), + m_itemBackground(Qt::red), + m_selectionBackground(QColor(245, 245, 255)), + m_selectionBorder(QColor(50, 50, 255)), + m_zoomBorder(Qt::green), + m_zoomBackground(QColor(100, 255, 100, 80)) +{ + setBackgroundRole(QPalette::Base); + setAutoFillBackground(true); + setMouseTracking(true); +} + + +/** + * 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); +} + + +/** + * Update the time selection + */ +void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify) +{ + m_timeSelectionStart = start; + m_timeSelectionEnd = end; + + if (notify) { + emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd); + } + + 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; +} + + +/** + * Return the item at position + */ +const VisibleItem* TimelineWidget::itemAtPosition(const QPoint& pos) +{ + foreach (const VisibleItem& item, m_visibleItems) { + if (pos.x() < item.rect.left() || pos.y() < item.rect.top()) + continue; + + if (pos.x() > item.rect.right() || pos.y() > item.rect.bottom()) + continue; + + return &item; + } + + return NULL; +} + + +/** + * Calculate the row order by total gpu time per shader + */ +void TimelineWidget::calculateRows() +{ + typedef QPair HeatProgram; + QList heats; + int idx; + + m_programRowMap.clear(); + m_rowCount = 0; + + for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) { + const Frame& frame = *itr; + + for (Call::const_iterator jtr = frame.calls.begin(); jtr != frame.calls.end(); ++jtr) { + const Call& call = *jtr; + + while (call.program >= heats.size()) { + heats.append(HeatProgram(0, heats.size())); + m_programRowMap.append(m_programRowMap.size()); + } + + heats[call.program].first += call.gpuDuration; + } + } + + qSort(heats); + idx = heats.size() - 1; + + for (QList::iterator itr = heats.begin(); itr != heats.end(); ++itr, --idx) { + HeatProgram& pair = *itr; + + if (pair.first == 0) { + m_programRowMap[pair.second] = -1; + } else { + m_programRowMap[pair.second] = idx; + m_rowCount++; + } + } +} + + +/** + * 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().gpuStart; + m_timeMax = m_profile->frames.back().gpuStart + m_profile->frames.back().gpuDuration; + + 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); + + 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 = width() - m_axisWidth; + m_viewHeight = height() - m_axisHeight; + + /* 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) +{ + if (!m_profile) { + return; + } + + /* Display tooltip if necessary */ + if (e->buttons() == Qt::NoButton) { + const VisibleItem* item = itemAtPosition(e->pos()); + + if (item) { + const trace::Profile::Call* call = item->call; + + QString text; + text = QString::fromStdString(call->name); + text += QString("\nCall: %1").arg(call->no); + text += QString("\nGPU Time: %1").arg(call->gpuDuration); + text += QString("\nCPU Time: %1").arg(call->cpuDuration); + text += QString("\nPixels Drawn: %1").arg(call->pixels); + + QToolTip::showText(e->globalPos(), text); + } + } + + m_mousePosition = e->pos(); + + if (e->buttons().testFlag(Qt::LeftButton)) { + QToolTip::hideText(); + + 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) { + int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth); + int64_t up = positionToTime(e->pos().x() - m_axisWidth); + + setSelection(qMin(down, up), qMax(down, up)); + } + + update(); + } +} + + +void TimelineWidget::mousePressEvent(QMouseEvent *e) +{ + if (e->buttons() & Qt::LeftButton) { + if (e->pos().y() < m_axisHeight) { + if (QApplication::keyboardModifiers() & Qt::ControlModifier) { + m_mousePressMode = RulerZoom; + } else { + m_mousePressMode = RulerSelect; + + int64_t time = positionToTime(e->pos().x() - m_axisWidth); + m_timeSelectionStart = time; + m_timeSelectionEnd = time; + } + } else { + m_mousePressMode = DragView; + } + + 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 */ + int64_t down = positionToTime(m_mousePressPosition.x() - m_axisWidth); + int64_t up = positionToTime(e->pos().x() - m_axisWidth); + + 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 (m_mousePressMode == RulerSelect) { + setSelection(m_timeSelectionStart, m_timeSelectionEnd, true); + } +} + + +void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e) +{ + int64_t time = positionToTime(e->pos().x() - m_axisWidth); + + if (e->pos().y() < m_axisHeight) { + int64_t time = positionToTime(e->pos().x() - m_axisWidth); + + for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) { + const Frame& frame = *itr; + + if (frame.gpuStart + frame.gpuDuration < time) + continue; + + if (frame.gpuStart > time) + break; + + setSelection(frame.gpuStart, frame.gpuStart + frame.gpuDuration, true); + return; + } + } + + if (const VisibleItem* item = itemAtPosition(e->pos())) { + emit jumpToCall(item->call->no); + } else if (time < m_timeSelectionStart || time > m_timeSelectionEnd) { + setSelection(0, 0, true); + } +} + + +void TimelineWidget::wheelEvent(QWheelEvent *e) +{ + if (!m_profile) { + return; + } + + int zoomPercent = 10; + + 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::paintHeatmapColumn(int x, QPainter& painter, QVector& rows) +{ + double timePerPixel = m_timeWidth; + timePerPixel /= m_viewWidth; + + for(int i = 0, y = 0; i < rows.size(); ++i, y += m_rowHeight) { + if (rows[i] == 0) + continue; + + if (y > m_viewHeight) + continue; + + double heat = rows[i] / timePerPixel; + heat = qBound(0.0, heat, 1.0); + heat *= 255.0; + + painter.setPen(QColor(255, 255 - heat, 255 - heat)); + painter.drawLine(x, y, x, y + m_rowHeight); + + rows[i] = 0; + } +} + + +/** + * Render the whole widget + */ +void TimelineWidget::paintEvent(QPaintEvent *e) +{ + if (!m_profile) + return; + + QVector heatMap(m_programRowMap.size(), 0); + QPainter painter(this); + int64_t timeEnd; + int selectionRight; + int selectionLeft; + int rowEnd; + int lastX; + + + /* + * Draw the active selection background + */ + if (m_timeSelectionStart != m_timeSelectionEnd) { + selectionLeft = timeToPosition(m_timeSelectionStart) + m_axisWidth; + selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth; + + selectionLeft = qBound(-1, selectionLeft, width() + 1); + selectionRight = qBound(-1, selectionRight, width() + 1); + + painter.setPen(Qt::NoPen); + painter.setBrush(m_selectionBackground); + painter.drawRect(selectionLeft, m_axisHeight, selectionRight - selectionLeft, m_viewHeight); + } + + + /* + * Draw profile heatmap + */ + rowEnd = m_row + (m_viewHeight / m_rowHeight) + 1; + timeEnd = m_time + m_timeWidth; + m_visibleItems.clear(); + lastX = 0; + + painter.translate(m_axisWidth + 1, m_axisHeight + 1 - (m_scrollY % m_rowHeight)); + painter.setBrush(m_itemBackground); + painter.setPen(m_itemBorder); + + for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) { + const Frame& frame = *itr; + + if (frame.gpuStart > timeEnd) + break; + + if (frame.gpuStart + frame.gpuDuration < m_time) + continue; + + for (Call::const_iterator jtr = frame.calls.begin(); jtr != frame.calls.end(); ++jtr) { + const Call& call = *jtr; + int row = m_programRowMap[call.program]; + + if (call.gpuStart + call.gpuDuration < m_time || call.gpuStart > timeEnd) + continue; + + if (row < m_row || row > rowEnd) + continue; + + double left = qMax(0.0, timeToPosition(call.gpuStart)); + double right = timeToPosition(call.gpuStart + call.gpuDuration); + + int leftX = left; + int rightX = right; + + if (lastX != leftX) { + paintHeatmapColumn(lastX, painter, heatMap); + lastX = leftX; + } + + row -= m_row; + + if (rightX <= lastX + 1) { + if (lastX == rightX) { + /* Fully contained in this X */ + heatMap[row] += call.gpuDuration; + } else { + /* Split call time between the two pixels it occupies */ + int64_t time = positionToTime(rightX); + + heatMap[row] += time - call.gpuStart; + paintHeatmapColumn(lastX, painter, heatMap); + + heatMap[row] += (call.gpuDuration + call.gpuStart) - time; + lastX = rightX; + } + } else { + leftX = (left + 0.5); + rightX = (right + 0.5); + + QRect rect; + rect.setLeft(leftX); + rect.setWidth(rightX - leftX); + rect.setTop(row * m_rowHeight); + rect.setHeight(m_rowHeight); + + VisibleItem vis; + vis.rect = painter.transform().mapRect(rect); + vis.frame = &frame; + vis.call = &call; + m_visibleItems.push_back(vis); + + painter.drawRect(rect); + + if (rect.width() > 6) { + rect.adjust(1, 0, -1, 0); + painter.setPen(m_itemForeground); + painter.drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, QString::fromStdString(call.name)); + painter.setPen(m_itemBorder); + } + } + } + } + + /* Paint the last column if needed */ + paintHeatmapColumn(lastX, painter, heatMap); + + + /* + * Draw the axis border and background + */ + painter.resetTransform(); + painter.setPen(Qt::NoPen); + painter.setBrush(m_axisBackground); + painter.drawRect(0, 0, m_viewWidth + m_axisWidth, m_axisHeight); + painter.drawRect(0, m_axisHeight, m_axisWidth, m_viewHeight); + + painter.setPen(m_axisBorder); + painter.drawLine(0, m_axisHeight, m_axisWidth + m_viewWidth, m_axisHeight); + painter.drawLine(m_axisWidth, 0, m_axisWidth, m_viewHeight + m_axisHeight); + + + /* + * Draw horizontal axis + */ + for (Frame::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) { + const Frame& frame = *itr; + int left, right, width; + bool drawText = true; + QString text; + + if (frame.gpuStart > timeEnd) + break; + + if (frame.gpuStart + frame.gpuDuration < m_time) + continue; + + left = timeToPosition(frame.gpuStart); + right = timeToPosition(frame.gpuStart + frame.gpuDuration) + 0.5; + + left = qBound(0, left, m_viewWidth) + m_axisWidth; + right = qBound(0, right, m_viewWidth) + m_axisWidth; + + width = right - left; + + text = QString("Frame %1").arg(frame.no); + + if (painter.fontMetrics().width(text) > width) { + text = QString("%1").arg(frame.no); + + if (painter.fontMetrics().width(text) > width) { + drawText = false; + } + } + + if (drawText) { + painter.drawText(left, 0, width, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, text); + } + + if (width > 5) { + painter.drawLine(left, 0, left, m_axisHeight); + painter.drawLine(right, 0, right, m_axisHeight); + } + } + + + /* + * Draw vertical axis + */ + painter.translate(0, -(m_scrollY % m_rowHeight)); + + for (int i = 0; i < m_programRowMap.size(); ++i) { + int y = (m_programRowMap[i] - m_row) * m_rowHeight; + + if (m_programRowMap[i] < 0 || y < -m_rowHeight || y > m_viewHeight) + continue; + + y += m_axisHeight + 1; + painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(i)); + painter.drawLine(0, y + m_rowHeight, m_axisWidth, y + m_rowHeight); + } + + /* Draw the top left square again to cover up any hanging over text */ + painter.resetTransform(); + painter.setPen(Qt::NoPen); + painter.setBrush(m_axisBackground); + painter.drawRect(0, 0, m_axisWidth, m_axisHeight); + + + /* + * Draw the active selection border + */ + if (m_timeSelectionStart != m_timeSelectionEnd) { + painter.setPen(m_selectionBorder); + painter.drawLine(selectionLeft, 0, selectionLeft, m_viewHeight + m_axisHeight); + painter.drawLine(selectionRight, 0, selectionRight, m_viewHeight + m_axisHeight); + painter.drawLine(selectionLeft, m_axisHeight, selectionRight, m_axisHeight); + } + + + /* + * Draw the ruler zoom + */ + if (m_mousePressMode == RulerZoom) { + int x1 = m_mousePressPosition.x(); + int x2 = m_mousePosition.x(); + int y1 = m_axisHeight; + int y2 = height(); + + painter.setPen(m_zoomBorder); + painter.drawLine(x1, 0, x1, y2); + painter.drawLine(x2, 0, x2, y2); + painter.drawLine(x1, y1, x2, y1); + + painter.setPen(Qt::NoPen); + painter.setBrush(m_zoomBackground); + painter.drawRect(x1, y1, x2 - x1, y2); + } +} + +#include "timelinewidget.moc" diff --git a/gui/timelinewidget.h b/gui/timelinewidget.h new file mode 100644 index 0000000..be8f92a --- /dev/null +++ b/gui/timelinewidget.h @@ -0,0 +1,122 @@ +#ifndef TIMELINEWIDGET_H +#define TIMELINEWIDGET_H + +#include +#include +#include +#include "trace_profiler.hpp" + +struct VisibleItem { + QRect rect; + const trace::Profile::Frame* frame; + const trace::Profile::Call* call; +}; + +class TimelineWidget : public QWidget +{ + Q_OBJECT + + enum MousePressMode { + NoMousePress, + DragView, + RulerZoom, + RulerSelect + }; + +public: + TimelineWidget(QWidget *parent = 0); + + void setProfile(trace::Profile* profile); + +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 selectionChanged(int64_t start, int64_t end); + +private: + void setSelection(int64_t start, int64_t end, bool notify = false); + void setRowScroll(int position, bool notify = true); + void setTimeScroll(int64_t time, bool notify = true); + + void paintHeatmapColumn(int x, QPainter& painter, QVector& rows); + + double timeToPosition(int64_t time); + int64_t positionToTime(int pos); + + const VisibleItem* itemAtPosition(const QPoint& pos); + + void calculateRows(); + +private: + /* Data */ + trace::Profile* m_profile; + QList m_programRowMap; + QList m_visibleItems; + + /* 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_timeMin; + int64_t m_timeMax; + int64_t m_timeWidth; + int64_t m_timeWidthMin; + int64_t m_timeWidthMax; + int64_t m_timeSelectionStart; + int64_t m_timeSelectionEnd; + + /* 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_axisBorder; + QBrush m_axisBackground; + QPen m_itemBorder; + QPen m_itemForeground; + QBrush m_itemBackground; + QPen m_selectionBorder; + QBrush m_selectionBackground; + QPen m_zoomBorder; + QBrush m_zoomBackground; +}; + +#endif // TIMELINEWIDGET_H diff --git a/gui/ui/mainwindow.ui b/gui/ui/mainwindow.ui index cb60ad9..5348a57 100644 --- a/gui/ui/mainwindow.ui +++ b/gui/ui/mainwindow.ui @@ -65,14 +65,13 @@ - - &Trace + @@ -80,8 +79,16 @@ + + + &View + + + + + @@ -622,6 +629,22 @@ Show Errors Dock + + + false + + + Show Profile Results + + + + + false + + + &Profile + + stateDock vertexDataDock errorsDock diff --git a/gui/ui/profiledialog.ui b/gui/ui/profiledialog.ui new file mode 100644 index 0000000..2f5629b --- /dev/null +++ b/gui/ui/profiledialog.ui @@ -0,0 +1,276 @@ + + + ProfileDialog + + + Qt::WindowModal + + + + 0 + 0 + 1079 + 768 + + + + Profile Results + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::Vertical + + + + + 0 + 1 + + + + Qt::WheelFocus + + + + 0 + + + 0 + + + + + 2 + + + + + + 0 + 0 + + + + Qt::WheelFocus + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + 10000 + + + Qt::Horizontal + + + + + + + + + + + 0 + 0 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + false + + + false + + + + + + + + + TimelineWidget + QWidget +
timelinewidget.h
+ 1 + + horizontalScrollValueChanged(int) + verticalScrollValueChanged(int) + horizontalScrollMaxChanged(int) + verticalScrollMaxChanged(int) + jumpToCall(int) + setHorizontalScrollValue(int) + setVerticalScrollValue(int) + +
+
+ + + + m_horizontalScrollBar + sliderMoved(int) + m_timeline + setHorizontalScrollValue(int) + + + 402 + 195 + + + 402 + 93 + + + + + m_verticalScrollBar + sliderMoved(int) + m_timeline + setVerticalScrollValue(int) + + + 813 + 93 + + + 402 + 93 + + + + + m_timeline + horizontalScrollValueChanged(int) + m_horizontalScrollBar + setValue(int) + + + 402 + 93 + + + 402 + 195 + + + + + m_timeline + verticalScrollValueChanged(int) + m_verticalScrollBar + setValue(int) + + + 402 + 93 + + + 813 + 93 + + + + + m_timeline + horizontalScrollMaxChanged(int) + ProfileDialog + setHorizontalScrollMax(int) + + + 504 + 277 + + + 511 + 383 + + + + + m_timeline + verticalScrollMaxChanged(int) + ProfileDialog + setVerticalScrollMax(int) + + + 504 + 277 + + + 511 + 383 + + + + + m_table + doubleClicked(QModelIndex) + ProfileDialog + tableDoubleClicked(QModelIndex) + + + 511 + 671 + + + 511 + 383 + + + + + + jumpToCall(int) + setVerticalScrollMax(int) + setHorizontalScrollMax(int) + tableDoubleClicked(QModelIndex) + +
diff --git a/gui/ui/profilereplaydialog.ui b/gui/ui/profilereplaydialog.ui new file mode 100644 index 0000000..f5991ae --- /dev/null +++ b/gui/ui/profilereplaydialog.ui @@ -0,0 +1,112 @@ + + + ProfileReplayDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 353 + 165 + + + + Profile Configuration + + + Allows setting options on the profiling process. + + + true + + + + + + Profiling + + + + + + GPU times + + + true + + + + + + + CPU times + + + true + + + + + + + Pixels drawn per call + + + true + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ProfileReplayDialog + accept() + + + 176 + 142 + + + 176 + 82 + + + + + buttonBox + rejected() + ProfileReplayDialog + reject() + + + 176 + 142 + + + 176 + 82 + + + + + -- 2.43.0