From 039b65f89f97c67445ebce3ebb20217c4d7e379d Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 26 Jun 2019 19:39:14 +0100 Subject: [PATCH] Add a viewer of the diagnostic log in the UI itself --- qrenderdoc/Code/CaptureContext.cpp | 24 + qrenderdoc/Code/CaptureContext.h | 5 + .../Code/Interface/PersistantConfig.cpp | 2 +- qrenderdoc/Code/Interface/QRDInterface.h | 33 ++ qrenderdoc/Widgets/Extended/RDTreeView.cpp | 3 +- qrenderdoc/Windows/DebugMessageView.h | 1 - qrenderdoc/Windows/LogView.cpp | 450 ++++++++++++++++++ qrenderdoc/Windows/LogView.h | 97 ++++ qrenderdoc/Windows/LogView.ui | 316 ++++++++++++ qrenderdoc/Windows/MainWindow.cpp | 17 +- qrenderdoc/Windows/MainWindow.h | 2 + qrenderdoc/Windows/MainWindow.ui | 6 + qrenderdoc/Windows/PythonShell.cpp | 12 + qrenderdoc/qrenderdoc.pro | 3 + qrenderdoc/qrenderdoc_local.vcxproj | 15 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 15 + renderdoc/android/android.cpp | 4 +- renderdoc/api/replay/renderdoc_replay.h | 5 +- renderdoc/api/replay/renderdoc_tostr.inl | 14 + renderdoc/api/replay/replay_enums.h | 3 + renderdoc/replay/entry_points.cpp | 5 + 21 files changed, 1018 insertions(+), 14 deletions(-) create mode 100644 qrenderdoc/Windows/LogView.cpp create mode 100644 qrenderdoc/Windows/LogView.h create mode 100644 qrenderdoc/Windows/LogView.ui diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index f550723f8..b57c5219c 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -46,6 +46,7 @@ #include "Windows/Dialogs/LiveCapture.h" #include "Windows/Dialogs/SettingsDialog.h" #include "Windows/EventBrowser.h" +#include "Windows/LogView.h" #include "Windows/MainWindow.h" #include "Windows/PerformanceCounterViewer.h" #include "Windows/PipelineState/PipelineStateViewer.h" @@ -1896,6 +1897,18 @@ IDebugMessageView *CaptureContext::GetDebugMessageView() return m_DebugMessageView; } +IDiagnosticLogView *CaptureContext::GetDiagnosticLogView() +{ + if(m_DiagnosticLogView) + return m_DiagnosticLogView; + + m_DiagnosticLogView = new LogView(*this, m_MainWindow); + m_DiagnosticLogView->setObjectName(lit("diagnosticLogView")); + setupDockWindow(m_DiagnosticLogView); + + return m_DiagnosticLogView; +} + ICommentView *CaptureContext::GetCommentView() { if(m_CommentView) @@ -2003,6 +2016,11 @@ void CaptureContext::ShowDebugMessageView() m_MainWindow->showDebugMessageView(); } +void CaptureContext::ShowDiagnosticLogView() +{ + m_MainWindow->showDiagnosticLogView(); +} + void CaptureContext::ShowCommentView() { m_MainWindow->showCommentView(); @@ -2122,6 +2140,10 @@ QWidget *CaptureContext::CreateBuiltinWindow(const rdcstr &objectName) { return GetDebugMessageView()->Widget(); } + else if(objectName == "diagnosticLogView") + { + return GetDiagnosticLogView()->Widget(); + } else if(objectName == "commentView") { return GetCommentView()->Widget(); @@ -2166,6 +2188,8 @@ void CaptureContext::BuiltinWindowClosed(QWidget *window) m_MeshPreview = NULL; else if(m_DebugMessageView && m_DebugMessageView->Widget() == window) m_DebugMessageView = NULL; + else if(m_DiagnosticLogView && m_DiagnosticLogView->Widget() == window) + m_DiagnosticLogView = NULL; else if(m_CommentView && m_CommentView->Widget() == window) m_CommentView = NULL; else if(m_StatisticsViewer && m_StatisticsViewer->Widget() == window) diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 2ef50fdbd..f95b56185 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -45,6 +45,7 @@ class BufferViewer; class TextureViewer; class CaptureDialog; class DebugMessageView; +class LogView; class CommentView; class PerformanceCounterViewer; class StatisticsViewer; @@ -193,6 +194,7 @@ public: IPipelineStateViewer *GetPipelineViewer() override; ICaptureDialog *GetCaptureDialog() override; IDebugMessageView *GetDebugMessageView() override; + IDiagnosticLogView *GetDiagnosticLogView() override; ICommentView *GetCommentView() override; IPerformanceCounterViewer *GetPerformanceCounterViewer() override; IStatisticsViewer *GetStatisticsViewer() override; @@ -207,6 +209,7 @@ public: bool HasMeshPreview() override { return m_MeshPreview != NULL; } bool HasCaptureDialog() override { return m_CaptureDialog != NULL; } bool HasDebugMessageView() override { return m_DebugMessageView != NULL; } + bool HasDiagnosticLogView() override { return m_DiagnosticLogView != NULL; } bool HasCommentView() override { return m_CommentView != NULL; } bool HasPerformanceCounterViewer() override { return m_PerformanceCounterViewer != NULL; } bool HasStatisticsViewer() override { return m_StatisticsViewer != NULL; } @@ -220,6 +223,7 @@ public: void ShowPipelineViewer() override; void ShowCaptureDialog() override; void ShowDebugMessageView() override; + void ShowDiagnosticLogView() override; void ShowCommentView() override; void ShowPerformanceCounterViewer() override; void ShowStatisticsViewer() override; @@ -383,6 +387,7 @@ private: PipelineStateViewer *m_PipelineViewer = NULL; CaptureDialog *m_CaptureDialog = NULL; DebugMessageView *m_DebugMessageView = NULL; + LogView *m_DiagnosticLogView = NULL; CommentView *m_CommentView = NULL; PerformanceCounterViewer *m_PerformanceCounterViewer = NULL; StatisticsViewer *m_StatisticsViewer = NULL; diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp index fbbbd6349..d0db2e14f 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.cpp +++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp @@ -238,7 +238,7 @@ void PersistantConfig::AddAndroidHosts() SetConfigSetting("MaxConnectTimeout", QString::number(Android_MaxConnectTimeout)); rdcstr androidHosts; - RENDERDOC_EnumerateAndroidDevices(&androidHosts); + RENDERDOC_EnumerateAndroidDevices(androidHosts); for(const QString &hostName : QString(androidHosts).split(QLatin1Char(','), QString::SkipEmptyParts)) { diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 9bd562446..453f80cde 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -441,6 +441,22 @@ protected: DECLARE_REFLECTION_STRUCT(IDebugMessageView); +DOCUMENT("The diagnostic log viewing window."); +struct IDiagnosticLogView +{ + DOCUMENT( + "Retrieves the QWidget for this :class:`DiagnosticLogView` if PySide2 is available, or " + "otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); + virtual QWidget *Widget() = 0; + +protected: + IDiagnosticLogView() = default; + ~IDiagnosticLogView() = default; +}; + +DECLARE_REFLECTION_STRUCT(IDiagnosticLogView); + DOCUMENT("The capture comments window."); struct ICommentView { @@ -1597,6 +1613,13 @@ If no bookmark exists, this function will do nothing. )"); virtual IDebugMessageView *GetDebugMessageView() = 0; + DOCUMENT(R"(Retrieve the current singleton :class:`LogView`. + +:return: The current window, which is created (but not shown) it there wasn't one open. +:rtype: LogView +)"); + virtual IDiagnosticLogView *GetDiagnosticLogView() = 0; + DOCUMENT(R"(Retrieve the current singleton :class:`CommentView`. :return: The current window, which is created (but not shown) it there wasn't one open. @@ -1688,6 +1711,13 @@ If no bookmark exists, this function will do nothing. )"); virtual bool HasDebugMessageView() = 0; + DOCUMENT(R"(Check if there is a current :class:`DiagnosticLogView` open. + +:return: ``True`` if there is a window open. +:rtype: ``bool`` +)"); + virtual bool HasDiagnosticLogView() = 0; + DOCUMENT(R"(Check if there is a current :class:`CommentView` open. :return: ``True`` if there is a window open. @@ -1748,6 +1778,9 @@ place if needed. DOCUMENT( "Raise the current :class:`DebugMessageView`, showing it in the default place if needed."); virtual void ShowDebugMessageView() = 0; + DOCUMENT( + "Raise the current :class:`DiagnosticLogView`, showing it in the default place if needed."); + virtual void ShowDiagnosticLogView() = 0; DOCUMENT("Raise the current :class:`CommentView`, showing it in the default place if needed."); virtual void ShowCommentView() = 0; DOCUMENT( diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.cpp b/qrenderdoc/Widgets/Extended/RDTreeView.cpp index 4a520de5c..b52241bd1 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeView.cpp @@ -165,9 +165,8 @@ void RDTreeView::leaveEvent(QEvent *e) void RDTreeView::keyPressEvent(QKeyEvent *e) { - emit(keyPress(e)); - QTreeView::keyPressEvent(e); + emit(keyPress(e)); } bool RDTreeView::viewportEvent(QEvent *event) diff --git a/qrenderdoc/Windows/DebugMessageView.h b/qrenderdoc/Windows/DebugMessageView.h index b530cf039..11f3629c6 100644 --- a/qrenderdoc/Windows/DebugMessageView.h +++ b/qrenderdoc/Windows/DebugMessageView.h @@ -67,7 +67,6 @@ private: Ui::DebugMessageView *ui; ICaptureContext &m_Ctx; - QVector m_Messages; DebugMessageItemModel *m_ItemModel; DebugMessageFilterModel *m_FilterModel; diff --git a/qrenderdoc/Windows/LogView.cpp b/qrenderdoc/Windows/LogView.cpp new file mode 100644 index 000000000..cf0c62839 --- /dev/null +++ b/qrenderdoc/Windows/LogView.cpp @@ -0,0 +1,450 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017-2019 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 "LogView.h" +#include +#include +#include +#include +#include "Code/QRDUtils.h" +#include "Widgets/Extended/RDHeaderView.h" +#include "ui_LogView.h" + +enum Columns +{ + Column_Source, + Column_PID, + Column_Timestamp, + Column_Location, + Column_Type, + Column_Message, + Column_Count, +}; + +class LogItemModel : public QAbstractItemModel +{ +public: + LogItemModel(LogView *view) : QAbstractItemModel(view), m_Viewer(view) {} + void refresh() + { + emit beginResetModel(); + emit endResetModel(); + } + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override + { + if(row < 0 || row >= rowCount()) + return QModelIndex(); + + return createIndex(row, column); + } + + QModelIndex parent(const QModelIndex &index) const override { return QModelIndex(); } + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + if(parent == QModelIndex()) + return m_Viewer->m_Messages.count(); + return 0; + } + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return Column_Count; } + Qt::ItemFlags flags(const QModelIndex &index) const override + { + if(!index.isValid()) + return 0; + + return QAbstractItemModel::flags(index); + } + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if(orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch(section) + { + case Column_Source: return lit("Source"); + case Column_PID: return lit("PID"); + case Column_Timestamp: return lit("Timestamp"); + case Column_Location: return lit("Location"); + case Column_Type: return lit("Type"); + case Column_Message: return lit("Message"); + default: break; + } + } + + return QVariant(); + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if(index.isValid()) + { + int row = index.row(); + int col = index.column(); + + if(col >= 0 && col < columnCount() && row < rowCount()) + { + const LogMessage &msg = m_Viewer->m_Messages[row]; + + if(role == Qt::DisplayRole) + { + switch(col) + { + case Column_Source: return msg.Source; + case Column_PID: return QString::number(msg.PID); + case Column_Timestamp: return msg.Timestamp.toString(lit("HH:mm:ss")); + case Column_Location: return msg.Location; + case Column_Type: return ToQStr(msg.Type); + case Column_Message: + { + QVariant desc = msg.Message; + RichResourceTextInitialise(desc); + return desc; + } + default: break; + } + } + else if(msg.Type == LogType::Error) + { + if(role == Qt::BackgroundRole) + return QBrush(QColor(255, 70, 70)); + if(role == Qt::ForegroundRole) + return QBrush(QColor(0, 0, 0)); + } + } + } + + return QVariant(); + } + +private: + LogView *m_Viewer; +}; + +class LogFilterModel : public QSortFilterProxyModel +{ +public: + LogFilterModel(LogView *view) : QSortFilterProxyModel(view), m_Viewer(view) {} + bool m_UseRegexp = false; + bool m_IncludeTextMatches = true; + QString m_FilterText; + QRegularExpression m_FilterRegexp; + QSet m_HiddenPIDs; + QSet m_HiddenTypes; + + void refresh() { invalidateFilter(); } +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override + { + return isVisibleRow(sourceRow); + } + + bool isVisibleRow(int sourceRow) const + { + const LogMessage &msg = m_Viewer->m_Messages[sourceRow]; + + if(m_HiddenPIDs.contains(msg.PID)) + return false; + + if(m_HiddenTypes.contains((uint32_t)msg.Type)) + return false; + + if(m_UseRegexp) + { + if(m_FilterRegexp.isValid()) + { + return (m_FilterRegexp.match(msg.Message).hasMatch()) == m_IncludeTextMatches; + } + } + else + { + if(!m_FilterText.isEmpty()) + { + return (msg.Message.contains(m_FilterText, Qt::CaseInsensitive)) == m_IncludeTextMatches; + } + } + + return true; + } + +private: + LogView *m_Viewer; +}; + +static QList logTypeStrings; + +LogView::LogView(ICaptureContext &ctx, QWidget *parent) + : QFrame(parent), ui(new Ui::LogView), m_Ctx(ctx) +{ + ui->setupUi(this); + + m_ItemModel = new LogItemModel(this); + m_FilterModel = new LogFilterModel(this); + + m_FilterModel->setSourceModel(m_ItemModel); + ui->messages->setModel(m_FilterModel); + + ui->messages->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + + m_TypeModel = new QStandardItemModel(0, 1, this); + + m_TypeModel->appendRow(new QStandardItem(tr("Log Type"))); + + for(LogType type : values()) + { + // don't bother allowing fatal filtering. The UI is no longer running when one of these is + // logged + if(type == LogType::Fatal) + continue; + + logTypeStrings.push_back(ToQStr(type)); + + QStandardItem *item = new QStandardItem(ToQStr(type)); + item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + item->setData(Qt::Checked, Qt::CheckStateRole); + m_TypeModel->appendRow(item); + } + + ui->typeFilter->setModel(m_TypeModel); + + m_PIDModel = new QStandardItemModel(0, 1, this); + + m_PIDModel->appendRow(new QStandardItem(tr("PID"))); + + ui->pidFilter->setModel(m_PIDModel); + + { + RDHeaderView *header = new RDHeaderView(Qt::Horizontal, this); + ui->messages->setHeader(header); + + header->setColumnStretchHints({-1, -1, -1, -1, -1, 1}); + } + + messages_refresh(); + + QObject::connect(m_TypeModel, &QStandardItemModel::itemChanged, this, &LogView::typeFilter_changed); + QObject::connect(m_PIDModel, &QStandardItemModel::itemChanged, this, &LogView::pidFilter_changed); + + QObject::connect(ui->messages, &RDTreeView::keyPress, this, &LogView::messages_keyPress); + + QObject::connect(&m_RefreshTimer, &QTimer::timeout, this, &LogView::messages_refresh); + m_RefreshTimer.setSingleShot(false); + m_RefreshTimer.setInterval(125); + m_RefreshTimer.start(); +} + +LogView::~LogView() +{ + m_Ctx.BuiltinWindowClosed(this); + + m_Messages.clear(); + m_ItemModel->refresh(); + + delete ui; +} + +void LogView::on_openExternal_clicked() +{ + QString logPath = QString::fromUtf8(RENDERDOC_GetLogFile()); + if(QFileInfo::exists(logPath)) + QDesktopServices::openUrl(QUrl::fromLocalFile(logPath)); +} + +void LogView::on_save_clicked() +{ + QString filename = + RDDialog::getSaveFileName(this, tr("Export log to disk"), QString(), + tr("Log Files (*.log);;Text files (*.txt);;All files (*)")); + + if(filename.isEmpty()) + return; + + QFile *f = new QFile(filename); + + if(!f->open(QIODevice::WriteOnly | QFile::Truncate)) + { + delete f; + RDDialog::critical(this, tr("Error exporting log"), + tr("Couldn't open file '%1' for writing").arg(filename)); + return; + } + + rdcstr contents; + RENDERDOC_GetLogFileContents(contents); + + f->write(QByteArray(contents.c_str(), contents.count())); + + delete f; +} + +void LogView::on_textFilter_textChanged(const QString &text) +{ + m_FilterModel->m_FilterText = text; + m_FilterModel->m_FilterRegexp = QRegularExpression(text); + m_FilterModel->m_FilterRegexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + m_FilterModel->refresh(); +} + +void LogView::on_textFilterMeaning_currentIndexChanged(int index) +{ + // 0 is Include, 1 is Exclude + m_FilterModel->m_IncludeTextMatches = (index == 0); + m_FilterModel->refresh(); +} + +void LogView::on_regexpFilter_toggled() +{ + m_FilterModel->m_UseRegexp = ui->regexpFilter->isChecked(); + m_FilterModel->refresh(); +} + +void LogView::messages_keyPress(QKeyEvent *event) +{ + if(event->matches(QKeySequence::Copy)) + { + QModelIndexList items = ui->messages->selectionModel()->selectedIndexes(); + + QList rows; + + for(QModelIndex idx : items) + { + if(!rows.contains(idx.row())) + rows.push_back(idx.row()); + } + + qSort(rows); + + int columns = m_ItemModel->columnCount(); + + QString clipboardText; + for(int r : rows) + { + const LogMessage &msg = m_Messages[r]; + + clipboardText += QFormatStr("%1 PID %2: [%3] %4 - %5 - %6\n") + .arg(msg.Source, -8) + .arg(msg.PID, 6) + .arg(msg.Timestamp.toString(lit("HH:mm:ss"))) + .arg(msg.Location, 26) + .arg(ToQStr(msg.Type), -7) + .arg(msg.Message); + } + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(clipboardText.trimmed()); + } +} + +void LogView::typeFilter_changed(QStandardItem *item) +{ + uint32_t type = m_TypeModel->indexFromItem(item).row() - 1; + + if(item->checkState() == Qt::Checked) + m_FilterModel->m_HiddenTypes.remove(type); + else + m_FilterModel->m_HiddenTypes.insert(type); + + m_FilterModel->refresh(); + + ui->typeFilter->setCurrentIndex(0); +} + +void LogView::pidFilter_changed(QStandardItem *item) +{ + uint32_t PID = item->text().toUInt(); + + if(item->checkState() == Qt::Checked) + m_FilterModel->m_HiddenPIDs.remove(PID); + else + m_FilterModel->m_HiddenPIDs.insert(PID); + + m_FilterModel->refresh(); + + ui->pidFilter->setCurrentIndex(0); +} + +void LogView::messages_refresh() +{ + rdcstr contents; + RENDERDOC_GetLogFileContents(contents); + + if(prevOffset == contents.size()) + return; + + // look at all new lines since the last one we saw + QStringList lines = QString(contents.substr(prevOffset)).split(QRegularExpression(lit("[\r\n]"))); + prevOffset = contents.size(); + + QString r = + lit("^" // start of the line + "([A-Z][A-Z][A-Z][A-Z]) " // project + "([0-9]+): " // PID + "\\[([0-9][0-9]):([0-9][0-9]):([0-9][0-9])\\] " // timestamp + "\\s*([^(]+)\\(\\s*([0-9]+)\\) - " // filename.ext( line) + "([A-Za-z]+)\\s+- " // type + "(.*)"); + + QRegularExpression logRegex(r); + + for(const QString &line : lines) + { + QRegularExpressionMatch match = logRegex.match(line); + + if(match.hasMatch()) + { + LogMessage msg; + msg.Source = match.captured(1); + + if(msg.Source == lit("ADRD")) + msg.Source = tr("Android"); + else if(msg.Source == lit("QTRD")) + msg.Source = tr("UI"); + else if(msg.Source == lit("RDOC")) + msg.Source = tr("Core"); + + msg.PID = match.captured(2).toUInt(); + msg.Timestamp = + QTime(match.captured(3).toUInt(), match.captured(4).toUInt(), match.captured(5).toUInt()); + msg.Location = QFormatStr("%1(%2)").arg(match.captured(6)).arg(match.captured(7)); + msg.Type = (LogType)logTypeStrings.indexOf(match.captured(8)); + msg.Message = match.captured(9).trimmed(); + + m_Messages.push_back(msg); + + if(!m_PIDs.contains(msg.PID)) + { + m_PIDs.append(msg.PID); + QStandardItem *item = new QStandardItem(QString::number(msg.PID)); + item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + item->setData(Qt::Checked, Qt::CheckStateRole); + m_PIDModel->appendRow(item); + } + } + } + + if(!lines.isEmpty()) + m_ItemModel->refresh(); + + if(ui->followNew->isChecked()) + ui->messages->scrollToBottom(); +} diff --git a/qrenderdoc/Windows/LogView.h b/qrenderdoc/Windows/LogView.h new file mode 100644 index 000000000..e7926a397 --- /dev/null +++ b/qrenderdoc/Windows/LogView.h @@ -0,0 +1,97 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017-2019 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 +#include "Code/Interface/QRDInterface.h" + +namespace Ui +{ +class LogView; +} + +class QStandardItem; +class QStandardItemModel; +class QAction; +class QMenu; +class LogItemModel; +class LogFilterModel; + +struct LogMessage +{ + QString Source; + uint32_t PID; + QTime Timestamp; + QString Location; + LogType Type; + QString Message; +}; + +class LogView : public QFrame, public IDiagnosticLogView +{ + Q_OBJECT + +public: + explicit LogView(ICaptureContext &ctx, QWidget *parent = 0); + ~LogView(); + + // ILogView + QWidget *Widget() override { return this; } +private slots: + // automatic slots + void on_openExternal_clicked(); + void on_save_clicked(); + void on_textFilter_textChanged(const QString &text); + void on_textFilterMeaning_currentIndexChanged(int index); + void on_regexpFilter_toggled(); + + // manual slots + void messages_refresh(); + void messages_keyPress(QKeyEvent *event); + void pidFilter_changed(QStandardItem *item); + void typeFilter_changed(QStandardItem *item); + +private: + Ui::LogView *ui; + ICaptureContext &m_Ctx; + + size_t prevOffset = 0; + + QVector m_Messages; + + QList m_PIDs; + + LogItemModel *m_ItemModel = NULL; + LogFilterModel *m_FilterModel = NULL; + + friend class LogItemModel; + friend class LogFilterModel; + + QStandardItemModel *m_PIDModel = NULL; + QStandardItemModel *m_TypeModel = NULL; + + QTimer m_RefreshTimer; +}; diff --git a/qrenderdoc/Windows/LogView.ui b/qrenderdoc/Windows/LogView.ui new file mode 100644 index 000000000..3092369a8 --- /dev/null +++ b/qrenderdoc/Windows/LogView.ui @@ -0,0 +1,316 @@ + + + LogView + + + + 0 + 0 + 581 + 404 + + + + Diagnostic Log + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 5 + + + 0 + + + 2 + + + 6 + + + 2 + + + + + Follow new messages + + + + :/arrow_refresh.png:/arrow_refresh.png + + + true + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Open in external editor + + + + :/action_hover.png:/action_hover.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Save + + + + :/save.png:/save.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 5 + + + QFrame::Panel + + + QFrame::Raised + + + + 2 + + + 6 + + + 2 + + + 6 + + + 2 + + + + + Filter + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + 8 + + + + + + + + 0 + 0 + + + + 5 + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + 7 + + + + + + + Text: + + + + + + + + 0 + 0 + + + + 2 + + + QComboBox::NoInsert + + + + Include + + + + + Exclude + + + + + + + + Regex + + + + + + + + 0 + 0 + + + + + + + + + + + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOn + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ContiguousSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + 0 + + + false + + + true + + + false + + + true + + + + + + + + RDTreeView + QTreeView +
Widgets/Extended/RDTreeView.h
+
+
+ + + + +
diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 73f8b79c2..431529891 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -2580,13 +2580,6 @@ void MainWindow::on_action_View_Documentation_triggered() QDesktopServices::openUrl(QUrl::fromUserInput(lit("https://renderdoc.org/docs"))); } -void MainWindow::on_action_View_Diagnostic_Log_File_triggered() -{ - QString logPath = QString::fromUtf8(RENDERDOC_GetLogFile()); - if(QFileInfo::exists(logPath)) - QDesktopServices::openUrl(QUrl::fromLocalFile(logPath)); -} - void MainWindow::on_action_Source_on_GitHub_triggered() { QDesktopServices::openUrl(QUrl::fromUserInput(lit("https://github.com/baldurk/renderdoc"))); @@ -2603,6 +2596,16 @@ void MainWindow::on_action_Show_Tips_triggered() RDDialog::show(&tipsDialog); } +void MainWindow::on_action_View_Diagnostic_Log_File_triggered() +{ + QWidget *logView = m_Ctx.GetDiagnosticLogView()->Widget(); + + if(ui->toolWindowManager->toolWindows().contains(logView)) + ToolWindowManager::raiseToolWindow(logView); + else + ui->toolWindowManager->addToolWindow(logView, mainToolArea()); +} + void MainWindow::on_action_Counter_Viewer_triggered() { QWidget *performanceCounterViewer = m_Ctx.GetPerformanceCounterViewer()->Widget(); diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 4aa311501..6860e7e54 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -101,6 +101,7 @@ public: void showPipelineViewer() { on_action_Pipeline_State_triggered(); } void showCaptureDialog() { on_action_Launch_Application_triggered(); } void showDebugMessageView() { on_action_Errors_and_Warnings_triggered(); } + void showDiagnosticLogView() { on_action_View_Diagnostic_Log_File_triggered(); } void showCommentView() { on_action_Comments_triggered(); } void showStatisticsViewer() { on_action_Statistics_Viewer_triggered(); } void showTimelineBar() { on_action_Timeline_triggered(); } @@ -142,6 +143,7 @@ private slots: void on_action_Settings_triggered(); void on_action_View_Documentation_triggered(); void on_action_View_Diagnostic_Log_File_triggered(); + void on_action_Diagnostic_Log_triggered() { on_action_View_Diagnostic_Log_File_triggered(); } void on_action_Source_on_GitHub_triggered(); void on_action_Build_Release_Downloads_triggered(); void on_action_Show_Tips_triggered(); diff --git a/qrenderdoc/Windows/MainWindow.ui b/qrenderdoc/Windows/MainWindow.ui index 201f814c2..0904e5b3f 100644 --- a/qrenderdoc/Windows/MainWindow.ui +++ b/qrenderdoc/Windows/MainWindow.ui @@ -163,6 +163,7 @@ + @@ -500,6 +501,11 @@ Manage Extensions + + + Dia&gnostic Log + + diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index f551fb148..de50874d3 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -263,6 +263,10 @@ struct CaptureContextInvoker : ICaptureContext { return InvokeRetFunction(&ICaptureContext::GetDebugMessageView); } + virtual IDiagnosticLogView *GetDiagnosticLogView() override + { + return InvokeRetFunction(&ICaptureContext::GetDiagnosticLogView); + } virtual ICommentView *GetCommentView() override { return InvokeRetFunction(&ICaptureContext::GetCommentView); @@ -316,6 +320,10 @@ struct CaptureContextInvoker : ICaptureContext { return InvokeRetFunction(&ICaptureContext::HasDebugMessageView); } + virtual bool HasDiagnosticLogView() override + { + return InvokeRetFunction(&ICaptureContext::HasDiagnosticLogView); + } virtual bool HasCommentView() override { return InvokeRetFunction(&ICaptureContext::HasCommentView); @@ -366,6 +374,10 @@ struct CaptureContextInvoker : ICaptureContext { InvokeVoidFunction(&ICaptureContext::ShowDebugMessageView); } + virtual void ShowDiagnosticLogView() override + { + InvokeVoidFunction(&ICaptureContext::ShowDiagnosticLogView); + } virtual void ShowCommentView() override { InvokeVoidFunction(&ICaptureContext::ShowCommentView); } virtual void ShowPerformanceCounterViewer() override { diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 489dfd107..df8ccff2b 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -211,6 +211,7 @@ SOURCES += Code/qrenderdoc.cpp \ Windows/BufferViewer.cpp \ Widgets/Extended/RDTableView.cpp \ Windows/DebugMessageView.cpp \ + Windows/LogView.cpp \ Windows/CommentView.cpp \ Windows/StatisticsViewer.cpp \ Windows/TimelineBar.cpp \ @@ -287,6 +288,7 @@ HEADERS += Code/CaptureContext.h \ Windows/BufferViewer.h \ Widgets/Extended/RDTableView.h \ Windows/DebugMessageView.h \ + Windows/LogView.h \ Windows/CommentView.h \ Windows/StatisticsViewer.h \ Windows/TimelineBar.h \ @@ -331,6 +333,7 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/BufferViewer.ui \ Windows/ShaderViewer.ui \ Windows/DebugMessageView.ui \ + Windows/LogView.ui \ Windows/CommentView.ui \ Windows/StatisticsViewer.ui \ Windows/Dialogs/SettingsDialog.ui \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index e06b2f905..1980619d3 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -606,6 +606,7 @@ + @@ -716,6 +717,7 @@ + @@ -930,6 +932,7 @@ + @@ -1152,6 +1155,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\include" -I"$(ProjectDir)3rdparty\qt\include\QtWidgets" -I"$(ProjectDir)3rdparty\qt\include\QtGui" -I"$(ProjectDir)3rdparty\qt\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\include" -I"$(ProjectDir)3rdparty\qt\include\QtWidgets" -I"$(ProjectDir)3rdparty\qt\include\QtGui" -I"$(ProjectDir)3rdparty\qt\include\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" @@ -1404,6 +1413,12 @@ UIC %(Filename).ui $(IntDir)generated\ui_%(Filename).h + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) + "$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" + UIC %(Filename).ui + $(IntDir)generated\ui_%(Filename).h + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) "$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index fa5ca0a42..4020e3fc1 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -717,6 +717,12 @@ Windows\Dialogs + + Windows + + + Generated Files + @@ -1070,6 +1076,9 @@ Code\Interface + + Generated Files + @@ -1451,6 +1460,12 @@ Windows\Dialogs + + Windows + + + Windows + diff --git a/renderdoc/android/android.cpp b/renderdoc/android/android.cpp index 9b58ef1f9..09873fa22 100644 --- a/renderdoc/android/android.cpp +++ b/renderdoc/android/android.cpp @@ -564,7 +564,7 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetAndroidFriendlyName(cons friendly = Android::GetFriendlyName(deviceID); } -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdcstr *deviceList) +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdcstr &deviceList) { std::string adbStdout = Android::adbExecCommand("", "devices", ".", true).strStdout; @@ -592,7 +592,7 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdc } } - *deviceList = ret; + deviceList = ret; } extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_AndroidInitialise() diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 1c74c8ffc..6949b5ad1 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -2293,6 +2293,9 @@ analysis program. )"); extern "C" RENDERDOC_API const char *RENDERDOC_CC RENDERDOC_GetLogFile(); +DOCUMENT("Internal function for fetching the contents of a log"); +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetLogFileContents(rdcstr &logfile); + DOCUMENT("Internal function for logging text simply."); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_LogText(const char *text); @@ -2351,7 +2354,7 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetAndroidFriendlyName(cons rdcstr &friendly); DOCUMENT("Internal function for enumerating android devices."); -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdcstr *deviceList); +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdcstr &deviceList); DOCUMENT("Internal function for initialising android use."); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_AndroidInitialise(); diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index e931234e6..607b1a399 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -182,6 +182,20 @@ rdcstr DoStringise(const EnvSep &el) END_ENUM_STRINGISE(); } +template <> +rdcstr DoStringise(const LogType &el) +{ + BEGIN_ENUM_STRINGISE(LogType) + { + STRINGISE_ENUM_CLASS(Debug); + STRINGISE_ENUM_CLASS_NAMED(Comment, "Log"); + STRINGISE_ENUM_CLASS(Warning); + STRINGISE_ENUM_CLASS(Error); + STRINGISE_ENUM_CLASS(Fatal); + } + END_ENUM_STRINGISE(); +} + template <> rdcstr DoStringise(const Topology &el) { diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index c82f830e3..e6cd2bdb6 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -3373,6 +3373,7 @@ DOCUMENT(R"(The type of a log message enum class LogType : int32_t { Debug, + First = Debug, Comment, Warning, Error, @@ -3382,6 +3383,8 @@ enum class LogType : int32_t DECLARE_REFLECTION_ENUM(LogType); +ITERABLE_OPERATORS(LogType); + #if defined(ENABLE_PYTHON_FLAG_ENUMS) ENABLE_PYTHON_FLAG_ENUMS; diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index 44d1ad2a2..d32cb079d 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -251,6 +251,11 @@ extern "C" RENDERDOC_API const char *RENDERDOC_CC RENDERDOC_GetLogFile() return RDCGETLOGFILE(); } +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetLogFileContents(rdcstr &logfile) +{ + logfile = FileIO::logfile_readall(RDCGETLOGFILE()); +} + extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_InitGlobalEnv(GlobalEnvironment env, const rdcarray &args) {