diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 266934383..519620a6f 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -54,6 +54,7 @@ #include "Windows/PixelHistoryView.h" #include "Windows/PythonShell.h" #include "Windows/ResourceInspector.h" +#include "Windows/ShaderMessageViewer.h" #include "Windows/ShaderViewer.h" #include "Windows/StatisticsViewer.h" #include "Windows/TextureViewer.h" @@ -1560,10 +1561,18 @@ bool CaptureContext::IsResourceReplaced(ResourceId id) return m_ReplacedResources.contains(id); } -void CaptureContext::RegisterReplacement(ResourceId id) +ResourceId CaptureContext::GetResourceReplacement(ResourceId id) { - if(!m_ReplacedResources.contains(id)) - m_ReplacedResources.push_back(id); + auto it = m_ReplacedResources.find(id); + if(it != m_ReplacedResources.end()) + return it.value(); + + return ResourceId(); +} + +void CaptureContext::RegisterReplacement(ResourceId from, ResourceId to) +{ + m_ReplacedResources[from] = to; CacheResources(); @@ -1572,7 +1581,7 @@ void CaptureContext::RegisterReplacement(ResourceId id) void CaptureContext::UnregisterReplacement(ResourceId id) { - m_ReplacedResources.removeOne(id); + m_ReplacedResources.remove(id); CacheResources(); @@ -2414,7 +2423,8 @@ void CaptureContext::ApplyShaderEdit(IShaderViewer *viewer, ResourceId id, Shade { r->ReplaceResource(from, to); - GUIInvoke::call(GetMainWindow()->Widget(), [this, from]() { RegisterReplacement(from); }); + GUIInvoke::call(GetMainWindow()->Widget(), + [this, from, to]() { RegisterReplacement(from, to); }); } if(ptr) GUIInvoke::call(ptr, [viewer, errs]() { viewer->ShowErrors(errs); }); @@ -2445,6 +2455,11 @@ IShaderViewer *CaptureContext::ViewShader(const ShaderReflection *shader, Resour return ShaderViewer::ViewShader(*this, shader, pipeline, m_MainWindow->Widget()); } +IShaderMessageViewer *CaptureContext::ViewShaderMessages(ShaderStageMask stages) +{ + return new ShaderMessageViewer(*this, stages, m_MainWindow); +} + IBufferViewer *CaptureContext::ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const rdcstr &format) { diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index bef4c6167..1e06a6d5d 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -121,7 +121,8 @@ public: void SetRemoteHost(int hostIndex); void RefreshStatus() override { SetEventID({}, m_SelectedEventID, m_EventID, true); } bool IsResourceReplaced(ResourceId id) override; - void RegisterReplacement(ResourceId id) override; + ResourceId GetResourceReplacement(ResourceId id) override; + void RegisterReplacement(ResourceId from, ResourceId to) override; void UnregisterReplacement(ResourceId id) override; void RefreshUIStatus(const rdcarray &exclude, bool updateSelectedEvent, bool updateEvent); @@ -259,6 +260,8 @@ public: IShaderViewer *ViewShader(const ShaderReflection *shader, ResourceId pipeline) override; + IShaderMessageViewer *ViewShaderMessages(ShaderStageMask stages) override; + IBufferViewer *ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const rdcstr &format = "") override; IBufferViewer *ViewTextureAsBuffer(ResourceId id, const Subresource &sub, @@ -381,7 +384,7 @@ private: QMap m_CustomNames; int m_CustomNameCachedID = 1; - QVector m_ReplacedResources; + QMap m_ReplacedResources; const SDFile *m_StructuredFile = NULL; SDFile m_DummySDFile; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 753240c7c..e5f94ab70 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -939,7 +939,17 @@ QWidget. :param int instruction: The instruction to toggle breakpoint at. If this is ``-1`` the nearest instruction after the current caret position is used. )"); - virtual void ToggleBreakpoint(int32_t instruction = -1) = 0; + virtual void ToggleBreakpointOnInstruction(int32_t instruction = -1) = 0; + + DOCUMENT(R"(Toggles a breakpoint at a given disassembly line (starting from 1). + +:param int disassemblyLine: The line of the disassembly to toggle a breakpoint on. +)"); + virtual void ToggleBreakpointOnDisassemblyLine(int32_t disassemblyLine) = 0; + + DOCUMENT(R"(Runs execution forward to the next breakpoint, or the end of the trace. +)"); + virtual void RunForward() = 0; DOCUMENT(R"(Show a list of shader compilation errors or warnings. @@ -960,6 +970,47 @@ protected: DECLARE_REFLECTION_STRUCT(IShaderViewer); +DOCUMENT("A shader message list window."); +struct IShaderMessageViewer +{ + DOCUMENT(R"(Retrieves the PySide2 QWidget for this :class:`ShaderMessageViewer` if PySide2 is available, or otherwise +returns a unique opaque pointer that can be passed back to any RenderDoc functions expecting a +QWidget. + +:return: Return the widget handle, either a PySide2 handle or an opaque handle. +:rtype: QWidget +)"); + virtual QWidget *Widget() = 0; + + DOCUMENT(R"(Return the EID that this viewer is displaying messages from. + +:return: The EID. +:rtype int +)"); + virtual uint32_t GetEvent() = 0; + + DOCUMENT(R"(Return the shader messages displayed in this viewer. + +:return: The shader messages. +:rtype List[renderdoc.ShaderMessage] +)"); + virtual rdcarray GetShaderMessages() = 0; + + DOCUMENT(R"(Returns whether or not this viewer is out of date - if the shaders have been edited +since the messages were fetched. + +:return: ``True`` if the viewer is out of date. +:rtype bool +)"); + virtual bool IsOutOfDate() = 0; + +protected: + IShaderMessageViewer() = default; + ~IShaderMessageViewer() = default; +}; + +DECLARE_REFLECTION_STRUCT(IShaderMessageViewer); + DOCUMENT("A constant buffer preview window."); struct IConstantBufferPreviewer { @@ -1511,14 +1562,24 @@ been made. )"); virtual bool IsResourceReplaced(ResourceId id) = 0; + DOCUMENT(R"(Return the id of a replacement for the given resource. See +:meth:`RegisterReplacement` and :meth:`IsResourceReplaced`. + +:param renderdoc.ResourceId id: The id of the resource to check. +:return: The replacement id, or a null id if the resource hasn't been replaced +:rtype: renderdoc.ResourceId +)"); + virtual ResourceId GetResourceReplacement(ResourceId id) = 0; + DOCUMENT(R"(Register that a resource has replaced, so that the UI can be updated to reflect the change. This should be called at the same time as :meth:`ReplayController.ReplaceResource`. -:param renderdoc.ResourceId id: The id of the resource that has been replaced. +:param renderdoc.ResourceId from: The id of the resource being replaced. +:param renderdoc.ResourceId to: The id of the resource replacing it. )"); - virtual void RegisterReplacement(ResourceId id) = 0; + virtual void RegisterReplacement(ResourceId from, ResourceId to) = 0; DOCUMENT(R"(Register that a replacement has been removed, so that the UI can be updated to reflect the change. @@ -2244,6 +2305,14 @@ through the execution of a given shader. )"); virtual IShaderViewer *ViewShader(const ShaderReflection *shader, ResourceId pipeline) = 0; + DOCUMENT(R"(Show a new :class:`ShaderMessageViewer` window, showing the current event's messages. + +:param renderdoc.ShaderStageMask stages: The initial stages being viewed. +:return: The new :class:`ShaderMessageViewer` window opened, but not shown. +:rtype: ShaderMessageViewer +)"); + virtual IShaderMessageViewer *ViewShaderMessages(ShaderStageMask stages) = 0; + DOCUMENT(R"(Show a new :class:`BufferViewer` window, showing a read-only view of buffer data. :param int byteOffset: The offset in bytes to the start of the buffer data to show. diff --git a/qrenderdoc/Code/QRDUtils.cpp b/qrenderdoc/Code/QRDUtils.cpp index 79a7670a6..3798749fe 100644 --- a/qrenderdoc/Code/QRDUtils.cpp +++ b/qrenderdoc/Code/QRDUtils.cpp @@ -181,6 +181,14 @@ QString GetTruncatedResourceName(ICaptureContext &ctx, ResourceId id) return name; } +struct ShaderMessageLink +{ + uint32_t eid; + uint32_t numMessages; +}; + +Q_DECLARE_METATYPE(ShaderMessageLink); + // this is an opaque struct that contains the data to render, hit-test, etc for some text that // contains links to resources. It will update and cache the names of the resources. struct RichResourceText @@ -249,6 +257,22 @@ struct RichResourceText fragmentIndexFromBlockIndex.push_back(i); fragmentIndexFromBlockIndex.push_back(i); } + else if(v.userType() == qMetaTypeId()) + { + ShaderMessageLink link = v.value(); + + QString msgstr = + QApplication::translate("qrenderdoc", "%n msg(s)", "Shader messages", link.numMessages); + + html += lit("" + "%1") + .arg(msgstr) + .arg(highdpi ? lit("@2x") : QString()); + text += msgstr; + + fragmentIndexFromBlockIndex.push_back(i); + fragmentIndexFromBlockIndex.push_back(i); + } else if(v.type() == QVariant::UInt) { html += lit("EID @%1") @@ -402,7 +426,7 @@ void RichResourceTextInitialise(QVariant &var, ICaptureContext *ctx) // do a simple string search first before using regular expressions if(!text.contains(lit("ResourceId::")) && !text.contains(lit("GPUAddress::")) && - !text.contains(QLatin1Char('@'))) + !text.contains(lit("__rd_msgs::")) && !text.contains(QLatin1Char('@'))) return; // two forms: GPUAddress::012345 - typeless @@ -433,7 +457,8 @@ void RichResourceTextInitialise(QVariant &var, ICaptureContext *ctx) // use regexp to split up into fragments of text and resourceid. The resourceid is then // formatted on the fly in RichResourceText::cacheDocument - static QRegularExpression resRE(lit("(ResourceId::)([0-9]*)|(@)([0-9]+)")); + static QRegularExpression resRE( + lit("(ResourceId::)([0-9]+)|(@)([0-9]+)|(__rd_msgs::)([0-9]+):([0-9]+)")); match = resRE.match(text); @@ -472,6 +497,20 @@ void RichResourceTextInitialise(QVariant &var, ICaptureContext *ctx) linkedText->fragments.push_back(id); } + else if(match.captured(5) == lit("__rd_msgs::")) + { + ShaderMessageLink link; + link.eid = match.captured(6).toUInt(); + link.numMessages = match.captured(7).toUInt(); + + // push any text that preceeded the msgs link. + if(match.capturedStart(5) > 0) + linkedText->fragments.push_back(text.left(match.capturedStart(5))); + + text.remove(0, match.capturedEnd(7)); + + linkedText->fragments.push_back(QVariant::fromValue(link)); + } else { eid = match.captured(4).toUInt(); @@ -670,7 +709,8 @@ void RichResourceTextPaint(const QWidget *owner, QPainter *painter, QRect rect, if(frag >= 0) { QVariant v = linkedText->fragments[frag]; - if(v.userType() == qMetaTypeId() && v.value() != ResourceId()) + if((v.userType() == qMetaTypeId() && v.value() != ResourceId()) || + v.userType() == qMetaTypeId()) { layout->blockBoundingRect(block); QRectF blockrect = layout->blockBoundingRect(block); @@ -929,6 +969,23 @@ bool RichResourceTextMouseEvent(const QWidget *owner, const QVariant &var, QRect return true; } + else if(v.userType() == qMetaTypeId()) + { + ShaderMessageLink link = v.value(); + + if(event->type() == QEvent::MouseButtonRelease && linkedText->ctxptr) + { + ICaptureContext &ctx = *(ICaptureContext *)linkedText->ctxptr; + + ctx.SetEventID({}, link.eid, link.eid, false); + + IShaderMessageViewer *shad = ctx.ViewShaderMessages(ShaderStageMask::All); + + ctx.AddDockWindow(shad->Widget(), DockReference::MainToolArea, NULL); + } + + return true; + } else if(v.type() == QVariant::UInt) { uint32_t eid = v.value(); diff --git a/qrenderdoc/Windows/EventBrowser.cpp b/qrenderdoc/Windows/EventBrowser.cpp index 49579d127..cdc4ea978 100644 --- a/qrenderdoc/Windows/EventBrowser.cpp +++ b/qrenderdoc/Windows/EventBrowser.cpp @@ -294,6 +294,7 @@ void EventBrowser::OnCaptureClosed() void EventBrowser::OnEventChanged(uint32_t eventId) { SelectEvent(eventId); + RefreshShaderMessages(); repopulateBookmarks(); highlightBookmarks(); } @@ -540,6 +541,8 @@ void EventBrowser::on_events_currentItemChanged(RDTreeWidgetItem *current, RDTre m_Ctx.SetEventID({this}, tag.EID, tag.lastEID); + RefreshShaderMessages(); + const DrawcallDescription *draw = m_Ctx.GetDrawcall(tag.lastEID); ui->stepPrev->setEnabled(draw && draw->previous); @@ -1127,6 +1130,33 @@ void EventBrowser::RefreshIcon(RDTreeWidgetItem *item, EventItemTag tag) item->setIcon(COL_NAME, QIcon()); } +void EventBrowser::RefreshShaderMessages() +{ + uint32_t eventId = m_Ctx.CurEvent(); + + RDTreeWidgetItem *item = ui->events->currentItem(); + + if(item->tag().value().EID != eventId) + return; + + const DrawcallDescription *draw = m_Ctx.GetDrawcall(eventId); + const rdcarray &msgs = m_Ctx.CurPipelineState().GetShaderMessages(); + + if(draw) + { + QString name(draw->name); + + if(!msgs.empty()) + name += lit(" __rd_msgs::%1:%2").arg(draw->eventId).arg(msgs.count()); + + QVariant v = name; + + RichResourceTextInitialise(v, &m_Ctx); + + item->setText(0, v); + } +} + bool EventBrowser::FindEventNode(RDTreeWidgetItem *&found, RDTreeWidgetItem *parent, uint32_t eventId) { // do a reverse search to find the last match (in case of 'set' markers that diff --git a/qrenderdoc/Windows/EventBrowser.h b/qrenderdoc/Windows/EventBrowser.h index cf27d3848..85051f5c8 100644 --- a/qrenderdoc/Windows/EventBrowser.h +++ b/qrenderdoc/Windows/EventBrowser.h @@ -140,6 +140,7 @@ private: QMap m_BookmarkButtons; void RefreshIcon(RDTreeWidgetItem *item, EventItemTag tag); + void RefreshShaderMessages(); Ui::EventBrowser *ui; ICaptureContext &m_Ctx; diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index 2362c2aef..730c64e10 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -144,6 +144,11 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, ui->gsShaderSaveButton, ui->fsShaderSaveButton, ui->csShaderSaveButton, }; + QToolButton *messageButtons[] = { + ui->vsShaderMessagesButton, ui->tcsShaderMessagesButton, ui->tesShaderMessagesButton, + ui->gsShaderMessagesButton, ui->fsShaderMessagesButton, ui->csShaderMessagesButton, + }; + QToolButton *viewPredicateBufferButtons[] = { ui->predicateBufferViewButton, ui->csPredicateBufferViewButton, }; @@ -191,6 +196,10 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, for(QToolButton *b : saveButtons) QObject::connect(b, &QToolButton::clicked, this, &VulkanPipelineStateViewer::shaderSave_clicked); + for(QToolButton *b : messageButtons) + QObject::connect(b, &QToolButton::clicked, this, + &VulkanPipelineStateViewer::shaderMessages_clicked); + for(QToolButton *b : viewPredicateBufferButtons) QObject::connect(b, &QToolButton::clicked, this, &VulkanPipelineStateViewer::predicateBufferView_clicked); @@ -810,6 +819,14 @@ void VulkanPipelineStateViewer::clearState() for(QToolButton *b : shaderButtons) b->setEnabled(false); + QToolButton *messageButtons[] = { + ui->vsShaderMessagesButton, ui->tcsShaderMessagesButton, ui->tesShaderMessagesButton, + ui->gsShaderMessagesButton, ui->fsShaderMessagesButton, ui->csShaderMessagesButton, + }; + + for(QToolButton *b : messageButtons) + b->setVisible(false); + const QPixmap &tick = Pixmaps::tick(this); const QPixmap &cross = Pixmaps::cross(this); @@ -2263,6 +2280,22 @@ void VulkanPipelineStateViewer::setState() stage->reflection); } + QToolButton *messageButtons[] = { + ui->vsShaderMessagesButton, ui->tcsShaderMessagesButton, ui->tesShaderMessagesButton, + ui->gsShaderMessagesButton, ui->fsShaderMessagesButton, ui->csShaderMessagesButton, + }; + + int numMessages[6] = {}; + + for(const ShaderMessage &msg : state.shaderMessages) + numMessages[(uint32_t)msg.stage]++; + + for(uint32_t i = 0; i < ARRAY_COUNT(numMessages); i++) + { + messageButtons[i]->setVisible(numMessages[i] > 0); + messageButtons[i]->setText(tr("%n Message(s)", "", numMessages[i])); + } + bool xfbSet = false; vs = ui->xfbBuffers->verticalScrollBar()->value(); ui->xfbBuffers->beginUpdate(); @@ -3121,6 +3154,18 @@ void VulkanPipelineStateViewer::shaderSave_clicked() m_Common.SaveShaderFile(shaderDetails); } +void VulkanPipelineStateViewer::shaderMessages_clicked() +{ + const VKPipe::Shader *stage = stageForSender(qobject_cast(QObject::sender())); + + if(stage == NULL) + return; + + IShaderMessageViewer *shad = m_Ctx.ViewShaderMessages(MaskForStage(stage->stage)); + + m_Ctx.AddDockWindow(shad->Widget(), DockReference::AddTo, this); +} + void VulkanPipelineStateViewer::predicateBufferView_clicked() { const VKPipe::ConditionalRendering &cr = m_Ctx.CurVulkanPipelineState()->conditionalRendering; diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h index ad2cd308c..835a8bc87 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h @@ -77,8 +77,8 @@ private slots: // manual slots void shaderView_clicked(); - void shaderSave_clicked(); + void shaderMessages_clicked(); void predicateBufferView_clicked(); void resource_itemActivated(RDTreeWidgetItem *item, int column); void resource_hoverItemChanged(RDTreeWidgetItem *hover); diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui index a24e62c5d..43d87b9b8 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui @@ -594,6 +594,26 @@ + + + + View Shader Messages + + + Messages + + + + :/text_add.png:/text_add.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -880,6 +900,26 @@ + + + + View Shader Messages + + + Messages + + + + :/text_add.png:/text_add.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -1166,6 +1206,26 @@ + + + + View Shader Messages + + + Messages + + + + :/text_add.png:/text_add.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -1452,6 +1512,26 @@ + + + + View Shader Messages + + + Messages + + + + :/text_add.png:/text_add.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -2799,6 +2879,26 @@ + + + + View Shader Messages + + + Messages + + + + :/text_add.png:/text_add.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -3642,6 +3742,26 @@ + + + + View Shader Messages + + + Messages + + + + :/text_add.png:/text_add.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 7c486dc2e..44151e8d6 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -542,9 +542,13 @@ struct CaptureContextInvoker : ObjectForwarder { return InvokeRetFunction(&ICaptureContext::IsResourceReplaced, id); } - virtual void RegisterReplacement(ResourceId id) override + virtual ResourceId GetResourceReplacement(ResourceId id) override { - InvokeVoidFunction(&ICaptureContext::RegisterReplacement, id); + return InvokeRetFunction(&ICaptureContext::GetResourceReplacement, id); + } + virtual void RegisterReplacement(ResourceId from, ResourceId to) override + { + InvokeVoidFunction(&ICaptureContext::RegisterReplacement, from, to); } virtual void UnregisterReplacement(ResourceId id) override { @@ -765,6 +769,11 @@ struct CaptureContextInvoker : ObjectForwarder return InvokeRetFunction(&ICaptureContext::ViewShader, shader, pipeline); } + virtual IShaderMessageViewer *ViewShaderMessages(ShaderStageMask stages) override + { + return InvokeRetFunction(&ICaptureContext::ViewShaderMessages, stages); + } + virtual IBufferViewer *ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const rdcstr &format = "") override { diff --git a/qrenderdoc/Windows/ShaderMessageViewer.cpp b/qrenderdoc/Windows/ShaderMessageViewer.cpp new file mode 100644 index 000000000..8d23be4c4 --- /dev/null +++ b/qrenderdoc/Windows/ShaderMessageViewer.cpp @@ -0,0 +1,657 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 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 "ShaderMessageViewer.h" +#include +#include +#include +#include +#include "Code/QRDUtils.h" +#include "Code/Resources.h" +#include "Widgets/Extended/RDHeaderView.h" +#include "Widgets/Extended/RDLineEdit.h" +#include "toolwindowmanager/ToolWindowManager.h" +#include "ui_ShaderMessageViewer.h" + +ButtonDelegate::ButtonDelegate(const QIcon &icon, QWidget *parent) + : m_Icon(icon), QStyledItemDelegate(parent) +{ +} + +void ButtonDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + // draw the background to get selection etc + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &option, painter); + + QStyleOptionButton button; + + QSize sz = sizeHint(option, index); + button.rect = option.rect; + button.rect.setLeft(button.rect.center().x() - sz.width() / 2); + button.rect.setTop(button.rect.center().y() - sz.height() / 2); + button.rect.setSize(sz); + button.icon = m_Icon; + button.iconSize = sz; + button.state = QStyle::State_Enabled; + + if(m_ClickedIndex == index) + button.state |= QStyle::State_Sunken; + + QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter); +} + +QSize ButtonDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionButton button; + button.icon = m_Icon; + button.state = QStyle::State_Enabled; + + return QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &button, + option.decorationSize); +} + +bool ButtonDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if(event->type() == QEvent::MouseButtonPress) + { + m_ClickedIndex = index; + } + else if(event->type() == QEvent::MouseMove) + { + QMouseEvent *e = (QMouseEvent *)event; + + if(m_ClickedIndex != index || (e->buttons() & Qt::LeftButton) == 0) + { + m_ClickedIndex = QModelIndex(); + } + else + { + QPoint p = e->pos(); + + QSize sz = option.decorationSize; + QRect rect = option.rect; + rect.setLeft(rect.center().x() - sz.width() / 2); + rect.setTop(rect.center().y() - sz.height() / 2); + rect.setSize(sz); + + if(!rect.contains(p)) + { + m_ClickedIndex = QModelIndex(); + } + } + } + else if(event->type() == QEvent::MouseButtonRelease) + { + if(m_ClickedIndex == index) + { + m_ClickedIndex = QModelIndex(); + + QMouseEvent *e = (QMouseEvent *)event; + + QPoint p = e->pos(); + + QSize sz = option.decorationSize; + QRect rect = option.rect; + rect.setLeft(rect.center().x() - sz.width() / 2); + rect.setTop(rect.center().y() - sz.height() / 2); + rect.setSize(sz); + + if(rect.contains(p)) + emit messageClicked(index); + } + } + + return true; +} + +ShaderMessageViewer::ShaderMessageViewer(ICaptureContext &ctx, ShaderStageMask stages, QWidget *parent) + : QFrame(parent), ui(new Ui::ShaderMessageViewer), m_Ctx(ctx) +{ + ui->setupUi(this); + + ui->messages->setFont(Formatter::PreferredFont()); + + ui->messages->setMouseTracking(true); + + m_API = m_Ctx.APIProps().pipelineType; + + QObject::connect(ui->vertex, &QToolButton::toggled, [this](bool) { refreshMessages(); }); + QObject::connect(ui->hull, &QToolButton::toggled, [this](bool) { refreshMessages(); }); + QObject::connect(ui->domain, &QToolButton::toggled, [this](bool) { refreshMessages(); }); + QObject::connect(ui->geometry, &QToolButton::toggled, [this](bool) { refreshMessages(); }); + QObject::connect(ui->pixel, &QToolButton::toggled, [this](bool) { refreshMessages(); }); + QObject::connect(ui->filterButton, &QToolButton::clicked, [this]() { refreshMessages(); }); + QObject::connect(ui->filter, &RDLineEdit::returnPressed, [this]() { refreshMessages(); }); + + ui->vertex->setText(ToQStr(ShaderStage::Vertex, m_API)); + ui->hull->setText(ToQStr(ShaderStage::Hull, m_API)); + ui->domain->setText(ToQStr(ShaderStage::Domain, m_API)); + ui->geometry->setText(ToQStr(ShaderStage::Geometry, m_API)); + ui->pixel->setText(ToQStr(ShaderStage::Pixel, m_API)); + + m_EID = m_Ctx.CurEvent(); + m_Drawcall = m_Ctx.GetDrawcall(m_EID); + + const PipeState &pipe = m_Ctx.CurPipelineState(); + + // check if we have multiview enabled + m_Multiview = pipe.MultiviewBroadcastCount() > 1; + + // only display sample information if one of the targets is multisampled + m_Multisampled = false; + rdcarray outs = pipe.GetOutputTargets(); + outs.push_back(pipe.GetDepthTarget()); + for(const BoundResource &o : outs) + { + if(o.resourceId == ResourceId()) + continue; + + const TextureDescription *tex = m_Ctx.GetTexture(o.resourceId); + + if(tex->msSamp > 1) + { + m_Multisampled = true; + break; + } + } + + RDHeaderView *header = new RDHeaderView(Qt::Horizontal, this); + ui->messages->setHeader(header); + header->setStretchLastSection(true); + header->setMinimumSectionSize(40); + + int sortColumn = 0; + + m_debugDelegate = new ButtonDelegate(Icons::wrench(), this); + + if(m_Drawcall && (m_Drawcall->flags & DrawFlags::Dispatch)) + { + ui->stageFilters->hide(); + + ui->messages->setColumns({lit("Debug"), tr("Workgroup"), lit("Thread"), lit("Message")}); + sortColumn = 1; + + ui->messages->setItemDelegateForColumn(0, m_debugDelegate); + + m_OrigShaders[5] = pipe.GetShader(ShaderStage::Compute); + } + else + { + ui->messages->setColumns({lit("Debug"), lit("Go to"), tr("Location"), lit("Message")}); + sortColumn = 2; + + m_gotoDelegate = new ButtonDelegate(Icons::find(), this); + + ui->messages->setItemDelegateForColumn(0, m_debugDelegate); + ui->messages->setItemDelegateForColumn(1, m_gotoDelegate); + + QCheckBox *boxes[] = { + ui->vertex, ui->hull, ui->domain, ui->geometry, ui->pixel, + }; + + for(ShaderStage s : values()) + { + if(s == ShaderStage::Compute) + continue; + + uint32_t idx = (uint32_t)s; + + m_OrigShaders[idx] = pipe.GetShader(s); + + boxes[idx]->setChecked(bool(stages & MaskForStage(s))); + + // if there's no shader bound, we currently don't support adding stages at runtime so just + // hide this box as no messages can come from the unbound stage + if(m_OrigShaders[idx] == ResourceId()) + boxes[idx]->hide(); + } + } + + ui->messages->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(ui->messages, &RDTreeWidget::customContextMenuRequested, [this](const QPoint &pos) { + QModelIndex idx = ui->messages->indexAt(pos); + RDTreeWidgetItem *item = ui->messages->itemForIndex(idx); + + QMenu contextMenu(this); + + QAction copy(tr("&Copy"), this); + + contextMenu.addAction(©); + + copy.setIcon(Icons::copy()); + + QObject::connect(©, &QAction::triggered, + [this, pos, item]() { ui->messages->copyItem(pos, item); }); + + QAction debugAction(tr("&Debug"), this); + debugAction.setIcon(Icons::wrench()); + QAction gotoAction(tr("&Go to"), this); + gotoAction.setIcon(Icons::find()); + + QObject::connect(&debugAction, &QAction::triggered, + [this, idx]() { m_debugDelegate->messageClicked(idx); }); + + QObject::connect(&gotoAction, &QAction::triggered, + [this, idx]() { m_gotoDelegate->messageClicked(idx); }); + + contextMenu.addAction(&debugAction); + if(m_gotoDelegate) + contextMenu.addAction(&gotoAction); + + RDDialog::show(&contextMenu, ui->messages->viewport()->mapToGlobal(pos)); + }); + + QObject::connect(m_debugDelegate, &ButtonDelegate::messageClicked, [this](const QModelIndex &idx) { + RDTreeWidgetItem *item = ui->messages->itemForIndex(idx); + + int msgIdx = 0; + if(item) + msgIdx = item->tag().toInt(); + else + return; + + ShaderMessage msg = m_Messages[msgIdx]; + + const ShaderReflection *refl = m_Ctx.CurPipelineState().GetShaderReflection(msg.stage); + + if(refl->debugInfo.debuggable) + { + bool done = false; + ShaderDebugTrace *trace = NULL; + + m_Ctx.Replay().AsyncInvoke([this, &trace, &done, msg](IReplayController *r) { + if(msg.stage == ShaderStage::Compute) + trace = r->DebugThread(msg.location.compute.workgroup, msg.location.compute.thread); + else if(msg.stage == ShaderStage::Vertex) + trace = r->DebugVertex(msg.location.vertex.vertexIndex, msg.location.vertex.instance, + msg.location.vertex.vertexIndex, msg.location.vertex.view); + else if(msg.stage == ShaderStage::Pixel) + trace = r->DebugPixel(msg.location.pixel.x, msg.location.pixel.y, + msg.location.pixel.sample, msg.location.pixel.primitive); + + if(trace->debugger == NULL) + { + r->FreeTrace(trace); + trace = NULL; + } + + done = true; + }); + + QString debugContext; + + if(msg.stage == ShaderStage::Compute) + debugContext = lit("Group [%1,%2,%3] Thread [%4,%5,%6]") + .arg(msg.location.compute.workgroup[0]) + .arg(msg.location.compute.workgroup[1]) + .arg(msg.location.compute.workgroup[2]) + .arg(msg.location.compute.thread[0]) + .arg(msg.location.compute.thread[1]) + .arg(msg.location.compute.thread[2]); + else if(msg.stage == ShaderStage::Vertex) + debugContext = tr("Vertex %1").arg(msg.location.vertex.vertexIndex); + else if(msg.stage == ShaderStage::Pixel) + debugContext = tr("Pixel %1,%2").arg(msg.location.pixel.x).arg(msg.location.pixel.y); + + // wait a short while before displaying the progress dialog (which won't show if we're already + // done by the time we reach it) + for(int i = 0; !done && i < 100; i++) + QThread::msleep(5); + + ShowProgressDialog(this, tr("Debugging %1").arg(debugContext), [&done]() { return done; }); + + if(!trace) + { + RDDialog::critical(this, tr("Debug Error"), tr("Error debugging pixel.")); + return; + } + + const ShaderBindpointMapping &bindMapping = + m_Ctx.CurPipelineState().GetBindpointMapping(msg.stage); + ResourceId pipeline = msg.stage == ShaderStage::Compute + ? m_Ctx.CurPipelineState().GetComputePipelineObject() + : m_Ctx.CurPipelineState().GetGraphicsPipelineObject(); + + // viewer takes ownership of the trace + IShaderViewer *s = m_Ctx.DebugShader(&bindMapping, refl, pipeline, trace, debugContext); + + if(msg.disassemblyLine >= 0) + { + s->ToggleBreakpointOnDisassemblyLine(msg.disassemblyLine); + s->RunForward(); + } + + m_Ctx.AddDockWindow(s->Widget(), DockReference::AddTo, this); + } + else + { + RDDialog::critical( + this, tr("Shader can't be debugged"), + tr("The shader does not support debugging: %1").arg(refl->debugInfo.debugStatus)); + } + }); + + if(m_gotoDelegate) + { + QObject::connect(m_gotoDelegate, &ButtonDelegate::messageClicked, [this](const QModelIndex &idx) { + RDTreeWidgetItem *item = ui->messages->itemForIndex(idx); + + int msgIdx = 0; + if(item) + msgIdx = item->tag().toInt(); + else + return; + + ShaderMessage msg = m_Messages[msgIdx]; + + m_Ctx.SetEventID({}, m_EID, m_EID); + + if(msg.stage == ShaderStage::Vertex) + { + m_Ctx.ShowMeshPreview(); + m_Ctx.GetMeshPreview()->SetCurrentInstance(msg.location.vertex.instance); + m_Ctx.GetMeshPreview()->SetCurrentView(msg.location.vertex.view); + // TODO, not accurate for indices + m_Ctx.GetMeshPreview()->ScrollToRow(msg.location.vertex.vertexIndex, MeshDataStage::VSIn); + } + else if(msg.stage == ShaderStage::Pixel) + { + m_Ctx.ShowTextureViewer(); + Subresource sub = m_Ctx.GetTextureViewer()->GetSelectedSubresource(); + sub.sample = msg.location.pixel.sample; + m_Ctx.GetTextureViewer()->SetSelectedSubresource(sub); + + // select an actual output. Prefer the first colour output, but if there's no colour output + // pick depth. + rdcarray cols = m_Ctx.CurPipelineState().GetOutputTargets(); + bool hascol = false; + for(size_t i = 0; i < cols.size(); i++) + hascol |= cols[i].resourceId != ResourceId(); + + if(hascol) + m_Ctx.GetTextureViewer()->ViewFollowedResource(FollowType::OutputColor, + ShaderStage::Pixel, 0, 0); + else + m_Ctx.GetTextureViewer()->ViewFollowedResource(FollowType::OutputDepth, + ShaderStage::Pixel, 0, 0); + m_Ctx.GetTextureViewer()->GotoLocation(msg.location.pixel.x, msg.location.pixel.y); + } + else + { + qCritical() << "Can't go to a compute thread"; + } + }); + } + + // deliberately copy m_OrigShaders to m_ReplacedShaders. This is impossible because we should + // either see a ResourceId() for unedited, or a new resource for edited. This means when we first + // get OnEventChanged() called we will definitely detect the situation as 'stale' and refresh the + // messages. + memcpy(m_ReplacedShaders, m_OrigShaders, sizeof(m_ReplacedShaders)); + + header->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); + + ui->staleStatus->hide(); + + ui->label->setText(tr("Shader messages from @%1 - %2") + .arg(m_EID) + .arg(m_Drawcall ? m_Drawcall->name : rdcstr("Unknown draw"))); + + setWindowTitle(tr("Shader messages at @%1").arg(m_EID)); + + m_Ctx.AddCaptureViewer(this); + + OnEventChanged(m_Ctx.CurEvent()); + + ui->messages->sortByColumn(sortColumn, Qt::SortOrder::AscendingOrder); + + for(int i = 0; i < 4; i++) + { + header->setSectionResizeMode(i, QHeaderView::Interactive); + ui->messages->resizeColumnToContents(i); + } +} + +ShaderMessageViewer::~ShaderMessageViewer() +{ + m_Ctx.RemoveCaptureViewer(this); + delete ui; +} + +bool ShaderMessageViewer::IsOutOfDate() +{ + return ui->staleStatus->isVisible(); +} + +void ShaderMessageViewer::OnCaptureLoaded() +{ +} + +void ShaderMessageViewer::OnCaptureClosed() +{ + ToolWindowManager::closeToolWindow(this); +} + +void ShaderMessageViewer::OnEventChanged(uint32_t eventId) +{ + ResourceId shaders[6]; + bool editsChanged = false; + QString staleReason; + + for(ShaderStage s : values()) + { + uint32_t idx = (uint32_t)s; + shaders[idx] = m_Ctx.GetResourceReplacement(m_OrigShaders[idx]); + + // either an edit has been applied, updated, or removed if these don't match + if(shaders[idx] != m_ReplacedShaders[idx]) + { + editsChanged = true; + staleReason += QFormatStr(", %1").arg(ToQStr(s, m_API)); + } + } + + // if the edits haven't changed, just skip + if(!editsChanged) + return; + + // if it's the current event we can update with the latest + if(m_EID == eventId) + { + m_Messages = m_Ctx.CurPipelineState().GetShaderMessages(); + + // not stale anymore + ui->staleStatus->hide(); + + // update current set of replaced shaders + memcpy(m_ReplacedShaders, shaders, sizeof(m_ReplacedShaders)); + + refreshMessages(); + } + else + { + staleReason.remove(0, 2); + + // otherwise we can't - just update the stale status + ui->staleStatus->show(); + ui->staleStatus->setText( + tr("Messages are stale because edits to %1 shaders have changed since they were fetched.\n" + "Select the event @%2 to refresh.") + .arg(staleReason) + .arg(m_EID)); + + ui->messages->beginUpdate(); + + for(int i = 0; i < ui->messages->topLevelItemCount(); i++) + ui->messages->topLevelItem(i)->setItalic(true); + + ui->messages->endUpdate(); + } +} + +void ShaderMessageViewer::refreshMessages() +{ + ShaderStageMask mask = ShaderStageMask::Compute; + + if(!m_Drawcall || !(m_Drawcall->flags & DrawFlags::Dispatch)) + { + mask = ShaderStageMask::Unknown; + + if(ui->vertex->isChecked()) + mask |= ShaderStageMask::Vertex; + if(ui->hull->isChecked()) + mask |= ShaderStageMask::Hull; + if(ui->domain->isChecked()) + mask |= ShaderStageMask::Domain; + if(ui->geometry->isChecked()) + mask |= ShaderStageMask::Geometry; + if(ui->pixel->isChecked()) + mask |= ShaderStageMask::Pixel; + } + + int vs = ui->messages->verticalScrollBar()->value(); + int curMsg = -1; + { + RDTreeWidgetItem *item = ui->messages->currentItem(); + if(item) + curMsg = item->tag().toInt(); + } + RDTreeWidgetItem *newCurrentItem = NULL; + ui->messages->beginUpdate(); + ui->messages->clear(); + + QString filter = ui->filter->text().trimmed(); + + for(int i = 0; i < m_Messages.count(); i++) + { + const ShaderMessage &msg = m_Messages[i]; + + // filter by stages + if(!(MaskForStage(msg.stage) & mask)) + continue; + + QString text(msg.message); + + QString location; + if(msg.stage == ShaderStage::Vertex) + { + // only show the view if the draw has multiview enabled + if(m_Multiview) + { + location += lit("View %1, ").arg(msg.location.vertex.view); + } + + // only show the instance if the draw is actually instanced + if(m_Drawcall && (m_Drawcall->flags & DrawFlags::Instanced) && m_Drawcall->numInstances > 1) + { + location += lit("Inst %1, ").arg(msg.location.vertex.instance); + } + + if(m_Drawcall && (m_Drawcall->flags & DrawFlags::Indexed)) + { + location += lit("Idx %1").arg(msg.location.vertex.vertexIndex); + } + else + { + location += lit("Vert %1").arg(msg.location.vertex.vertexIndex); + } + } + else if(msg.stage == ShaderStage::Pixel) + { + location = QFormatStr("%1 %2,%3") + .arg(IsD3D(m_API) ? lit("Pixel") : lit("Frag")) + .arg(msg.location.pixel.x) + .arg(msg.location.pixel.y); + + if(msg.location.pixel.primitive == ~0U) + location += lit(", Prim ?"); + else + location += lit(", Prim %1").arg(msg.location.pixel.primitive); + + if(m_Multisampled) + { + if(msg.location.pixel.sample == ~0U) + location += lit(", Samp ?"); + else + location += lit(", Samp %1").arg(msg.location.pixel.sample); + } + } + else if(msg.stage == ShaderStage::Compute) + { + } + else + { + // no location info for other stages + location = tr("Unknown shader"); + } + + // filter by text on location and messag + if(!filter.isEmpty() && !text.contains(filter, Qt::CaseInsensitive) && + !location.contains(filter, Qt::CaseInsensitive)) + continue; + + RDTreeWidgetItem *node = NULL; + + if(msg.stage == ShaderStage::Compute) + { + node = new RDTreeWidgetItem({ + QString(), QFormatStr("%1, %2, %3") + .arg(msg.location.compute.workgroup[0]) + .arg(msg.location.compute.workgroup[1]) + .arg(msg.location.compute.workgroup[2]), + QFormatStr("%1, %2, %3") + .arg(msg.location.compute.thread[0]) + .arg(msg.location.compute.thread[1]) + .arg(msg.location.compute.thread[2]), + text, + }); + } + else + { + node = new RDTreeWidgetItem({QString(), QString(), location, text}); + } + + if(node) + { + if(i == curMsg) + newCurrentItem = node; + + node->setItalic(ui->staleStatus->isVisible()); + node->setTag(i); + ui->messages->addTopLevelItem(node); + } + } + + ui->messages->clearSelection(); + ui->messages->endUpdate(); + ui->messages->verticalScrollBar()->setValue(vs); + + if(newCurrentItem) + { + ui->messages->setCurrentItem(newCurrentItem); + ui->messages->scrollToItem(newCurrentItem); + } +} diff --git a/qrenderdoc/Windows/ShaderMessageViewer.h b/qrenderdoc/Windows/ShaderMessageViewer.h new file mode 100644 index 000000000..b7160b157 --- /dev/null +++ b/qrenderdoc/Windows/ShaderMessageViewer.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2021 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/Interface/QRDInterface.h" + +namespace Ui +{ +class ShaderMessageViewer; +} + +class ButtonDelegate : public QStyledItemDelegate +{ +private: + Q_OBJECT + + QModelIndex m_ClickedIndex; + QIcon m_Icon; + +public: + ButtonDelegate(const QIcon &icon, QWidget *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, + const QModelIndex &index) override; +signals: + void messageClicked(const QModelIndex &index); +}; + +class ShaderMessageViewer : public QFrame, public IShaderMessageViewer, public ICaptureViewer +{ + Q_OBJECT + +public: + explicit ShaderMessageViewer(ICaptureContext &ctx, ShaderStageMask stages, QWidget *parent = 0); + ~ShaderMessageViewer(); + + // IShaderMessageViewer + QWidget *Widget() override { return this; } + uint32_t GetEvent() override { return m_EID; }; + rdcarray GetShaderMessages() override { return m_Messages; }; + bool IsOutOfDate() override; + + // ICaptureViewer + void OnCaptureLoaded() override; + void OnCaptureClosed() override; + void OnSelectedEventChanged(uint32_t eventId) override {} + void OnEventChanged(uint32_t eventId) override; +private slots: + +private: + void refreshMessages(); + + Ui::ShaderMessageViewer *ui; + ICaptureContext &m_Ctx; + + ButtonDelegate *m_debugDelegate = NULL; + ButtonDelegate *m_gotoDelegate = NULL; + + bool m_Multiview = false, m_Multisampled = false; + + GraphicsAPI m_API; + uint32_t m_EID; + const DrawcallDescription *m_Drawcall; + rdcarray m_Messages; + + ResourceId m_OrigShaders[6]; + ResourceId m_ReplacedShaders[6]; +}; diff --git a/qrenderdoc/Windows/ShaderMessageViewer.ui b/qrenderdoc/Windows/ShaderMessageViewer.ui new file mode 100644 index 000000000..17f9b3b7d --- /dev/null +++ b/qrenderdoc/Windows/ShaderMessageViewer.ui @@ -0,0 +1,230 @@ + + + ShaderMessageViewer + + + + 0 + 0 + 469 + 303 + + + + X Shader Messages @ EID Y + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + These are messages from @1234 - DrawIndexed(6, 1) + + + + + + + The results are out of date because of reasons. + + + + + + + Filter + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Show messages from: + + + + + + + Vertex + + + true + + + + + + + Hull + + + + + + + Domain + + + + + + + Geometry + + + + + + + Pixel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter message text + + + + + + + Filter + + + + + + + + + + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + 0 + + + false + + + false + + + true + + + false + + + true + + + false + + + + + + + + RDTreeWidget + QTreeView +
Widgets/Extended/RDTreeWidget.h
+
+ + RDLabel + QLabel +
Widgets/Extended/RDLabel.h
+
+ + RDLineEdit + QLineEdit +
Widgets/Extended/RDLineEdit.h
+
+
+ + +
diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index c83451a8b..5289cd086 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -680,7 +680,7 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR m_Ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_F5 | Qt::ShiftModifier).toString(), this, [this](QWidget *) { runBack(); }); m_Ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_F9).toString(), this, - [this](QWidget *) { ToggleBreakpoint(); }); + [this](QWidget *) { ToggleBreakpointOnInstruction(); }); // event filter to pick up tooltip events ui->constants->installEventFilter(this); @@ -694,6 +694,8 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR QPointer me(this); + m_DeferredInit = true; + m_Ctx.Replay().AsyncInvoke([this, me](IReplayController *r) { if(!me) return; @@ -754,6 +756,11 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR gotoSourceDebugging(); updateDebugState(); } + + m_DeferredInit = false; + for(std::function &f : m_DeferredCommands) + f(this); + m_DeferredCommands.clear(); }); }); @@ -1288,7 +1295,7 @@ void ShaderViewer::debug_contextMenu(const QPoint &pos) QObject::connect(&addBreakpoint, &QAction::triggered, [this, scintillaPos] { m_DisassemblyView->setSelection(scintillaPos, scintillaPos); - ToggleBreakpoint(); + ToggleBreakpointOnInstruction(); }); QObject::connect(&runCursor, &QAction::triggered, [this, scintillaPos] { m_DisassemblyView->setSelection(scintillaPos, scintillaPos); @@ -3914,8 +3921,15 @@ void ShaderViewer::SetCurrentStep(uint32_t step) updateDebugState(); } -void ShaderViewer::ToggleBreakpoint(int32_t instruction) +void ShaderViewer::ToggleBreakpointOnInstruction(int32_t instruction) { + if(m_DeferredInit) + { + m_DeferredCommands.push_back( + [instruction](ShaderViewer *v) { v->ToggleBreakpointOnInstruction(instruction); }); + return; + } + if(!m_Trace || m_States.empty()) return; @@ -3944,7 +3958,7 @@ void ShaderViewer::ToggleBreakpoint(int32_t instruction) if(fileMap.contains(i)) { for(size_t inst : fileMap[i]) - ToggleBreakpoint((int)inst); + ToggleBreakpointOnInstruction((int)inst); return; } @@ -4023,6 +4037,23 @@ void ShaderViewer::ToggleBreakpoint(int32_t instruction) } } +void ShaderViewer::ToggleBreakpointOnDisassemblyLine(int32_t disassemblyLine) +{ + // instructionForDisassemblyLine expects scintilla 0-based line numbers + ToggleBreakpointOnInstruction(instructionForDisassemblyLine(disassemblyLine - 1)); +} + +void ShaderViewer::RunForward() +{ + if(m_DeferredInit) + { + m_DeferredCommands.push_back([](ShaderViewer *v) { v->RunForward(); }); + return; + } + + run(); +} + void ShaderViewer::ShowErrors(const rdcstr &errors) { if(m_Errors) diff --git a/qrenderdoc/Windows/ShaderViewer.h b/qrenderdoc/Windows/ShaderViewer.h index 2e73360e9..cac22d730 100644 --- a/qrenderdoc/Windows/ShaderViewer.h +++ b/qrenderdoc/Windows/ShaderViewer.h @@ -120,7 +120,9 @@ public: virtual uint32_t CurrentStep() override; virtual void SetCurrentStep(uint32_t step) override; - virtual void ToggleBreakpoint(int32_t instruction = -1) override; + virtual void ToggleBreakpointOnInstruction(int32_t instruction = -1) override; + virtual void ToggleBreakpointOnDisassemblyLine(int32_t disassemblyLine) override; + virtual void RunForward() override; virtual void ShowErrors(const rdcstr &errors) override; @@ -272,6 +274,11 @@ private: size_t m_CurrentStateIdx = 0; rdcarray m_Variables; + // true when debugging while we're populating the initial trace. Lets us queue up commands and + // process them once we've initialised properly + bool m_DeferredInit = false; + rdcarray> m_DeferredCommands; + QSemaphore m_BackgroundRunning; rdcarray m_AccessedResources; diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index b9d108fab..9035c00b3 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -188,6 +188,8 @@ SOURCES += Code/qrenderdoc.cpp \ Windows/MainWindow.cpp \ Windows/EventBrowser.cpp \ Windows/TextureViewer.cpp \ + Windows/ShaderViewer.cpp \ + Windows/ShaderMessageViewer.cpp \ Widgets/Extended/RDLineEdit.cpp \ Widgets/Extended/RDTextEdit.cpp \ Widgets/Extended/RDLabel.cpp \ @@ -269,6 +271,8 @@ HEADERS += Code/CaptureContext.h \ Windows/MainWindow.h \ Windows/EventBrowser.h \ Windows/TextureViewer.h \ + Windows/ShaderViewer.h \ + Windows/ShaderMessageViewer.h \ Widgets/Extended/RDLineEdit.h \ Widgets/Extended/RDTextEdit.h \ Widgets/Extended/RDLabel.h \ @@ -347,6 +351,7 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Widgets/BufferFormatSpecifier.ui \ Windows/BufferViewer.ui \ Windows/ShaderViewer.ui \ + Windows/ShaderMessageViewer.ui \ Windows/DebugMessageView.ui \ Windows/LogView.ui \ Windows/CommentView.ui \ @@ -400,12 +405,10 @@ SOURCES += $$_PRO_FILE_PWD_/3rdparty/scintilla/lexlib/*.cxx \ $$_PRO_FILE_PWD_/3rdparty/scintilla/lexers/*.cxx \ $$_PRO_FILE_PWD_/3rdparty/scintilla/src/*.cxx \ $$_PRO_FILE_PWD_/3rdparty/scintilla/qt/ScintillaEdit/*.cpp \ - $$_PRO_FILE_PWD_/3rdparty/scintilla/qt/ScintillaEditBase/*.cpp \ - Windows/ShaderViewer.cpp + $$_PRO_FILE_PWD_/3rdparty/scintilla/qt/ScintillaEditBase/*.cpp HEADERS += $$_PRO_FILE_PWD_/3rdparty/scintilla/lexlib/*.h \ $$_PRO_FILE_PWD_/3rdparty/scintilla/src/*.h \ $$_PRO_FILE_PWD_/3rdparty/scintilla/qt/ScintillaEdit/*.h \ - $$_PRO_FILE_PWD_/3rdparty/scintilla/qt/ScintillaEditBase/*.h \ - Windows/ShaderViewer.h + $$_PRO_FILE_PWD_/3rdparty/scintilla/qt/ScintillaEditBase/*.h diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 87a05af5c..b9aa30a89 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -673,6 +673,7 @@ + @@ -777,6 +778,7 @@ + @@ -972,6 +974,7 @@ + @@ -1358,6 +1361,12 @@ MOC %(Filename).h $(IntDir)generated\moc_%(Filename).cpp + + %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) + "$(QtBinDir)\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"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" + MOC %(Filename).h + $(IntDir)generated\moc_%(Filename).cpp + %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) "$(QtBinDir)\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"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" @@ -1643,6 +1652,12 @@ UIC %(Filename).ui $(IntDir)generated\ui_%(Filename).h + + %(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs) + "$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" + UIC %(Filename).ui + $(IntDir)generated\ui_%(Filename).h + %(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs) "$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 4e828851d..f12f2c643 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -744,6 +744,12 @@ Code + + Generated Files + + + Windows + @@ -1109,6 +1115,9 @@ Code + + Generated Files + @@ -1511,6 +1520,12 @@ Widgets\Extended + + Windows + + + Windows +