]> git.cworth.org Git - apitrace/blobdiff - gui/timelinewidget.cpp
Combine timeline and histogram tabs.
[apitrace] / gui / timelinewidget.cpp
index 0375831871570930eb9d5256aaaecf2e844a4671..0f30225d268c9bf72515ea91fea5fa2562d8354f 100644 (file)
@@ -2,7 +2,7 @@
 #include "profiledialog.h"
 #include "trace_profiler.hpp"
 
-#include <math.h>
+#include <qmath.h>
 #include <QColor>
 #include <QLocale>
 #include <QPainter>
@@ -18,8 +18,6 @@ typedef trace::Profile::Program Program;
 TimelineWidget::TimelineWidget(QWidget *parent)
     : QWidget(parent),
       m_profile(NULL),
-      m_timeSelectionStart(0),
-      m_timeSelectionEnd(0),
       m_rowHeight(20),
       m_axisWidth(50),
       m_axisHeight(30),
@@ -28,8 +26,12 @@ TimelineWidget::TimelineWidget(QWidget *parent)
       m_axisForeground(Qt::black),
       m_axisBackground(QColor(210, 210, 210)),
       m_itemBorder(Qt::red),
-      m_itemForeground(Qt::cyan),
-      m_itemBackground(Qt::red),
+      m_itemGpuForeground(Qt::cyan),
+      m_itemGpuBackground(Qt::red),
+      m_itemCpuForeground(QColor(255, 255, 0)),
+      m_itemCpuBackground(QColor(0, 0, 255)),
+      m_itemDeselectedForeground(Qt::white),
+      m_itemDeselectedBackground(QColor(155, 155, 155)),
       m_selectionBorder(Qt::green),
       m_selectionBackground(QColor(100, 255, 100, 8)),
       m_zoomBorder(QColor(255, 0, 255)),
@@ -38,6 +40,8 @@ TimelineWidget::TimelineWidget(QWidget *parent)
     setBackgroundRole(QPalette::Base);
     setAutoFillBackground(true);
     setMouseTracking(true);
+
+    m_selection.type = SelectNone;
 }
 
 
@@ -74,15 +78,47 @@ void TimelineWidget::setVerticalScrollValue(int value)
 
 
 /**
- * Update the time selection
+ * Set selection to nothing
+ */
+void TimelineWidget::selectNone(bool notify)
+{
+    m_selection.type = SelectNone;
+
+    if (notify) {
+        emit selectedNone();
+    }
+
+    update();
+}
+
+
+/**
+ * Set selection to a program
  */
