diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.cpp b/qrenderdoc/Widgets/Extended/RDTreeView.cpp new file mode 100644 index 000000000..6adcf70d4 --- /dev/null +++ b/qrenderdoc/Widgets/Extended/RDTreeView.cpp @@ -0,0 +1,64 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 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 "RDTreeView.h" +#include + +RDTreeView::RDTreeView(QWidget *parent) : QTreeView(NULL) +{ +} + +void RDTreeView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const +{ + if(m_DrawBranches) + { + QTreeView::drawBranches(painter, rect, index); + } + else + { + // draw only the expand item, not the branches + QRect primitive(0, rect.top(), indentation(), rect.height()); + + // if root isn't decorated, skip + if(!rootIsDecorated() && !index.parent().isValid()) + return; + + // if no children, nothing to render + if(model()->rowCount(index) == 0) + return; + + QStyleOptionViewItem opt = viewOptions(); + + opt.rect = primitive; + + // unfortunately QStyle::State_Children doesn't render ONLY the + // open-toggle-button, but the vertical line upwards to a previous sibling. + // For consistency, draw one downwards too. + opt.state = QStyle::State_Children | QStyle::State_Sibling; + if(isExpanded(index)) + opt.state |= QStyle::State_Open; + + style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); + } +} diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.h b/qrenderdoc/Widgets/Extended/RDTreeView.h new file mode 100644 index 000000000..307793ffe --- /dev/null +++ b/qrenderdoc/Widgets/Extended/RDTreeView.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 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 + +class RDTreeView : public QTreeView +{ + Q_OBJECT +public: + explicit RDTreeView(QWidget *parent = 0); + + void setDrawBranches(bool draw) { m_DrawBranches = draw; } +private: + void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; + + bool m_DrawBranches = true; +}; diff --git a/qrenderdoc/Windows/BufferViewer.h b/qrenderdoc/Windows/BufferViewer.h index 5d02a7e84..fa3abb079 100644 --- a/qrenderdoc/Windows/BufferViewer.h +++ b/qrenderdoc/Windows/BufferViewer.h @@ -54,7 +54,15 @@ public: void OnSelectedEventChanged(uint32_t eventID) {} void OnEventChanged(uint32_t eventID); - void RT_FetchMeshData(IReplayRenderer *r); + void ScrollToRow(int row, MeshDataStage stage = eMeshDataStage_VSIn) + { + if(stage == eMeshDataStage_VSOut) + ScrollToRow(m_ModelVSOut, row); + else if(stage == eMeshDataStage_GSOut) + ScrollToRow(m_ModelGSOut, row); + else + ScrollToRow(m_ModelVSIn, row); + } private slots: // automatic slots @@ -94,6 +102,7 @@ private: IReplayOutput *m_Output; void RT_UpdateAndDisplay(IReplayRenderer *); + void RT_FetchMeshData(IReplayRenderer *r); MeshDisplay m_Config; diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp new file mode 100644 index 000000000..9a4c2fbea --- /dev/null +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -0,0 +1,724 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 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 +#include +#include "3rdparty/toolwindowmanager/ToolWindowManager.h" +#include "Windows/BufferViewer.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(CaptureContext &ctx, ResourceId tex, const TextureDisplay &display, + QObject *parent) + : QAbstractItemModel(parent), m_Ctx(ctx) + { + m_Tex = m_Ctx.GetTexture(tex); + m_Display = display; + + FormatComponentType compType = m_Tex->format.compType; + + if(compType == eCompType_None) + compType = display.typeHint; + + m_IsUint = (compType == eCompType_UInt); + m_IsSint = (compType == eCompType_SInt); + m_IsFloat = (!m_IsUint && !m_IsSint); + + if(compType == eCompType_Depth) + m_IsDepth = true; + + if(m_Tex->format.special) + { + switch(m_Tex->format.specialFormat) + { + case eSpecial_D16S8: + case eSpecial_D24S8: + case eSpecial_D32S8: + case eSpecial_S8: m_IsDepth = true; break; + default: break; + } + } + } + + void setHistory(const rdctype::array &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 &mods = getMods(parent); + const FetchDrawcall *draw = m_Ctx.GetDrawcall(mods.front().eventID); + + if(draw->flags & eDraw_Clear) + 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 "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 "Loading..."; + + return QVariant(); + } + + if(role == Qt::DisplayRole) + { + // main text + if(col == 0) + { + if(isEvent(index)) + { + const QList &mods = getMods(index); + const FetchDrawcall *drawcall = m_Ctx.GetDrawcall(mods.front().eventID); + if(!drawcall) + return QVariant(); + + QString ret = ""; + QList drawstack; + const FetchDrawcall *parent = m_Ctx.GetDrawcall(drawcall->parent); + while(parent) + { + drawstack.push_back(parent); + parent = m_Ctx.GetDrawcall(parent->parent); + } + + if(!drawstack.isEmpty()) + { + ret += "> " + ToQStr(drawstack.back()->name); + + if(drawstack.count() > 3) + ret += " ..."; + + ret += "\n"; + + if(drawstack.count() > 2) + ret += "> " + ToQStr(drawstack[1]->name) + "\n"; + if(drawstack.count() > 1) + ret += "> " + ToQStr(drawstack[0]->name) + "\n"; + + ret += "\n"; + } + + bool passed = true; + bool uavnowrite = false; + + if(mods.front().uavWrite) + { + ret += tr("EID %1\n%2\nBound as UAV or copy - potential modification") + .arg(mods.front().eventID) + .arg(ToQStr(drawcall->name)); + + if(memcmp(mods[0].preMod.col.value_u, mods[0].postMod.col.value_u, + 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 ? "" : failureString(mods[0]); + + ret += tr("EID %1\n%2%3\n%4 Fragments touching pixel\n") + .arg(mods.front().eventID) + .arg(ToQStr(drawcall->name)) + .arg(failure) + .arg(mods.count()); + } + + return ret; + } + else + { + const PixelModification &mod = getMod(index); + + if(mod.uavWrite) + { + QString ret = tr("Potential UAV/Copy write"); + + if(mod.preMod.col.value_u[0] == mod.postMod.col.value_u[0] && + mod.preMod.col.value_u[1] == mod.postMod.col.value_u[1] && + mod.preMod.col.value_u[2] == mod.postMod.col.value_u[2] && + mod.preMod.col.value_u[3] == mod.postMod.col.value_u[3]) + { + ret += tr("\nNo change in tex value"); + } + + return ret; + } + else + { + QString ret = tr("Primitive %1\n").arg(mod.primitiveID); + + 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.uavWrite) + return tr("Tex Before\n\n") + modString(mod.preMod); + return tr("Shader Out\n\n") + modString(mod.shaderOut); + } + } + + // 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).shaderOut); + } + } + + if(role == Qt::BackgroundRole && (m_IsDepth || m_IsFloat)) + { + // pre mod color + if(col == 2) + { + if(isEvent(index)) + return backgroundBrush(getMods(index).first().preMod); + else + return backgroundBrush(getMod(index).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 &mods = getMods(index); + + bool passed = false; + for(const PixelModification &m : mods) + passed |= m.passed(); + + if(mods[0].uavWrite && + memcmp(mods[0].preMod.col.value_u, mods[0].postMod.col.value_u, 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)); + } + } + + 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.uavWrite) + tag.primitive = mod.primitiveID; + } + + return QVariant::fromValue(tag); + } + } + + return QVariant(); + } + +private: + CaptureContext &m_Ctx; + + const FetchTexture *m_Tex; + TextureDisplay m_Display; + bool m_IsDepth = false, m_IsUint = false, m_IsSint = false, m_IsFloat = true; + + bool m_Loading = true; + QVector> m_History; + QVector m_ModList; + + // 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 &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(row > modRowMask) + qCritical() << "Packing failure - more than 255 modifications in one event"; + + return ((parent.internalId() & eventRowMask) << modRowBits) | (row & modRowMask); + } + } + + QBrush backgroundBrush(const ModificationValue &val) const + { + float rangesize = (m_Display.rangemax - m_Display.rangemin); + + float r = val.col.value_f[0]; + float g = val.col.value_f[1]; + float b = val.col.value_f[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.value_f[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); + + { + r = (float)powf(r, 1.0f / 2.2f); + g = (float)powf(g, 1.0f / 2.2f); + b = (float)powf(b, 1.0f / 2.2f); + } + + return QBrush(QColor::fromRgb((int)(255.0f * r), (int)(255.0f * g), (int)(255.0f * b))); + } + + QString modString(const ModificationValue &val) const + { + QString s = ""; + + int numComps = (int)(m_Tex->format.compCount); + + static const QString colourLetterPrefix[] = {"R: ", "G: ", "B: ", "A: "}; + + if(!m_IsDepth) + { + if(m_IsUint) + { + for(int i = 0; i < numComps; i++) + s += colourLetterPrefix[i] + Formatter::Format(val.col.value_u[i]) + "\n"; + } + else if(m_IsSint) + { + for(int i = 0; i < numComps; i++) + s += colourLetterPrefix[i] + Formatter::Format(val.col.value_i[i]) + "\n"; + } + else + { + for(int i = 0; i < numComps; i++) + s += colourLetterPrefix[i] + Formatter::Format(val.col.value_f[i]) + "\n"; + } + } + + if(val.depth >= 0.0f) + s += "\nD: " + Formatter::Format(val.depth); + else if(val.depth < -1.5f) + s += "\nD: ?"; + else + s += "\nD: -"; + + if(val.stencil >= 0) + s += "\nS: 0x" + QString("%1").arg(val.stencil, 2, 16, QChar('0')).toUpper(); + else if(val.stencil == -2) + s += "\nS: ?"; + else + s += "\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"); + + return s; + } +}; + +PixelHistoryView::PixelHistoryView(CaptureContext &ctx, ResourceId id, QPoint point, + const TextureDisplay &display, QWidget *parent) + : QFrame(parent), ui(new Ui::PixelHistoryView), m_Ctx(ctx) +{ + ui->setupUi(this); + + m_Pixel = point; + m_Display = display; + + FetchTexture *tex = m_Ctx.GetTexture(id); + + QString title = + tr("Pixel History on %1 for (%2, %3)").arg(ToQStr(tex->name)).arg(point.x()).arg(point.y()); + if(tex->msSamp > 1) + title += tr(" @ Sample %1").arg(display.sampleIdx); + setWindowTitle(title); + + QString channelStr = ""; + if(display.Red) + channelStr += "R"; + if(display.Green) + channelStr += "G"; + if(display.Blue) + channelStr += "B"; + + if(channelStr.length() > 1) + channelStr += tr(" channels"); + else + channelStr += tr(" channel"); + + if(!display.Red && !display.Green && !display.Blue && display.Alpha) + channelStr = "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 += + "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, this); + ui->events->setModel(m_Model); + + ui->events->setDrawBranches(false); + + 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); +} + +PixelHistoryView::~PixelHistoryView() +{ + ui->events->setModel(NULL); + delete ui; +} + +void PixelHistoryView::OnLogfileLoaded() +{ +} + +void PixelHistoryView::OnLogfileClosed() +{ + ToolWindowManager::closeToolWindow(this); +} + +void PixelHistoryView::setHistory(const rdctype::array &history) +{ + m_Model->setHistory(history); +} + +void PixelHistoryView::startDebug(EventTag tag) +{ + m_Ctx.SetEventID({this}, tag.eventID, tag.eventID); + // TODO Shader debugging +} + +void PixelHistoryView::jumpToPrimitive(EventTag tag) +{ + m_Ctx.SetEventID({this}, tag.eventID, tag.eventID); + m_Ctx.showMeshPreview(); + + BufferViewer *viewer = m_Ctx.meshPreview(); + + const FetchDrawcall *draw = m_Ctx.CurDrawcall(); + + if(draw) + { + uint32_t vertIdx = Topology_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(); + 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); + + 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(); + if(tag.eventID > 0) + m_Ctx.SetEventID({this}, tag.eventID, tag.eventID); +} + +// TODO TimelineBar diff --git a/qrenderdoc/Windows/PixelHistoryView.h b/qrenderdoc/Windows/PixelHistoryView.h new file mode 100644 index 000000000..8e4060f46 --- /dev/null +++ b/qrenderdoc/Windows/PixelHistoryView.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 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 +#include "Code/CaptureContext.h" + +namespace Ui +{ +class PixelHistoryView; +} + +class CaptureContext; +class PixelHistoryItemModel; +struct EventTag; + +class PixelHistoryView : public QFrame, public ILogViewerForm +{ + Q_OBJECT + +public: + explicit PixelHistoryView(CaptureContext &ctx, ResourceId id, QPoint point, + const TextureDisplay &display, QWidget *parent = 0); + ~PixelHistoryView(); + + void OnLogfileLoaded(); + void OnLogfileClosed(); + void OnSelectedEventChanged(uint32_t eventID) {} + void OnEventChanged(uint32_t eventID) {} + void setHistory(const rdctype::array &history); + +private slots: + // automatic slots + void on_events_customContextMenuRequested(const QPoint &pos); + void on_events_doubleClicked(const QModelIndex &index); + +private: + Ui::PixelHistoryView *ui; + CaptureContext &m_Ctx; + + TextureDisplay m_Display; + QPoint m_Pixel; + PixelHistoryItemModel *m_Model; + bool m_ShowFailures = true; + void startDebug(EventTag tag); + void jumpToPrimitive(EventTag tag); +}; diff --git a/qrenderdoc/Windows/PixelHistoryView.ui b/qrenderdoc/Windows/PixelHistoryView.ui new file mode 100644 index 000000000..612ca90b7 --- /dev/null +++ b/qrenderdoc/Windows/PixelHistoryView.ui @@ -0,0 +1,75 @@ + + + PixelHistoryView + + + + 0 + 0 + 662 + 569 + + + + Pixel History + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + ***code overwritten preview*** Preview colours displayed in visible range {min} - {max} with {red, blue, green} channels. + +Right click to debug an event, hide failed events, or jump to the modification's primitive in the mesh view. + + + true + + + + + + + color: #f00; + + + Failed events are currently hidden + + + + + + + Qt::PreventContextMenu + + + 16 + + + false + + + + + + + + RDTreeView + QTreeView +
Widgets/Extended/RDTreeView.h
+
+
+ + +
diff --git a/qrenderdoc/Windows/TextureViewer.cpp b/qrenderdoc/Windows/TextureViewer.cpp index 76d6749c4..e8ff192d6 100644 --- a/qrenderdoc/Windows/TextureViewer.cpp +++ b/qrenderdoc/Windows/TextureViewer.cpp @@ -42,6 +42,7 @@ #include "Widgets/TextureGoto.h" #include "BufferViewer.h" #include "MainWindow.h" +#include "PixelHistoryView.h" #include "ui_TextureViewer.h" float area(const QSizeF &s) @@ -3264,6 +3265,45 @@ void TextureViewer::on_saveTex_clicked() } } +void TextureViewer::on_pixelHistory_clicked() +{ + FetchTexture *texptr = GetCurrentTexture(); + + if(!texptr || !m_Output) + return; + + int x = m_PickedPoint.x() >> (int)m_TexDisplay.mip; + int y = m_PickedPoint.y() >> (int)m_TexDisplay.mip; + + PixelHistoryView *hist = + new PixelHistoryView(m_Ctx, texptr->ID, QPoint(x, y), m_TexDisplay, m_Ctx.mainWindow()); + + m_Ctx.setupDockWindow(hist); + + ToolWindowManager *manager = ToolWindowManager::managerOf(this); + + ToolWindowManager::AreaReference ref(ToolWindowManager::RightOf, manager->areaOf(this), 0.2f); + manager->addToolWindow(hist, ref); + + // add a short delay so that controls repainting after a new panel appears can get at the + // render thread before we insert the long blocking pixel history task + LambdaThread *thread = new LambdaThread([this, texptr, x, y, hist]() { + QThread::msleep(150); + m_Ctx.Renderer().AsyncInvoke([this, texptr, x, y, hist](IReplayRenderer *r) { + rdctype::array *history = new rdctype::array(); + r->PixelHistory(texptr->ID, (uint32_t)x, (int32_t)y, m_TexDisplay.sliceFace, m_TexDisplay.mip, + m_TexDisplay.sampleIdx, m_TexDisplay.typeHint, history); + + GUIInvoke::call([hist, history] { + hist->setHistory(*history); + delete history; + }); + }); + }); + thread->selfDelete(true); + thread->start(); +} + void TextureViewer::on_texListShow_clicked() { if(ui->textureListFrame->isVisible()) diff --git a/qrenderdoc/Windows/TextureViewer.h b/qrenderdoc/Windows/TextureViewer.h index d5d007fcd..b8f76d3f0 100644 --- a/qrenderdoc/Windows/TextureViewer.h +++ b/qrenderdoc/Windows/TextureViewer.h @@ -159,6 +159,7 @@ private slots: void on_viewTexBuffer_clicked(); void on_texListShow_clicked(); void on_saveTex_clicked(); + void on_pixelHistory_clicked(); void on_cancelTextureListFilter_clicked(); void on_textureListFilter_editTextChanged(const QString &text); diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index a3d555021..9565d5c51 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -142,7 +142,8 @@ SOURCES += Code/qrenderdoc.cpp \ Widgets/Extended/RDTableWidget.cpp \ Windows/Dialogs/SuggestRemoteDialog.cpp \ Windows/Dialogs/VirtualFileDialog.cpp \ - Windows/Dialogs/RemoteManager.cpp + Windows/Dialogs/RemoteManager.cpp \ + Windows/PixelHistoryView.cpp HEADERS += Code/CaptureContext.h \ Code/qprocessinfo.h \ @@ -187,7 +188,8 @@ HEADERS += Code/CaptureContext.h \ Widgets/Extended/RDTableWidget.h \ Windows/Dialogs/SuggestRemoteDialog.h \ Windows/Dialogs/VirtualFileDialog.h \ - Windows/Dialogs/RemoteManager.h + Windows/Dialogs/RemoteManager.h \ + Windows/PixelHistoryView.h FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/MainWindow.ui \ @@ -214,7 +216,8 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/Dialogs/OrderedListEditor.ui \ Windows/Dialogs/SuggestRemoteDialog.ui \ Windows/Dialogs/VirtualFileDialog.ui \ - Windows/Dialogs/RemoteManager.ui + Windows/Dialogs/RemoteManager.ui \ + Windows/PixelHistoryView.ui RESOURCES += Resources/resources.qrc diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 8f68d94d6..e977517be 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -669,6 +669,7 @@ + @@ -685,6 +686,7 @@ + @@ -701,6 +703,7 @@ + @@ -737,6 +740,7 @@ + @@ -872,6 +876,7 @@ + @@ -947,6 +952,12 @@ MOC %(Filename).h generated\moc_%(Filename).cpp + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) + $(ProjectDir)3rdparty\qt\$(Platform)\bin\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$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(ProjectDir)generated\moc_%(Filename).cpp + MOC %(Filename).h + generated\moc_%(Filename).cpp + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) $(ProjectDir)3rdparty\qt\$(Platform)\bin\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$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(ProjectDir)generated\moc_%(Filename).cpp @@ -1097,6 +1108,12 @@ MOC %(Filename).h generated\moc_%(Filename).cpp + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) + $(ProjectDir)3rdparty\qt\$(Platform)\bin\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$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(ProjectDir)generated\moc_%(Filename).cpp + MOC %(Filename).h + generated\moc_%(Filename).cpp + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) $(ProjectDir)3rdparty\qt\$(Platform)\bin\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$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015 -I$(ProjectDir)3rdparty\qt\$(Platform)\include -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtWidgets -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtGui -I$(ProjectDir)3rdparty\qt\$(Platform)\include\QtCore %(Fullpath) -o $(ProjectDir)generated\moc_%(Filename).cpp @@ -1257,6 +1274,12 @@ UIC %(Filename).ui generated\ui_%(Filename).h + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) + $(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe %(Fullpath) -o $(ProjectDir)generated\ui_%(Filename).h + UIC %(Filename).ui + generated\ui_%(Filename).h + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) $(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe %(Fullpath) -o $(ProjectDir)generated\ui_%(Filename).h diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 6604ba132..a571b9697 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -510,6 +510,18 @@ Windows\Dialogs + + Generated Files + + + Windows + + + Generated Files + + + Widgets\Extended + @@ -782,6 +794,9 @@ Generated Files + + Generated Files + @@ -1195,5 +1210,14 @@ Windows\Dialogs + + Windows + + + Windows + + + Widgets\Extended + \ No newline at end of file diff --git a/renderdoc/api/replay/data_types.h b/renderdoc/api/replay/data_types.h index 06a8ad57f..4d5b2462e 100644 --- a/renderdoc/api/replay/data_types.h +++ b/renderdoc/api/replay/data_types.h @@ -515,4 +515,10 @@ struct PixelModification bool32 shaderDiscarded; bool32 depthTestFailed; bool32 stencilTestFailed; + + bool passed() const + { + return !sampleMasked && !backfaceCulled && !depthClipped && !viewClipped && !scissorClipped && + !shaderDiscarded && !depthTestFailed && !stencilTestFailed; + } };