mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 01:20:42 +00:00
882 lines
25 KiB
C++
882 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.Passed())
|
|
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).Passed())
|
|
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).Passed())
|
|
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
|
|
{
|
|
if(!val.IsValid())
|
|
return QBrush();
|
|
|
|
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;
|
|
|
|
if(!val.IsValid())
|
|
return tr("Unavailable");
|
|
|
|
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);
|
|
}
|