Files
renderdoc/qrenderdoc/Windows/PixelHistoryView.cpp
T
baldurk d4ddb565d0 Add a per-shader debuggable flag to allow finer grained status
* E.g. on D3D12 we can debug DXBC shaders but not DXIL shaders. On vulkan this
  will allow us to have the UI work better when encountering shaders with
  unsupported capabilities or extensions.
2020-06-18 17:22:45 +01:00

876 lines
25 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2020 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 "PixelHistoryView.h"
#include <float.h>
#include <math.h>
#include <QAction>
#include <QMenu>
#include "toolwindowmanager/ToolWindowManager.h"
#include "ui_PixelHistoryView.h"
struct EventTag
{
uint32_t eventId = 0;
uint32_t primitive = ~0U;
};
Q_DECLARE_METATYPE(EventTag);
class PixelHistoryItemModel : public QAbstractItemModel
{
public:
PixelHistoryItemModel(ICaptureContext &ctx, ResourceId tex, const TextureDisplay &display,
const QPalette &palette, QObject *parent)
: QAbstractItemModel(parent), m_Ctx(ctx), m_Palette(palette)
{
m_Tex = m_Ctx.GetTexture(tex);
m_Display = display;
CompType compType = m_Tex->format.compType;
if(compType == CompType::Typeless)
compType = display.typeCast;
m_IsUint = (compType == CompType::UInt);
m_IsSint = (compType == CompType::SInt);
m_IsFloat = (!m_IsUint && !m_IsSint);
if(compType == CompType::Depth)
m_IsDepth = true;
switch(m_Tex->format.type)
{
case ResourceFormatType::D16S8:
case ResourceFormatType::D24S8:
case ResourceFormatType::D32S8:
case ResourceFormatType::S8: m_IsDepth = true; break;
default: break;
}
}
void setHistory(const rdcarray<PixelModification> &history)
{
m_ModList.reserve(history.count());
for(const PixelModification &h : history)
m_ModList.push_back(h);
m_Loading = false;
emit beginResetModel();
setShowFailures(true);
emit endResetModel();
}
void setShowFailures(bool show)
{
emit beginResetModel();
m_History.clear();
m_History.reserve(m_ModList.count());
for(const PixelModification &h : m_ModList)
{
if(!show && !h.Passed())
continue;
if(m_History.isEmpty() || m_History.back().back().eventId != h.eventId)
m_History.push_back({h});
else
m_History.back().push_back(h);
}
emit endResetModel();
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if(row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount())
return QModelIndex();
return createIndex(row, column, makeTag(row, parent));
}
QModelIndex parent(const QModelIndex &index) const override
{
if(m_Loading || isEvent(index))
return QModelIndex();
int eventRow = getEventRow(index);
return createIndex(eventRow, 0, makeTag(eventRow, QModelIndex()));
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if(m_Loading)
return parent.isValid() ? 0 : 1;
if(!parent.isValid())
return m_History.count();
if(isEvent(parent))
{
const QList<PixelModification> &mods = getMods(parent);
const DrawcallDescription *draw = m_Ctx.GetDrawcall(mods.front().eventId);
if(draw && draw->flags & (DrawFlags::Clear | DrawFlags::PassBoundary))
return 0;
return mods.count();
}
return 0;
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 5; }
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if(!index.isValid())
return 0;
return QAbstractItemModel::flags(index);
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0)
return lit("Event");
// sizes for the colour previews
if(orientation == Qt::Horizontal && role == Qt::SizeHintRole && (section == 2 || section == 4))
return QSize(18, 0);
return QVariant();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if(index.isValid())
{
int col = index.column();
// preview columns
if(col == 2 || col == 4)
{
if(role == Qt::SizeHintRole)
return QSize(16, 0);
}
if(m_Loading)
{
if(role == Qt::DisplayRole && col == 0)
return tr("Loading...");
return QVariant();
}
if(role == Qt::DisplayRole)
{
// main text
if(col == 0)
{
if(isEvent(index))
{
const QList<PixelModification> &mods = getMods(index);
const DrawcallDescription *drawcall = m_Ctx.GetDrawcall(mods.front().eventId);
if(!drawcall)
return QVariant();
QString ret;
QList<const DrawcallDescription *> drawstack;
const DrawcallDescription *parent = drawcall->parent;
while(parent)
{
drawstack.push_back(parent);
parent = parent->parent;
}
if(!drawstack.isEmpty())
{
ret += lit("> ") + drawstack.back()->name;
if(drawstack.count() > 3)
ret += lit(" ...");
ret += lit("\n");
if(drawstack.count() > 2)
ret += lit("> ") + drawstack[1]->name + lit("\n");
if(drawstack.count() > 1)
ret += lit("> ") + drawstack[0]->name + lit("\n");
ret += lit("\n");
}
bool passed = true;
bool uavnowrite = false;
if(mods.front().directShaderWrite)
{
ret += tr("EID %1\n%2\nBound as UAV or copy - potential modification")
.arg(mods.front().eventId)
.arg(drawcall->name);
if(memcmp(mods[0].preMod.col.uintValue, mods[0].postMod.col.uintValue,
sizeof(uint32_t) * 4) == 0)
{
ret += tr("\nNo change in tex value");
uavnowrite = true;
}
}
else
{
passed = false;
for(const PixelModification &m : mods)
passed |= m.Passed();
QString failure = passed ? QString() : failureString(mods[0]);
ret += tr("EID %1\n%2%3\n%4 Fragments touching pixel\n")
.arg(mods.front().eventId)
.arg(drawcall->name)
.arg(failure)
.arg(mods.count());
}
return ret;
}
else
{
const PixelModification &mod = getMod(index);
if(mod.directShaderWrite)
{
QString ret = tr("Potential UAV/Copy write");
if(mod.preMod.col.uintValue[0] == mod.postMod.col.uintValue[0] &&
mod.preMod.col.uintValue[1] == mod.postMod.col.uintValue[1] &&
mod.preMod.col.uintValue[2] == mod.postMod.col.uintValue[2] &&
mod.preMod.col.uintValue[3] == mod.postMod.col.uintValue[3])
{
ret += tr("\nNo change in tex value");
}
return ret;
}
else
{
QString ret = tr("Primitive %1\n").arg(mod.primitiveID);
if(mod.primitiveID == ~0U)
ret = tr("Unknown primitive\n");
if(mod.shaderDiscarded)
ret += failureString(mod);
return ret;
}
}
}
// pre mod/shader out text
if(col == 1)
{
if(isEvent(index))
{
return tr("Tex Before\n\n") + modString(getMods(index).first().preMod);
}
else
{
const PixelModification &mod = getMod(index);
if(mod.unboundPS)
return tr("No Pixel\nShader\nBound");
if(mod.directShaderWrite)
return tr("Tex Before\n\n") + modString(mod.preMod);
return tr("Shader Out\n\n") + modString(mod.shaderOut, 4);
}
}
// post mod text
if(col == 3)
{
if(isEvent(index))
return tr("Tex After\n\n") + modString(getMods(index).last().postMod);
else
return tr("Tex After\n\n") + modString(getMod(index).postMod);
}
}
if(role == Qt::BackgroundRole && (m_IsDepth || m_IsFloat))
{
// pre mod color
if(col == 2)
{
if(isEvent(index))
{
return backgroundBrush(getMods(index).first().preMod);
}
else
{
const PixelModification &mod = getMod(index);
if(mod.directShaderWrite)
return backgroundBrush(mod.preMod);
return backgroundBrush(mod.shaderOut);
}
}
else if(col == 4)
{
if(isEvent(index))
return backgroundBrush(getMods(index).last().postMod);
else
return backgroundBrush(getMod(index).postMod);
}
}
// text backgrounds marking pass/fail
if(role == Qt::BackgroundRole && (col == 0 || col == 1 || col == 3))
{
// rest
if(isEvent(index))
{
const QList<PixelModification> &mods = getMods(index);
bool passed = false;
for(const PixelModification &m : mods)
passed |= m.Passed();
if(mods[0].directShaderWrite &&
memcmp(mods[0].preMod.col.uintValue, mods[0].postMod.col.uintValue,
sizeof(uint32_t) * 4) == 0)
return QBrush(QColor::fromRgb(235, 235, 235));
return passed ? QBrush(QColor::fromRgb(235, 255, 235))
: QBrush(QColor::fromRgb(255, 235, 235));
}
else
{
if(getMod(index).shaderDiscarded)
return QBrush(QColor::fromRgb(255, 235, 235));
}
}
// Since we change the background color for some cells, also change the foreground color to
// ensure contrast with all UI themes
if(role == Qt::ForegroundRole && (col == 0 || col == 1 || col == 3))
{
QColor textColor =
contrastingColor(QColor::fromRgb(235, 235, 235), m_Palette.color(QPalette::Text));
if(isEvent(index))
{
return QBrush(textColor);
}
else
{
if(getMod(index).shaderDiscarded)
return QBrush(textColor);
}
}
if(role == Qt::UserRole)
{
EventTag tag;
if(isEvent(index))
{
tag.eventId = getMods(index).first().eventId;
}
else
{
const PixelModification &mod = getMod(index);
tag.eventId = mod.eventId;
if(!mod.directShaderWrite)
tag.primitive = mod.primitiveID;
}
return QVariant::fromValue(tag);
}
}
return QVariant();
}
const QVector<PixelModification> &modifications() { return m_ModList; }
ResourceId texID() { return m_Tex->resourceId; }
private:
ICaptureContext &m_Ctx;
const TextureDescription *m_Tex;
TextureDisplay m_Display;
bool m_IsDepth = false, m_IsUint = false, m_IsSint = false, m_IsFloat = true;
bool m_Loading = true;
QVector<QList<PixelModification>> m_History;
QVector<PixelModification> m_ModList;
const QPalette &m_Palette;
// mask for top bit of quintptr
static const quintptr eventTagMask = 1ULL << (Q_PROCESSOR_WORDSIZE * 8 - 1);
// 1 byte on 32-bit, 2 bytes on 64-bit
static const quintptr modRowBits = Q_PROCESSOR_WORDSIZE * 2;
// mask without top bit and however many bits we have for modification mask
static const quintptr eventRowMask = UINTPTR_MAX >> (1 + modRowBits);
static const quintptr modRowMask = (1 << modRowBits) - 1;
inline bool isEvent(QModelIndex parent) const { return parent.internalId() & eventTagMask; }
int getEventRow(QModelIndex index) const
{
if(isEvent(index))
return index.row();
else
return (index.internalId() & ~eventTagMask) >> modRowBits;
}
int getModRow(QModelIndex index) const { return int(index.internalId() & modRowMask); }
const QList<PixelModification> &getMods(QModelIndex index) const
{
return m_History[index.row()];
}
const PixelModification &getMod(QModelIndex index) const
{
return m_History[getEventRow(index)][getModRow(index)];
}
quintptr makeTag(int row, QModelIndex parent) const
{
if(!parent.isValid())
{
// event
return eventTagMask | row;
}
else
{
// modification
if(quintptr(row) > modRowMask)
qCritical() << "Packing failure - more than 255 modifications in one event";
return ((parent.internalId() & eventRowMask) << modRowBits) | (quintptr(row) & modRowMask);
}
}
QBrush backgroundBrush(const ModificationValue &val) const
{
float rangesize = (m_Display.rangeMax - m_Display.rangeMin);
float r = val.col.floatValue[0];
float g = val.col.floatValue[1];
float b = val.col.floatValue[2];
if(!m_Display.red)
r = 0.0f;
if(!m_Display.green)
g = 0.0f;
if(!m_Display.blue)
b = 0.0f;
if(m_Display.red && !m_Display.green && !m_Display.blue && !m_Display.alpha)
g = b = r;
if(!m_Display.red && m_Display.green && !m_Display.blue && !m_Display.alpha)
r = b = g;
if(!m_Display.red && !m_Display.green && m_Display.blue && !m_Display.alpha)
g = r = b;
if(!m_Display.red && !m_Display.green && !m_Display.blue && m_Display.alpha)
g = b = r = val.col.floatValue[3];
r = qBound(0.0f, (r - m_Display.rangeMin) / rangesize, 1.0f);
g = qBound(0.0f, (g - m_Display.rangeMin) / rangesize, 1.0f);
b = qBound(0.0f, (b - m_Display.rangeMin) / rangesize, 1.0f);
if(m_IsDepth)
r = g = b = qBound(0.0f, (val.depth - m_Display.rangeMin) / rangesize, 1.0f);
// Convert from linear color to sRGB
{
r = (r <= 0.0031308f) ? r * 12.92f : 1.055f * (float)powf(r, 1.0f / 2.4f) - 0.055f;
g = (g <= 0.0031308f) ? g * 12.92f : 1.055f * (float)powf(g, 1.0f / 2.4f) - 0.055f;
b = (b <= 0.0031308f) ? b * 12.92f : 1.055f * (float)powf(b, 1.0f / 2.4f) - 0.055f;
}
// Round to nearest value in [0,255]
return QBrush(QColor::fromRgb((int)(255.0f * r + 0.5f), (int)(255.0f * g + 0.5f),
(int)(255.0f * b + 0.5f)));
}
QString modString(const ModificationValue &val, int forceComps = 0) const
{
QString s;
int numComps = (int)(m_Tex->format.compCount);
if(forceComps > 0)
numComps = forceComps;
static const QString colourLetterPrefix[] = {lit("R: "), lit("G: "), lit("B: "), lit("A: ")};
if(!m_IsDepth)
{
if(m_IsUint)
{
for(int i = 0; i < numComps; i++)
s += colourLetterPrefix[i] + Formatter::Format(val.col.uintValue[i]) + lit("\n");
}
else if(m_IsSint)
{
for(int i = 0; i < numComps; i++)
s += colourLetterPrefix[i] + Formatter::Format(val.col.intValue[i]) + lit("\n");
}
else
{
for(int i = 0; i < numComps; i++)
s += colourLetterPrefix[i] + Formatter::Format(val.col.floatValue[i]) + lit("\n");
}
}
if(val.depth >= 0.0f)
s += lit("\nD: ") + Formatter::Format(val.depth);
else if(val.depth < -1.5f)
s += lit("\nD: ?");
else
s += lit("\nD: -");
if(val.stencil >= 0)
s += lit("\nS: 0x") + Formatter::Format(uint8_t(val.stencil & 0xff), true);
else if(val.stencil == -2)
s += lit("\nS: ?");
else
s += lit("\nS: -");
return s;
}
QString failureString(const PixelModification &mod) const
{
QString s;
if(mod.sampleMasked)
s += tr("\nMasked by SampleMask");
if(mod.backfaceCulled)
s += tr("\nBackface culled");
if(mod.depthClipped)
s += tr("\nDepth Clipped");
if(mod.scissorClipped)
s += tr("\nScissor Clipped");
if(mod.shaderDiscarded)
s += tr("\nShader executed a discard");
if(mod.depthTestFailed)
s += tr("\nDepth test failed");
if(mod.stencilTestFailed)
s += tr("\nStencil test failed");
if(mod.predicationSkipped)
s += tr("\nPredicated rendering skipped");
return s;
}
};
PixelHistoryView::PixelHistoryView(ICaptureContext &ctx, ResourceId id, QPoint point,
const TextureDisplay &display, QWidget *parent)
: QFrame(parent), ui(new Ui::PixelHistoryView), m_Ctx(ctx)
{
ui->setupUi(this);
ui->events->setFont(Formatter::PreferredFont());
m_Pixel = point;
m_Display = display;
m_ID = id;
updateWindowTitle();
QString channelStr;
if(display.red)
channelStr += lit("R");
if(display.green)
channelStr += lit("G");
if(display.blue)
channelStr += lit("B");
if(channelStr.length() > 1)
channelStr += tr(" channels");
else
channelStr += tr(" channel");
if(!display.red && !display.green && !display.blue && display.alpha)
channelStr = lit("Alpha");
QString text;
text = tr("Preview colours displayed in visible range %1 - %2 with %3 visible.\n\n")
.arg(Formatter::Format(display.rangeMin))
.arg(Formatter::Format(display.rangeMax))
.arg(channelStr);
text +=
tr("Double click to jump to an event.\n"
"Right click to debug an event, or hide failed events.");
ui->label->setText(text);
ui->eventsHidden->setVisible(false);
m_Model = new PixelHistoryItemModel(ctx, id, display, palette(), this);
ui->events->setModel(m_Model);
ui->events->hideBranches();
ui->events->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->events->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
ui->events->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
ui->events->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
ui->events->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents);
m_Ctx.AddCaptureViewer(this);
}
void PixelHistoryView::updateWindowTitle()
{
QString title = tr("Pixel History on %1 for (%2, %3)")
.arg(m_Ctx.GetResourceName(m_ID))
.arg(m_Pixel.x())
.arg(m_Pixel.y());
TextureDescription *tex = m_Ctx.GetTexture(m_ID);
if(tex->msSamp > 1)
title += tr(" @ Sample %1").arg(m_Display.subresource.sample);
setWindowTitle(title);
}
PixelHistoryView::~PixelHistoryView()
{
disableTimelineHighlight();
ui->events->setModel(NULL);
m_Ctx.RemoveCaptureViewer(this);
delete ui;
}
void PixelHistoryView::enableTimelineHighlight()
{
if(m_Ctx.HasTimelineBar())
m_Ctx.GetTimelineBar()->HighlightHistory(m_Model->texID(), m_Model->modifications().toList());
}
void PixelHistoryView::disableTimelineHighlight()
{
if(m_Ctx.HasTimelineBar())
m_Ctx.GetTimelineBar()->HighlightHistory(ResourceId(), {});
}
void PixelHistoryView::enterEvent(QEvent *event)
{
enableTimelineHighlight();
}
void PixelHistoryView::leaveEvent(QEvent *event)
{
disableTimelineHighlight();
}
void PixelHistoryView::OnCaptureLoaded()
{
}
void PixelHistoryView::OnCaptureClosed()
{
ToolWindowManager::closeToolWindow(this);
}
void PixelHistoryView::OnEventChanged(uint32_t eventId)
{
updateWindowTitle();
}
void PixelHistoryView::SetHistory(const rdcarray<PixelModification> &history)
{
m_Model->setHistory(history);
enableTimelineHighlight();
}
void PixelHistoryView::startDebug(EventTag tag)
{
m_Ctx.SetEventID({this}, tag.eventId, tag.eventId);
const ShaderReflection *shaderDetails =
m_Ctx.CurPipelineState().GetShaderReflection(ShaderStage::Pixel);
if(!m_Ctx.APIProps().shaderDebugging)
{
RDDialog::critical(this, tr("Can't debug pixel"),
tr("This API does not support shader debugging"));
return;
}
else if(!shaderDetails)
{
RDDialog::critical(this, tr("Can't debug pixel"),
tr("No pixel shader bound at event %1").arg(tag.eventId));
return;
}
else if(!shaderDetails->debugInfo.debuggable)
{
RDDialog::critical(
this, tr("Can't debug pixel"),
tr("This shader doesn't support debugging: %1").arg(shaderDetails->debugInfo.debugStatus));
return;
}
bool done = false;
ShaderDebugTrace *trace = NULL;
m_Ctx.Replay().AsyncInvoke([this, &trace, &done, tag](IReplayController *r) {
trace = r->DebugPixel((uint32_t)m_Pixel.x(), (uint32_t)m_Pixel.y(),
m_Display.subresource.sample, tag.primitive);
if(trace->debugger == NULL)
{
r->FreeTrace(trace);
trace = NULL;
}
done = true;
});
QString debugContext =
QFormatStr("Pixel %1,%2 @ %3").arg(m_Pixel.x()).arg(m_Pixel.y()).arg(tag.eventId);
// 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(ShaderStage::Pixel);
ResourceId pipeline = m_Ctx.CurPipelineState().GetGraphicsPipelineObject();
// viewer takes ownership of the trace
IShaderViewer *s = m_Ctx.DebugShader(&bindMapping, shaderDetails, pipeline, trace, debugContext);
m_Ctx.AddDockWindow(s->Widget(), DockReference::MainToolArea, NULL);
}
void PixelHistoryView::jumpToPrimitive(EventTag tag)
{
m_Ctx.SetEventID({this}, tag.eventId, tag.eventId);
m_Ctx.ShowMeshPreview();
IBufferViewer *viewer = m_Ctx.GetMeshPreview();
const DrawcallDescription *draw = m_Ctx.CurDrawcall();
if(draw)
{
uint32_t vertIdx = RENDERDOC_VertexOffset(draw->topology, tag.primitive);
if(vertIdx != ~0U)
viewer->ScrollToRow(vertIdx);
}
}
void PixelHistoryView::on_events_customContextMenuRequested(const QPoint &pos)
{
QModelIndex index = ui->events->indexAt(pos);
QMenu contextMenu(this);
QAction hideFailed(tr("&Show failed events"), this);
hideFailed.setCheckable(true);
hideFailed.setChecked(m_ShowFailures);
contextMenu.addAction(&hideFailed);
QObject::connect(&hideFailed, &QAction::toggled, [this](bool checked) {
m_Model->setShowFailures(m_ShowFailures = checked);
ui->eventsHidden->setVisible(!m_ShowFailures);
});
if(!index.isValid())
{
RDDialog::show(&contextMenu, ui->events->viewport()->mapToGlobal(pos));
return;
}
EventTag tag = m_Model->data(index, Qt::UserRole).value<EventTag>();
if(tag.eventId == 0)
{
RDDialog::show(&contextMenu, ui->events->viewport()->mapToGlobal(pos));
return;
}
QAction jumpAction(tr("&Go to primitive %1 at Event %2").arg(tag.primitive).arg(tag.eventId), this);
QString debugText;
if(tag.primitive == ~0U)
{
debugText =
tr("&Debug Pixel (%1, %2) at Event %3").arg(m_Pixel.x()).arg(m_Pixel.y()).arg(tag.eventId);
}
else
{
debugText = tr("&Debug Pixel (%1, %2) primitive %3 at Event %4")
.arg(m_Pixel.x())
.arg(m_Pixel.y())
.arg(tag.eventId)
.arg(tag.primitive);
contextMenu.addAction(&jumpAction);
}
QAction debugAction(debugText, this);
contextMenu.addAction(&debugAction);
if(!m_Ctx.APIProps().shaderDebugging)
{
debugAction.setToolTip(tr("This API does not support shader debugging"));
debugAction.setEnabled(false);
}
// can't check if the shader supports debugging here because we don't have its details.
QObject::connect(&jumpAction, &QAction::triggered, [this, tag]() { jumpToPrimitive(tag); });
QObject::connect(&debugAction, &QAction::triggered, [this, tag]() { startDebug(tag); });
RDDialog::show(&contextMenu, ui->events->viewport()->mapToGlobal(pos));
}
void PixelHistoryView::on_events_doubleClicked(const QModelIndex &index)
{
EventTag tag = m_Model->data(index, Qt::UserRole).value<EventTag>();
if(tag.eventId > 0)
m_Ctx.SetEventID({this}, tag.eventId, tag.eventId);
}