-void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify)
+void TimelineWidget::selectProgram(unsigned program, bool notify)
 {
-    m_timeSelectionStart = start;
-    m_timeSelectionEnd = end;
+    m_selection.program = program;
+    m_selection.type = SelectProgram;
 
     if (notify) {
-        emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd);
+        emit selectedProgram(program);
+    }
+
+    update();
+}
+
+
+/**
+ * Set selection to a period of time
+ */
+void TimelineWidget::selectTime(int64_t start, int64_t end, bool notify)
+{
+    m_selection.timeStart = start;
+    m_selection.timeEnd = end;
+    m_selection.type = SelectTime;
+
+    if (notify) {
+        emit selectedTime(start, end);
     }
 
     update();
@@ -158,7 +194,7 @@ std::vector<unsigned>::const_iterator binarySearchTimespanIndexed(
         int64_t time)
 {
     int lower = 0;
-    int upper = end - begin;
+    int upper = end - begin - 1;
     int pos = (lower + upper) / 2;
     std::vector<unsigned>::const_iterator itr = begin + pos;
 
@@ -236,6 +272,27 @@ const Call* TimelineWidget::cpuCallAtTime(int64_t time)
 /**
  * Find the draw call at time
  */
+const Call* TimelineWidget::drawCallAtTime(int64_t time)
+{
+    if (!m_profile) {
+        return NULL;
+    }
+
+    for (int i = 0; i < m_rowPrograms.size(); ++i) {
+        const Call* call = drawCallAtTime(time, m_rowPrograms[i]);
+
+        if (call) {
+            return call;
+        }
+    }
+
+    return NULL;
+}
+
+
+/**
+ * Find the draw call at time for a selected program
+ */
 const Call* TimelineWidget::drawCallAtTime(int64_t time, int program)
 {
     if (!m_profile) {
@@ -314,7 +371,7 @@ void TimelineWidget::setProfile(trace::Profile* profile)
 
     setTimeScroll(m_time);
     setRowScroll(0);
-
+    selectNone();
     update();
 }
 
@@ -371,7 +428,7 @@ void TimelineWidget::resizeEvent(QResizeEvent *e)
 {
     /* Update viewport size */
     m_viewWidth = qMax(0, width() - m_axisWidth);
-    m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight);
+    m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight * 2);
 
     /* Update vertical scroll bar */
     if (m_profile) {
@@ -411,25 +468,48 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
                     tooltip = true;
                 }
             } else {
-                int row = (y - m_rowHeight + m_scrollY) / m_rowHeight;
+                const Call* call = NULL;
 
-                if (row < m_rowPrograms.size()) {
-                    const Call* call = drawCallAtTime(time, m_rowPrograms[row]);
-
-                    if (call) {
-                        QString text;
-                        text  = QString::fromStdString(call->name);
-                        text += QString("\nCall: %1").arg(call->no);
-                        text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
-                        text += QString("\nGPU Start: %1").arg(getTimeString(call->gpuStart));
-                        text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
-                        text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
-                        text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
-
-                        QToolTip::showText(e->globalPos(), text);
-                        tooltip = true;
+                if (y < m_rowHeight * 2) {
+                    call = drawCallAtTime(time);
+                } else {
+                    int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
+
+                    if (row < m_rowPrograms.size()) {
+                        call = drawCallAtTime(time, m_rowPrograms[row]);
                     }
                 }
+
+                if (call) {
+                    QString text;
+                    text  = QString::fromStdString(call->name);
+                    text += QString("\nCall: %1").arg(call->no);
+                    text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
+                    text += QString("\nGPU Start: %1").arg(getTimeString(call->gpuStart));
+                    text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
+                    text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
+                    text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
+
+                    QToolTip::showText(e->globalPos(), text);
+                    tooltip = true;
+                }
+            }
+        } else if (m_mousePosition.x() < m_axisWidth && m_mousePosition.y() > m_axisHeight) {
+            int y = m_mousePosition.y() - m_axisHeight;
+
+            if (y < m_rowHeight) {
+                QToolTip::showText(e->globalPos(), "All CPU calls");
+                tooltip = true;
+            } else if (y < m_rowHeight * 2) {
+                QToolTip::showText(e->globalPos(), "All GPU calls");
+                tooltip = true;
+            } else {
+                int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
+
+                if (row < m_rowPrograms.size()) {
+                    QToolTip::showText(e->globalPos(), QString("All calls in Shader Program %1").arg(m_rowPrograms[row]));
+                    tooltip = true;
+                }
             }
         }
     } else if (e->buttons().testFlag(Qt::LeftButton)) {
@@ -448,7 +528,7 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
             int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
             int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
 
-            setSelection(qMin(down, up), qMax(down, up));
+            selectTime(qMin(down, up), qMax(down, up), true);
         }
 
         update();
@@ -468,18 +548,16 @@ void TimelineWidget::mousePressEvent(QMouseEvent *e)
                 m_mousePressMode = RulerZoom;
             } else {
                 m_mousePressMode = RulerSelect;
-
-                int64_t time = positionToTime(e->pos().x() - m_axisWidth);
-                m_timeSelectionStart = time;
-                m_timeSelectionEnd = time;
             }
-        } else {
+        } else if (e->pos().x() >= m_axisWidth) {
             m_mousePressMode = DragView;
+        } else {
+            m_mousePressMode = NoMousePress;
         }
 
         m_mousePressPosition  = e->pos();
         m_mousePressTime = m_time;
-        m_mousePressRow   = m_scrollY;
+        m_mousePressRow  = m_scrollY;
 
         update();
     }
@@ -493,6 +571,8 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
     }
 
     /* Calculate new time view based on selected area */
+    int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y());
+
     int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
     int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
 
@@ -505,8 +585,23 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
 
         m_mousePressMode = NoMousePress;
         setTimeScroll(left);
-    } else if (m_mousePressMode == RulerSelect) {
-        setSelection(m_timeSelectionStart, m_timeSelectionEnd, true);
+    } else {
+        if (dxy <= 2) {
+            if (m_selection.type == SelectTime) {
+                if (left < m_selection.timeStart || right > m_selection.timeEnd || e->pos().x() < m_axisWidth) {
+                    selectNone(true);
+                }
+            } else if (m_selection.type == SelectProgram) {
+                int y = e->pos().y() - m_axisHeight;
+                int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
+
+                if (row < 0 || m_rowPrograms[row] != m_selection.program) {
+                    selectNone(true);
+                }
+            }
+        } else if (m_mousePressMode == RulerSelect) {
+            selectTime(left, right, true);
+        }
     }
 }
 
