From f039dcdefcccc024244b110739b983ec78224c8a Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 2 Nov 2026 16:20:01 +0000 Subject: [PATCH] Add a filtering system for the constant buffer viewer. Closes #3889 --- docs/imgs/icons/filter.png | Bin 0 -> 800 bytes docs/window/buffer_viewer.rst | 11 +- qrenderdoc/Widgets/Extended/RDTreeView.cpp | 46 +++++++ qrenderdoc/Widgets/Extended/RDTreeView.h | 4 + qrenderdoc/Windows/BufferViewer.cpp | 138 +++++++++++++++++++-- qrenderdoc/Windows/BufferViewer.h | 9 +- qrenderdoc/Windows/BufferViewer.ui | 115 +++++++++++++++-- 7 files changed, 298 insertions(+), 25 deletions(-) create mode 100644 docs/imgs/icons/filter.png diff --git a/docs/imgs/icons/filter.png b/docs/imgs/icons/filter.png new file mode 100644 index 0000000000000000000000000000000000000000..227e8436ca33decf6c36bd3efc520756bb4ce98c GIT binary patch literal 800 zcmV+*1K<3KP)VHkeS{&auNEkzQW zq9q7IS_E}B{7cLNFUn@rT`}{bo9;@=ixjo4>r#lID1xA}%^aG}U`Z6@!XXY(t8LlY zIor!Hg-jw z-gC3wJ7;~XrpCI;s*0NOB0bnFL4ii;BJz-B0)|>SS!qF-74Y2c_xSJkxY`CiA@$*w zD`@Dt9`oF4_ib2@)s@>TYD&xn$jZu1kj<;3X6@EOuzU&=)AOFW(H>Xp@MK6u#q&*M z7zStVPlevxbhsGi*`wC>mNu)^M61w3(#cTM^^JHEA7|%1v%^jo6%1hus_lUy()nOI zG&9ugdhebb4=k;tK&Pd?B}5`D{67Eq%+SSM$vXlJv!jw1`7n5)@9XCUT&ve%X)S`l z!V;ziyZbsuy}ObXXq1z=7{8gnHAx#AN@qm`=(Jj`rY}FMPnQC_j|KR8Nx ee80uN0t^6@FjF1moG}Xk0000index(i, 0), 0, keygen); } +RDTreeViewSelectedItemState RDTreeView::saveSelectedItemState() +{ + RDTreeViewSelectedItemState ret; + QModelIndexList sel = selectionModel()->selectedIndexes(); + if(!sel.empty()) + { + QModelIndex i = sel[0].sibling(sel[0].row(), 0); + while(i.isValid()) + { + ret.insert(0, qHash(i.data(Qt::DisplayRole).toString())); + i = i.parent(); + } + } + + return ret; +} + +void RDTreeView::applySelectedItemState(RDTreeViewSelectedItemState state) +{ + QModelIndex cur; + + for(int i = 0; i < state.count(); i++) + { + bool found = false; + + for(int row = 0, count = model()->rowCount(cur); row < count; row++) + { + QModelIndex check = model()->index(row, 0, cur); + if(qHash(model()->data(check).toString()) == state[i]) + { + found = true; + cur = check; + break; + } + } + + if(!found) + return; + } + + QItemSelection sel(cur, cur.sibling(cur.row(), model()->columnCount() - 1)); + + scrollTo(cur); + selectionModel()->select(sel, QItemSelectionModel::ClearAndSelect); +} + void RDTreeView::copySelection() { QModelIndexList sel = selectionModel()->selectedRows(); diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.h b/qrenderdoc/Widgets/Extended/RDTreeView.h index b8cadc8c8..0fa3dac4a 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.h +++ b/qrenderdoc/Widgets/Extended/RDTreeView.h @@ -33,6 +33,7 @@ class RDTreeView; typedef QSet RDTreeViewExpansionState; +typedef QList RDTreeViewSelectedItemState; class RDTreeViewDelegate : public RichTextViewDelegate { @@ -129,6 +130,9 @@ public: }); } + RDTreeViewSelectedItemState saveSelectedItemState(); + void applySelectedItemState(RDTreeViewSelectedItemState state); + // Internally tracked expansions RDTreeViewExpansionState &getInternalExpansion(uint expansionID) { diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index 86d49c953..bde7a110d 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -41,6 +41,7 @@ #include "Widgets/CollapseGroupBox.h" #include "Widgets/ComputeDebugSelector.h" #include "Widgets/Extended/RDLabel.h" +#include "Widgets/Extended/RDLineEdit.h" #include "Widgets/Extended/RDSplitter.h" #include "Windows/Dialogs/AxisMappingDialog.h" #include "Windows/Dialogs/CameraControlsDialog.h" @@ -2629,6 +2630,33 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) ui->setFormat->setVisible(false); + QObject::connect(ui->fixedVars, &RDTreeWidget::keyPress, [this](QKeyEvent *ev) { + if(!IsCBufferView()) + return; + + if((ev->modifiers() & Qt::ControlModifier) && ev->key() == Qt::Key_F) + { + ui->filterCBuffer->setChecked(true); + } + }); + + ui->filterStrip->setVisible(false); + + QObject::connect(ui->filterExpression, &RDLineEdit::keyPress, [this](QKeyEvent *event) { + if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) + OnEventChanged(m_Ctx.CurEvent()); + if(event->key() == Qt::Key_Escape) + ui->filterCBuffer->setChecked(false); + }); + + m_FilterTimeout = new QTimer(this); + m_FilterTimeout->setInterval(600); + m_FilterTimeout->setSingleShot(true); + connect(m_FilterTimeout, &QTimer::timeout, [this]() { OnEventChanged(m_Ctx.CurEvent()); }); + + QObject::connect(ui->filterExpression, &RDLineEdit::textChanged, + [this](const QString &) { m_FilterTimeout->start(); }); + ui->controlType->setCurrentIndex(0); on_controlType_currentIndexChanged(0); @@ -2831,7 +2859,17 @@ void BufferViewer::SetupRawView() m_InnerSplitter->addWidget(m_FixedGroup); m_InnerSplitter->addWidget(m_RepeatedGroup); - m_VLayout->addWidget(ui->meshToolbar); + QFrame *meshToolbar = new QFrame(this); + meshToolbar->setFrameShape(QFrame::Panel); + meshToolbar->setFrameShadow(QFrame::Plain); + + QVBoxLayout *meshToolbarLayout = new QVBoxLayout(meshToolbar); + meshToolbarLayout->setSpacing(2); + meshToolbarLayout->setContentsMargins(0, 0, 0, 0); + meshToolbarLayout->addWidget(ui->mainMeshToolbar); + meshToolbarLayout->addWidget(ui->filterStrip); + + m_VLayout->addWidget(meshToolbar); // 0 will be variable, but set it to something here so QSplitter doesn't barf m_OuterSplitter->insertWidget(0, ui->inTable); m_OuterSplitter->insertWidget(1, ui->formatSpecifier); @@ -2971,12 +3009,22 @@ void BufferViewer::SetupMeshView() QObject::connect(ui->out2Table->horizontalHeader(), &QHeaderView::customContextMenuRequested, [this](const QPoint &pos) { meshHeaderMenu(MeshDataStage::GSOut, pos); }); + QFrame *meshToolbar = new QFrame(this); + meshToolbar->setFrameShape(QFrame::Panel); + meshToolbar->setFrameShadow(QFrame::Plain); + + QVBoxLayout *meshToolbarLayout = new QVBoxLayout(meshToolbar); + meshToolbarLayout->setSpacing(2); + meshToolbarLayout->setContentsMargins(0, 0, 0, 0); + meshToolbarLayout->addWidget(ui->mainMeshToolbar); + meshToolbarLayout->addWidget(ui->filterStrip); + QVBoxLayout *vertical = new QVBoxLayout(this); vertical->setSpacing(3); vertical->setContentsMargins(3, 3, 3, 3); - vertical->addWidget(ui->meshToolbar); + vertical->addWidget(meshToolbar); vertical->addWidget(ui->dockarea); QTimer *renderTimer = new QTimer(this); @@ -3977,6 +4025,8 @@ void BufferViewer::OnEventChanged(uint32_t eventId) RDTreeViewExpansionState state; ui->fixedVars->saveExpansion(state, 0); + RDTreeViewSelectedItemState selItemState = ui->fixedVars->saveSelectedItemState(); + ui->fixedVars->beginUpdate(); ui->fixedVars->clear(); @@ -3984,7 +4034,7 @@ void BufferViewer::OnEventChanged(uint32_t eventId) if(!vars.isEmpty()) { UI_AddFixedVariables(ui->fixedVars->invisibleRootItem(), 0, - bufdata->inConfig.fixedVars.type.members, vars); + bufdata->inConfig.fixedVars.type.members, vars, false); if(IsCBufferView() && !bufdata->cb.bytesBacked) UI_RemoveOffsets(ui->fixedVars->invisibleRootItem()); @@ -4009,6 +4059,8 @@ void BufferViewer::OnEventChanged(uint32_t eventId) ui->fixedVars->getInternalExpansion(qHash(ToQStr(m_CurCBuffer.shader))), 0); else ui->fixedVars->applyExpansion(state, 0); + + ui->fixedVars->applySelectedItemState(selItemState); } on_rowOffset_valueChanged(ui->rowOffset->value()); @@ -4288,11 +4340,19 @@ void BufferViewer::UI_AddTaskPayloads(RDTreeWidgetItem *root, size_t baseOffset, } } -void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOffset, +bool BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOffset, const rdcarray &consts, - const rdcarray &vars) + const rdcarray &vars, bool ignoreFilter) { + bool ret = false; + + Qt::CaseSensitivity filterCase = + ui->filterCaseSensitive->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive; + const bool showPadding = ui->showPadding->isChecked() && m_CurCBuffer.bytesBacked; + const QString filterExpression = ignoreFilter ? QString() : ui->filterExpression->text(); + const QStringList filterWords = + filterExpression.split(QRegularExpression(lit("\\s+")), Qt::SkipEmptyParts); if(consts.size() != vars.size()) qCritical() << "Shader variable mismatch"; @@ -4304,7 +4364,7 @@ void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOff const ShaderConstant &c = consts[idx]; const ShaderVariable &v = vars[idx]; - if(showPadding && c.byteOffset > offset) + if(showPadding && c.byteOffset > offset && filterWords.isEmpty()) { uint32_t size = c.byteOffset - offset; @@ -4319,6 +4379,7 @@ void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOff pad->setTag(QVariant::fromValue(FixedVarTag(size))); root->addChild(pad); + ret = true; offset = c.byteOffset; } @@ -4361,10 +4422,18 @@ void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOff n->setTag(QVariant::fromValue(FixedVarTag(v.name, baseOffset + c.byteOffset))); - root->addChild(n); - UI_FixedAddMatrixRows(n, c, v); + bool anyPassFilter = filterWords.empty(); + QString filterName = v.name; + for(QString filter : filterWords) + { + if(filterName.contains(filter, filterCase)) + anyPassFilter = true; + } + + const bool rootPassFilter = anyPassFilter; + // if it's an array the value (v) will be expanded with one element in each of v.members, but // the constant (c) will just have the type with a number of elements if(c.type.elements > 1) @@ -4393,7 +4462,9 @@ void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOff // because v.members[...].members will be the actual struct members because of the expansion if(c.type.baseType == VarType::Struct) { - UI_AddFixedVariables(el, elOffset, c.type.members, v.members[e].members); + if(UI_AddFixedVariables(el, elOffset, c.type.members, v.members[e].members, + rootPassFilter || ignoreFilter)) + anyPassFilter = true; } else { @@ -4409,7 +4480,7 @@ void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOff if(c.type.baseType == VarType::Struct && e + 1 == v.members.size()) break; - if(showPadding && c.type.arrayByteStride > elSize) + if(showPadding && c.type.arrayByteStride > elSize && filterWords.isEmpty()) { uint32_t size = c.type.arrayByteStride - elSize; @@ -4430,12 +4501,23 @@ void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOff // for single structs, recurse else if(v.type == VarType::Struct) { - UI_AddFixedVariables(n, c.byteOffset, c.type.members, v.members); + if(UI_AddFixedVariables(n, c.byteOffset, c.type.members, v.members, + rootPassFilter || ignoreFilter)) + anyPassFilter = true; } + ret = ret || anyPassFilter; + + if(anyPassFilter) + root->addChild(n); + else + delete n; + // advance by the tight scalar-packed advance, so we can detect padding offset += BufferFormatter::GetVarAdvance(PackingRules::Scalar(), c); } + + return ret; } void BufferViewer::UI_RemoveOffsets(RDTreeWidgetItem *root) @@ -5467,6 +5549,8 @@ void BufferViewer::updateLabelsAndLayout() { setWindowTitle(tr("Mesh Viewer")); + ui->filterCBuffer->setVisible(false); + if(m_Ctx.IsCaptureLoaded()) { GraphicsAPI pipeType = m_Ctx.APIProps().pipelineType; @@ -5596,10 +5680,14 @@ void BufferViewer::updateLabelsAndLayout() title += QFormatStr(" - %1").arg(bufName); + ui->filterCBuffer->setVisible(true); + setWindowTitle(title); } else { + ui->filterCBuffer->setVisible(false); + setWindowTitle(m_Ctx.GetResourceName(m_BufferID) + lit(" - Contents")); } } @@ -6165,6 +6253,30 @@ void BufferViewer::on_setFormat_toggled(bool checked) reflection->constantBlocks[m_CBufferSlot.slot].variables, 0)); } +void BufferViewer::on_filterCBuffer_toggled(bool checked) +{ + if(!checked) + { + ui->filterExpression->setText(QString()); + ui->filterStrip->setVisible(false); + } + else + { + ui->filterStrip->setVisible(true); + ui->filterExpression->setFocus(Qt::OtherFocusReason); + } + + if(m_FilterTimeout->isActive()) + m_FilterTimeout->stop(); + + OnEventChanged(m_Ctx.CurEvent()); +} + +void BufferViewer::on_filterCaseSensitive_toggled(bool checked) +{ + OnEventChanged(m_Ctx.CurEvent()); +} + void BufferViewer::on_resetMeshFilterButton_clicked() { SetMeshFilter(MeshFilter::None); @@ -6254,9 +6366,9 @@ void BufferViewer::processFormat(const QString &format) ui->rowOffsetLabel->setVisible(true); ui->rowOffset->setVisible(true); // insert after the offsetLine - if(ui->rowOffset->parentWidget() != ui->meshToolbar) + if(ui->rowOffset->parentWidget() != ui->mainMeshToolbar) { - QHBoxLayout *hbox = qobject_cast(ui->meshToolbar->layout()); + QHBoxLayout *hbox = qobject_cast(ui->mainMeshToolbar->layout()); int i = 0; for(; i < hbox->count(); i++) diff --git a/qrenderdoc/Windows/BufferViewer.h b/qrenderdoc/Windows/BufferViewer.h index 1f97e27d3..e7c30560f 100644 --- a/qrenderdoc/Windows/BufferViewer.h +++ b/qrenderdoc/Windows/BufferViewer.h @@ -40,6 +40,7 @@ class RDSpinBox64; class QItemSelection; class QMenu; class QPushButton; +class QTimer; class QVBoxLayout; class RDLabel; class RDTableView; @@ -146,6 +147,8 @@ private slots: void on_axisMappingCombo_currentIndexChanged(int index); void on_axisMappingButton_clicked(); void on_setFormat_toggled(bool checked); + void on_filterCBuffer_toggled(bool checked); + void on_filterCaseSensitive_toggled(bool checked); void on_resetMeshFilterButton_clicked(); void on_camParameters_clicked(); void on_guessButton_clicked(); @@ -213,9 +216,9 @@ private: void UI_AddTaskPayloads(RDTreeWidgetItem *root, size_t baseOffset, const rdcarray &consts, BufferData *buffer); - void UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOffset, + bool UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOffset, const rdcarray &consts, - const rdcarray &vars); + const rdcarray &vars, bool ignoreFilter); void UI_RemoveOffsets(RDTreeWidgetItem *root); void UI_FixedAddMatrixRows(RDTreeWidgetItem *n, const ShaderConstant &c, const ShaderVariable &v); @@ -339,6 +342,8 @@ private: QAction *m_RemoveFilter = NULL; QAction *m_GotoTask = NULL; + QTimer *m_FilterTimeout = NULL; + RDSpinBox64 *byteRangeStart = NULL; RDSpinBox64 *byteRangeLength = NULL; diff --git a/qrenderdoc/Windows/BufferViewer.ui b/qrenderdoc/Windows/BufferViewer.ui index 46de460fd..ab3d98a62 100644 --- a/qrenderdoc/Windows/BufferViewer.ui +++ b/qrenderdoc/Windows/BufferViewer.ui @@ -545,7 +545,7 @@ 0 0 - 267 + 257 321 @@ -795,12 +795,87 @@ false - + + + + 700 + 50 + 223 + 24 + + + + + 3 + + + 6 + + + 0 + + + 6 + + + 2 + + + + + + + + :/filter.png + + + + + + + Filter + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + + + + + + + + Filter with case-sensitivity + + + Aa + + + true + + + Qt::ToolButtonTextOnly + + + true + + + + + + 0 10 - 767 + 1137 28 @@ -816,12 +891,6 @@ 28 - - QFrame::Panel - - - QFrame::Raised - 2 @@ -946,6 +1015,29 @@ + + + + Filter constants by name + + + + + + + :/filter.png:/filter.png + + + true + + + Qt::ToolButtonIconOnly + + + true + + + @@ -1154,6 +1246,11 @@
Widgets/BufferFormatSpecifier.h
1 + + RDLineEdit + QLineEdit +
Widgets/Extended/RDLineEdit.h
+