Files
renderdoc/qrenderdoc/Windows/ShaderMessageViewer.cpp
T
2022-02-17 17:38:32 +00:00

851 lines
26 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2022 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(); });
QMenu *menu = new QMenu(this);
QAction *action = new QAction(tr("Export to &Text"));
action->setIcon(Icons::save());
QObject::connect(action, &QAction::triggered, this, &ShaderMessageViewer::exportText);
menu->addAction(action);
action = new QAction(tr("Export to &CSV"));
action->setIcon(Icons::save());
QObject::connect(action, &QAction::triggered, this, &ShaderMessageViewer::exportCSV);
menu->addAction(action);
ui->exportButton->setMenu(menu);
QObject::connect(ui->exportButton, &QToolButton::clicked, this, &ShaderMessageViewer::exportText);
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_Action = m_Ctx.GetAction(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_Action && (m_Action->flags & ActionFlags::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;
const 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([&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;
const 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;
sub.slice = msg.location.pixel.view;
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_Action ? m_Ctx.GetEventBrowser()->GetEventName(m_Action->eventId)
: rdcstr("Unknown action")));
setWindowTitle(tr("Shader messages at @%1").arg(m_EID));
m_Ctx.AddCaptureViewer(this);
OnEventChanged(m_Ctx.CurEvent());
ui->messages->setSortComparison(
[this](int col, Qt::SortOrder order, const RDTreeWidgetItem *a, const RDTreeWidgetItem *b) {
if(order == Qt::DescendingOrder)
std::swap(a, b);
const ShaderMessage &am = m_Messages[a->tag().toInt()];
const ShaderMessage &bm = m_Messages[b->tag().toInt()];
if(col == 3)
{
return am.message < bm.message;
}
else if(col == 2 || m_OrigShaders[5] == ResourceId())
{
// sort by location either if it's selected, or if it's not dispatch in which case we
// default to location sorting (don't try to sort by the button-only columns that have no
// data)
// sort by stage first
if(am.stage != bm.stage)
return am.stage < bm.stage;
if(am.stage == ShaderStage::Vertex)
{
const ShaderVertexMessageLocation &aloc = am.location.vertex;
const ShaderVertexMessageLocation &bloc = bm.location.vertex;
if(aloc.view != bloc.view)
return aloc.view < bloc.view;
if(aloc.instance != bloc.instance)
return aloc.instance < bloc.instance;
return aloc.vertexIndex < bloc.vertexIndex;
}
else if(am.stage == ShaderStage::Pixel)
{
const ShaderPixelMessageLocation &aloc = am.location.pixel;
const ShaderPixelMessageLocation &bloc = bm.location.pixel;
if(aloc.x != bloc.x)
return aloc.x < bloc.x;
if(aloc.y != bloc.y)
return aloc.y < bloc.y;
if(aloc.primitive != bloc.primitive)
return aloc.primitive < bloc.primitive;
if(aloc.view != bloc.view)
return aloc.view < bloc.view;
return aloc.sample < bloc.sample;
}
else if(am.stage == ShaderStage::Compute)
{
// column 2 is the thread column for compute
return am.location.compute.thread < bm.location.compute.thread;
}
else
{
// can't sort these, pretend they're all equal
return false;
}
}
else if(col == 1)
{
return am.location.compute.workgroup < bm.location.compute.workgroup;
}
return false;
});
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::exportText()
{
exportData(false);
}
void ShaderMessageViewer::exportCSV()
{
exportData(true);
}
void ShaderMessageViewer::exportData(bool csv)
{
QString filter;
QString title;
if(csv)
{
filter = tr("CSV Files (*.csv)");
title = tr("Export buffer to CSV");
}
else
{
filter = tr("Text Files (*.txt)");
title = tr("Export buffer to text");
}
QString filename =
RDDialog::getSaveFileName(this, title, QString(), tr("%1;;All files (*)").arg(filter));
if(filename.isEmpty())
return;
QFile *f = new QFile(filename);
QIODevice::OpenMode flags = QIODevice::WriteOnly | QFile::Truncate | QIODevice::Text;
if(!f->open(flags))
{
delete f;
RDDialog::critical(this, tr("Error exporting file"),
tr("Couldn't open file '%1' for writing").arg(filename));
return;
}
LambdaThread *exportThread = new LambdaThread([this, csv, f]() {
QTextStream s(f);
bool compute = (m_OrigShaders[5] != ResourceId());
if(csv)
{
if(compute)
s << tr("Workgroup,Thread,Message\n");
else
s << tr("Location,Message\n");
}
const int start = compute ? 1 : 2;
const int end = 3;
int locationWidth = 0;
for(int i = 0; i < ui->messages->topLevelItemCount(); i++)
{
RDTreeWidgetItem *node = ui->messages->topLevelItem(i);
locationWidth = qMax(locationWidth, node->text(start).length());
if(compute)
locationWidth = qMax(locationWidth, node->text(start + 1).length());
}
for(int i = 0; i < ui->messages->topLevelItemCount(); i++)
{
RDTreeWidgetItem *node = ui->messages->topLevelItem(i);
if(csv)
{
int col = start;
for(; col <= end - 1; col++)
s << "\"" << node->text(col) << "\",";
s << "\"" << node->text(col).replace(QLatin1Char('"'), lit("\"\"")) << "\"\n";
}
else
{
int col = start;
for(; col <= end - 1; col++)
s << QFormatStr("%1").arg(node->text(col), -locationWidth) << "\t";
s << node->text(col) << "\n";
}
}
f->close();
delete f;
});
exportThread->start();
// 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; exportThread->isRunning() && i < 100; i++)
QThread::msleep(5);
ShowProgressDialog(this, tr("Exporting messages"),
[exportThread]() { return !exportThread->isRunning(); });
exportThread->deleteLater();
}
void ShaderMessageViewer::refreshMessages()
{
ShaderStageMask mask = ShaderStageMask::Compute;
if(!m_Action || !(m_Action->flags & ActionFlags::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_Action && (m_Action->flags & ActionFlags::Instanced) && m_Action->numInstances > 1)
{
location += lit("Inst %1, ").arg(msg.location.vertex.instance);
}
if(m_Action && (m_Action->flags & ActionFlags::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);
// only show the view if the draw has multiview enabled
if(m_Multiview)
{
location += lit(", View %1").arg(msg.location.pixel.view);
}
if(m_Multisampled && msg.location.pixel.sample != ~0U)
{
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);
}
}