@@ -523,7 +618,7 @@ void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
             const Frame* frame = frameAtTime(time);
 
             if (frame) {
-                setSelection(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
+                selectTime(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
                 return;
             }
         } else if (row == 0) {
@@ -543,10 +638,13 @@ void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
                 return;
             }
         }
-    }
+    } else {
+        int y = e->pos().y() - m_axisHeight;
+        int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
 
-    if (time < m_timeSelectionStart || time > m_timeSelectionEnd) {
-        setSelection(0, 0, true);
+        if (row >= 0 && row < m_rowPrograms.size()) {
+            selectProgram(m_rowPrograms[row], true);
+        }
     }
 }
 
@@ -557,6 +655,10 @@ void TimelineWidget::wheelEvent(QWheelEvent *e)
         return;
     }
 
+    if (e->pos().x() < m_axisWidth) {
+        return;
+    }
+
     int zoomPercent = 10;
 
     /* If holding Ctrl key then zoom 2x faster */
@@ -588,22 +690,31 @@ void TimelineWidget::wheelEvent(QWheelEvent *e)
 /**
  * Paints a single pixel column of the heat map
  */
-void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool isCpu)
+void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool gpu, bool selected)
 {
     if (heat == 0) {
         return;
     }
 
-    double timePerPixel = m_timeWidth;
-    timePerPixel /= m_viewWidth;
+    if (m_selection.type == SelectTime) {
+        selected = x >= m_selectionLeft && x <= m_selectionRight;
+    }
 
+    double timePerPixel = m_timeWidth / (double)m_viewWidth;
     double colour = heat / timePerPixel;
-    colour = qBound(0.0, colour * 255.0, 255.0);
 
-    if (isCpu) {
-        painter.setPen(QColor(255 - colour, 255 - colour, 255));
-    } else {
+    /* Gamma correction */
+    colour = qPow(colour, 1.0 / 2.0);
+
+    if (!selected) {
+        colour = qBound(0.0, colour * 100.0, 100.0);
+        painter.setPen(QColor(255 - colour, 255 - colour, 255 - colour));
+    } else if (gpu) {
+        colour = qBound(0.0, colour * 255.0, 255.0);
         painter.setPen(QColor(255, 255 - colour, 255 - colour));
+    } else {
+        colour = qBound(0.0, colour * 255.0, 255.0);
+        painter.setPen(QColor(255 - colour, 255 - colour, 255));
     }
 
     painter.drawLine(x, 0, x, m_rowHeight - 1);
@@ -611,156 +722,176 @@ void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool isCpu
 
 
 /**
- * Render the whole widget
+ * Draws a call on the heatmap
  */
-void TimelineWidget::paintEvent(QPaintEvent *e)
+bool TimelineWidget::drawCall(QPainter& painter, const trace::Profile::Call& call, int& lastX, int64_t& heat, bool gpu)
 {
-    if (!m_profile)
-        return;
+    int64_t start, duration, end;
 
-    QPainter painter(this);
+    if (gpu) {
+        start = call.gpuStart;
+        duration = call.gpuDuration;
+    } else {
+        start = call.cpuStart;
+        duration = call.cpuDuration;
+    }
 
-    int rowEnd = qMin(m_row + (m_viewHeight / m_rowHeight) + 1, m_rowCount);
-    int64_t timeEnd = m_time + m_timeWidth;
-    int64_t heat = 0;
-    int lastX = 0;
-    int widgetHeight = height();
-    int widgetWidth = width();
+    end = start + duration;
 
-    /* Draw GPU rows */
-    painter.translate(m_axisWidth, m_axisHeight + m_rowHeight - (m_scrollY % m_rowHeight));
+    if (start > m_timeEnd) {
+        return false;
+    }
 
-    for (int row = m_row; row < rowEnd; ++row) {
-        Program& program = m_profile->programs[m_rowPrograms[row]];
-        lastX = 0;
-        heat = 0;
+    if (end < m_time) {
+        return true;
+    }
 
-        for (std::vector<unsigned>::const_iterator itr = program.calls.begin(); itr != program.calls.end(); ++itr) {
-            const Call& call = m_profile->calls[*itr];
-            int64_t gpuEnd = call.gpuStart + call.gpuDuration;
+    double left  = timeToPosition(start);
+    double right = timeToPosition(end);
 
-            if (call.gpuStart > timeEnd) {
-                break;
-            }
+    int leftX  = left;
+    int rightX = right;
 
-            if (gpuEnd < m_time) {
-                continue;
-            }
+    bool selected = true;
 
-            double left  = timeToPosition(call.gpuStart);
-            double right = timeToPosition(gpuEnd);
+    if (m_selection.type == SelectProgram) {
+        selected = call.program == m_selection.program;
+    }
 
-            int leftX  = left;
-            int rightX = right;
+    /* Draw last heat if needed */
+    if (leftX != lastX) {
+        drawHeat(painter, lastX, heat, gpu, selected);
+        lastX = leftX;
+        heat = 0;
+    }
 
-            /* Draw last heat if needed */
-            if (leftX != lastX) {
-                drawHeat(painter, lastX, heat, false);
-                lastX = leftX;
-                heat = 0;
-            }
+    if (rightX <= leftX + 1) {
+        if (rightX == lastX) {
+            /* Fully contained in this X */
+            heat += duration;
+        } else {
+            /* Split call time between the two pixels it occupies */
+            int64_t time = positionToTime(rightX);
+            heat += time - start;
 
-            if (rightX <= leftX + 1) {
-                if (rightX == lastX) {
-                    /* Fully contained in this X */
-                    heat += call.gpuDuration;
-                } else {
-                    /* Split call time between the two pixels it occupies */
-                    int64_t time = positionToTime(rightX);
+            drawHeat(painter, lastX, heat, gpu, selected);
 
-                    heat += time - call.gpuStart;
-                    drawHeat(painter, lastX, heat, false);
+            heat = end - time;
+            lastX = rightX;
+        }
+    } else {
+        QRect rect;
+        rect.setLeft(left + 0.5);
+        rect.setWidth(right - left);
+        rect.setTop(0);
+        rect.setHeight(m_rowHeight);
+
+        if (m_selection.type == SelectTime) {
+            selected = (start >= m_selection.timeStart && start <= m_selection.timeEnd)
+                    || (end >= m_selection.timeStart && end <= m_selection.timeEnd);
+        }
 
-                    heat = gpuEnd - time;
-                    lastX = rightX;
-                }
+        /* Draw background rect */
+        if (selected) {
+            if (gpu) {
+                painter.fillRect(rect, m_itemGpuBackground);
             } else {
-                QRect rect;
-                rect.setLeft(left + 0.5);
-                rect.setWidth(right - left);
-                rect.setTop(0);
-                rect.setHeight(m_rowHeight);
-
-                painter.fillRect(rect, m_itemBackground);
+                painter.fillRect(rect, m_itemCpuBackground);
+            }
+        } else {
+            painter.fillRect(rect, m_itemDeselectedBackground);
+        }
 
-                if (rect.width() > 6) {
-                    rect.adjust(1, 0, -1, -2);
-                    painter.setPen(m_itemForeground);
+        /* If wide enough, draw text */
+        if (rect.width() > 6) {
+            rect.adjust(1, 0, -1, -2);
 
-                    painter.drawText(rect,
-                                     Qt::AlignLeft | Qt::AlignVCenter,
-                                     painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
+            if (selected) {
+                if (gpu) {
+                    painter.setPen(m_itemGpuForeground);
+                } else {
+                    painter.setPen(m_itemCpuForeground);
                 }
+            } else {
+                painter.setPen(m_itemDeselectedForeground);
             }
-        }
 
-        painter.translate(0, m_rowHeight);
+            painter.drawText(rect,
+                             Qt::AlignLeft | Qt::AlignVCenter,
+                             painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
+        }
     }
 
-    /* Draw CPU row */
-    painter.resetTransform();
-    painter.translate(m_axisWidth, m_axisHeight);
-    painter.fillRect(0, 0, m_viewWidth, m_rowHeight, Qt::white);
+    return true;
+}
 
-    for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
-        const Call& call = *itr;
-        int64_t cpuEnd = call.cpuStart + call.cpuDuration;
 
-        if (call.cpuStart > timeEnd) {
-            continue;
-        }
+/**
+ * Render the whole widget
+ */
+void TimelineWidget::paintEvent(QPaintEvent *e)
+{
+    if (!m_profile)
+        return;
 
-        if (cpuEnd < m_time) {
-            continue;
-        }
+    QPainter painter(this);
 
-        double left = timeToPosition(call.cpuStart);
-        double right = timeToPosition(cpuEnd);
+    int rowEnd = qMin(m_row + qCeil(m_viewHeight / (double)m_rowHeight) + 1, m_rowCount);
+    int64_t heatGPU = 0, heatCPU = 0;
+    int lastCpuX = 0, lastGpuX = 0;
+    int widgetHeight = height();
+    int widgetWidth = width();
 
-        int leftX = left;
-        int rightX = right;
+    m_timeEnd = m_time + m_timeWidth;
+    m_selectionLeft  = timeToPosition(m_selection.timeStart);
+    m_selectionRight = (timeToPosition(m_selection.timeEnd) + 0.5);
 
-        /* Draw last heat if needed */
-        if (leftX != lastX) {
-            drawHeat(painter, lastX, heat, true);
-            lastX = leftX;
-            heat = 0;
-        }
 
-        if (rightX <= leftX + 1) {
-            if (rightX == lastX) {
-                /* Fully contained in this X */
-                heat += call.cpuDuration;
-            } else {
-                /* Split call time between the two pixels it occupies */
-                int64_t time = positionToTime(rightX);
+    /* Draw program rows */
+    painter.translate(m_axisWidth, m_axisHeight + m_rowHeight * 2 - (m_scrollY % m_rowHeight));
+
+    for (int row = m_row; row < rowEnd; ++row) {
+        Program& program = m_profile->programs[m_rowPrograms[row]];
+        lastGpuX = 0;
+        heatGPU = 0;
 
-                heat += time - call.cpuStart;
-                drawHeat(painter, lastX, heat, true);
+        for (std::vector<unsigned>::const_iterator itr = program.calls.begin(); itr != program.calls.end(); ++itr) {
+            const Call& call = m_profile->calls[*itr];
 
-                heat = cpuEnd - time;
-                lastX = rightX;
+            if (!drawCall(painter, call, lastGpuX, heatGPU, true)) {
+                break;
             }
-        } else {
-            QRect rect;
-            rect.setLeft(left + 0.5);
-            rect.setWidth(right - left);
-            rect.setTop(0);
-            rect.setHeight(m_rowHeight);
+        }
 
-            painter.fillRect(rect, QColor(0, 0, 255));
+        painter.translate(0, m_rowHeight);
+    }
 
-            if (rect.width() > 6) {
-                rect.adjust(1, 0, -1, -2);
-                painter.setPen(QColor(255, 255, 0));
 
-                painter.drawText(rect,
-                                 Qt::AlignLeft | Qt::AlignVCenter,
-                                 painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
-            }
+    /* Draw CPU/GPU rows */
+    painter.resetTransform();
+    painter.translate(m_axisWidth, m_axisHeight);
+    painter.fillRect(0, 0, m_viewWidth, m_rowHeight * 2, Qt::white);
+
+    lastCpuX = lastGpuX = 0;
+    heatCPU = heatGPU = 0;
+
+    for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
+        const Call& call = *itr;
+
+        /* Draw gpu row */
+        if (call.pixels >= 0) {
+            painter.translate(0, m_rowHeight);
+            drawCall(painter, call, lastGpuX, heatGPU, true);
+            painter.translate(0, -m_rowHeight);
+        }
+
+        /* Draw cpu row */
+        if (!drawCall(painter, call, lastCpuX, heatCPU, false)) {
+            break;
         }
     }
 
+
     /* Draw axis */
     painter.resetTransform();
     painter.setPen(m_axisBorder);
@@ -773,8 +904,9 @@ void TimelineWidget::paintEvent(QPaintEvent *e)
     painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground);
     painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight);
 
+
     /* Draw the program numbers */
-    painter.translate(0, m_axisHeight + m_rowHeight);
+    painter.translate(0, m_axisHeight + m_rowHeight * 2);
 
     for (int row = m_row; row < rowEnd; ++row) {
         int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight);
@@ -782,13 +914,21 @@ void TimelineWidget::paintEvent(QPaintEvent *e)
         painter.setPen(m_axisForeground);
         painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row]));
 
-        painter.setPen(m_axisBorder);
-        painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
+        if (m_selection.type == SelectProgram && m_selection.program == m_rowPrograms[row]) {
+            painter.setPen(m_selectionBorder);
+            painter.drawLine(0, qMax(0, y - 1), widgetWidth, qMax(0, y - 1));
+            painter.drawLine(0, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
+            painter.drawLine(m_axisWidth - 1, y - 1, m_axisWidth - 1, y + m_rowHeight - 1);
+        } else {
+            painter.setPen(m_axisBorder);
+            painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
 
-        painter.setPen(m_axisLine);
-        painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
+            painter.setPen(m_axisLine);
+            painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
+        }
     }
 
+
     /* Draw the "CPU" axis label */
     painter.resetTransform();
     painter.translate(0, m_axisHeight);
@@ -804,13 +944,27 @@ void TimelineWidget::paintEvent(QPaintEvent *e)
     painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
 
 
+    /* Draw the "GPU" axis label */
+    painter.translate(0, m_rowHeight);
+
+    painter.setPen(m_axisBorder);
+    painter.setBrush(m_axisBackground);
+    painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
+
+    painter.setPen(m_axisForeground);
+    painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "GPU");
+
+    painter.setPen(m_axisBorder);
+    painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
+
+
     /* Draw the frame numbers */
     painter.resetTransform();
 
     painter.setPen(m_axisForeground);
     painter.translate(m_axisWidth, 0);
 
-    int lastLabel = -9999;
+    int lastLabel = -999; /* Ensure first label gets drawn */
 
     double scroll = m_time;
     scroll /= m_timeWidth;
@@ -822,7 +976,7 @@ void TimelineWidget::paintEvent(QPaintEvent *e)
         bool draw = true;
         int width;
 
-        if (frame.cpuStart > timeEnd) {
+        if (frame.cpuStart > m_timeEnd) {
             break;
         }
 
@@ -873,6 +1027,7 @@ void TimelineWidget::paintEvent(QPaintEvent *e)
         }
     }
 
+
     /* Draw "Frame" axis label */
     painter.resetTransform();
 
@@ -883,28 +1038,30 @@ void TimelineWidget::paintEvent(QPaintEvent *e)
     painter.setPen(m_axisForeground);
     painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame");
 
-    /* Draw the active selection border */
-    if (m_timeSelectionStart != m_timeSelectionEnd) {
-        int selectionLeft  = timeToPosition(m_timeSelectionStart) + m_axisWidth;
-        int selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth;
 
+    /* Draw the active selection border */
+    if (m_selection.type == SelectTime) {
         painter.setPen(m_selectionBorder);
 
-        if (selectionLeft >= m_axisWidth && selectionLeft < widgetWidth) {
-            painter.drawLine(selectionLeft, 0, selectionLeft, widgetHeight);
+        m_selectionLeft += m_axisWidth;
+        m_selectionRight += m_axisWidth;
+
+        if (m_selectionLeft >= m_axisWidth && m_selectionLeft < widgetWidth) {
+            painter.drawLine(m_selectionLeft, 0, m_selectionLeft, widgetHeight);
         }
 
-        if (selectionRight >= m_axisWidth && selectionRight < widgetWidth) {
-            painter.drawLine(selectionRight, 0, selectionRight, widgetHeight);
+        if (m_selectionRight >= m_axisWidth && m_selectionRight < widgetWidth) {
+            painter.drawLine(m_selectionRight, 0, m_selectionRight, widgetHeight);
         }
 
-        selectionLeft = qBound(m_axisWidth, selectionLeft, widgetWidth);
-        selectionRight = qBound(m_axisWidth, selectionRight, widgetWidth);
+        m_selectionLeft = qBound(m_axisWidth, m_selectionLeft, widgetWidth);
+        m_selectionRight = qBound(m_axisWidth, m_selectionRight, widgetWidth);
 
-        painter.drawLine(selectionLeft, m_axisHeight - 1, selectionRight, m_axisHeight - 1);
-        painter.fillRect(selectionLeft, 0, selectionRight - selectionLeft, widgetHeight, m_selectionBackground);
+        painter.drawLine(m_selectionLeft, m_axisHeight - 1, m_selectionRight, m_axisHeight - 1);
+        painter.fillRect(m_selectionLeft, 0, m_selectionRight - m_selectionLeft, widgetHeight, m_selectionBackground);
     }
 
+
     /* Draw the ruler zoom */
     if (m_mousePressMode == RulerZoom) {
         int x1 = m_mousePressPosition.x();