Add a filtering system for the constant buffer viewer. Closes #3889

This commit is contained in:
baldurk committed 2026-11-02 16:36:36 +00:00
1 parent aa42dd6e45
commit f039dcdefc
7 files changed
+298 -25

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

+10 -1
View File
@@ -9,7 +9,7 @@ When opening a buffer for view, it can display both fixed non-repeating "struct-
The format is defined initially by whatever reflection data is available. The format can be customised, as detailed in :doc:`../how/how_buffer_format`.
Constant buffers are assumed to be AoS data in all cases. Other views of buffers via non-constant bindings such as UAVs and storage buffers, or views opened directly from a buffer from the resource inspector, will default towards SoA data unless the layout explicitly declares a fixed set of SoA data.
Constant buffers are assumed to be SoA data in all cases. Other views of buffers via non-constant bindings such as UAVs and storage buffers, or views opened directly from a buffer from the resource inspector, will default towards AoS data unless the layout explicitly declares a fixed set of SoA data.
.. figure:: ../imgs/Screenshots/AOSBuffer.png
@@ -28,6 +28,15 @@ The initial values of the offset and length will be taken from the relevant bind
In addition to this, you can specify a row offset which is useful in remaining at the same row while watching the change in a buffer between different events.
Filtering in constant buffer view
---------------------------------
.. |filter| image:: ../imgs/icons/filter.png
When viewing a constant buffer and it is known that the data is only a small set of fixed data, an additional |filter| option is available.
This will add a filtering toolbar that allows a subset of the variables to be shown. The filter is a set of words, space separated, that by default match case insensitively against variables in the declared structure. Any variable that matches any of the words will be displayed. If the name of a structure is matched, all children of that structure will be displayed.
Exporting and display
---------------------
@@ -454,6 +454,52 @@ void RDTreeView::applyExpansion(const RDTreeViewExpansionState &state, const Exp
applyExpansionToRow(state, model()->index(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();
+4
View File
@@ -33,6 +33,7 @@
class RDTreeView;
typedef QSet<uint> RDTreeViewExpansionState;
typedef QList<uint> RDTreeViewSelectedItemState;
class RDTreeViewDelegate : public RichTextViewDelegate
{
@@ -129,6 +130,9 @@ public:
});
}
RDTreeViewSelectedItemState saveSelectedItemState();
void applySelectedItemState(RDTreeViewSelectedItemState state);
// Internally tracked expansions
RDTreeViewExpansionState &getInternalExpansion(uint expansionID)
{
+125 -13
View File
@@ -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<ShaderConstant> &consts,
const rdcarray<ShaderVariable> &vars)
const rdcarray<ShaderVariable> &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<QHBoxLayout *>(ui->meshToolbar->layout());
QHBoxLayout *hbox = qobject_cast<QHBoxLayout *>(ui->mainMeshToolbar->layout());
int i = 0;
for(; i < hbox->count(); i++)
+7 -2
View File
@@ -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<ShaderConstant> &consts, BufferData *buffer);
void UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOffset,
bool UI_AddFixedVariables(RDTreeWidgetItem *root, uint32_t baseOffset,
const rdcarray<ShaderConstant> &consts,
const rdcarray<ShaderVariable> &vars);
const rdcarray<ShaderVariable> &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;
+106 -9
View File
@@ -545,7 +545,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>267</width>
<width>257</width>
<height>321</height>
</rect>
</property>
@@ -795,12 +795,87 @@
<bool>false</bool>
</attribute>
</widget>
<widget class="QFrame" name="meshToolbar">
<widget class="QWidget" name="filterStrip" native="true">
<property name="geometry">
<rect>
<x>700</x>
<y>50</y>
<width>223</width>
<height>24</height>
</rect>
</property>
<layout class="QHBoxLayout" name="filterStripLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="filterStripIcon">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../Resources/resources.qrc">:/filter.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterStripLabel">
<property name="text">
<string>Filter</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="RDLineEdit" name="filterExpression"/>
</item>
<item>
<widget class="QToolButton" name="filterCaseSensitive">
<property name="toolTip">
<string>Filter with case-sensitivity</string>
</property>
<property name="text">
<string>Aa</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="mainMeshToolbar" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>10</y>
<width>767</width>
<width>1137</width>
<height>28</height>
</rect>
</property>
@@ -816,12 +891,6 @@
<height>28</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="spacing">
<number>2</number>
@@ -946,6 +1015,29 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="filterCBuffer">
<property name="toolTip">
<string>Filter constants by name</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../Resources/resources.qrc">
<normaloff>:/filter.png</normaloff>:/filter.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="resourceDetails">
<property name="toolTip">
@@ -1154,6 +1246,11 @@
<header>Widgets/BufferFormatSpecifier.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>RDLineEdit</class>
<extends>QLineEdit</extends>
<header>Widgets/Extended/RDLineEdit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../Resources/resources.qrc"/>