mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-22 22:46:41 +00:00
Expose shader printf messages in the UI
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.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<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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user