]> git.cworth.org Git - apitrace/commitdiff
Add gui support for trace profiling.
authorJames Benton <jbenton@vmware.com>
Wed, 8 Aug 2012 16:09:07 +0000 (17:09 +0100)
committerJames Benton <jbenton@vmware.com>
Wed, 8 Aug 2012 16:11:23 +0000 (17:11 +0100)
16 files changed:
common/trace_profiler.cpp
common/trace_profiler.hpp
gui/CMakeLists.txt
gui/mainwindow.cpp
gui/mainwindow.h
gui/profiledialog.cpp [new file with mode: 0644]
gui/profiledialog.h [new file with mode: 0644]
gui/profiletablemodel.cpp [new file with mode: 0644]
gui/profiletablemodel.h [new file with mode: 0644]
gui/retracer.cpp
gui/retracer.h
gui/timelinewidget.cpp [new file with mode: 0644]
gui/timelinewidget.h [new file with mode: 0644]
gui/ui/mainwindow.ui
gui/ui/profiledialog.ui [new file with mode: 0644]
gui/ui/profilereplaydialog.ui [new file with mode: 0644]

index 02d88dcbc2d167219bca63a6f9a193b1020ed4e8..8a3b137bbbe8d94bb32a8dd3b0f943af3b6d1274 100644 (file)
@@ -25,6 +25,9 @@
 
 #include "trace_profiler.hpp"
 #include <iostream>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
 
 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);
+    }
+}
 }
index c36fd77f6e0bf491dbd880f148343405faed3197..c8b38b9a73be2c294ed1a5afbc1a7c3c5f6af556 100644 (file)
 #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:
@@ -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;
index 070aca70faaac02b58a1f9195506a8cd3a4ccbbd..6037b7dab00c3ca021e8d65035c000209d4d3f3b 100644 (file)
@@ -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
index 50f7d9124a9f4dac6184c4cf42ea14f38e89d35a..caf9e370236ebed41d8aa29409cc3e253b1457a4 100644 (file)
@@ -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 <QAction>
 #include <QApplication>
@@ -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<QImage>&)),
             this, SLOT(replayThumbnailsFound(const QList<QImage>&)));
     connect(m_retracer, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
@@ -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."));
     }
index 1346b86377415ba5e16a1c625a0480683fcd59eb..2248127f76aae57b8acd4728c7b090ad628564da 100644 (file)
@@ -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<QImage> &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 (file)
index 0000000..5568b54
--- /dev/null
@@ -0,0 +1,83 @@
+#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"
diff --git a/gui/profiledialog.h b/gui/profiledialog.h
new file mode 100644 (file)
index 0000000..9946efe
--- /dev/null
@@ -0,0 +1,34 @@
+#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
diff --git a/gui/profiletablemodel.cpp b/gui/profiletablemodel.cpp
new file mode 100644 (file)
index 0000000..845ff14
--- /dev/null
@@ -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<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"
diff --git a/gui/profiletablemodel.h b/gui/profiletablemodel.h
new file mode 100644 (file)
index 0000000..fe7979a
--- /dev/null
@@ -0,0 +1,65 @@
+#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
index bbe638ca0fc0635f41e10ff9ffdbcf225f0bd0a5..87d9f37dee89b655fa1c95ce0a0512a515457a38 100644 (file)
@@ -5,6 +5,8 @@
 
 #include "image.hpp"
 
+#include "trace_profiler.hpp"
+
 #include <QDebug>
 #include <QVariant>
 #include <QList>
@@ -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<QList<ApiTraceError> >();
 
@@ -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<QImage> 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);
     }
index d6da7ac5fd5e385cfca96bcab85553122a958b86..e889d8887cf3753ce609d86daefa6233d7134ddf 100644 (file)
@@ -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<QImage> &thumbnails);
     void error(const QString &msg);
     void retraceErrors(const QList<ApiTraceError> &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 (file)
index 0000000..e6d2712
--- /dev/null
@@ -0,0 +1,694 @@
+#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"
diff --git a/gui/timelinewidget.h b/gui/timelinewidget.h
new file mode 100644 (file)
index 0000000..be8f92a
--- /dev/null
@@ -0,0 +1,122 @@
+#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
index cb60ad9f3b36214d6f3cf02cbe847cb54d42d286..5348a5789e2834c21a1422ac2d735a1b48b1c490 100644 (file)
     <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>&amp;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>&amp;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>&amp;Profile</string>
+   </property>
+  </action>
   <zorder>stateDock</zorder>
   <zorder>vertexDataDock</zorder>
   <zorder>errorsDock</zorder>
diff --git a/gui/ui/profiledialog.ui b/gui/ui/profiledialog.ui
new file mode 100644 (file)
index 0000000..2f5629b
--- /dev/null
@@ -0,0 +1,276 @@
+<?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>
diff --git a/gui/ui/profilereplaydialog.ui b/gui/ui/profilereplaydialog.ui
new file mode 100644 (file)
index 0000000..f5991ae
--- /dev/null
@@ -0,0 +1,112 @@
+<?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>