From d3d1bcd13621ed59882c9e90c0393cce22332457 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 13 Jul 2017 18:44:41 +0100 Subject: [PATCH] Add work in progress timeline bar * Has an EID scale bar along the top, and simple zoom/pan, but nothing else. --- qrenderdoc/Code/CaptureContext.cpp | 24 ++ qrenderdoc/Code/CaptureContext.h | 5 + qrenderdoc/Code/Interface/QRDInterface.h | 82 +++++-- qrenderdoc/Windows/MainWindow.cpp | 12 + qrenderdoc/Windows/MainWindow.h | 2 + qrenderdoc/Windows/PythonShell.cpp | 9 + qrenderdoc/Windows/TimelineBar.cpp | 293 +++++++++++++++++++++++ qrenderdoc/Windows/TimelineBar.h | 81 +++++++ qrenderdoc/qrenderdoc.pro | 2 + qrenderdoc/qrenderdoc_local.vcxproj | 8 + 10 files changed, 504 insertions(+), 14 deletions(-) create mode 100644 qrenderdoc/Windows/TimelineBar.cpp create mode 100644 qrenderdoc/Windows/TimelineBar.h diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 322c25a4c..5fadd91fa 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -46,6 +46,7 @@ #include "Windows/ShaderViewer.h" #include "Windows/StatisticsViewer.h" #include "Windows/TextureViewer.h" +#include "Windows/TimelineBar.h" #include "QRDUtils.h" CaptureContext::CaptureContext(QString paramFilename, QString remoteHost, uint32_t remoteIdent, @@ -728,6 +729,18 @@ IStatisticsViewer *CaptureContext::GetStatisticsViewer() return m_StatisticsViewer; } +ITimelineBar *CaptureContext::GetTimelineBar() +{ + if(m_TimelineBar) + return m_TimelineBar; + + m_TimelineBar = new TimelineBar(*this, m_MainWindow); + m_TimelineBar->setObjectName(lit("timelineBar")); + setupDockWindow(m_TimelineBar); + + return m_TimelineBar; +} + IPythonShell *CaptureContext::GetPythonShell() { if(m_PythonShell) @@ -780,6 +793,11 @@ void CaptureContext::ShowStatisticsViewer() m_MainWindow->showStatisticsViewer(); } +void CaptureContext::ShowTimelineBar() +{ + m_MainWindow->showTimelineBar(); +} + void CaptureContext::ShowPythonShell() { m_MainWindow->showPythonShell(); @@ -878,6 +896,10 @@ QWidget *CaptureContext::CreateBuiltinWindow(const QString &objectName) { return GetStatisticsViewer()->Widget(); } + else if(objectName == lit("timelineBar")) + { + return GetTimelineBar()->Widget(); + } else if(objectName == lit("pythonShell")) { return GetPythonShell()->Widget(); @@ -904,6 +926,8 @@ void CaptureContext::BuiltinWindowClosed(QWidget *window) m_DebugMessageView = NULL; else if(m_StatisticsViewer && m_StatisticsViewer->Widget() == window) m_StatisticsViewer = NULL; + else if(m_TimelineBar && m_TimelineBar->Widget() == window) + m_TimelineBar = NULL; else if(m_PythonShell && m_PythonShell->Widget() == window) m_PythonShell = NULL; else diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index eebd593c5..63e82d29d 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -46,6 +46,7 @@ class TextureViewer; class CaptureDialog; class DebugMessageView; class StatisticsViewer; +class TimelineBar; class PythonShell; QString ConfigFilePath(const QString &filename); @@ -131,6 +132,7 @@ public: ICaptureDialog *GetCaptureDialog() override; IDebugMessageView *GetDebugMessageView() override; IStatisticsViewer *GetStatisticsViewer() override; + ITimelineBar *GetTimelineBar() override; IPythonShell *GetPythonShell() override; bool HasEventBrowser() override { return m_EventBrowser != NULL; } @@ -141,6 +143,7 @@ public: bool HasCaptureDialog() override { return m_CaptureDialog != NULL; } bool HasDebugMessageView() override { return m_DebugMessageView != NULL; } bool HasStatisticsViewer() override { return m_StatisticsViewer != NULL; } + bool HasTimelineBar() override { return m_TimelineBar != NULL; } bool HasPythonShell() override { return m_PythonShell != NULL; } void ShowEventBrowser() override; void ShowAPIInspector() override; @@ -150,6 +153,7 @@ public: void ShowCaptureDialog() override; void ShowDebugMessageView() override; void ShowStatisticsViewer() override; + void ShowTimelineBar() override; void ShowPythonShell() override; IShaderViewer *EditShader(bool customShader, const QString &entryPoint, const QStringMap &files, @@ -271,5 +275,6 @@ private: CaptureDialog *m_CaptureDialog = NULL; DebugMessageView *m_DebugMessageView = NULL; StatisticsViewer *m_StatisticsViewer = NULL; + TimelineBar *m_TimelineBar = NULL; PythonShell *m_PythonShell = NULL; }; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index e429a46f2..247fca9c4 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -104,7 +104,8 @@ struct IMainWindow typedef std::function ShortcutCallback; DOCUMENT( - "Retrieves the QWidget for this :class:`MainWindow` if PySide2 is available, or ``None``."); + "Retrieves the QWidget for this :class:`MainWindow` if PySide2 is available, or otherwise a " + "unique opaque pointer that can be passed to RenderDoc functions expecting a QWidget."); virtual QWidget *Widget() = 0; DOCUMENT(R"(Register a callback for a particular key shortcut. @@ -138,7 +139,8 @@ DOCUMENT("The event browser window."); struct IEventBrowser { DOCUMENT( - "Retrieves the QWidget for this :class:`EventBrowser` if PySide2 is available, or ``None``."); + "Retrieves the QWidget for this :class:`EventBrowser` if PySide2 is available, or otherwise " + "unique opaque pointer that can be passed to RenderDoc functions expecting a QWidget."); virtual QWidget *Widget() = 0; DOCUMENT("Updates the duration column if the selected time unit changes."); @@ -155,7 +157,8 @@ DOCUMENT("The API inspector window."); struct IAPIInspector { DOCUMENT( - "Retrieves the QWidget for this :class:`APIInspector` if PySide2 is available, or ``None``."); + "Retrieves the QWidget for this :class:`APIInspector` if PySide2 is available, or otherwise " + "unique opaque pointer that can be passed to RenderDoc functions expecting a QWidget."); virtual QWidget *Widget() = 0; DOCUMENT("Refresh the current API view - useful if callstacks are now available."); @@ -173,7 +176,8 @@ struct IPipelineStateViewer { DOCUMENT( "Retrieves the QWidget for this :class:`PipelineStateViewer` if PySide2 is available, or " - "``None``."); + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; DOCUMENT(R"(Prompt the user to save the binary form of the given shader to disk. @@ -194,7 +198,8 @@ struct ITextureViewer { DOCUMENT( "Retrieves the QWidget for this :class:`TextureViewer` if PySide2 is available, or " - "``None``."); + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; DOCUMENT(R"(Open a texture view, optionally raising this window to the foreground. @@ -221,7 +226,8 @@ DOCUMENT("The buffer viewer window, either a raw buffer or the geometry pipeline struct IBufferViewer { DOCUMENT( - "Retrieves the QWidget for this :class:`BufferViewer` if PySide2 is available, or ``None``."); + "Retrieves the QWidget for this :class:`BufferViewer` if PySide2 is available, or otherwise " + "unique opaque pointer that can be passed to RenderDoc functions expecting a QWidget."); virtual QWidget *Widget() = 0; DOCUMENT(R"(Scroll to the given row in the given stage's data. @@ -262,7 +268,8 @@ struct ICaptureDialog { DOCUMENT( "Retrieves the QWidget for this :class:`CaptureDialog` if PySide2 is available, or " - "``None``."); + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; DOCUMENT(R"(Determines if the window is in inject or launch mode. @@ -344,7 +351,8 @@ struct IDebugMessageView { DOCUMENT( "Retrieves the QWidget for this :class:`DebugMessageView` if PySide2 is available, or " - "``None``."); + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; protected: @@ -359,7 +367,8 @@ struct IStatisticsViewer { DOCUMENT( "Retrieves the QWidget for this :class:`StatisticsViewer` if PySide2 is available, or " - "``None``."); + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; protected: @@ -367,14 +376,40 @@ protected: ~IStatisticsViewer() = default; }; +DOCUMENT("The timeline bar."); +struct ITimelineBar +{ + DOCUMENT( + "Retrieves the QWidget for this :class:`TimelineBar` if PySide2 is available, or otherwise " + "unique opaque pointer that can be passed to RenderDoc functions expecting a QWidget."); + virtual QWidget *Widget() = 0; + + DOCUMENT(R"(Highlights the frame usage of the specified resource. + +:param ~renderdoc.ResourceId id: The ID of the resource to highlight. +)"); + virtual void HighlightResourceUsage(ResourceId id) = 0; + + DOCUMENT(R"(Highlights the modifications in a frame of a given resource. + +:param ~renderdoc.ResourceId id: The ID of the resource that is being modified. +:param list history: A list of :class:`~renderdoc.PixelModification` events to display. +)"); + virtual void HighlightHistory(ResourceId id, const QList &history) = 0; + +protected: + ITimelineBar() = default; + ~ITimelineBar() = default; +}; + DECLARE_REFLECTION_STRUCT(IStatisticsViewer); DOCUMENT("The interactive python shell."); struct IPythonShell { DOCUMENT( - "Retrieves the QWidget for this :class:`PythonShell` if PySide2 is available, or " - "``None``."); + "Retrieves the QWidget for this :class:`PythonShell` if PySide2 is available, or otherwise " + "unique opaque pointer that can be passed to RenderDoc functions expecting a QWidget."); virtual QWidget *Widget() = 0; protected: @@ -410,7 +445,8 @@ struct IShaderViewer typedef std::function CloseCallback; DOCUMENT( - "Retrieves the QWidget for this :class:`ShaderViewer` if PySide2 is available, or ``None``."); + "Retrieves the QWidget for this :class:`ShaderViewer` if PySide2 is available, or otherwise " + "unique opaque pointer that can be passed to RenderDoc functions expecting a QWidget."); virtual QWidget *Widget() = 0; DOCUMENT(R"(Retrieves the current step in the debugging. @@ -451,7 +487,8 @@ struct IConstantBufferPreviewer { DOCUMENT( "Retrieves the QWidget for this :class:`ConstantBufferPreviewer` if PySide2 is available, or " - "``None``."); + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; protected: @@ -466,7 +503,8 @@ struct IPixelHistoryView { DOCUMENT( "Retrieves the QWidget for this :class:`PixelHistoryView` if PySide2 is available, or " - "``None``."); + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; DOCUMENT(R"(Set the history displayed in this window. @@ -1082,6 +1120,13 @@ as well as messages generated during replay and analysis. )"); virtual IStatisticsViewer *GetStatisticsViewer() = 0; + DOCUMENT(R"(Retrieve the current singleton :class:`TimelineBar`. + +:return: The current window, which is created (but not shown) it there wasn't one open. +:rtype: TimelineBar +)"); + virtual ITimelineBar *GetTimelineBar() = 0; + DOCUMENT(R"(Retrieve the current singleton :class:`PythonShell`. :return: The current window, which is created (but not shown) it there wasn't one open. @@ -1145,6 +1190,13 @@ as well as messages generated during replay and analysis. )"); virtual bool HasStatisticsViewer() = 0; + DOCUMENT(R"(Check if there is a current :class:`TimelineBar` open. + +:return: ``True`` if there is a window open. +:rtype: ``bool`` +)"); + virtual bool HasTimelineBar() = 0; + DOCUMENT(R"(Check if there is a current :class:`PythonShell` open. :return: ``True`` if there is a window open. @@ -1170,6 +1222,8 @@ as well as messages generated during replay and analysis. DOCUMENT( "Raise the current :class:`StatisticsViewer`, showing it in the default place if needed."); virtual void ShowStatisticsViewer() = 0; + DOCUMENT("Raise the current :class:`TimelineBar`, showing it in the default place if needed."); + virtual void ShowTimelineBar() = 0; DOCUMENT("Raise the current :class:`PythonShell`, showing it in the default place if needed."); virtual void ShowPythonShell() = 0; diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index b62ca136a..56aaf4839 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -1486,6 +1486,18 @@ void MainWindow::on_action_Statistics_Viewer_triggered() ui->toolWindowManager->addToolWindow(stats, mainToolArea()); } +void MainWindow::on_action_Timeline_triggered() +{ + QWidget *stats = m_Ctx.GetTimelineBar()->Widget(); + + if(ui->toolWindowManager->toolWindows().contains(stats)) + ToolWindowManager::raiseToolWindow(stats); + else + ui->toolWindowManager->addToolWindow( + stats, + ToolWindowManager::AreaReference(ToolWindowManager::TopWindowSide, mainToolArea().area())); +} + void MainWindow::on_action_Python_Shell_triggered() { QWidget *py = m_Ctx.GetPythonShell()->Widget(); diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 7061776f0..91757575b 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -92,6 +92,7 @@ public: void showCaptureDialog() { on_action_Launch_Application_triggered(); } void showDebugMessageView() { on_action_Errors_and_Warnings_triggered(); } void showStatisticsViewer() { on_action_Statistics_Viewer_triggered(); } + void showTimelineBar() { on_action_Timeline_triggered(); } void showPythonShell() { on_action_Python_Shell_triggered(); } private slots: // automatic slots @@ -108,6 +109,7 @@ private slots: void on_action_Launch_Application_triggered(); void on_action_Errors_and_Warnings_triggered(); void on_action_Statistics_Viewer_triggered(); + void on_action_Timeline_triggered(); void on_action_Python_Shell_triggered(); void on_action_Inject_into_Process_triggered(); void on_action_Resolve_Symbols_triggered(); diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index bafdd1f6e..f9b26e986 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -201,6 +201,10 @@ struct CaptureContextInvoker : ICaptureContext { return InvokeRetFunction(&ICaptureContext::GetStatisticsViewer); } + virtual ITimelineBar *GetTimelineBar() override + { + return InvokeRetFunction(&ICaptureContext::GetTimelineBar); + } virtual IPythonShell *GetPythonShell() override { return InvokeRetFunction(&ICaptureContext::GetPythonShell); @@ -237,6 +241,10 @@ struct CaptureContextInvoker : ICaptureContext { return InvokeRetFunction(&ICaptureContext::HasStatisticsViewer); } + virtual bool HasTimelineBar() override + { + return InvokeRetFunction(&ICaptureContext::HasTimelineBar); + } virtual bool HasPythonShell() override { return InvokeRetFunction(&ICaptureContext::HasPythonShell); @@ -271,6 +279,7 @@ struct CaptureContextInvoker : ICaptureContext { InvokeVoidFunction(&ICaptureContext::ShowStatisticsViewer); } + virtual void ShowTimelineBar() override { InvokeVoidFunction(&ICaptureContext::ShowTimelineBar); } virtual void ShowPythonShell() override { InvokeVoidFunction(&ICaptureContext::ShowPythonShell); } virtual IShaderViewer *EditShader(bool customShader, const QString &entryPoint, const QStringMap &files, IShaderViewer::SaveCallback saveCallback, diff --git a/qrenderdoc/Windows/TimelineBar.cpp b/qrenderdoc/Windows/TimelineBar.cpp new file mode 100644 index 000000000..c4af528b1 --- /dev/null +++ b/qrenderdoc/Windows/TimelineBar.cpp @@ -0,0 +1,293 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "TimelineBar.h" +#include +#include +#include +#include + +TimelineBar::TimelineBar(ICaptureContext &ctx, QWidget *parent) + : QAbstractScrollArea(parent), m_Ctx(ctx) +{ + m_Ctx.AddLogViewer(this); + + setMouseTracking(true); + + setFrameShape(NoFrame); + + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + QObject::connect(horizontalScrollBar(), &QScrollBar::valueChanged, + [this](int value) { m_pan = -value; }); + + setWindowTitle(tr("Timeline")); +} + +TimelineBar::~TimelineBar() +{ + m_Ctx.BuiltinWindowClosed(this); + + m_Ctx.RemoveLogViewer(this); +} + +void TimelineBar::HighlightResourceUsage(ResourceId id) +{ +} + +void TimelineBar::HighlightHistory(ResourceId id, const QList &history) +{ +} + +void TimelineBar::OnLogfileClosed() +{ + setWindowTitle(tr("Timeline")); + + m_Events.clear(); +} + +void TimelineBar::OnLogfileLoaded() +{ + setWindowTitle(tr("Timeline - Frame #%1").arg(m_Ctx.FrameInfo().frameNumber)); + + addEvents(m_Ctx.CurDrawcalls()); + + m_zoom = 1.0; + m_pan = 0.0; + m_lastPos = QPointF(); + + layout(); +} + +void TimelineBar::OnEventChanged(uint32_t eventID) +{ +} + +QSize TimelineBar::minimumSizeHint() const +{ + return QSize(margin * 4 + borderWidth * 2 + 100, margin * 4 + borderWidth * 2 + 40); +} + +void TimelineBar::resizeEvent(QResizeEvent *e) +{ + layout(); +} + +void TimelineBar::layout() +{ + QFontMetrics fm(Formatter::PreferredFont()); + + // outer margin + border + inner margin + width of title + scale bar text advance + m_leftCoord = margin + borderWidth + margin + fm.width(scaleTitle) + fm.height(); + + m_totalSize = viewport()->width() - m_leftCoord - margin - borderWidth - margin; + + uint32_t maxEID = m_Events.isEmpty() ? 0 : m_Events.back(); + + int stepSize = 1; + int stepMagnitude = 1; + + m_scaleLabelWidth = fm.width(QString::number(maxEID)) + fm.height(); + m_scaleLabelStep = stepSize * stepMagnitude; + + qreal virtualSize = m_totalSize * m_zoom; + + while(virtualSize > 0 && (maxEID / m_scaleLabelStep) * m_scaleLabelWidth > virtualSize) + { + // increment 1, 2, 5, 10, 20, 50, 100, ... + if(stepSize == 1) + { + stepSize = 2; + } + else if(stepSize == 2) + { + stepSize = 5; + } + else if(stepSize == 5) + { + stepSize = 1; + stepMagnitude *= 10; + } + + m_scaleLabelStep = stepSize * stepMagnitude; + } + + int numLabels = maxEID / m_scaleLabelStep + 1; + + m_scaleLabelWidth = virtualSize / numLabels; + + horizontalScrollBar()->setRange(0, virtualSize - m_totalSize); + horizontalScrollBar()->setSingleStep(m_scaleLabelWidth); + horizontalScrollBar()->setPageStep(m_totalSize); + horizontalScrollBar()->setValue(-m_pan); + + viewport()->update(); +} + +void TimelineBar::mousePressEvent(QMouseEvent *e) +{ + m_lastPos = e->localPos(); +} + +void TimelineBar::mouseReleaseEvent(QMouseEvent *e) +{ +} + +void TimelineBar::mouseMoveEvent(QMouseEvent *e) +{ + if(e->buttons() == Qt::LeftButton) + { + qreal delta = e->localPos().x() - m_lastPos.x(); + m_pan += delta; + + m_pan = qBound(-m_totalSize * (m_zoom - 1.0), m_pan, 0.0); + + m_lastPos = e->localPos(); + + layout(); + } +} + +void TimelineBar::wheelEvent(QWheelEvent *e) +{ + float mod = (1.0 + e->delta() / 2500.0f); + + qreal prevZoom = m_zoom; + + m_zoom = qMax(1.0, m_zoom * mod); + + qreal zoomDelta = (m_zoom / prevZoom); + + // adjust the pan so that it's still in bounds, and so the zoom acts centred on the mouse + qreal newPan = m_pan; + + newPan -= (e->x() - m_leftCoord); + newPan = newPan * zoomDelta; + newPan += (e->x() - m_leftCoord); + + m_pan = qBound(-m_totalSize * (m_zoom - 1.0), newPan, 0.0); + + e->accept(); + + layout(); +} + +void TimelineBar::paintEvent(QPaintEvent *e) +{ + QPainter p(viewport()); + + p.setFont(font()); + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::HighQualityAntialiasing); + + QRectF r = viewport()->rect(); + + p.fillRect(r, palette().brush(QPalette::Window)); + + r = r.marginsRemoved(QMarginsF(margin, margin, margin, margin)); + + p.fillRect(r, palette().brush(QPalette::Text)); + + r = r.marginsRemoved(QMarginsF(borderWidth, borderWidth, borderWidth, borderWidth)); + + p.fillRect(r, palette().brush(QPalette::Base)); + + QTextOption to; + + to.setWrapMode(QTextOption::NoWrap); + to.setAlignment(Qt::AlignLeft | Qt::AlignTop); + + QRectF scaleRect = r; + scaleRect.setHeight(qMin(scaleRect.height(), p.fontMetrics().height() + margin * 2)); + + p.setPen(QPen(palette().brush(QPalette::Text), 0.0)); + p.drawLine(scaleRect.bottomLeft() + QPointF(0, 0.5), scaleRect.bottomRight() + QPointF(0, 0.5)); + + scaleRect = scaleRect.marginsRemoved(QMargins(margin, margin, margin, margin)); + QString text = scaleTitle; + p.drawText(scaleRect, text, to); + + scaleRect.setLeft(scaleRect.left() + p.fontMetrics().width(text) + scaleRect.height()); + + if(!m_Ctx.LogLoaded()) + return; + + p.setClipRect(scaleRect); + + scaleRect.setLeft(scaleRect.left() + m_pan); + + uint32_t maxEID = m_Events.isEmpty() ? 0 : m_Events.back(); + + to.setAlignment(Qt::AlignCenter | Qt::AlignTop); + + p.setFont(Formatter::PreferredFont()); + + for(uint32_t i = 0; i <= maxEID; i += m_scaleLabelStep) + { + if(scaleRect.left() + m_scaleLabelWidth >= 0) + { + QRectF labelRect = scaleRect; + labelRect.setWidth(m_scaleLabelWidth); + p.drawText(labelRect, QString::number(i), to); + } + + scaleRect.setLeft(scaleRect.left() + m_scaleLabelWidth); + + if(scaleRect.width() <= 0) + break; + } + + p.setClipRect(viewport()->rect()); +} + +uint32_t TimelineBar::eventAt(qreal x) +{ + if(m_Events.isEmpty()) + return 0; + + // pan x + x -= m_pan; + + // normalise x between 0 and 1, left to right of bar area. + x -= m_leftCoord; + x /= m_totalSize; + + // apply zoom factor + x /= m_zoom; + + // x = 0 is the left side of EID 0, x = 1 is the right side of the last EID + return uint32_t(x * (m_Events.back() + 1)); +} + +void TimelineBar::addEvents(const rdctype::array &curDraws) +{ + for(const DrawcallDescription &d : curDraws) + { + addEvents(d.children); + + if(!(d.flags & (DrawFlags::SetMarker | DrawFlags::PushMarker)) || (d.flags & DrawFlags::APICalls)) + m_Events.push_back(d.eventID); + } +} diff --git a/qrenderdoc/Windows/TimelineBar.h b/qrenderdoc/Windows/TimelineBar.h new file mode 100644 index 000000000..77caf10b6 --- /dev/null +++ b/qrenderdoc/Windows/TimelineBar.h @@ -0,0 +1,81 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include +#include "Code/CaptureContext.h" + +class TimelineBar : public QAbstractScrollArea, public ITimelineBar, public ILogViewer +{ + Q_OBJECT + +public: + explicit TimelineBar(ICaptureContext &ctx, QWidget *parent = 0); + ~TimelineBar(); + + QSize minimumSizeHint() const override; + + // IStatisticsViewer + QWidget *Widget() override { return this; } + void HighlightResourceUsage(ResourceId id) override; + void HighlightHistory(ResourceId id, const QList &history) override; + // ILogViewerForm + void OnLogfileLoaded() override; + void OnLogfileClosed() override; + void OnSelectedEventChanged(uint32_t eventID) override {} + void OnEventChanged(uint32_t eventID) override; + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void wheelEvent(QWheelEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + ICaptureContext &m_Ctx; + + QVector m_Events; + + const qreal margin = 2.0; + const qreal borderWidth = 1.0; + const QString scaleTitle = lit("EID:"); + + qreal m_leftCoord = 0; + qreal m_totalSize = 0; + + int m_scaleLabelStep = 0; + qreal m_scaleLabelWidth = 0; + + qreal m_zoom = 1.0; + qreal m_pan = 0.0; + QPointF m_lastPos; + + void layout(); + + uint32_t eventAt(qreal x); + void addEvents(const rdctype::array &curDraws); +}; diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 6851e7f70..df2aab217 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -200,6 +200,7 @@ SOURCES += Code/qrenderdoc.cpp \ Widgets/Extended/RDTableView.cpp \ Windows/DebugMessageView.cpp \ Windows/StatisticsViewer.cpp \ + Windows/TimelineBar.cpp \ Windows/Dialogs/SettingsDialog.cpp \ Windows/Dialogs/OrderedListEditor.cpp \ Widgets/Extended/RDTableWidget.cpp \ @@ -260,6 +261,7 @@ HEADERS += Code/CaptureContext.h \ Widgets/Extended/RDTableView.h \ Windows/DebugMessageView.h \ Windows/StatisticsViewer.h \ + Windows/TimelineBar.h \ Windows/Dialogs/SettingsDialog.h \ Windows/Dialogs/OrderedListEditor.h \ Widgets/Extended/RDTableWidget.h \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 609752aba..b48e8a00d 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -626,6 +626,7 @@ + @@ -701,6 +702,7 @@ + NotUsing @@ -1175,6 +1177,12 @@ MOC %(Filename).h $(IntDir)generated\moc_%(Filename).cpp + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) + $(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I$(ProjectDir) -I$(SolutionDir)\renderdoc\api\replay -I$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(IntDir)generated\moc_%(Filename).cpp + MOC %(Filename).h + $(IntDir)generated\moc_%(Filename).cpp + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) $(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I$(ProjectDir) -I$(SolutionDir)\renderdoc\api\replay -I$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(IntDir)generated\moc_%(Filename).cpp