/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2016-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 "BufferViewer.h" #include #include #include #include #include #include #include #include #include "Code/Resources.h" #include "ui_BufferViewer.h" class CameraWrapper { public: virtual ~CameraWrapper() {} virtual bool Update(QRect winSize) = 0; virtual Camera *camera() = 0; virtual void MouseWheel(QWheelEvent *e) = 0; virtual void MouseClick(QMouseEvent *e) { m_DragStartPos = e->pos(); } virtual void MouseMove(QMouseEvent *e) { if(e->buttons() & Qt::LeftButton) { if(m_DragStartPos.x() < 0) { m_DragStartPos = e->pos(); } m_DragStartPos = e->pos(); } else { m_DragStartPos = QPoint(-1, -1); } } virtual void KeyUp(QKeyEvent *e) { if(e->key() == Qt::Key_A || e->key() == Qt::Key_D) setMove(Direction::Horiz, 0); if(e->key() == Qt::Key_Q || e->key() == Qt::Key_E) setMove(Direction::Vert, 0); if(e->key() == Qt::Key_W || e->key() == Qt::Key_S) setMove(Direction::Fwd, 0); if(e->modifiers() && Qt::ShiftModifier) m_CurrentSpeed = 3.0f; else m_CurrentSpeed = 1.0f; } virtual void KeyDown(QKeyEvent *e) { if(e->key() == Qt::Key_W) setMove(Direction::Fwd, 1); if(e->key() == Qt::Key_S) setMove(Direction::Fwd, -1); if(e->key() == Qt::Key_Q) setMove(Direction::Vert, 1); if(e->key() == Qt::Key_E) setMove(Direction::Vert, -1); if(e->key() == Qt::Key_D) setMove(Direction::Horiz, 1); if(e->key() == Qt::Key_A) setMove(Direction::Horiz, -1); if(e->modifiers() && Qt::ShiftModifier) m_CurrentSpeed = 3.0f; else m_CurrentSpeed = 1.0f; } float SpeedMultiplier = 0.05f; protected: enum class Direction { Fwd, Horiz, Vert, Num }; int move(Direction dir) { return m_CurrentMove[(int)dir]; } float currentSpeed() { return m_CurrentSpeed * SpeedMultiplier; } QPoint dragStartPos() { return m_DragStartPos; } private: float m_CurrentSpeed = 1.0f; int m_CurrentMove[(int)Direction::Num] = {0, 0, 0}; void setMove(Direction dir, int val) { m_CurrentMove[(int)dir] = val; } QPoint m_DragStartPos = QPoint(-1, -1); }; class ArcballWrapper : public CameraWrapper { public: ArcballWrapper() { m_Cam = Camera_InitArcball(); } virtual ~ArcballWrapper() { Camera_Shutdown(m_Cam); } Camera *camera() override { return m_Cam; } void Reset(FloatVector pos, float dist) { Camera_ResetArcball(m_Cam); setLookAtPos(pos); SetDistance(dist); } void SetDistance(float dist) { m_Distance = qAbs(dist); Camera_SetArcballDistance(m_Cam, m_Distance); } bool Update(QRect size) override { m_WinSize = size; return false; } void MouseWheel(QWheelEvent *e) override { float mod = (1.0f - e->delta() / 2500.0f); SetDistance(qMax(1e-6f, m_Distance * mod)); } void MouseMove(QMouseEvent *e) override { if(dragStartPos().x() > 0) { if(e->buttons() == Qt::MiddleButton || (e->buttons() == Qt::LeftButton && e->modifiers() & Qt::AltModifier)) { float xdelta = (float)(e->pos().x() - dragStartPos().x()) / 300.0f; float ydelta = (float)(e->pos().y() - dragStartPos().y()) / 300.0f; xdelta *= qMax(1.0f, m_Distance); ydelta *= qMax(1.0f, m_Distance); FloatVector pos, fwd, right, up; Camera_GetBasis(m_Cam, &pos, &fwd, &right, &up); m_LookAt.x -= right.x * xdelta; m_LookAt.y -= right.y * xdelta; m_LookAt.z -= right.z * xdelta; m_LookAt.x += up.x * ydelta; m_LookAt.y += up.y * ydelta; m_LookAt.z += up.z * ydelta; Camera_SetPosition(m_Cam, m_LookAt.x, m_LookAt.y, m_LookAt.z); } else if(e->buttons() == Qt::LeftButton) { RotateArcball(dragStartPos(), e->pos()); } } CameraWrapper::MouseMove(e); } FloatVector lookAtPos() { return m_LookAt; } void setLookAtPos(const FloatVector &v) { m_LookAt = v; Camera_SetPosition(m_Cam, v.x, v.y, v.z); } private: Camera *m_Cam; QRect m_WinSize; float m_Distance = 10.0f; FloatVector m_LookAt; void RotateArcball(QPoint from, QPoint to) { float ax = ((float)from.x() / (float)m_WinSize.width()) * 2.0f - 1.0f; float ay = ((float)from.y() / (float)m_WinSize.height()) * 2.0f - 1.0f; float bx = ((float)to.x() / (float)m_WinSize.width()) * 2.0f - 1.0f; float by = ((float)to.y() / (float)m_WinSize.height()) * 2.0f - 1.0f; // this isn't a 'true arcball' but it handles extreme aspect ratios // better. We basically 'centre' around the from point always being // 0,0 (straight out of the screen) as if you're always dragging // the arcball from the middle, and just use the relative movement int minDimension = qMin(m_WinSize.width(), m_WinSize.height()); ax = ay = 0; bx = ((float)(to.x() - from.x()) / (float)minDimension) * 2.0f; by = ((float)(to.y() - from.y()) / (float)minDimension) * 2.0f; ay = -ay; by = -by; Camera_RotateArcball(m_Cam, ax, ay, bx, by); } }; class FlycamWrapper : public CameraWrapper { public: FlycamWrapper() { m_Cam = Camera_InitFPSLook(); } virtual ~FlycamWrapper() { Camera_Shutdown(m_Cam); } Camera *camera() override { return m_Cam; } void Reset(FloatVector pos) { m_Position = pos; m_Rotation = FloatVector(); Camera_SetPosition(m_Cam, m_Position.x, m_Position.y, m_Position.z); Camera_SetFPSRotation(m_Cam, m_Rotation.x, m_Rotation.y, m_Rotation.z); } bool Update(QRect size) override { FloatVector pos, fwd, right, up; Camera_GetBasis(m_Cam, &pos, &fwd, &right, &up); float speed = currentSpeed(); int horizMove = move(CameraWrapper::Direction::Horiz); if(horizMove) { m_Position.x += right.x * speed * (float)horizMove; m_Position.y += right.y * speed * (float)horizMove; m_Position.z += right.z * speed * (float)horizMove; } int vertMove = move(CameraWrapper::Direction::Vert); if(vertMove) { // this makes less intuitive sense, instead go 'absolute' up // m_Position.x += up.x * speed * (float)vertMove; // m_Position.y += up.y * speed * (float)vertMove; // m_Position.z += up.z * speed * (float)vertMove; m_Position.y += speed * (float)vertMove; } int fwdMove = move(CameraWrapper::Direction::Fwd); if(fwdMove) { m_Position.x += fwd.x * speed * (float)fwdMove; m_Position.y += fwd.y * speed * (float)fwdMove; m_Position.z += fwd.z * speed * (float)fwdMove; } if(horizMove || vertMove || fwdMove) { Camera_SetPosition(m_Cam, m_Position.x, m_Position.y, m_Position.z); return true; } return false; } void MouseWheel(QWheelEvent *e) override {} void MouseMove(QMouseEvent *e) override { if(dragStartPos().x() > 0 && e->buttons() == Qt::LeftButton) { m_Rotation.y -= (float)(e->pos().x() - dragStartPos().x()) / 300.0f; m_Rotation.x -= (float)(e->pos().y() - dragStartPos().y()) / 300.0f; Camera_SetFPSRotation(m_Cam, m_Rotation.x, m_Rotation.y, m_Rotation.z); } CameraWrapper::MouseMove(e); } private: Camera *m_Cam; FloatVector m_Position, m_Rotation; }; struct BufferData { BufferData() { refcount.store(1); data = end = NULL; stride = 0; } void ref() { refcount.ref(); } void deref() { bool alive = refcount.deref(); if(!alive) { delete[] data; delete this; } } QAtomicInteger refcount; byte *data; byte *end; size_t stride; }; uint32_t CalcIndex(BufferData *data, uint32_t vertID, int32_t baseVertex) { byte *idxData = data->data + vertID * sizeof(uint32_t); if(idxData + sizeof(uint32_t) > data->end) return ~0U; uint32_t idx = *(uint32_t *)idxData; // apply base vertex but clamp to 0 if subtracting if(baseVertex < 0) { uint32_t subtract = (uint32_t)(-baseVertex); if(idx < subtract) idx = 0; else idx -= subtract; } else if(baseVertex > 0) { idx += (uint32_t)baseVertex; } return idx; } class BufferItemModel : public QAbstractItemModel { public: BufferItemModel(RDTableView *v, QObject *parent) : QAbstractItemModel(parent) { view = v; view->setModel(this); } void beginReset() { emit beginResetModel(); } void endReset() { cacheColumns(); m_ColumnCount = columnLookup.count() + reservedColumnCount(); emit endResetModel(); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if(row < 0 || row >= rowCount()) return QModelIndex(); return createIndex(row, column); } QModelIndex parent(const QModelIndex &index) const override { return QModelIndex(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return numRows; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return m_ColumnCount; } 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(section < m_ColumnCount && orientation == Qt::Horizontal && role == Qt::DisplayRole) { if(section == 0) { return meshView ? lit("VTX") : lit("Element"); } else if(section == 1 && meshView) { return lit("IDX"); } else { const FormatElement &el = elementForColumn(section); if(el.format.compCount == 1) return el.name; QChar comps[] = {QLatin1Char('x'), QLatin1Char('y'), QLatin1Char('z'), QLatin1Char('w')}; return QFormatStr("%1.%2").arg(el.name).arg(comps[componentForIndex(section)]); } } return QVariant(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if(index.isValid()) { if(role == Qt::SizeHintRole) { QStyleOptionViewItem opt = view->viewOptions(); opt.features |= QStyleOptionViewItem::HasDisplay; // pad these columns to allow for sufficiently wide data if(index.column() < reservedColumnCount()) opt.text = lit("999999"); else opt.text = data(index).toString(); opt.text.replace(QLatin1Char('\n'), QChar::LineSeparator); opt.styleObject = NULL; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); return style->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), opt.widget); } uint32_t row = index.row(); int col = index.column(); if((role == Qt::BackgroundRole || role == Qt::ForegroundRole) && col >= reservedColumnCount()) { if(meshView) { int elIdx = columnLookup[col - reservedColumnCount()]; int compIdx = componentForIndex(col); if(elIdx == positionEl) { if(role == Qt::ForegroundRole) return QBrush(Qt::black); if(compIdx != 3 || !meshInput) { // C# SkyBlue return QBrush(QColor::fromRgb(135, 206, 235)); } else { // C# LightCyan return QBrush(QColor::fromRgb(224, 255, 255)); } } else if(secondaryEnabled && elIdx == secondaryEl) { if(role == Qt::ForegroundRole) return QBrush(Qt::black); if((secondaryElAlpha && compIdx == 3) || (!secondaryElAlpha && compIdx != 3)) { // C# LightGreen return QBrush(QColor::fromRgb(144, 238, 144)); } else { return QBrush(QColor::fromRgb(200, 238, 200)); } } } else { const FormatElement &el = elementForColumn(col); if(el.rgb && el.buffer < buffers.size()) { const byte *data = buffers[el.buffer]->data; const byte *end = buffers[el.buffer]->end; data += buffers[el.buffer]->stride * row; data += el.offset; // only slightly wasteful, we need to fetch all variants together // since some formats are packed and can't be read individually QVariantList list = el.GetVariants(data, end); if(!list.isEmpty()) { QMetaType::Type vt = (QMetaType::Type)list[0].type(); QColor rgb; if(vt == QMetaType::Double) { double r = qBound(0.0, list[0].toDouble(), 1.0); double g = list.size() > 1 ? qBound(0.0, list[1].toDouble(), 1.0) : 0.0; double b = list.size() > 2 ? qBound(0.0, list[2].toDouble(), 1.0) : 0.0; rgb = QColor::fromRgbF(r, g, b); } else if(vt == QMetaType::Float) { float r = qBound(0.0f, list[0].toFloat(), 1.0f); float g = list.size() > 1 ? qBound(0.0f, list[1].toFloat(), 1.0f) : 0.0; float b = list.size() > 2 ? qBound(0.0f, list[2].toFloat(), 1.0f) : 0.0; rgb = QColor::fromRgbF(r, g, b); } else if(vt == QMetaType::UInt || vt == QMetaType::UShort || vt == QMetaType::UChar) { uint r = qBound(0U, list[0].toUInt(), 255U); uint g = list.size() > 1 ? qBound(0U, list[1].toUInt(), 255U) : 0.0; uint b = list.size() > 2 ? qBound(0U, list[2].toUInt(), 255U) : 0.0; rgb = QColor::fromRgb(r, g, b); } else if(vt == QMetaType::Int || vt == QMetaType::Short || vt == QMetaType::SChar) { int r = qBound(0, list[0].toInt(), 255); int g = list.size() > 1 ? qBound(0, list[1].toInt(), 255) : 0.0; int b = list.size() > 2 ? qBound(0, list[2].toInt(), 255) : 0.0; rgb = QColor::fromRgb(r, g, b); } if(role == Qt::BackgroundRole) return QBrush(rgb); else if(role == Qt::ForegroundRole) return QBrush(contrastingColor(rgb, QColor::fromRgb(0, 0, 0))); } } } } if(role == Qt::DisplayRole) { if(col >= 0 && col < m_ColumnCount && row < numRows) { if(col == 0) return row; uint32_t idx = row; if(indices && indices->data) { idx = CalcIndex(indices, row, baseVertex); if(primRestart && idx == primRestart) return col == 1 ? lit("--") : lit(" Restart"); if(idx == ~0U) return QVariant(); } if(col == 1 && meshView) { // if we have separate displayIndices, fetch that for display instead if(displayIndices && displayIndices->data) idx = CalcIndex(displayIndices, row, baseVertex); return idx; } const FormatElement &el = elementForColumn(col); uint32_t instIdx = 0; if(el.instancerate > 0) instIdx = curInstance / el.instancerate; if(el.buffer < buffers.size()) { const byte *data = buffers[el.buffer]->data; const byte *end = buffers[el.buffer]->end; if(!el.perinstance) data += buffers[el.buffer]->stride * idx; else data += buffers[el.buffer]->stride * instIdx; data += el.offset; // only slightly wasteful, we need to fetch all variants together // since some formats are packed and can't be read individually QVariantList list = el.GetVariants(data, end); int comp = componentForIndex(col); if(comp < list.count()) { QString ret; uint32_t rowdim = el.matrixdim; uint32_t coldim = el.format.compCount; for(uint32_t r = 0; r < rowdim; r++) { if(r > 0) ret += lit("\n"); if(el.rowmajor) ret += interpretVariant(list[comp + r * coldim], el); else ret += interpretVariant(list[r + comp * rowdim], el); } return ret; } } } } } return QVariant(); } RDTableView *view = NULL; int32_t baseVertex = 0; uint32_t curInstance = 0; uint32_t numRows = 0; bool meshView = true; bool meshInput = false; // we can have two index buffers for VSOut data: // the original index buffer is used for the displayed value (in displayIndices), and the actual // potentially remapped or permuated index buffer used for fetching data (in indices). BufferData *displayIndices = NULL; BufferData *indices = NULL; QList columns; QList buffers; uint32_t primRestart = 0; void setPosColumn(int pos) { QVector roles = {Qt::BackgroundRole, Qt::ForegroundRole}; if(positionEl != pos) { if(positionEl >= 0) emit dataChanged(index(0, firstColumnForElement(positionEl)), index(rowCount() - 1, lastColumnForElement(positionEl)), roles); if(pos >= 0) emit dataChanged(index(0, firstColumnForElement(pos)), index(rowCount() - 1, lastColumnForElement(pos)), roles); } positionEl = pos; } int posColumn() { return positionEl; } QString posName() { if(positionEl >= 0 && positionEl < columns.count()) return columns[positionEl].name; return QString(); } void setSecondaryColumn(int sec, bool secEnabled, bool secAlpha) { QVector roles = {Qt::BackgroundRole, Qt::ForegroundRole}; if(secondaryEl != sec || secondaryElAlpha != secAlpha || secondaryEnabled != secEnabled) { if(secondaryEl >= 0 && secondaryEl != sec) emit dataChanged(index(0, firstColumnForElement(secondaryEl)), index(rowCount() - 1, lastColumnForElement(secondaryEl)), roles); if(sec >= 0) emit dataChanged(index(0, firstColumnForElement(sec)), index(rowCount() - 1, lastColumnForElement(sec)), roles); } secondaryEl = sec; secondaryElAlpha = secAlpha; secondaryEnabled = secEnabled; } int secondaryColumn() { return secondaryEl; } bool secondaryAlpha() { return secondaryElAlpha; } QString secondaryName() { if(secondaryEl >= 0 && secondaryEl < columns.count()) return columns[secondaryEl].name; return QString(); } int elementIndexForColumn(int col) const { if(col < reservedColumnCount()) return -1; return columnLookup[col - reservedColumnCount()]; } const FormatElement &elementForColumn(int col) const { return columns[columnLookup[col - reservedColumnCount()]]; } private: // maps from column number (0-based from data, so excluding VTX/IDX columns) // to the column element in the columns list, and lists its component. // // So a float4, float3, int set of columns would be: // { 0, 0, 0, 0, 1, 1, 1, 2 }; // { 0, 1, 2, 3, 0, 1, 2, 0 }; QVector columnLookup; QVector componentLookup; int m_ColumnCount = 0; int positionEl = -1; int secondaryEl = -1; bool secondaryElAlpha = false; bool secondaryEnabled = false; int reservedColumnCount() const { return (meshView ? 2 : 1); } int componentForIndex(int col) const { return componentLookup[col - reservedColumnCount()]; } int firstColumnForElement(int el) const { for(int i = 0; i < columnLookup.count(); i++) { if(columnLookup[i] == el) return reservedColumnCount() + i; } return 0; } int lastColumnForElement(int el) const { for(int i = columnLookup.count() - 1; i >= 0; i--) { if(columnLookup[i] == el) return reservedColumnCount() + i; } return columnCount() - 1; } void cacheColumns() { columnLookup.clear(); columnLookup.reserve(columns.count() * 4); componentLookup.clear(); componentLookup.reserve(columns.count() * 4); for(int i = 0; i < columns.count(); i++) { FormatElement &fmt = columns[i]; uint32_t compCount; switch(fmt.format.specialFormat) { case SpecialFormat::BC6: case SpecialFormat::ETC2: case SpecialFormat::R11G11B10: case SpecialFormat::R5G6B5: case SpecialFormat::R9G9B9E5: compCount = 3; break; case SpecialFormat::BC1: case SpecialFormat::BC7: case SpecialFormat::BC3: case SpecialFormat::BC2: case SpecialFormat::R10G10B10A2: case SpecialFormat::R5G5B5A1: case SpecialFormat::R4G4B4A4: case SpecialFormat::ASTC: compCount = 4; break; case SpecialFormat::BC5: case SpecialFormat::R4G4: case SpecialFormat::D16S8: case SpecialFormat::D24S8: case SpecialFormat::D32S8: compCount = 2; break; case SpecialFormat::BC4: case SpecialFormat::S8: compCount = 1; break; case SpecialFormat::YUV: case SpecialFormat::EAC: default: compCount = fmt.format.compCount; } for(uint32_t c = 0; c < compCount; c++) { columnLookup.push_back(i); componentLookup.push_back((int)c); } } } QString interpretVariant(QVariant &v, const FormatElement &el) const { QString ret; QMetaType::Type vt = (QMetaType::Type)v.type(); if(vt == QMetaType::Double) { double d = v.toDouble(); // pad with space on left if sign is missing, to better align if(d < 0.0) ret = Formatter::Format(d); else if(d > 0.0) ret = lit(" ") + Formatter::Format(d); else if(qIsNaN(d)) ret = lit(" NaN"); else // force negative and positive 0 together ret = lit(" ") + Formatter::Format(0.0); } else if(vt == QMetaType::Float) { float f = v.toFloat(); // pad with space on left if sign is missing, to better align if(f < 0.0) ret = Formatter::Format(f); else if(f > 0.0) ret = lit(" ") + Formatter::Format(f); else if(qIsNaN(f)) ret = lit(" NaN"); else // force negative and positive 0 together ret = lit(" ") + Formatter::Format(0.0); } else if(vt == QMetaType::UInt || vt == QMetaType::UShort || vt == QMetaType::UChar) { uint u = v.toUInt(); if(el.hex && el.format.specialFormat == SpecialFormat::Unknown) ret = Formatter::HexFormat(u, el.format.compByteWidth); else ret = Formatter::Format(u, el.hex); } else if(vt == QMetaType::Int || vt == QMetaType::Short || vt == QMetaType::SChar) { int i = v.toInt(); if(i > 0) ret = lit(" ") + Formatter::Format(i); else ret = Formatter::Format(i); } else { ret = v.toString(); } return ret; } }; struct CachedElData { const FormatElement *el = NULL; const byte *data = NULL; const byte *end = NULL; size_t stride; int byteSize; uint32_t instIdx = 0; QByteArray nulls; }; void CacheDataForIteration(QVector &cache, const QList &columns, const QList buffers, uint32_t inst) { cache.reserve(columns.count()); for(int col = 0; col < columns.count(); col++) { const FormatElement &el = columns[col]; CachedElData d; d.el = ⪙ d.byteSize = el.byteSize(); d.nulls = QByteArray(d.byteSize, '\0'); if(el.instancerate > 0) d.instIdx = inst / el.instancerate; if(el.buffer < buffers.size()) { d.data = buffers[el.buffer]->data; d.end = buffers[el.buffer]->end; d.stride = buffers[el.buffer]->stride; d.data += el.offset; if(el.perinstance) d.data += d.stride * d.instIdx; } cache.push_back(d); } } BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) : QFrame(parent), ui(new Ui::BufferViewer), m_Ctx(ctx) { ui->setupUi(this); m_ModelVSIn = new BufferItemModel(ui->vsinData, this); m_ModelVSOut = new BufferItemModel(ui->vsoutData, this); m_ModelGSOut = new BufferItemModel(ui->gsoutData, this); m_Flycam = new FlycamWrapper(); m_Arcball = new ArcballWrapper(); m_CurrentCamera = m_Arcball; m_Output = NULL; memset(&m_Config, 0, sizeof(m_Config)); m_Config.type = MeshDataStage::VSIn; m_Config.wireframeDraw = true; ui->outputTabs->setCurrentIndex(0); m_CurStage = MeshDataStage::VSIn; ui->vsinData->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui->vsoutData->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui->gsoutData->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui->rowOffset->setFont(Formatter::PreferredFont()); ui->instance->setFont(Formatter::PreferredFont()); ui->camSpeed->setFont(Formatter::PreferredFont()); ui->fovGuess->setFont(Formatter::PreferredFont()); ui->aspectGuess->setFont(Formatter::PreferredFont()); ui->nearGuess->setFont(Formatter::PreferredFont()); ui->farGuess->setFont(Formatter::PreferredFont()); m_ModelVSIn->meshView = m_ModelVSOut->meshView = m_ModelGSOut->meshView = m_MeshView = meshview; m_ModelVSIn->meshInput = true; if(meshview) SetupMeshView(); else SetupRawView(); m_ExportMenu = new QMenu(this); m_ExportCSV = new QAction(tr("Export to &CSV"), this); m_ExportCSV->setIcon(Icons::save()); m_ExportBytes = new QAction(tr("Export to &Bytes"), this); m_ExportBytes->setIcon(Icons::save()); m_ExportMenu->addAction(m_ExportCSV); m_ExportMenu->addAction(m_ExportBytes); m_DebugVert = new QAction(tr("&Debug this Vertex"), this); m_DebugVert->setIcon(Icons::wrench()); ui->exportDrop->setMenu(m_ExportMenu); QObject::connect(m_ExportCSV, &QAction::triggered, [this] { exportData(BufferExport(BufferExport::CSV)); }); QObject::connect(m_ExportBytes, &QAction::triggered, [this] { exportData(BufferExport(BufferExport::RawBytes)); }); QObject::connect(m_DebugVert, &QAction::triggered, this, &BufferViewer::debugVertex); QObject::connect(ui->exportDrop, &QToolButton::clicked, [this] { exportData(BufferExport(BufferExport::CSV)); }); ui->vsinData->setContextMenuPolicy(Qt::CustomContextMenu); ui->vsoutData->setContextMenuPolicy(Qt::CustomContextMenu); ui->gsoutData->setContextMenuPolicy(Qt::CustomContextMenu); QMenu *menu = new QMenu(this); QObject::connect(ui->vsinData, &RDTableView::customContextMenuRequested, [this, menu](const QPoint &pos) { stageRowMenu(MeshDataStage::VSIn, menu, pos); }); menu = new QMenu(this); QObject::connect( ui->vsoutData, &RDTableView::customContextMenuRequested, [this, menu](const QPoint &pos) { stageRowMenu(MeshDataStage::VSOut, menu, pos); }); menu = new QMenu(this); QObject::connect( ui->gsoutData, &RDTableView::customContextMenuRequested, [this, menu](const QPoint &pos) { stageRowMenu(MeshDataStage::GSOut, menu, pos); }); ui->dockarea->setAllowFloatingWindow(false); ui->dockarea->setRubberBandLineWidth(50); ui->controlType->addItems({tr("Arcball"), tr("WASD")}); ui->controlType->adjustSize(); ui->drawRange->addItems({tr("Only this draw"), tr("Show previous instances"), tr("Show all instances"), tr("Show whole pass")}); ui->drawRange->adjustSize(); ui->drawRange->setCurrentIndex(0); ui->solidShading->addItems({tr("None"), tr("Solid Colour"), tr("Flat Shaded"), tr("Secondary")}); ui->solidShading->adjustSize(); ui->solidShading->setCurrentIndex(0); // wireframe only available on solid shaded options ui->wireframeRender->setEnabled(false); ui->fovGuess->setValue(90.0); on_controlType_currentIndexChanged(0); QObject::connect(ui->vsinData->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BufferViewer::data_selected); QObject::connect(ui->vsoutData->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BufferViewer::data_selected); QObject::connect(ui->gsoutData->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BufferViewer::data_selected); QObject::connect(ui->vsinData, &RDTableView::clicked, [this]() { m_CurView = ui->vsinData; }); QObject::connect(ui->vsoutData, &RDTableView::clicked, [this]() { m_CurView = ui->vsoutData; }); QObject::connect(ui->gsoutData, &RDTableView::clicked, [this]() { m_CurView = ui->gsoutData; }); QObject::connect(ui->vsinData->verticalScrollBar(), &QScrollBar::valueChanged, this, &BufferViewer::data_scrolled); QObject::connect(ui->vsoutData->verticalScrollBar(), &QScrollBar::valueChanged, this, &BufferViewer::data_scrolled); QObject::connect(ui->gsoutData->verticalScrollBar(), &QScrollBar::valueChanged, this, &BufferViewer::data_scrolled); QObject::connect(ui->fovGuess, OverloadedSlot::of(&QDoubleSpinBox::valueChanged), this, &BufferViewer::camGuess_changed); QObject::connect(ui->aspectGuess, OverloadedSlot::of(&QDoubleSpinBox::valueChanged), this, &BufferViewer::camGuess_changed); QObject::connect(ui->nearGuess, OverloadedSlot::of(&QDoubleSpinBox::valueChanged), this, &BufferViewer::camGuess_changed); QObject::connect(ui->farGuess, OverloadedSlot::of(&QDoubleSpinBox::valueChanged), this, &BufferViewer::camGuess_changed); QObject::connect(ui->matrixType, OverloadedSlot::of(&QComboBox::currentIndexChanged), [this](int) { camGuess_changed(0.0); }); Reset(); m_Ctx.AddLogViewer(this); } void BufferViewer::SetupRawView() { ui->formatSpecifier->setVisible(true); ui->outputTabs->setVisible(false); ui->vsoutData->setVisible(false); ui->gsoutData->setVisible(false); // hide buttons we don't want in the toolbar ui->syncViews->setVisible(false); ui->instanceLabel->setVisible(false); ui->instance->setVisible(false); ui->vsinData->setWindowTitle(tr("Buffer Contents")); ui->dockarea->addToolWindow(ui->vsinData, ToolWindowManager::EmptySpace); ui->dockarea->setToolWindowProperties(ui->vsinData, ToolWindowManager::HideCloseButton); ui->formatSpecifier->setWindowTitle(tr("Buffer Format")); ui->dockarea->addToolWindow(ui->formatSpecifier, ToolWindowManager::AreaReference( ToolWindowManager::BottomOf, ui->dockarea->areaOf(ui->vsinData), 0.5f)); ui->dockarea->setToolWindowProperties(ui->formatSpecifier, ToolWindowManager::HideCloseButton); QObject::connect(ui->formatSpecifier, &BufferFormatSpecifier::processFormat, this, &BufferViewer::processFormat); QVBoxLayout *vertical = new QVBoxLayout(this); vertical->setSpacing(3); vertical->setContentsMargins(0, 0, 0, 0); vertical->addWidget(ui->meshToolbar); vertical->addWidget(ui->dockarea); } void BufferViewer::SetupMeshView() { setWindowTitle(tr("Mesh Output")); // hide buttons we don't want in the toolbar ui->byteRangeLine->setVisible(false); ui->byteRangeStartLabel->setVisible(false); ui->byteRangeStart->setVisible(false); ui->byteRangeLengthLabel->setVisible(false); ui->byteRangeLength->setVisible(false); ui->formatSpecifier->setVisible(false); ui->cameraControlsGroup->setVisible(false); ui->outputTabs->setWindowTitle(tr("Preview")); ui->dockarea->addToolWindow(ui->outputTabs, ToolWindowManager::EmptySpace); ui->dockarea->setToolWindowProperties(ui->outputTabs, ToolWindowManager::HideCloseButton); ui->vsinData->setWindowTitle(tr("VS Input")); ui->dockarea->addToolWindow( ui->vsinData, ToolWindowManager::AreaReference(ToolWindowManager::TopOf, ui->dockarea->areaOf(ui->outputTabs), 0.5f)); ui->dockarea->setToolWindowProperties(ui->vsinData, ToolWindowManager::HideCloseButton); ui->vsoutData->setWindowTitle(tr("VS Output")); ui->dockarea->addToolWindow( ui->vsoutData, ToolWindowManager::AreaReference(ToolWindowManager::RightOf, ui->dockarea->areaOf(ui->vsinData), 0.5f)); ui->dockarea->setToolWindowProperties(ui->vsoutData, ToolWindowManager::HideCloseButton); ui->gsoutData->setWindowTitle(tr("GS/DS Output")); ui->dockarea->addToolWindow( ui->gsoutData, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, ui->dockarea->areaOf(ui->vsoutData), 0.5f)); ui->dockarea->setToolWindowProperties(ui->gsoutData, ToolWindowManager::HideCloseButton); ToolWindowManager::raiseToolWindow(ui->vsoutData); m_HeaderMenu = new QMenu(this); m_ResetColumnSel = new QAction(tr("Reset Selected Columns"), this); m_SelectPosColumn = new QAction(tr("Select as Position"), this); m_SelectSecondColumn = new QAction(tr("Select as Secondary"), this); m_SelectSecondAlphaColumn = new QAction(tr("Select Alpha as Secondary"), this); m_HeaderMenu->addAction(m_ResetColumnSel); m_HeaderMenu->addSeparator(); m_HeaderMenu->addAction(m_SelectPosColumn); m_HeaderMenu->addAction(m_SelectSecondColumn); m_HeaderMenu->addAction(m_SelectSecondAlphaColumn); QObject::connect(m_ResetColumnSel, &QAction::triggered, [this]() { guessPositionColumn((BufferItemModel *)m_CurView->model()); guessSecondaryColumn((BufferItemModel *)m_CurView->model()); updatePreviewColumns(); INVOKE_MEMFN(RT_UpdateAndDisplay); }); QObject::connect(m_SelectPosColumn, &QAction::triggered, [this]() { BufferItemModel *model = (BufferItemModel *)m_CurView->model(); model->setPosColumn(m_ContextColumn); updatePreviewColumns(); INVOKE_MEMFN(RT_UpdateAndDisplay); }); QObject::connect(m_SelectSecondColumn, &QAction::triggered, [this]() { BufferItemModel *model = (BufferItemModel *)m_CurView->model(); model->setSecondaryColumn(m_ContextColumn, m_Config.solidShadeMode == SolidShade::Secondary, false); updatePreviewColumns(); INVOKE_MEMFN(RT_UpdateAndDisplay); }); QObject::connect(m_SelectSecondAlphaColumn, &QAction::triggered, [this]() { BufferItemModel *model = (BufferItemModel *)m_CurView->model(); model->setSecondaryColumn(m_ContextColumn, m_Config.solidShadeMode == SolidShade::Secondary, true); updatePreviewColumns(); INVOKE_MEMFN(RT_UpdateAndDisplay); }); ui->vsinData->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); ui->vsoutData->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); ui->gsoutData->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(ui->vsinData->horizontalHeader(), &QHeaderView::customContextMenuRequested, [this](const QPoint &pos) { meshHeaderMenu(MeshDataStage::VSIn, pos); }); QObject::connect(ui->vsoutData->horizontalHeader(), &QHeaderView::customContextMenuRequested, [this](const QPoint &pos) { meshHeaderMenu(MeshDataStage::VSOut, pos); }); QObject::connect(ui->gsoutData->horizontalHeader(), &QHeaderView::customContextMenuRequested, [this](const QPoint &pos) { meshHeaderMenu(MeshDataStage::GSOut, pos); }); QVBoxLayout *vertical = new QVBoxLayout(this); vertical->setSpacing(3); vertical->setContentsMargins(0, 0, 0, 0); vertical->addWidget(ui->meshToolbar); vertical->addWidget(ui->dockarea); QTimer *renderTimer = new QTimer(this); QObject::connect(renderTimer, &QTimer::timeout, this, &BufferViewer::render_timer); renderTimer->setSingleShot(false); renderTimer->setInterval(10); renderTimer->start(); } void BufferViewer::meshHeaderMenu(MeshDataStage stage, const QPoint &pos) { int col = tableForStage(stage)->horizontalHeader()->logicalIndexAt(pos); if(col < 2) return; m_CurView = tableForStage(stage); m_ContextColumn = modelForStage(stage)->elementIndexForColumn(col); m_SelectSecondAlphaColumn->setEnabled( modelForStage(stage)->elementForColumn(col).format.compCount == 4); m_HeaderMenu->popup(tableForStage(stage)->horizontalHeader()->mapToGlobal(pos)); } void BufferViewer::stageRowMenu(MeshDataStage stage, QMenu *menu, const QPoint &pos) { m_CurView = tableForStage(stage); menu->clear(); if(m_MeshView && stage != MeshDataStage::GSOut) { menu->addAction(m_DebugVert); menu->addSeparator(); } menu->addAction(m_ExportCSV); menu->addAction(m_ExportBytes); menu->popup(m_CurView->viewport()->mapToGlobal(pos)); } BufferViewer::~BufferViewer() { if(m_ModelVSIn->indices) m_ModelVSIn->indices->deref(); for(auto vb : m_ModelVSIn->buffers) vb->deref(); if(m_ModelVSOut->indices) m_ModelVSOut->indices->deref(); // only VSOut has display indices - GSOut has no indices at all, and VSIn has no special case if(m_ModelVSOut->displayIndices) m_ModelVSOut->displayIndices->deref(); for(auto vb : m_ModelVSOut->buffers) vb->deref(); for(auto vb : m_ModelGSOut->buffers) vb->deref(); delete m_Arcball; delete m_Flycam; if(m_MeshView) m_Ctx.BuiltinWindowClosed(this); m_Ctx.RemoveLogViewer(this); delete ui; } void BufferViewer::OnLogfileLoaded() { Reset(); if(!m_MeshView) return; WId renderID = ui->render->winId(); m_Ctx.Replay().BlockInvoke([renderID, this](IReplayController *r) { m_Output = r->CreateOutput(m_Ctx.CurWindowingSystem(), m_Ctx.FillWindowingData(renderID), ReplayOutputType::Mesh); ui->render->setOutput(m_Output); RT_UpdateAndDisplay(r); }); } void BufferViewer::OnLogfileClosed() { Reset(); if(!m_MeshView) ToolWindowManager::closeToolWindow(this); } void BufferViewer::OnEventChanged(uint32_t eventID) { int vsinHoriz = ui->vsinData->horizontalScrollBar()->value(); int vsoutHoriz = ui->vsoutData->horizontalScrollBar()->value(); int gsoutHoriz = ui->gsoutData->horizontalScrollBar()->value(); QString highlightNames[6] = { m_ModelVSIn->posName(), m_ModelVSIn->secondaryName(), m_ModelVSOut->posName(), m_ModelVSOut->secondaryName(), m_ModelGSOut->posName(), m_ModelGSOut->secondaryName(), }; const DrawcallDescription *draw = m_Ctx.CurDrawcall(); if(m_MeshView) { ClearModels(); CalcColumnWidth(); ClearModels(); m_ModelVSIn->primRestart = 0; m_ModelVSOut->primRestart = 0; m_ModelGSOut->primRestart = 0; if(m_Ctx.CurPipelineState().IsStripRestartEnabled() && draw && (draw->flags & DrawFlags::UseIBuffer) && IsStrip(draw->topology)) { m_ModelVSIn->primRestart = m_Ctx.CurPipelineState().GetStripRestartIndex(); if(draw->indexByteWidth == 1) m_ModelVSIn->primRestart &= 0xff; else if(draw->indexByteWidth == 2) m_ModelVSIn->primRestart &= 0xffff; m_ModelVSOut->primRestart = m_ModelVSIn->primRestart; } } EnableCameraGuessControls(); m_ModelVSIn->curInstance = m_Config.curInstance; m_ModelVSOut->curInstance = m_Config.curInstance; m_ModelGSOut->curInstance = m_Config.curInstance; m_ModelVSIn->beginReset(); m_ModelVSOut->beginReset(); m_ModelGSOut->beginReset(); m_ModelVSIn->baseVertex = draw ? draw->baseVertex : 0; ui->instance->setEnabled(draw && draw->numInstances > 1); if(!ui->instance->isEnabled()) ui->instance->setValue(0); if(draw) ui->instance->setMaximum(qMax(0, int(draw->numInstances) - 1)); if(m_MeshView) { configureMeshColumns(); if(m_ModelVSIn->posColumn() == -1 || highlightNames[0] != m_ModelVSIn->posName()) guessPositionColumn(m_ModelVSIn); if(m_ModelVSIn->secondaryColumn() == -1 || highlightNames[1] != m_ModelVSIn->secondaryName()) guessSecondaryColumn(m_ModelVSIn); if(m_ModelVSOut->posColumn() == -1 || highlightNames[2] != m_ModelVSOut->posName()) guessSecondaryColumn(m_ModelVSOut); if(m_ModelVSOut->secondaryColumn() == -1 || highlightNames[3] != m_ModelVSOut->secondaryName()) guessPositionColumn(m_ModelVSOut); if(m_ModelGSOut->posColumn() == -1 || highlightNames[4] != m_ModelGSOut->posName()) guessPositionColumn(m_ModelGSOut); if(m_ModelGSOut->secondaryColumn() == -1 || highlightNames[5] != m_ModelGSOut->secondaryName()) guessSecondaryColumn(m_ModelGSOut); } m_Ctx.Replay().AsyncInvoke([this, vsinHoriz, vsoutHoriz, gsoutHoriz](IReplayController *r) { if(m_MeshView) { RT_FetchMeshData(r); } else { BufferData *buf = new BufferData; rdctype::array data; if(m_IsBuffer) { uint64_t len = m_ByteSize; if(len == UINT64_MAX) len = 0; data = r->GetBufferData(m_BufferID, m_ByteOffset, len); } else { data = r->GetTextureData(m_BufferID, m_TexArrayIdx, m_TexMip); } buf->data = new byte[data.count]; memcpy(buf->data, data.elems, data.count); buf->end = buf->data + data.count; // calculate tight stride buf->stride = 0; for(const FormatElement &el : m_ModelVSIn->columns) buf->stride += el.byteSize(); buf->stride = qMax((size_t)1, buf->stride); m_ModelVSIn->numRows = uint32_t((data.count + buf->stride - 1) / buf->stride); // ownership passes to model m_ModelVSIn->buffers.push_back(buf); } updatePreviewColumns(); RT_UpdateAndDisplay(r); GUIInvoke::call([this, vsinHoriz, vsoutHoriz, gsoutHoriz] { m_ModelVSIn->endReset(); m_ModelVSOut->endReset(); m_ModelGSOut->endReset(); ApplyRowAndColumnDims(m_ModelVSIn->columnCount(), ui->vsinData); ApplyRowAndColumnDims(m_ModelVSOut->columnCount(), ui->vsoutData); ApplyRowAndColumnDims(m_ModelGSOut->columnCount(), ui->gsoutData); int numRows = qMax(qMax(m_ModelVSIn->numRows, m_ModelVSOut->numRows), m_ModelGSOut->numRows); ui->rowOffset->setMaximum(qMax(0, numRows - 1)); ScrollToRow(m_ModelVSIn, ui->rowOffset->value()); ScrollToRow(m_ModelVSOut, ui->rowOffset->value()); ScrollToRow(m_ModelGSOut, ui->rowOffset->value()); ui->vsinData->horizontalScrollBar()->setValue(vsinHoriz); ui->vsoutData->horizontalScrollBar()->setValue(vsoutHoriz); ui->gsoutData->horizontalScrollBar()->setValue(gsoutHoriz); }); }); } void BufferViewer::RT_FetchMeshData(IReplayController *r) { const DrawcallDescription *draw = m_Ctx.CurDrawcall(); QPair ib = m_Ctx.CurPipelineState().GetIBuffer(); QVector vbs = m_Ctx.CurPipelineState().GetVBuffers(); rdctype::array idata; if(ib.first != ResourceId() && draw && (draw->flags & DrawFlags::UseIBuffer)) idata = r->GetBufferData(ib.first, ib.second + draw->indexOffset * draw->indexByteWidth, draw->numIndices * draw->indexByteWidth); uint32_t *indices = NULL; if(m_ModelVSIn->indices) m_ModelVSIn->indices->deref(); m_ModelVSIn->indices = new BufferData(); if(draw && draw->indexByteWidth != 0 && idata.count != 0) { indices = new uint32_t[draw->numIndices]; m_ModelVSIn->indices->data = (byte *)indices; m_ModelVSIn->indices->end = (byte *)(indices + draw->numIndices); } uint32_t maxIndex = 0; if(draw) maxIndex = qMax(1U, draw->numIndices) - 1; if(draw && idata.count > 0) { maxIndex = 0; if(draw->indexByteWidth == 1) { uint8_t primRestart = m_ModelVSIn->primRestart & 0xff; for(size_t i = 0; i < (size_t)idata.count && (uint32_t)i < draw->numIndices; i++) { if(primRestart && idata.elems[i] == primRestart) continue; indices[i] = (uint32_t)idata.elems[i]; maxIndex = qMax(maxIndex, indices[i]); } } else if(draw->indexByteWidth == 2) { uint16_t primRestart = m_ModelVSIn->primRestart & 0xffff; uint16_t *src = (uint16_t *)idata.elems; for(size_t i = 0; i < (size_t)idata.count / sizeof(uint16_t) && (uint32_t)i < draw->numIndices; i++) { if(primRestart && idata.elems[i] == primRestart) continue; indices[i] = (uint32_t)src[i]; maxIndex = qMax(maxIndex, indices[i]); } } else if(draw->indexByteWidth == 4) { uint16_t primRestart = m_ModelVSIn->primRestart; memcpy(indices, idata.elems, qMin((size_t)idata.count, draw->numIndices * sizeof(uint32_t))); for(uint32_t i = 0; i < draw->numIndices; i++) { if(primRestart && idata.elems[i] == primRestart) continue; maxIndex = qMax(maxIndex, indices[i]); } } } int vbIdx = 0; for(BoundVBuffer vb : vbs) { bool used = false; bool pi = false; bool pv = false; for(const FormatElement &col : m_ModelVSIn->columns) { if(col.buffer == vbIdx) { used = true; if(col.perinstance) pi = true; else pv = true; } } vbIdx++; uint32_t maxIdx = 0; uint32_t offset = 0; if(used && draw) { if(pi) { maxIdx = qMax(1U, draw->numInstances) - 1; offset = draw->instanceOffset; } if(pv) { maxIdx = qMax(maxIndex, maxIdx); offset = draw->vertexOffset; if(draw->baseVertex > 0) maxIdx += (uint32_t)draw->baseVertex; } if(pi && pv) qCritical() << "Buffer used for both instance and vertex rendering!"; } BufferData *buf = new BufferData; if(used) { rdctype::array bufdata = r->GetBufferData( vb.Buffer, vb.ByteOffset + offset * vb.ByteStride, (maxIdx + 1) * vb.ByteStride); buf->data = new byte[bufdata.count]; memcpy(buf->data, bufdata.elems, bufdata.count); buf->end = buf->data + bufdata.count; buf->stride = vb.ByteStride; } // ref passes to model m_ModelVSIn->buffers.push_back(buf); } m_PostVS = r->GetPostVSData(m_Config.curInstance, MeshDataStage::VSOut); m_ModelVSOut->numRows = m_PostVS.numVerts; if(draw && m_PostVS.idxbuf != ResourceId() && (draw->flags & DrawFlags::UseIBuffer)) idata = r->GetBufferData(m_PostVS.idxbuf, 0, draw->numIndices * draw->indexByteWidth); indices = NULL; if(m_ModelVSOut->indices) m_ModelVSOut->indices->deref(); if(m_ModelVSOut->displayIndices) m_ModelVSOut->displayIndices->deref(); if(m_ModelVSIn->indices) { // display the same index values m_ModelVSOut->displayIndices = m_ModelVSIn->indices; m_ModelVSOut->displayIndices->ref(); m_ModelVSOut->indices = new BufferData(); if(draw && draw->indexByteWidth != 0 && idata.count != 0) { indices = new uint32_t[draw->numIndices]; m_ModelVSOut->indices->data = (byte *)indices; m_ModelVSOut->indices->end = (byte *)(indices + draw->numIndices); if(draw->indexByteWidth == 1) { for(size_t i = 0; i < (size_t)idata.count && (uint32_t)i < draw->numIndices; i++) indices[i] = (uint32_t)idata.elems[i]; } else if(draw->indexByteWidth == 2) { uint16_t *src = (uint16_t *)idata.elems; for(size_t i = 0; i < (size_t)idata.count / sizeof(uint16_t) && (uint32_t)i < draw->numIndices; i++) indices[i] = (uint32_t)src[i]; } else if(draw->indexByteWidth == 4) { memcpy(indices, idata.elems, qMin((size_t)idata.count, draw->numIndices * sizeof(uint32_t))); } } } if(m_PostVS.buf != ResourceId()) { BufferData *postvs = new BufferData; rdctype::array bufdata = r->GetBufferData(m_PostVS.buf, m_PostVS.offset, 0); postvs->data = new byte[bufdata.count]; memcpy(postvs->data, bufdata.elems, bufdata.count); postvs->end = postvs->data + bufdata.count; postvs->stride = m_PostVS.stride; // ref passes to model m_ModelVSOut->buffers.push_back(postvs); } m_PostGS = r->GetPostVSData(m_Config.curInstance, MeshDataStage::GSOut); m_ModelGSOut->numRows = m_PostGS.numVerts; indices = NULL; m_ModelGSOut->indices = NULL; if(m_PostGS.buf != ResourceId()) { BufferData *postgs = new BufferData; rdctype::array bufdata = r->GetBufferData(m_PostGS.buf, m_PostGS.offset, 0); postgs->data = new byte[bufdata.count]; memcpy(postgs->data, bufdata.elems, bufdata.count); postgs->end = postgs->data + bufdata.count; postgs->stride = m_PostGS.stride; // ref passes to model m_ModelGSOut->buffers.push_back(postgs); } if(!draw) return; uint32_t eventID = draw->eventID; bool calcNeeded = false; { QMutexLocker autolock(&m_BBoxLock); calcNeeded = !m_BBoxes.contains(eventID); } if(!calcNeeded) { resetArcball(); return; } { QMutexLocker autolock(&m_BBoxLock); m_BBoxes.insert(eventID, BBoxData()); } CalcBoundingBoxData *bbox = new CalcBoundingBoxData; BufferItemModel *models[] = {m_ModelVSIn, m_ModelVSOut, m_ModelGSOut}; bbox->inst = m_ModelVSIn->curInstance; bbox->baseVertex = draw->baseVertex; bbox->eventID = eventID; for(size_t i = 0; i < ARRAY_COUNT(bbox->input); i++) { bbox->input[i].elements = models[i]->columns; bbox->input[i].buffers = models[i]->buffers; bbox->input[i].indices = models[i]->indices; bbox->input[i].count = models[i]->numRows; // add ref all this buffer data if(bbox->input[i].indices) bbox->input[i].indices->ref(); for(int j = 0; j < bbox->input[i].buffers.count(); j++) if(bbox->input[i].buffers[j]) bbox->input[i].buffers[j]->ref(); } // fire up a thread to calculate the bounding box LambdaThread *thread = new LambdaThread([this, bbox] { calcBoundingData(*bbox); GUIInvoke::call([this, bbox]() { updateBoundingBox(*bbox); }); }); thread->selfDelete(true); thread->start(); // give the thread a few ms to finish, so we don't get a tiny flicker on small/fast meshes thread->wait(10); } void BufferViewer::calcBoundingData(CalcBoundingBoxData &bbox) { for(size_t stage = 0; stage < ARRAY_COUNT(bbox.input); stage++) { const CalcBoundingBoxData::StageData &s = bbox.input[stage]; QList &minOutputList = bbox.output.bounds[stage].Min; QList &maxOutputList = bbox.output.bounds[stage].Max; minOutputList.reserve(s.elements.count()); maxOutputList.reserve(s.elements.count()); for(int i = 0; i < s.elements.count(); i++) { minOutputList.push_back(FloatVector(FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX)); maxOutputList.push_back(FloatVector(-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX)); } QVector cache; CacheDataForIteration(cache, s.elements, s.buffers, bbox.inst); // possible optimisation here if this shows up as a hot spot - sort and unique the indices and // iterate in ascending order, to be more cache friendly for(uint32_t row = 0; row < s.count; row++) { uint32_t idx = row; if(s.indices && s.indices->data) { idx = CalcIndex(s.indices, row, bbox.baseVertex); if(idx == ~0U) continue; } for(int col = 0; col < s.elements.count(); col++) { const CachedElData &d = cache[col]; const FormatElement *el = d.el; float *minOut = (float *)&minOutputList[col]; float *maxOut = (float *)&maxOutputList[col]; if(d.data) { const byte *bytes = d.data; if(!el->perinstance) bytes += d.stride * idx; QVariantList list = el->GetVariants(bytes, d.end); for(int comp = 0; comp < list.count(); comp++) { const QVariant &v = list[comp]; QMetaType::Type vt = (QMetaType::Type)v.type(); float fval = 0.0f; if(vt == QMetaType::Double) fval = (float)v.toDouble(); else if(vt == QMetaType::Float) fval = v.toFloat(); else if(vt == QMetaType::UInt || vt == QMetaType::UShort || vt == QMetaType::UChar) fval = (float)v.toUInt(); else if(vt == QMetaType::Int || vt == QMetaType::Short || vt == QMetaType::SChar) fval = (float)v.toInt(); else continue; if(qIsFinite(fval)) { minOut[comp] = qMin(minOut[comp], fval); maxOut[comp] = qMax(maxOut[comp], fval); } } } } } } } void BufferViewer::updateBoundingBox(const CalcBoundingBoxData &bbox) { { QMutexLocker autolock(&m_BBoxLock); m_BBoxes[bbox.eventID] = bbox.output; } if(m_Ctx.CurEvent() == bbox.eventID) UpdateMeshConfig(); resetArcball(); for(size_t i = 0; i < ARRAY_COUNT(bbox.input); i++) { if(bbox.input[i].indices) bbox.input[i].indices->deref(); for(int j = 0; j < bbox.input[i].buffers.count(); j++) if(bbox.input[i].buffers[j]) bbox.input[i].buffers[j]->ref(); } delete &bbox; } void BufferViewer::resetArcball() { BBoxData bbox; { QMutexLocker autolock(&m_BBoxLock); bbox = m_BBoxes[m_Ctx.CurEvent()]; } BufferItemModel *model = currentBufferModel(); int stage = currentStageIndex(); if(model) { int posEl = model->posColumn(); if(posEl >= 0 && posEl < model->columns.count() && posEl < bbox.bounds[stage].Min.count()) { FloatVector diag; diag.x = bbox.bounds[stage].Max[posEl].x - bbox.bounds[stage].Min[posEl].x; diag.y = bbox.bounds[stage].Max[posEl].y - bbox.bounds[stage].Min[posEl].y; diag.z = bbox.bounds[stage].Max[posEl].z - bbox.bounds[stage].Min[posEl].z; float len = qSqrt(diag.x * diag.x + diag.y * diag.y + diag.z * diag.z); if(diag.x >= 0.0f && diag.y >= 0.0f && diag.z >= 0.0f && len >= 1.0e-6f && len <= 1.0e+10f) { FloatVector mid; mid.x = bbox.bounds[stage].Min[posEl].x + diag.x * 0.5f; mid.y = bbox.bounds[stage].Min[posEl].y + diag.y * 0.5f; mid.z = bbox.bounds[stage].Min[posEl].z + diag.z * 0.5f; m_Arcball->Reset(mid, len * 0.7f); GUIInvoke::call([this, len]() { ui->camSpeed->setValue(len / 200.0f); }); } } } INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::guessPositionColumn(BufferItemModel *model) { int posEl = -1; if(!model->columns.empty()) { // prioritise system value over general "POSITION" string matching for(int i = 0; i < model->columns.count(); i++) { const FormatElement &el = model->columns[i]; if(el.systemValue == ShaderBuiltin::Position) { posEl = i; break; } } // look for an exact match for(int i = 0; posEl == -1 && i < model->columns.count(); i++) { const FormatElement &el = model->columns[i]; if(el.name.compare(lit("POSITION"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("POSITION0"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("POS"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("POS0"), Qt::CaseInsensitive) == 0) { posEl = i; break; } } // try anything containing position for(int i = 0; posEl == -1 && i < model->columns.count(); i++) { const FormatElement &el = model->columns[i]; if(el.name.contains(lit("POSITION"), Qt::CaseInsensitive)) { posEl = i; break; } } // OK last resort, just look for 'pos' for(int i = 0; posEl == -1 && i < model->columns.count(); i++) { const FormatElement &el = model->columns[i]; if(el.name.contains(lit("POS"), Qt::CaseInsensitive)) { posEl = i; break; } } // if we still have absolutely nothing, just use the first available element if(posEl == -1) { posEl = 0; } } model->setPosColumn(posEl); } void BufferViewer::guessSecondaryColumn(BufferItemModel *model) { int secondEl = -1; if(!model->columns.empty()) { // prioritise TEXCOORD over general COLOR for(int i = 0; i < model->columns.count(); i++) { const FormatElement &el = model->columns[i]; if(el.name.compare(lit("TEXCOORD"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("TEXCOORD0"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("TEX"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("TEX0"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("UV"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("UV0"), Qt::CaseInsensitive) == 0) { secondEl = i; break; } } for(int i = 0; secondEl == -1 && i < model->columns.count(); i++) { const FormatElement &el = model->columns[i]; if(el.name.compare(lit("COLOR"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("COLOR0"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("COL"), Qt::CaseInsensitive) == 0 || el.name.compare(lit("COL0"), Qt::CaseInsensitive) == 0) { secondEl = i; break; } } } model->setSecondaryColumn(secondEl, m_Config.solidShadeMode == SolidShade::Secondary, false); } void BufferViewer::updatePreviewColumns() { if(!m_MeshView) return; QVector vbs = m_Ctx.CurPipelineState().GetVBuffers(); const DrawcallDescription *draw = m_Ctx.CurDrawcall(); if(draw) { m_VSInPosition = MeshFormat(); m_VSInSecondary = MeshFormat(); if(!m_ModelVSIn->columns.empty()) { int elIdx = m_ModelVSIn->posColumn(); if(elIdx < 0 || elIdx >= m_ModelVSIn->columns.count()) elIdx = 0; m_VSInPosition.numVerts = draw->numIndices; m_VSInPosition.topo = draw->topology; m_VSInPosition.idxByteWidth = draw->indexByteWidth; m_VSInPosition.baseVertex = draw->baseVertex; QPair ib = m_Ctx.CurPipelineState().GetIBuffer(); m_VSInPosition.idxbuf = ib.first; m_VSInPosition.idxoffs = ib.second + draw->indexOffset * draw->indexByteWidth; { const FormatElement &el = m_ModelVSIn->columns[elIdx]; m_VSInPosition.buf = vbs[el.buffer].Buffer; m_VSInPosition.stride = vbs[el.buffer].ByteStride; m_VSInPosition.offset = vbs[el.buffer].ByteOffset + el.offset + draw->vertexOffset * m_VSInPosition.stride; m_VSInPosition.compCount = el.format.compCount; m_VSInPosition.compByteWidth = el.format.compByteWidth; m_VSInPosition.compType = el.format.compType; m_VSInPosition.bgraOrder = el.format.bgraOrder; m_VSInPosition.specialFormat = el.format.special ? el.format.specialFormat : SpecialFormat::Unknown; } elIdx = m_ModelVSIn->secondaryColumn(); if(elIdx >= 0 && elIdx < m_ModelVSIn->columns.count()) { const FormatElement &el = m_ModelVSIn->columns[elIdx]; m_VSInSecondary.buf = vbs[el.buffer].Buffer; m_VSInSecondary.stride = vbs[el.buffer].ByteStride; m_VSInSecondary.offset = vbs[el.buffer].ByteOffset + el.offset + draw->vertexOffset * m_VSInPosition.stride; m_VSInSecondary.compCount = el.format.compCount; m_VSInSecondary.compByteWidth = el.format.compByteWidth; m_VSInSecondary.compType = el.format.compType; m_VSInSecondary.bgraOrder = el.format.bgraOrder; m_VSInSecondary.specialFormat = el.format.special ? el.format.specialFormat : SpecialFormat::Unknown; m_VSInSecondary.showAlpha = m_ModelVSIn->secondaryAlpha(); } } m_PostVSPosition = MeshFormat(); m_PostVSSecondary = MeshFormat(); if(!m_ModelVSOut->columns.empty()) { int elIdx = m_ModelVSOut->posColumn(); if(elIdx < 0 || elIdx >= m_ModelVSOut->columns.count()) elIdx = 0; m_PostVSPosition = m_PostVS; m_PostVSPosition.offset += m_ModelVSOut->columns[elIdx].offset; elIdx = m_ModelVSOut->secondaryColumn(); if(elIdx >= 0 && elIdx < m_ModelVSOut->columns.count()) { m_PostVSSecondary = m_PostVS; m_PostVSSecondary.offset += m_ModelVSOut->columns[elIdx].offset; m_PostVSSecondary.showAlpha = m_ModelVSOut->secondaryAlpha(); } } m_PostGSPosition = MeshFormat(); m_PostGSSecondary = MeshFormat(); if(!m_ModelGSOut->columns.empty()) { int elIdx = m_ModelGSOut->posColumn(); if(elIdx < 0 || elIdx >= m_ModelGSOut->columns.count()) elIdx = 0; m_PostGSPosition = m_PostGS; m_PostGSPosition.offset += m_ModelGSOut->columns[elIdx].offset; elIdx = m_ModelGSOut->secondaryColumn(); if(elIdx >= 0 && elIdx < m_ModelGSOut->columns.count()) { m_PostGSSecondary = m_PostGS; m_PostGSSecondary.offset += m_ModelGSOut->columns[elIdx].offset; m_PostGSSecondary.showAlpha = m_ModelGSOut->secondaryAlpha(); } } m_PostGSPosition.idxByteWidth = 0; if(!(draw->flags & DrawFlags::UseIBuffer)) m_PostVSPosition.idxByteWidth = m_VSInPosition.idxByteWidth = 0; m_PostGSPosition.unproject = true; m_PostVSPosition.unproject = !m_Ctx.CurPipelineState().IsTessellationEnabled(); } else { m_VSInPosition = MeshFormat(); m_VSInSecondary = MeshFormat(); m_PostVSPosition = MeshFormat(); m_PostVSSecondary = MeshFormat(); m_PostGSPosition = MeshFormat(); m_PostGSSecondary = MeshFormat(); } UpdateMeshConfig(); } void BufferViewer::configureMeshColumns() { const DrawcallDescription *draw = m_Ctx.CurDrawcall(); QVector vinputs = m_Ctx.CurPipelineState().GetVertexInputs(); m_ModelVSIn->columns.reserve(vinputs.count()); for(const VertexInputAttribute &a : vinputs) { if(!a.Used) continue; FormatElement f(a.Name, a.VertexBuffer, a.RelativeByteOffset, a.PerInstance, a.InstanceRate, false, // row major matrix 1, // matrix dimension a.Format, false, false); m_ModelVSIn->columns.push_back(f); } if(draw == NULL) m_ModelVSIn->numRows = 0; else m_ModelVSIn->numRows = draw->numIndices; QVector vbs = m_Ctx.CurPipelineState().GetVBuffers(); Viewport vp = m_Ctx.CurPipelineState().GetViewport(0); m_Config.fov = ui->fovGuess->value(); m_Config.aspect = vp.width / vp.height; m_Config.highlightVert = 0; if(ui->aspectGuess->value() > 0.0) m_Config.aspect = ui->aspectGuess->value(); if(ui->nearGuess->value() > 0.0) m_PostVS.nearPlane = m_PostGS.nearPlane = ui->nearGuess->value(); if(ui->farGuess->value() > 0.0) m_PostVS.farPlane = m_PostGS.farPlane = ui->farGuess->value(); const ShaderReflection *vs = m_Ctx.CurPipelineState().GetShaderReflection(ShaderStage::Vertex); m_ModelVSOut->columns.clear(); if(draw && vs) { m_ModelVSOut->columns.reserve(vs->OutputSig.count); int i = 0, posidx = -1; for(const SigParameter &sig : vs->OutputSig) { FormatElement f; f.buffer = 0; f.name = sig.varName.count > 0 ? ToQStr(sig.varName) : ToQStr(sig.semanticIdxName); f.format.compByteWidth = sizeof(float); f.format.compCount = sig.compCount; f.format.compType = sig.compType; f.format.special = false; f.perinstance = false; f.instancerate = 1; f.rowmajor = false; f.matrixdim = 1; f.systemValue = sig.systemValue; if(f.systemValue == ShaderBuiltin::Position) posidx = i; m_ModelVSOut->columns.push_back(f); i++; } // shift position attribute up to first, keeping order otherwise // the same if(posidx > 0) { FormatElement pos = m_ModelVSOut->columns[posidx]; m_ModelVSOut->columns.insert(0, m_ModelVSOut->columns.takeAt(posidx)); } i = 0; uint32_t offset = 0; for(const FormatElement &sig : m_ModelVSOut->columns) { uint numComps = sig.format.compCount; uint elemSize = sig.format.compType == CompType::Double ? 8U : 4U; if(m_Ctx.CurPipelineState().HasAlignedPostVSData()) { if(numComps == 2) offset = AlignUp(offset, 2U * elemSize); else if(numComps > 2) offset = AlignUp(offset, 4U * elemSize); } m_ModelVSOut->columns[i++].offset = offset; offset += numComps * elemSize; } } m_ModelGSOut->columns.clear(); if(draw) { const ShaderReflection *last = m_Ctx.CurPipelineState().GetShaderReflection(ShaderStage::Geometry); if(last == NULL) last = m_Ctx.CurPipelineState().GetShaderReflection(ShaderStage::Domain); if(last) { m_ModelGSOut->columns.reserve(last->OutputSig.count); int i = 0, posidx = -1; for(const SigParameter &sig : last->OutputSig) { FormatElement f; f.buffer = 0; f.name = sig.varName.count > 0 ? ToQStr(sig.varName) : ToQStr(sig.semanticIdxName); f.format.compByteWidth = sizeof(float); f.format.compCount = sig.compCount; f.format.compType = sig.compType; f.format.special = false; f.perinstance = false; f.instancerate = 1; f.rowmajor = false; f.matrixdim = 1; f.systemValue = sig.systemValue; if(f.systemValue == ShaderBuiltin::Position) posidx = i; m_ModelGSOut->columns.push_back(f); i++; } // shift position attribute up to first, keeping order otherwise // the same if(posidx > 0) { FormatElement pos = m_ModelGSOut->columns[posidx]; m_ModelGSOut->columns.insert(0, m_ModelGSOut->columns.takeAt(posidx)); } i = 0; uint32_t offset = 0; for(const FormatElement &sig : m_ModelGSOut->columns) { uint numComps = sig.format.compCount; uint elemSize = sig.format.compType == CompType::Double ? 8U : 4U; if(m_Ctx.CurPipelineState().HasAlignedPostVSData()) { if(numComps == 2) offset = AlignUp(offset, 2U * elemSize); else if(numComps > 2) offset = AlignUp(offset, 4U * elemSize); } m_ModelGSOut->columns[i++].offset = offset; offset += numComps * elemSize; } } } } void BufferViewer::ApplyRowAndColumnDims(int numColumns, RDTableView *view) { int start = 0; // vertex/element view->setColumnWidth(start++, m_IdxColWidth); // mesh view only - index if(m_MeshView) view->setColumnWidth(start++, m_IdxColWidth); for(int i = start; i < numColumns; i++) view->setColumnWidth(i, m_DataColWidth); view->verticalHeader()->setDefaultSectionSize(m_DataRowHeight); } void BufferViewer::UpdateMeshConfig() { BBoxData bbox; uint32_t eventID = m_Ctx.CurEvent(); { QMutexLocker autolocker(&m_BBoxLock); if(m_BBoxes.contains(eventID)) bbox = m_BBoxes[eventID]; } m_Config.type = m_CurStage; switch(m_CurStage) { case MeshDataStage::VSIn: m_Config.position = m_VSInPosition; m_Config.second = m_VSInSecondary; break; case MeshDataStage::VSOut: m_Config.position = m_PostVSPosition; m_Config.second = m_PostVSSecondary; break; case MeshDataStage::GSOut: m_Config.position = m_PostGSPosition; m_Config.second = m_PostGSSecondary; break; default: break; } BufferItemModel *model = currentBufferModel(); int stage = currentStageIndex(); m_Config.showBBox = false; if(model) { int posEl = model->posColumn(); if(posEl >= 0 && posEl < model->columns.count() && posEl < bbox.bounds[stage].Min.count()) { m_Config.minBounds = bbox.bounds[stage].Min[posEl]; m_Config.maxBounds = bbox.bounds[stage].Max[posEl]; m_Config.showBBox = true; } } } void BufferViewer::render_mouseMove(QMouseEvent *e) { if(!m_Ctx.LogLoaded()) return; if(m_CurrentCamera) m_CurrentCamera->MouseMove(e); if(e->buttons() & Qt::RightButton) render_clicked(e); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::render_clicked(QMouseEvent *e) { if(!m_Ctx.LogLoaded()) return; QPoint curpos = e->pos(); if((e->buttons() & Qt::RightButton) && m_Output) { m_Ctx.Replay().AsyncInvoke(lit("PickVertex"), [this, curpos](IReplayController *r) { uint32_t instanceSelected = 0; uint32_t vertSelected = 0; std::tie(vertSelected, instanceSelected) = m_Output->PickVertex(m_Ctx.CurEvent(), (uint32_t)curpos.x(), (uint32_t)curpos.y()); if(vertSelected != ~0U) { GUIInvoke::call([this, vertSelected, instanceSelected] { int row = (int)vertSelected; if(instanceSelected != m_Config.curInstance) ui->instance->setValue(instanceSelected); BufferItemModel *model = currentBufferModel(); if(model && row >= 0 && row < model->rowCount()) ScrollToRow(model, row); SyncViews(currentTable(), true, true); }); } }); } if(m_CurrentCamera) m_CurrentCamera->MouseClick(e); ui->render->setFocus(); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::ScrollToRow(BufferItemModel *model, int row) { model->view->scrollTo(model->index(row, 0), QAbstractItemView::PositionAtTop); model->view->clearSelection(); model->view->selectRow(row); } void BufferViewer::ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const QString &format) { if(!m_Ctx.LogLoaded()) return; m_IsBuffer = true; m_ByteOffset = byteOffset; m_ByteSize = byteSize; m_BufferID = id; BufferDescription *buf = m_Ctx.GetBuffer(id); if(buf) { setWindowTitle(ToQStr(buf->name) + lit(" - Contents")); m_ObjectByteSize = buf->length; } processFormat(format); } void BufferViewer::ViewTexture(uint32_t arrayIdx, uint32_t mip, ResourceId id, const QString &format) { if(!m_Ctx.LogLoaded()) return; m_IsBuffer = false; m_TexArrayIdx = arrayIdx; m_TexMip = mip; m_BufferID = id; TextureDescription *tex = m_Ctx.GetTexture(id); if(tex) { setWindowTitle(ToQStr(tex->name) + lit(" - Contents")); m_ObjectByteSize = tex->byteSize; } processFormat(format); } void BufferViewer::render_mouseWheel(QWheelEvent *e) { if(m_CurrentCamera) m_CurrentCamera->MouseWheel(e); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::render_keyPress(QKeyEvent *e) { m_CurrentCamera->KeyDown(e); } void BufferViewer::render_keyRelease(QKeyEvent *e) { m_CurrentCamera->KeyUp(e); } void BufferViewer::render_timer() { if(m_CurrentCamera && m_CurrentCamera->Update(ui->render->rect())) INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::RT_UpdateAndDisplay(IReplayController *) { if(m_Output) { m_Config.cam = m_CurrentCamera->camera(); m_Output->SetMeshDisplay(m_Config); m_Output->Display(); } } RDTableView *BufferViewer::tableForStage(MeshDataStage stage) { if(stage == MeshDataStage::VSIn) return ui->vsinData; else if(stage == MeshDataStage::VSOut) return ui->vsoutData; else if(stage == MeshDataStage::GSOut) return ui->gsoutData; return NULL; } BufferItemModel *BufferViewer::modelForStage(MeshDataStage stage) { if(stage == MeshDataStage::VSIn) return m_ModelVSIn; else if(stage == MeshDataStage::VSOut) return m_ModelVSOut; else if(stage == MeshDataStage::GSOut) return m_ModelGSOut; return NULL; } bool BufferViewer::isCurrentRasterOut() { if(m_CurStage == MeshDataStage::VSIn) { return false; } else if(m_CurStage == MeshDataStage::VSOut) { if(m_Ctx.LogLoaded() && m_Ctx.CurPipelineState().IsTessellationEnabled()) return false; return true; } else if(m_CurStage == MeshDataStage::GSOut) { return true; } return false; } int BufferViewer::currentStageIndex() { if(m_CurStage == MeshDataStage::VSIn) return 0; else if(m_CurStage == MeshDataStage::VSOut) return 1; else if(m_CurStage == MeshDataStage::GSOut) return 2; return 0; } void BufferViewer::Reset() { m_Output = NULL; ClearModels(); m_BBoxes.clear(); ICaptureContext *ctx = &m_Ctx; // while a log is loaded, pass NULL into the widget if(!m_Ctx.LogLoaded()) ctx = NULL; { CustomPaintWidget *render = new CustomPaintWidget(ctx, this); render->setObjectName(ui->render->objectName()); render->setSizePolicy(ui->render->sizePolicy()); delete ui->render; ui->render = render; ui->renderContainerGridLayout->addWidget(ui->render, 1, 1, 1, 1); } QObject::connect(ui->render, &CustomPaintWidget::mouseMove, this, &BufferViewer::render_mouseMove); QObject::connect(ui->render, &CustomPaintWidget::clicked, this, &BufferViewer::render_clicked); QObject::connect(ui->render, &CustomPaintWidget::keyPress, this, &BufferViewer::render_keyPress); QObject::connect(ui->render, &CustomPaintWidget::keyRelease, this, &BufferViewer::render_keyRelease); QObject::connect(ui->render, &CustomPaintWidget::mouseWheel, this, &BufferViewer::render_mouseWheel); ui->render->setColours(QColor::fromRgbF(0.57f, 0.57f, 0.57f, 1.0f), QColor::fromRgbF(0.81f, 0.81f, 0.81f, 1.0f)); } void BufferViewer::ClearModels() { BufferItemModel *models[] = {m_ModelVSIn, m_ModelVSOut, m_ModelGSOut}; for(BufferItemModel *m : models) { if(!m) continue; m->beginReset(); if(m->indices) m->indices->deref(); m->indices = NULL; for(auto vb : m->buffers) vb->deref(); m->buffers.clear(); m->columns.clear(); m->numRows = 0; m->endReset(); } } void BufferViewer::CalcColumnWidth(int maxNumRows) { m_ModelVSIn->beginReset(); ResourceFormat floatFmt; floatFmt.compByteWidth = 4; floatFmt.compType = CompType::Float; floatFmt.compCount = 1; ResourceFormat intFmt; intFmt.compByteWidth = 4; intFmt.compType = CompType::UInt; intFmt.compCount = 1; QString headerText = lit("ColumnSizeTest"); m_ModelVSIn->columns.clear(); m_ModelVSIn->columns.push_back( FormatElement(headerText, 0, 0, false, 1, false, maxNumRows, floatFmt, false, false)); m_ModelVSIn->columns.push_back( FormatElement(headerText, 0, 4, false, 1, false, 1, floatFmt, false, false)); m_ModelVSIn->columns.push_back( FormatElement(headerText, 0, 8, false, 1, false, 1, floatFmt, false, false)); m_ModelVSIn->columns.push_back( FormatElement(headerText, 0, 12, false, 1, false, 1, intFmt, true, false)); m_ModelVSIn->columns.push_back( FormatElement(headerText, 0, 16, false, 1, false, 1, intFmt, false, false)); m_ModelVSIn->numRows = 2; if(m_ModelVSIn->indices) m_ModelVSIn->indices->deref(); m_ModelVSIn->indices = new BufferData; m_ModelVSIn->indices->stride = sizeof(uint32_t); m_ModelVSIn->indices->data = new byte[sizeof(uint32_t) * 2]; m_ModelVSIn->indices->end = m_ModelVSIn->indices->data + sizeof(uint32_t) * 2; uint32_t *indices = (uint32_t *)m_ModelVSIn->indices->data; indices[0] = 0; indices[1] = 1000000; m_ModelVSIn->buffers.clear(); struct TestData { float f[4]; uint32_t ui[3]; }; BufferData *bufdata = new BufferData; bufdata->stride = sizeof(TestData); bufdata->data = new byte[sizeof(TestData)]; bufdata->end = bufdata->data + sizeof(TestData); m_ModelVSIn->buffers.push_back(bufdata); TestData *test = (TestData *)bufdata->data; test->f[0] = 1.0f; test->f[1] = 1.2345e-20f; test->f[2] = 123456.7890123456789f; test->f[3] = -1.0f; test->ui[1] = 0x12345678; test->ui[2] = 0xffffffff; m_ModelVSIn->endReset(); // measure this data so we can use this as column widths ui->vsinData->resizeColumnsToContents(); // index/element column m_IdxColWidth = ui->vsinData->columnWidth(0); int col = 1; if(m_MeshView) col = 2; m_DataColWidth = 10; for(int c = 0; c < 5; c++) { int colWidth = ui->vsinData->columnWidth(col + c); m_DataColWidth = qMax(m_DataColWidth, colWidth); } ui->vsinData->resizeRowsToContents(); m_DataRowHeight = ui->vsinData->rowHeight(0); } void BufferViewer::data_selected(const QItemSelection &selected, const QItemSelection &deselected) { m_CurView = qobject_cast(QObject::sender()); if(selected.count() > 0) { UpdateHighlightVerts(); SyncViews(qobject_cast(QObject::sender()), true, false); INVOKE_MEMFN(RT_UpdateAndDisplay); } } void BufferViewer::data_scrolled(int scrollvalue) { SyncViews(qobject_cast(QObject::sender()), false, true); } void BufferViewer::camGuess_changed(double value) { m_Config.ortho = (ui->matrixType->currentIndex() == 1); m_Config.fov = ui->fovGuess->value(); m_Config.aspect = 1.0f; // take a guess for the aspect ratio, for if the user hasn't overridden it Viewport vp = m_Ctx.CurPipelineState().GetViewport(0); m_Config.aspect = vp.width / vp.height; if(ui->aspectGuess->value() > 0.0) m_Config.aspect = ui->aspectGuess->value(); // use estimates from post vs data (calculated from vertex position data) if the user // hasn't overridden the values m_Config.position.nearPlane = 0.1f; if(m_CurStage == MeshDataStage::VSOut) m_Config.position.nearPlane = m_PostVS.nearPlane; else if(m_CurStage == MeshDataStage::GSOut) m_Config.position.nearPlane = m_PostGS.nearPlane; if(ui->nearGuess->value() > 0.0) m_Config.position.nearPlane = ui->nearGuess->value(); m_Config.position.farPlane = 100.0f; if(m_CurStage == MeshDataStage::VSOut) m_Config.position.farPlane = m_PostVS.farPlane; else if(m_CurStage == MeshDataStage::GSOut) m_Config.position.farPlane = m_PostGS.farPlane; if(ui->nearGuess->value() > 0.0) m_Config.position.farPlane = ui->nearGuess->value(); if(ui->farGuess->value() > 0.0) m_Config.position.nearPlane = ui->farGuess->value(); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::processFormat(const QString &format) { QString errors; Reset(); QList cols = FormatElement::ParseFormatString(format, 0, true, errors); int maxNumRows = 1; for(const FormatElement &c : cols) maxNumRows = qMax(maxNumRows, (int)c.matrixdim); CalcColumnWidth(maxNumRows); ClearModels(); m_Format = format; ui->formatSpecifier->setFormat(format); m_ModelVSIn->columns = cols; uint32_t stride = 0; for(const FormatElement &el : m_ModelVSIn->columns) stride += el.byteSize(); stride = qMax(1U, stride); ui->byteRangeStart->setSingleStep((int)stride); ui->byteRangeLength->setSingleStep((int)stride); ui->byteRangeStart->setMaximum((int)m_ObjectByteSize); ui->byteRangeLength->setMaximum((int)m_ObjectByteSize); ui->byteRangeStart->setValue((int)m_ByteOffset); ui->byteRangeLength->setValue((int)m_ByteSize); ui->formatSpecifier->setErrors(errors); OnEventChanged(m_Ctx.CurEvent()); } void BufferViewer::on_byteRangeStart_valueChanged(int value) { m_ByteOffset = value; processFormat(m_Format); } void BufferViewer::on_byteRangeLength_valueChanged(int value) { m_ByteSize = value; processFormat(m_Format); } void BufferViewer::exportData(const BufferExport ¶ms) { if(!m_Ctx.LogLoaded()) return; if(!m_Ctx.CurDrawcall()) return; if(!m_CurView) return; QString filter; if(params.format == BufferExport::CSV) filter = tr("CSV Files (*.csv)"); else if(params.format == BufferExport::RawBytes) filter = tr("Binary Files (*.bin)"); QString filename = RDDialog::getSaveFileName(this, tr("Export buffer to bytes"), QString(), tr("%1;;All files (*.*)").arg(filter)); if(filename.isEmpty()) return; QFile *f = new QFile(filename); if(!f->open(QIODevice::WriteOnly | QFile::Truncate)) { delete f; RDDialog::critical(this, tr("Error exporting file"), tr("Couldn't open file '%1' for writing").arg(filename)); return; } BufferItemModel *model = (BufferItemModel *)m_CurView->model(); LambdaThread *exportThread = new LambdaThread([this, params, model, f]() { if(params.format == BufferExport::RawBytes) { if(!m_MeshView) { // this is the simplest possible case, we just dump the contents of the first buffer, as // it's tightly packed f->write((const char *)model->buffers[0]->data, int(model->buffers[0]->end - model->buffers[0]->data)); } else { // cache column data for the inner loop QVector cache; CacheDataForIteration(cache, model->columns, model->buffers, model->curInstance); // go row by row, finding the start of the row and dumping out the elements using their // offset and sizes for(int i = 0; i < model->rowCount(); i++) { uint32_t idx = model->data(model->index(i, 1), Qt::DisplayRole).toUInt(); for(int col = 0; col < cache.count(); col++) { const CachedElData &d = cache[col]; const FormatElement *el = d.el; if(d.data) { const char *bytes = (const char *)d.data; if(!el->perinstance) bytes += d.stride * idx; if(bytes + d.byteSize <= (const char *)d.end) f->write(bytes, d.byteSize); } // if we didn't continue above, something was wrong, so write nulls f->write(d.nulls); } } } } else if(params.format == BufferExport::CSV) { // this works identically no matter whether we're mesh view or what, we just iterate the // elements and call the model's data() QTextStream s(f); for(int i = 0; i < model->columnCount(); i++) { s << model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); if(i + 1 < model->columnCount()) s << ", "; } s << "\n"; for(int row = 0; row < model->rowCount(); row++) { for(int col = 0; col < model->columnCount(); col++) { s << model->data(model->index(row, col), Qt::DisplayRole).toString(); if(col + 1 < model->columnCount()) s << ", "; } s << "\n"; } } f->close(); delete f; }); exportThread->start(); ShowProgressDialog(this, tr("Exporting data"), [exportThread]() { return !exportThread->isRunning(); }); exportThread->deleteLater(); } void BufferViewer::debugVertex() { if(!m_Ctx.LogLoaded()) return; if(!m_Ctx.CurDrawcall()) return; if(!m_CurView) return; QModelIndex idx = m_CurView->selectionModel()->currentIndex(); if(!idx.isValid()) { GUIInvoke::call([this]() { RDDialog::critical(this, tr("Error debugging"), tr("Error debugging vertex - make sure a valid vertex is selected")); }); return; } uint32_t vertid = m_CurView->model()->data(m_CurView->model()->index(idx.row(), 0), Qt::DisplayRole).toUInt(); uint32_t index = m_CurView->model()->data(m_CurView->model()->index(idx.row(), 1), Qt::DisplayRole).toUInt(); m_Ctx.Replay().AsyncInvoke([this, vertid, index](IReplayController *r) { ShaderDebugTrace *trace = r->DebugVertex(vertid, m_Config.curInstance, index, m_Ctx.CurDrawcall()->instanceOffset, m_Ctx.CurDrawcall()->vertexOffset); if(trace->states.count == 0) { r->FreeTrace(trace); GUIInvoke::call([this]() { RDDialog::critical(this, tr("Error debugging"), tr("Error debugging vertex - make sure a valid vertex is selected")); }); return; } GUIInvoke::call([this, vertid, trace]() { QString debugContext = tr("Vertex %1").arg(vertid); if(m_Ctx.CurDrawcall()->numInstances > 1) debugContext += tr(", Instance %1").arg(m_Config.curInstance); const ShaderReflection *shaderDetails = m_Ctx.CurPipelineState().GetShaderReflection(ShaderStage::Pixel); const ShaderBindpointMapping &bindMapping = m_Ctx.CurPipelineState().GetBindpointMapping(ShaderStage::Pixel); // viewer takes ownership of the trace IShaderViewer *s = m_Ctx.DebugShader(&bindMapping, shaderDetails, ShaderStage::Pixel, trace, debugContext); m_Ctx.AddDockWindow(s->Widget(), DockReference::AddTo, this); }); }); } void BufferViewer::SyncViews(RDTableView *primary, bool selection, bool scroll) { if(!ui->syncViews->isChecked()) return; RDTableView *views[] = {ui->vsinData, ui->vsoutData, ui->gsoutData}; if(primary == NULL) { for(RDTableView *table : views) { if(table->hasFocus()) { primary = table; break; } } } if(primary == NULL) primary = views[0]; for(RDTableView *table : views) { if(table == primary) continue; if(selection) { QModelIndexList selected = primary->selectionModel()->selectedRows(); if(!selected.empty()) table->selectRow(selected[0].row()); } if(scroll) table->verticalScrollBar()->setValue(primary->verticalScrollBar()->value()); } } void BufferViewer::UpdateHighlightVerts() { m_Config.highlightVert = ~0U; if(!ui->highlightVerts->isChecked()) return; RDTableView *table = currentTable(); if(!table) return; QModelIndexList selected = table->selectionModel()->selectedRows(); if(selected.empty()) return; m_Config.highlightVert = selected[0].row(); } void BufferViewer::EnableCameraGuessControls() { ui->aspectGuess->setEnabled(isCurrentRasterOut()); ui->nearGuess->setEnabled(isCurrentRasterOut()); ui->farGuess->setEnabled(isCurrentRasterOut()); } void BufferViewer::on_outputTabs_currentChanged(int index) { ui->renderContainer->parentWidget()->layout()->removeWidget(ui->renderContainer); ui->outputTabs->widget(index)->layout()->addWidget(ui->renderContainer); if(index == 0) m_CurStage = MeshDataStage::VSIn; else if(index == 1) m_CurStage = MeshDataStage::VSOut; else if(index == 2) m_CurStage = MeshDataStage::GSOut; ui->drawRange->setEnabled(index > 0); on_resetCamera_clicked(); ui->autofitCamera->setEnabled(!isCurrentRasterOut()); EnableCameraGuessControls(); UpdateMeshConfig(); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::on_toggleControls_toggled(bool checked) { ui->cameraControlsGroup->setVisible(checked); EnableCameraGuessControls(); } void BufferViewer::on_syncViews_toggled(bool checked) { SyncViews(NULL, true, true); } void BufferViewer::on_highlightVerts_toggled(bool checked) { UpdateHighlightVerts(); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::on_wireframeRender_toggled(bool checked) { m_Config.wireframeDraw = checked; INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::on_solidShading_currentIndexChanged(int index) { ui->wireframeRender->setEnabled(index > 0); if(!ui->wireframeRender->isEnabled()) { ui->wireframeRender->setChecked(true); m_Config.wireframeDraw = true; } m_Config.solidShadeMode = (SolidShade)index; m_ModelVSIn->setSecondaryColumn(m_ModelVSIn->secondaryColumn(), m_Config.solidShadeMode == SolidShade::Secondary, m_ModelVSIn->secondaryAlpha()); m_ModelVSOut->setSecondaryColumn(m_ModelVSOut->secondaryColumn(), m_Config.solidShadeMode == SolidShade::Secondary, m_ModelVSOut->secondaryAlpha()); m_ModelGSOut->setSecondaryColumn(m_ModelGSOut->secondaryColumn(), m_Config.solidShadeMode == SolidShade::Secondary, m_ModelGSOut->secondaryAlpha()); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::on_drawRange_currentIndexChanged(int index) { /* "Only this draw", "Show previous instances", "Show all instances", "Show whole pass" */ m_Config.showPrevInstances = (index >= 1); m_Config.showAllInstances = (index >= 2); m_Config.showWholePass = (index >= 3); INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::on_controlType_currentIndexChanged(int index) { m_Arcball->Reset(FloatVector(), 10.0f); m_Flycam->Reset(FloatVector()); if(index == 0) { m_CurrentCamera = m_Arcball; } else { m_CurrentCamera = m_Flycam; if(isCurrentRasterOut()) m_Flycam->Reset(FloatVector(0.0f, 0.0f, 0.0f, 0.0f)); else m_Flycam->Reset(FloatVector(0.0f, 0.0f, -10.0f, 0.0f)); } INVOKE_MEMFN(RT_UpdateAndDisplay); } void BufferViewer::on_resetCamera_clicked() { if(isCurrentRasterOut()) ui->controlType->setCurrentIndex(1); else ui->controlType->setCurrentIndex(0); // make sure callback is called even if we're re-selecting same // camera type on_controlType_currentIndexChanged(ui->controlType->currentIndex()); } void BufferViewer::on_camSpeed_valueChanged(double value) { m_Arcball->SpeedMultiplier = m_Flycam->SpeedMultiplier = value; } void BufferViewer::on_instance_valueChanged(int value) { m_Config.curInstance = value; OnEventChanged(m_Ctx.CurEvent()); } void BufferViewer::on_rowOffset_valueChanged(int value) { ScrollToRow(m_ModelVSIn, value); ScrollToRow(m_ModelVSOut, value); ScrollToRow(m_ModelGSOut, value); } void BufferViewer::on_autofitCamera_clicked() { if(m_CurStage != MeshDataStage::VSIn) return; ui->controlType->setCurrentIndex(1); BBoxData bbox; { QMutexLocker autolock(&m_BBoxLock); bbox = m_BBoxes[m_Ctx.CurEvent()]; } BufferItemModel *model = NULL; int stage = 0; switch(m_CurStage) { case MeshDataStage::VSIn: model = m_ModelVSIn; stage = 0; break; case MeshDataStage::VSOut: model = m_ModelVSOut; stage = 1; break; case MeshDataStage::GSOut: model = m_ModelGSOut; stage = 2; break; default: break; } if(bbox.bounds[stage].Min.isEmpty()) return; if(!model) return; int posEl = model->posColumn(); if(posEl < 0 || posEl >= bbox.bounds[stage].Min.count()) return; FloatVector diag; diag.x = bbox.bounds[stage].Max[posEl].x - bbox.bounds[stage].Min[posEl].x; diag.y = bbox.bounds[stage].Max[posEl].y - bbox.bounds[stage].Min[posEl].y; diag.z = bbox.bounds[stage].Max[posEl].z - bbox.bounds[stage].Min[posEl].z; float len = qSqrt(diag.x * diag.x + diag.y * diag.y + diag.z * diag.z); if(diag.x >= 0.0f && diag.y >= 0.0f && diag.z >= 0.0f && len >= 1.0e-6f && len <= 1.0e+10f) { FloatVector mid; mid.x = bbox.bounds[stage].Min[posEl].x + diag.x * 0.5f; mid.y = bbox.bounds[stage].Min[posEl].y + diag.y * 0.5f; mid.z = bbox.bounds[stage].Min[posEl].z + diag.z * 0.5f; mid.z -= len; m_Flycam->Reset(mid); } INVOKE_MEMFN(RT_UpdateAndDisplay); }