Expose shader printf messages in the UI

This commit is contained in:
baldurk
2021-05-11 16:15:31 +01:00
parent c6c133bdd0
commit b4ec703d97
18 changed files with 1425 additions and 25 deletions
+20 -5
View File
@@ -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)
{
+5 -2
View File
@@ -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<ICaptureViewer *> &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<ResourceId, QString> m_CustomNames;
int m_CustomNameCachedID = 1;
QVector<ResourceId> m_ReplacedResources;
QMap<ResourceId, ResourceId> m_ReplacedResources;
const SDFile *m_StructuredFile = NULL;
SDFile m_DummySDFile;
+72 -3
View File
@@ -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<ShaderMessage> 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.
+60 -3
View File
@@ -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>())
{
ShaderMessageLink link = v.value<ShaderMessageLink>();
QString msgstr =
QApplication::translate("qrenderdoc", "%n msg(s)", "Shader messages", link.numMessages);
html += lit("<td valign=\"middle\"><img width=\"16\" src=':/text_add%3.png'></td>"
"<td valign=\"middle\">%1</td>")
.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("<td valign=\"middle\"><font color='#0000FF'><u>EID @%1</u></font></td>")
@@ -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<ResourceId>() && v.value<ResourceId>() != ResourceId())
if((v.userType() == qMetaTypeId<ResourceId>() && v.value<ResourceId>() != ResourceId()) ||
v.userType() == qMetaTypeId<ShaderMessageLink>())
{
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>())
{
ShaderMessageLink link = v.value<ShaderMessageLink>();
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<uint32_t>();
+30
View File
@@ -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<EventItemTag>().EID != eventId)
return;
const DrawcallDescription *draw = m_Ctx.GetDrawcall(eventId);
const rdcarray<ShaderMessage> &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
+1
View File
@@ -140,6 +140,7 @@ private:
QMap<uint32_t, QToolButton *> m_BookmarkButtons;
void RefreshIcon(RDTreeWidgetItem *item, EventItemTag tag);
void RefreshShaderMessages();
Ui::EventBrowser *ui;
ICaptureContext &m_Ctx;
@@ -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<QWidget *>(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;
@@ -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);
@@ -594,6 +594,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="vsShaderMessagesButton">
<property name="toolTip">
<string>View Shader Messages</string>
</property>
<property name="text">
<string>Messages</string>
</property>
<property name="icon">
<iconset resource="../../Resources/resources.qrc">
<normaloff>:/text_add.png</normaloff>:/text_add.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="vsShaderSpacer">
<property name="orientation">
@@ -880,6 +900,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tcsShaderMessagesButton">
<property name="toolTip">
<string>View Shader Messages</string>
</property>
<property name="text">
<string>Messages</string>
</property>
<property name="icon">
<iconset resource="../../Resources/resources.qrc">
<normaloff>:/text_add.png</normaloff>:/text_add.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="tcsShaderSpacer">
<property name="orientation">
@@ -1166,6 +1206,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tesShaderMessagesButton">
<property name="toolTip">
<string>View Shader Messages</string>
</property>
<property name="text">
<string>Messages</string>
</property>
<property name="icon">
<iconset resource="../../Resources/resources.qrc">
<normaloff>:/text_add.png</normaloff>:/text_add.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="tesShaderSpacer">
<property name="orientation">
@@ -1452,6 +1512,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="gsShaderMessagesButton">
<property name="toolTip">
<string>View Shader Messages</string>
</property>
<property name="text">
<string>Messages</string>
</property>
<property name="icon">
<iconset resource="../../Resources/resources.qrc">
<normaloff>:/text_add.png</normaloff>:/text_add.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="gsShaderSpacer">
<property name="orientation">
@@ -2799,6 +2879,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="fsShaderMessagesButton">
<property name="toolTip">
<string>View Shader Messages</string>
</property>
<property name="text">
<string>Messages</string>
</property>
<property name="icon">
<iconset resource="../../Resources/resources.qrc">
<normaloff>:/text_add.png</normaloff>:/text_add.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="fsShaderSpacer">
<property name="orientation">
@@ -3642,6 +3742,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="csShaderMessagesButton">
<property name="toolTip">
<string>View Shader Messages</string>
</property>
<property name="text">
<string>Messages</string>
</property>
<property name="icon">
<iconset resource="../../Resources/resources.qrc">
<normaloff>:/text_add.png</normaloff>:/text_add.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+11 -2
View File
@@ -542,9 +542,13 @@ struct CaptureContextInvoker : ObjectForwarder<ICaptureContext>
{
return InvokeRetFunction<bool>(&ICaptureContext::IsResourceReplaced, id);
}
virtual void RegisterReplacement(ResourceId id) override
virtual ResourceId GetResourceReplacement(ResourceId id) override
{
InvokeVoidFunction(&ICaptureContext::RegisterReplacement, id);
return InvokeRetFunction<ResourceId>(&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<ICaptureContext>
return InvokeRetFunction<IShaderViewer *>(&ICaptureContext::ViewShader, shader, pipeline);
}
virtual IShaderMessageViewer *ViewShaderMessages(ShaderStageMask stages) override
{
return InvokeRetFunction<IShaderMessageViewer *>(&ICaptureContext::ViewShaderMessages, stages);
}
virtual IBufferViewer *ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id,
const rdcstr &format = "") override
{
+657
View File
@@ -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 <QAction>
#include <QMenu>
#include <QMouseEvent>
#include <QScrollBar>
#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<BoundResource> 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<ShaderStage>())
{
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);
copy.setIcon(Icons::copy());
QObject::connect(&copy, &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<BoundResource> 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<ShaderStage>())
{
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);
}
}
+93
View File
@@ -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 <QFrame>
#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<ShaderMessage> 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<ShaderMessage> m_Messages;
ResourceId m_OrigShaders[6];
ResourceId m_ReplacedShaders[6];
};
+230
View File
@@ -0,0 +1,230 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ShaderMessageViewer</class>
<widget class="QFrame" name="ShaderMessageViewer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>469</width>
<height>303</height>
</rect>
</property>
<property name="windowTitle">
<string>X Shader Messages @ EID Y</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="RDLabel" name="label">
<property name="text">
<string>These are messages from @1234 - DrawIndexed(6, 1)</string>
</property>
</widget>
</item>
<item>
<widget class="RDLabel" name="staleStatus">
<property name="text">
<string>The results are out of date because of reasons.</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="filterGroup">
<property name="title">
<string>Filter</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QFrame" name="stageFilters">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="filterLabel">
<property name="text">
<string>Show messages from:</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="vertex">
<property name="text">
<string>Vertex</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="hull">
<property name="text">
<string>Hull</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="domain">
<property name="text">
<string>Domain</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="geometry">
<property name="text">
<string>Geometry</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="pixel">
<property name="text">
<string>Pixel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="textFilterFrame">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="RDLineEdit" name="filter">
<property name="placeholderText">
<string>Filter message text</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="filterButton">
<property name="text">
<string>Filter</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="RDTreeWidget" name="messages">
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="indentation">
<number>0</number>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RDTreeWidget</class>
<extends>QTreeView</extends>
<header>Widgets/Extended/RDTreeWidget.h</header>
</customwidget>
<customwidget>
<class>RDLabel</class>
<extends>QLabel</extends>
<header>Widgets/Extended/RDLabel.h</header>
</customwidget>
<customwidget>
<class>RDLineEdit</class>
<extends>QLineEdit</extends>
<header>Widgets/Extended/RDLineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+35 -4
View File
@@ -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<ShaderViewer> 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<void(ShaderViewer *)> &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)
+8 -1
View File
@@ -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<ShaderVariable> 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<std::function<void(ShaderViewer *)>> m_DeferredCommands;
QSemaphore m_BackgroundRunning;
rdcarray<AccessedResourceData> m_AccessedResources;
+7 -4
View File
@@ -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
+15
View File
@@ -673,6 +673,7 @@
</ClCompile>
<ClCompile Include="$(IntDir)generated\moc_SettingsDialog.cpp" />
<ClCompile Include="$(IntDir)generated\moc_ShaderViewer.cpp" />
<ClCompile Include="$(IntDir)generated\moc_ShaderMessageViewer.cpp" />
<ClCompile Include="$(IntDir)generated\moc_PythonShell.cpp" />
<ClCompile Include="$(IntDir)generated\moc_PixelHistoryView.cpp" />
<ClCompile Include="$(IntDir)generated\moc_StatisticsViewer.cpp" />
@@ -777,6 +778,7 @@
<ClCompile Include="Windows\PipelineState\PipelineStateViewer.cpp" />
<ClCompile Include="Windows\PipelineState\VulkanPipelineStateViewer.cpp" />
<ClCompile Include="Windows\ShaderViewer.cpp" />
<ClCompile Include="Windows\ShaderMessageViewer.cpp" />
<ClCompile Include="Windows\PythonShell.cpp" />
<ClCompile Include="Windows\PixelHistoryView.cpp" />
<ClCompile Include="Windows\PerformanceCounterViewer.cpp" />
@@ -972,6 +974,7 @@
<ClInclude Include="$(IntDir)generated\ui_ResourcePreview.h" />
<ClInclude Include="$(IntDir)generated\ui_SettingsDialog.h" />
<ClInclude Include="$(IntDir)generated\ui_ShaderViewer.h" />
<ClInclude Include="$(IntDir)generated\ui_ShaderMessageViewer.h" />
<ClInclude Include="$(IntDir)generated\ui_PythonShell.h" />
<ClInclude Include="$(IntDir)generated\ui_PixelHistoryView.h" />
<ClInclude Include="$(IntDir)generated\ui_PerformanceCounterViewer.h" />
@@ -1358,6 +1361,12 @@
<Message>MOC %(Filename).h</Message>
<Outputs>$(IntDir)generated\moc_%(Filename).cpp</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\ShaderMessageViewer.h">
<AdditionalInputs>%(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(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"</Command>
<Message>MOC %(Filename).h</Message>
<Outputs>$(IntDir)generated\moc_%(Filename).cpp</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\PythonShell.h">
<AdditionalInputs>%(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(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"</Command>
@@ -1643,6 +1652,12 @@
<Message>UIC %(Filename).ui</Message>
<Outputs>$(IntDir)generated\ui_%(Filename).h</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\ShaderMessageViewer.ui">
<AdditionalInputs>%(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h"</Command>
<Message>UIC %(Filename).ui</Message>
<Outputs>$(IntDir)generated\ui_%(Filename).h</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\PythonShell.ui">
<AdditionalInputs>%(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h"</Command>
@@ -744,6 +744,12 @@
<ClCompile Include="Code\MiniQtHelper.cpp">
<Filter>Code</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)generated\moc_ShaderMessageViewer.cpp">
<Filter>Generated Files</Filter>
</ClCompile>
<ClCompile Include="Windows\ShaderMessageViewer.cpp">
<Filter>Windows</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="3rdparty\flowlayout\FlowLayout.h">
@@ -1109,6 +1115,9 @@
<ClInclude Include="Code\MiniQtHelper.h">
<Filter>Code</Filter>
</ClInclude>
<ClInclude Include="$(IntDir)generated\ui_ShaderMessageViewer.h">
<Filter>Generated Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Code\pyrenderdoc\pyconversion.h">
@@ -1511,6 +1520,12 @@
<CustomBuild Include="Widgets\Extended\RDMenu.h">
<Filter>Widgets\Extended</Filter>
</CustomBuild>
<CustomBuild Include="Windows\ShaderMessageViewer.h">
<Filter>Windows</Filter>
</CustomBuild>
<CustomBuild Include="Windows\ShaderMessageViewer.ui">
<Filter>Windows</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<Image Include="Resources\action.png">