#include "trace_profiler.hpp"
#include <iostream>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
namespace trace {
Profiler::Profiler()
<< " " << 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);
+ }
+}
}
#define TRACE_PROFILER_H
#include <string>
+#include <vector>
#include <stdint.h>
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<Call>::iterator iterator;
+ typedef std::vector<Call>::const_iterator const_iterator;
+ };
+
+ struct Frame {
+ unsigned no;
+ int64_t gpuStart;
+ int64_t gpuDuration;
+ int64_t cpuStart;
+ int64_t cpuDuration;
+
+ std::vector<Call> calls;
+
+ typedef std::vector<Frame>::iterator iterator;
+ typedef std::vector<Frame>::const_iterator const_iterator;
+ };
+
+ std::vector<Frame> frames;
+};
+
class Profiler
{
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;
jumpwidget.cpp
mainwindow.cpp
main.cpp
+ profiledialog.cpp
+ profiletablemodel.cpp
retracer.cpp
saverthread.cpp
searchwidget.cpp
traceloader.cpp
traceprocess.cpp
trimprocess.cpp
+ timelinewidget.cpp
vertexdatainterpreter.cpp
)
ui/imageviewer.ui
ui/jumpwidget.ui
ui/mainwindow.ui
+ ui/profiledialog.ui
+ ui/profilereplaydialog.ui
ui/retracerdialog.ui
ui/settings.ui
ui/tracedialog.ui
#include "argumentseditor.h"
#include "imageviewer.h"
#include "jumpwidget.h"
+#include "profiledialog.h"
#include "retracer.h"
#include "searchwidget.h"
#include "settingsdialog.h"
#include "trimprocess.h"
#include "thumbnail.h"
#include "ui_retracerdialog.h"
+#include "ui_profilereplaydialog.h"
#include "vertexdatainterpreter.h"
+#include "trace_profiler.hpp"
#include <QAction>
#include <QApplication>
initConnections();
}
+MainWindow::~MainWindow()
+{
+ delete m_trace;
+ m_trace = 0;
+
+ delete m_proxyModel;
+ delete m_model;
+}
+
void MainWindow::createTrace()
{
if (!m_traceProcess->canTrace()) {
"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);
}
}
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);
}
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);
{
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_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;
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..."));
trimEvent();
}
-MainWindow::~MainWindow()
-{
- delete m_trace;
- m_trace = 0;
-
- delete m_proxyModel;
- delete m_model;
-}
-
static void
variantToString(const QVariant &var, QString &str)
{
m_traceProcess = new TraceProcess(this);
m_trimProcess = new TrimProcess(this);
+
+ m_profileDialog = new ProfileDialog();
}
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<QImage>&)),
this, SLOT(replayThumbnailsFound(const QList<QImage>&)));
connect(m_retracer, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
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()),
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)
QModelIndex idx = m_proxyModel->indexForCall(call);
if (idx.isValid()) {
m_ui.callView->setCurrentIndex(idx);
+ m_ui.callView->scrollTo(idx, QAbstractItemView::PositionAtTop);
break;
}
++itr;
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());
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."));
}
class ShadersSourceWidget;
class TraceProcess;
class TrimProcess;
+class ProfileDialog;
class VertexDataInterpreter;
+namespace trace { struct Profile; }
+
class MainWindow : public QMainWindow
{
Q_OBJECT
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<QImage> &thumbnails);
void replayError(const QString &msg);
void startedLoadingTrace();
ApiTraceFrame *currentFrame() const;
ApiTraceCall *currentCall() const;
+protected:
+ virtual void closeEvent(QCloseEvent * event);
private:
Ui_MainWindow m_ui;
ArgumentsEditor *m_argsEditor;
ApiTraceEvent *m_nonDefaultsLookupEvent;
+
+ ProfileDialog* m_profileDialog;
};
--- /dev/null
+#include "profiledialog.h"
+#include "profiletablemodel.h"
+#include <QSortFilterProxyModel>
+
+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"
--- /dev/null
+#ifndef PROFILEDIALOG_H
+#define PROFILEDIALOG_H
+
+#include "ui_profiledialog.h"
+#include <QDialog>
+
+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
--- /dev/null
+#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<ProfileTableRow>::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<ProfileTableRow>::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"
--- /dev/null
+#ifndef PROFILETABLEMODEL_H
+#define PROFILETABLEMODEL_H
+
+#include <QAbstractTableModel>
+#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<ProfileTableRow> m_rowData;
+ trace::Profile *m_profile;
+ int64_t m_timeMin;
+ int64_t m_timeMax;
+ int m_sortColumn;
+ Qt::SortOrder m_sortOrder;
+};
+
+#endif // PROFILEMODEL_H
#include "image.hpp"
+#include "trace_profiler.hpp"
+
#include <QDebug>
#include <QVariant>
#include <QList>
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<QList<ApiTraceError> >();
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;
m_captureThumbnails = enable;
}
-
/**
* Starting point for the retracing thread.
*
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;
QList<QImage> thumbnails;
QVariantMap parsedJson;
+ trace::Profile* profile = NULL;
process.setReadChannel(QProcess::StandardOutput);
if (process.waitForReadyRead(-1)) {
}
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();
emit foundThumbnails(thumbnails);
}
+ if (isProfiling() && profile) {
+ emit foundProfile(profile);
+ }
+
if (!errors.isEmpty()) {
emit retraceErrors(errors);
}
class ApiTraceState;
+namespace trace { struct Profile; }
+
class Retracer : public QThread
{
Q_OBJECT
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;
signals:
void finished(const QString &output);
void foundState(ApiTraceState *state);
+ void foundProfile(trace::Profile *profile);
void foundThumbnails(const QList<QImage> &thumbnails);
void error(const QString &msg);
void retraceErrors(const QList<ApiTraceError> &errors);
bool m_captureState;
bool m_captureThumbnails;
qlonglong m_captureCall;
+ bool m_profileGpu;
+ bool m_profileCpu;
+ bool m_profilePixels;
QProcessEnvironment m_processEnvironment;
};
--- /dev/null
+#include "timelinewidget.h"
+#include "trace_profiler.hpp"
+
+#include <math.h>
+#include <QColor>
+#include <QPainter>
+#include <QToolTip>
+#include <QMouseEvent>
+#include <QWheelEvent>
+#include <QApplication>
+
+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<uint64_t, unsigned> HeatProgram;
+ QList<HeatProgram> 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<HeatProgram>::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<uint64_t>& 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<uint64_t> 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"
--- /dev/null
+#ifndef TIMELINEWIDGET_H
+#define TIMELINEWIDGET_H
+
+#include <QWidget>
+#include <QList>
+#include <QPen>
+#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<uint64_t>& 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<int> m_programRowMap;
+ QList<VisibleItem> 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
<addaction name="actionGo"/>
<addaction name="actionGoFrameStart"/>
<addaction name="actionGoFrameEnd"/>
- <addaction name="separator"/>
- <addaction name="actionShowErrorsDock"/>
</widget>
<widget class="QMenu" name="menu_Trace">
<property name="title">
<string>&Trace</string>
</property>
<addaction name="actionReplay"/>
+ <addaction name="actionProfile"/>
<addaction name="actionStop"/>
<addaction name="actionLookupState"/>
<addaction name="actionShowThumbnails"/>
<addaction name="separator"/>
<addaction name="actionOptions"/>
</widget>
+ <widget class="QMenu" name="menuView">
+ <property name="title">
+ <string>&View</string>
+ </property>
+ <addaction name="actionShowErrorsDock"/>
+ <addaction name="actionShowProfileDialog"/>
+ </widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
+ <addaction name="menuView"/>
<addaction name="menu_Trace"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<string>Show Errors Dock</string>
</property>
</action>
+ <action name="actionShowProfileDialog">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Show Profile Results</string>
+ </property>
+ </action>
+ <action name="actionProfile">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&Profile</string>
+ </property>
+ </action>
<zorder>stateDock</zorder>
<zorder>vertexDataDock</zorder>
<zorder>errorsDock</zorder>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ProfileDialog</class>
+ <widget class="QDialog" name="ProfileDialog">
+ <property name="windowModality">
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1079</width>
+ <height>768</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Profile Results</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QWidget" name="timelineContainer" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::WheelFocus</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="TimelineWidget" name="m_timeline" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::WheelFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QScrollBar" name="m_verticalScrollBar">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QScrollBar" name="m_horizontalScrollBar">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>10000</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QTableView" name="m_table">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>TimelineWidget</class>
+ <extends>QWidget</extends>
+ <header>timelinewidget.h</header>
+ <container>1</container>
+ <slots>
+ <signal>horizontalScrollValueChanged(int)</signal>
+ <signal>verticalScrollValueChanged(int)</signal>
+ <signal>horizontalScrollMaxChanged(int)</signal>
+ <signal>verticalScrollMaxChanged(int)</signal>
+ <signal>jumpToCall(int)</signal>
+ <slot>setHorizontalScrollValue(int)</slot>
+ <slot>setVerticalScrollValue(int)</slot>
+ </slots>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>m_horizontalScrollBar</sender>
+ <signal>sliderMoved(int)</signal>
+ <receiver>m_timeline</receiver>
+ <slot>setHorizontalScrollValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>402</x>
+ <y>195</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>402</x>
+ <y>93</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>m_verticalScrollBar</sender>
+ <signal>sliderMoved(int)</signal>
+ <receiver>m_timeline</receiver>
+ <slot>setVerticalScrollValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>813</x>
+ <y>93</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>402</x>
+ <y>93</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>m_timeline</sender>
+ <signal>horizontalScrollValueChanged(int)</signal>
+ <receiver>m_horizontalScrollBar</receiver>
+ <slot>setValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>402</x>
+ <y>93</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>402</x>
+ <y>195</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>m_timeline</sender>
+ <signal>verticalScrollValueChanged(int)</signal>
+ <receiver>m_verticalScrollBar</receiver>
+ <slot>setValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>402</x>
+ <y>93</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>813</x>
+ <y>93</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>m_timeline</sender>
+ <signal>horizontalScrollMaxChanged(int)</signal>
+ <receiver>ProfileDialog</receiver>
+ <slot>setHorizontalScrollMax(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>504</x>
+ <y>277</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>511</x>
+ <y>383</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>m_timeline</sender>
+ <signal>verticalScrollMaxChanged(int)</signal>
+ <receiver>ProfileDialog</receiver>
+ <slot>setVerticalScrollMax(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>504</x>
+ <y>277</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>511</x>
+ <y>383</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>m_table</sender>
+ <signal>doubleClicked(QModelIndex)</signal>
+ <receiver>ProfileDialog</receiver>
+ <slot>tableDoubleClicked(QModelIndex)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>511</x>
+ <y>671</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>511</x>
+ <y>383</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+ <slots>
+ <signal>jumpToCall(int)</signal>
+ <slot>setVerticalScrollMax(int)</slot>
+ <slot>setHorizontalScrollMax(int)</slot>
+ <slot>tableDoubleClicked(QModelIndex)</slot>
+ </slots>
+</ui>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ProfileReplayDialog</class>
+ <widget class="QDialog" name="ProfileReplayDialog">
+ <property name="windowModality">
+ <enum>Qt::ApplicationModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>353</width>
+ <height>165</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Profile Configuration</string>
+ </property>
+ <property name="whatsThis">
+ <string>Allows setting options on the profiling process.</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="profileGroup">
+ <property name="title">
+ <string>Profiling</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="gpuTimesCB">
+ <property name="text">
+ <string>GPU times</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuTimesCB">
+ <property name="text">
+ <string>CPU times</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="pixelsDrawnCB">
+ <property name="text">
+ <string>Pixels drawn per call</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ProfileReplayDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>176</x>
+ <y>142</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>176</x>
+ <y>82</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ProfileReplayDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>176</x>
+ <y>142</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>176</x>
+ <y>82</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>