Explicitly handle unbounded arrays and display declared fixed vars

* GL and Vulkan allow buffers to have fixed variables before a trailing AoS
  unbounded array. These fixed variables can't be easily displayed in a table
  and previously we skipped them. Now we display these in a tree format.
* We also support formats which don't have an unbounded array at all and display
  these just with the tree. This will allow the BufferViewer to subsume the
  capabilities of the ConstantBufferPreviewer (though it needs to handle opaque
  non-buffer-backed variables, and slot-following).
This commit is contained in:
baldurk
2022-05-20 13:37:26 +01:00
parent d0720fe787
commit a36516c8a5
12 changed files with 630 additions and 177 deletions
+236 -75
View File
@@ -365,8 +365,56 @@ Packing::Rules BufferFormatter::EstimatePackingRules(const rdcarray<ShaderConsta
return pack;
}
ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, uint64_t maxLen,
QString &errors)
bool BufferFormatter::ContainsUnbounded(const rdcarray<ShaderConstant> &members)
{
for(size_t i = 0; i < members.size(); i++)
{
if(members[i].type.descriptor.elements == ~0U)
return true;
if(ContainsUnbounded(members[i].type.members))
return true;
}
return false;
}
bool BufferFormatter::CheckInvalidUnbounded(const ShaderConstant &structDef, QString &errors)
{
for(size_t i = 0; i < structDef.type.members.size(); i++)
{
const bool isLast = i == structDef.type.members.size() - 1;
// if it's not the last member and it's unbounded, that's a problem!
if(!isLast && structDef.type.members[i].type.descriptor.elements == ~0U)
{
errors = tr("%1 in %2 can't be unbounded when not the last member.\n")
.arg(structDef.type.members[i].name)
.arg(structDef.type.descriptor.name.empty() ? "implicit_root"
: structDef.type.descriptor.name);
return false;
}
// if it's not the last member, no child can have an unbounded array
if(!isLast && ContainsUnbounded(structDef.type.members[i].type.members))
{
errors = tr("%1 in %2 can't have unbounded array in a child.\n")
.arg(structDef.type.members[i].name)
.arg(structDef.type.descriptor.name.empty() ? "implicit_root"
: structDef.type.descriptor.name);
return false;
}
if(!CheckInvalidUnbounded(structDef.type.members[i], errors))
return false;
}
return true;
}
rdcpair<ShaderConstant, ShaderConstant> BufferFormatter::ParseFormatString(const QString &formatString,
uint64_t maxLen,
QString &errors)
{
StructFormatData root;
StructFormatData *cur = &root;
@@ -397,7 +445,7 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
"(?<vec>[1-9])?" // might be a vector
"(?<mat>x[1-9])?" // or a matrix
"(?<name>\\s+[A-Za-z@_][A-Za-z0-9@_]*)?" // get identifier name
"(?<array>\\s*\\[[0-9]+\\])?" // optional array dimension
"(?<array>\\s*\\[[0-9]*\\])?" // optional array dimension
"(\\s*:\\s*" // optional specifier after :
"(" // bitfield or semantic
"(?<bitfield>[1-9][0-9]*)|" // bitfield packing
@@ -431,12 +479,12 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
QRegularExpression structDeclRegex(
lit("^(struct|enum)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s*:\\s*([a-z]+))?$"));
QRegularExpression structUseRegex(
lit("^" // start of the line
"([A-Za-z_][A-Za-z0-9_]*)" // struct type name
"\\s*(\\*)?" // maybe a pointer
"\\s+([A-Za-z@_][A-Za-z0-9@_]*)" // variable name
"(\\s*\\[[0-9]+\\])?" // optional array dimension
"(\\s*:\\s*([1-9][0-9]*))?" // optional bitfield packing
lit("^" // start of the line
"([A-Za-z_][A-Za-z0-9_]*)" // struct type name
"\\s*(\\*)?" // maybe a pointer
"\\s+([A-Za-z@_][A-Za-z0-9@_]*)?" // variable name
"(\\s*\\[[0-9]*\\])?" // optional array dimension
"(\\s*:\\s*([1-9][0-9]*))?" // optional bitfield packing
"$"));
QRegularExpression enumValueRegex(
lit("^" // start of the line
@@ -800,7 +848,10 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
bool isPointer = !structMatch.captured(2).trimmed().isEmpty();
QString varName = structMatch.captured(3);
QString varName = structMatch.captured(3).trimmed();
if(varName.isEmpty())
varName = lit("data");
uint32_t specifiedOffset = ~0U;
for(const Annotation &annot : annotations)
@@ -828,6 +879,8 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
if(!arrayDim.isEmpty())
{
arrayDim = arrayDim.mid(1, arrayDim.count() - 2);
if(arrayDim.isEmpty())
arrayDim = lit("%1").arg(~0U);
bool ok = false;
arrayCount = arrayDim.toUInt(&ok);
if(!ok)
@@ -993,7 +1046,13 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
QString arrayDim = !match.captured(lit("array")).isEmpty()
? match.captured(lit("array")).trimmed()
: lit("[1]");
arrayDim = arrayDim.mid(1, arrayDim.count() - 2);
{
bool isArray = !arrayDim.isEmpty();
arrayDim = arrayDim.mid(1, arrayDim.count() - 2).trimmed();
if(isArray && arrayDim.isEmpty())
arrayDim = lit("%1").arg(~0U);
}
const bool isUnsigned = match.captured(lit("sign")).trimmed() == lit("unsigned");
@@ -1505,9 +1564,122 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
if(success && root.structDef.type.members.isEmpty() && !lastStruct.isEmpty())
root = structelems[lastStruct];
// on D3D it's not possible to have anything but AoS, no preamble, so always consider the root
// member to be the struct definition of an array, as long as we're declaring a UAV
if(IsD3D(m_API) && pack != Packing::D3DCB)
{
// if there's already only one root member just make it infinite
if(root.structDef.type.members.size() == 1)
{
root.structDef.type.members[0].type.descriptor.elements = ~0U;
}
else if(root.structDef.type.members.empty())
{
// do nothing, we'll hit the invalid failure case below
}
else
{
// otherwise wrap a struct around the members, to be the infinite AoS
rdcarray<ShaderConstant> inners;
inners.swap(root.structDef.type.members);
ShaderConstant el;
el.byteOffset = 0;
el.type.descriptor.type = VarType::Struct;
el.type.descriptor.elements = ~0U;
el.type.descriptor.arrayByteStride = root.structDef.type.descriptor.arrayByteStride;
root.structDef.type.members.push_back(el);
inners.swap(root.structDef.type.members[0].type.members);
}
}
root.structDef.type.descriptor.arrayByteStride =
AlignUp(root.offset, GetAlignment(pack, root.structDef));
if(!root.structDef.type.members.isEmpty() &&
root.structDef.type.members.back().type.descriptor.elements == ~0U)
{
root.structDef.type.descriptor.arrayByteStride =
AlignUp(root.structDef.type.members.back().type.descriptor.arrayByteStride,
GetAlignment(pack, root.structDef));
}
if(success)
{
// check that unbounded arrays are only the last member of each struct. Doing this separately
// makes the below check easier since we only have to consider last members
if(!CheckInvalidUnbounded(root.structDef, errors))
{
if(IsD3D(m_API) && pack != Packing::D3DCB)
errors += tr("D3D structured buffers always define an unbounded array.\n");
success = false;
}
}
// we only allow one 'infinite' array. You can't have an infinite member inside an already
// infinite struct. E.g. this is invalid:
//
// struct foo {
// uint a;
// float b[];
// };
//
// foo data[];
//
// but it's valid to have either this:
//
// struct foo {
// uint a;
// float b[3];
// };
//
// foo data[];
//
// or this:
//
// struct foo {
// uint a;
// float b[];
// };
//
// foo data;
if(success)
{
ShaderConstant *iter = &root.structDef;
bool foundInfinite = false;
QString infiniteArrayName;
while(iter)
{
if(iter->type.descriptor.elements == ~0U)
{
if(foundInfinite)
{
success = false;
errors = tr("Can't have unbounded %1[] as child of an unbounded %2[].\n")
.arg(iter->name)
.arg(infiniteArrayName);
if(IsD3D(m_API) && pack != Packing::D3DCB)
errors += tr("D3D structured buffers always define an unbounded array.\n");
break;
}
infiniteArrayName = iter->type.descriptor.name;
if(infiniteArrayName.isEmpty())
infiniteArrayName = tr("implicit_root");
foundInfinite = true;
}
// if there are no more members, stop looking
if(iter->type.members.empty())
break;
iter = &iter->type.members.back();
}
}
if(!success || root.structDef.type.members.isEmpty())
{
root.structDef.type.members.clear();
@@ -1519,6 +1691,7 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
el.name = "data";
el.type.descriptor.type = VarType::UInt;
el.type.descriptor.columns = 4;
el.type.descriptor.elements = ~0U;
if(maxLen > 0 && maxLen < 16)
el.type.descriptor.columns = 1;
@@ -1532,7 +1705,41 @@ ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, u
root.structDef.type.descriptor.arrayByteStride = el.type.descriptor.arrayByteStride;
}
return root.structDef;
// split the struct definition we have now into fixed and repeating. We've enforced above that
// there's only one struct which is unbounded (no other children at any level are unbounded) and
// it's the last member, so we find it, remove it from the hierarchy, and present it separately.
ShaderConstant repeat;
{
ShaderConstant *iter = &root.structDef;
rdcstr addedprefix;
while(iter)
{
// if there are no more members, stop looking, there's no repeated member
if(iter->type.members.empty())
break;
// add the prefix, so that the repeated element that's a child like buffer { foo { blah[] } }
// shows up as buffer.foo.blah
if(!iter->name.empty())
addedprefix += iter->name + ".";
// we want to search the members so we can remove from the current iter
if(iter->type.members.back().type.descriptor.elements == ~0U)
{
repeat = iter->type.members.back();
repeat.name = addedprefix + repeat.name;
repeat.type.descriptor.elements = 1;
iter->type.members.pop_back();
break;
}
iter = &iter->type.members.back();
}
}
return {root.structDef, repeat};
}
QString BufferFormatter::GetTextureFormatString(const TextureDescription &tex)
@@ -1661,73 +1868,19 @@ QString BufferFormatter::GetTextureFormatString(const TextureDescription &tex)
}
QString BufferFormatter::GetBufferFormatString(Packing::Rules pack, const ShaderResource &res,
const ResourceFormat &viewFormat,
uint64_t &baseByteOffset)
const ResourceFormat &viewFormat)
{
QString format;
if(!res.variableType.members.empty())
{
QString structName = res.variableType.descriptor.name;
if(structName.isEmpty())
structName = lit("el");
QList<QString> declaredStructs;
if(m_API == GraphicsAPI::Vulkan || m_API == GraphicsAPI::OpenGL)
{
const rdcarray<ShaderConstant> &members = res.variableType.members;
// if there is only one member in the root array, we can just call DeclareStruct directly
if(members.count() <= 1)
{
format = DeclareStruct(pack, declaredStructs, res.name, members, 0, QString());
}
else
{
// otherwise we need to build up the comment indicating which fixed-size members we
// skipped
QString fixedPrefixString = tr(" // members skipped as they are fixed size:\n");
baseByteOffset += members.back().byteOffset;
// list each member before the last, commented out.
for(int i = 0; i < members.count() - 1; i++)
{
QString arraySize;
if(members[i].type.descriptor.elements > 1 && members[i].type.descriptor.elements != ~0U)
arraySize = QFormatStr("[%1]").arg(members[i].type.descriptor.elements);
QString varName = members[i].name;
if(varName.isEmpty())
varName = QFormatStr("_child%1").arg(i);
fixedPrefixString += QFormatStr(" // %1 %2%3;\n")
.arg(members[i].type.descriptor.name)
.arg(varName)
.arg(arraySize);
}
fixedPrefixString +=
lit(" // final array struct @ byte offset %1\n").arg(members.back().byteOffset);
// construct a fake list of members with only the last arrayed one, to pass to
// DeclareStruct
rdcarray<ShaderConstant> fakeLastMember;
fakeLastMember.push_back(members.back());
// rebase offset of this member to 0 so that DeclareStruct doesn't think any padding is
// needed
fakeLastMember[0].byteOffset = 0;
if(fakeLastMember[0].type.descriptor.elements != ~0U)
fakeLastMember[0].type.descriptor.arrayByteStride *=
fakeLastMember[0].type.descriptor.elements;
format = DeclareStruct(pack, declaredStructs, res.name, fakeLastMember,
fakeLastMember[0].type.descriptor.arrayByteStride, fixedPrefixString);
}
}
else
{
format = DeclareStruct(pack, declaredStructs, res.variableType.descriptor.name,
res.variableType.members, 0, QString());
}
format = DeclareStruct(pack, declaredStructs, structName, res.variableType.members, 0, QString());
format = QFormatStr("%1\n\n%2").arg(DeclarePacking(pack)).arg(format);
}
else
@@ -2013,8 +2166,13 @@ QString BufferFormatter::DeclareStruct(Packing::Rules pack, QList<QString> &decl
}
QString arraySize;
if(members[i].type.descriptor.elements > 1 && members[i].type.descriptor.elements != ~0U)
arraySize = QFormatStr("[%1]").arg(members[i].type.descriptor.elements);
if(members[i].type.descriptor.elements > 1)
{
if(members[i].type.descriptor.elements != ~0U)
arraySize = QFormatStr("[%1]").arg(members[i].type.descriptor.elements);
else
arraySize = lit("[]");
}
QString varTypeName = members[i].type.descriptor.name;
@@ -2279,6 +2437,9 @@ ShaderVariable InterpretShaderVar(const ShaderConstant &elem, const byte *data,
ret.members = members;
}
}
else if(elem.type.descriptor.type == VarType::Struct && elem.type.members.isEmpty())
{
}
else if(elem.type.descriptor.elements > 1 && elem.type.descriptor.elements != ~0U)
{
rdcarray<ShaderVariable> arrayElements;
+6 -4
View File
@@ -141,7 +141,7 @@ struct Rules
vector_straddle_16b == o.vector_straddle_16b && tight_arrays == o.tight_arrays &&
trailing_overlap == o.trailing_overlap;
}
bool operator!=(Packing::Rules o) const { return !(*this == o); }
// is a vector's alignment equal to its component alignment? If not, vectors must have an
// a larger alignment e.g. for floats a float2 has 8 byte alignment, float3 and float4 have
// 16-byte alignment
@@ -198,14 +198,16 @@ public:
BufferFormatter() = default;
static void Init(GraphicsAPI api) { m_API = api; }
static ShaderConstant ParseFormatString(const QString &formatString, uint64_t maxLen,
QString &errors);
static rdcpair<ShaderConstant, ShaderConstant> ParseFormatString(const QString &formatString,
uint64_t maxLen, QString &errors);
static bool CheckInvalidUnbounded(const ShaderConstant &structDef, QString &errors);
static bool ContainsUnbounded(const rdcarray<ShaderConstant> &members);
static Packing::Rules EstimatePackingRules(const rdcarray<ShaderConstant> &members);
static QString GetTextureFormatString(const TextureDescription &tex);
static QString GetBufferFormatString(Packing::Rules pack, const ShaderResource &res,
const ResourceFormat &viewFormat, uint64_t &baseByteOffset);
const ResourceFormat &viewFormat);
static QString DeclareStruct(Packing::Rules pack, const QString &name,
const rdcarray<ShaderConstant> &members, uint32_t requiredByteStride);
+82 -50
View File
@@ -28,7 +28,7 @@
#include <QPainter>
RDSplitterHandle::RDSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
: QSplitterHandle(orientation, parent), m_index(-1), m_isCollapsed(false)
: QSplitterHandle(orientation, parent), m_parent(parent), m_index(-1), m_isCollapsed(false)
{
}
@@ -72,71 +72,100 @@ void RDSplitterHandle::paintEvent(QPaintEvent *event)
int w = width();
int h = height();
if(orientation() == Qt::Vertical)
if(!m_title.isEmpty())
{
painter.drawText(QRect(0, 0, w, 25), Qt::AlignHCenter, m_title);
}
else
{
painter.drawText(QRect(0, h / 2 - 12, w, 25), Qt::AlignHCenter, m_title);
if(orientation() == Qt::Vertical)
{
painter.drawText(QRect(0, 0, w, 25), Qt::AlignHCenter, m_title);
}
else
{
painter.drawText(QRect(0, h / 2 - 12, w, 25), Qt::AlignHCenter, m_title);
}
}
painter.setRenderHint(QPainter::Antialiasing, true);
// draw the arrow
if(orientation() == Qt::Vertical)
if(m_parent->childrenCollapsible())
{
if(m_isCollapsed)
{
m_arrowPoints[0] = QPoint(w / 2, h - 9);
m_arrowPoints[1] = QPoint(w / 2 - 10, h - 1);
m_arrowPoints[2] = QPoint(w / 2 + 10, h - 1);
}
else
{
m_arrowPoints[0] = QPoint(w / 2, h - 1);
m_arrowPoints[1] = QPoint(w / 2 - 10, h - 9);
m_arrowPoints[2] = QPoint(w / 2 + 10, h - 9);
}
}
else
{
if(m_isCollapsed)
{
m_arrowPoints[0] = QPoint(w - 9, h / 2 + 15);
m_arrowPoints[1] = QPoint(w - 1, h / 2 + 5);
m_arrowPoints[2] = QPoint(w - 1, h / 2 + 20);
}
else
{
m_arrowPoints[0] = QPoint(w - 1, h / 2 + 15);
m_arrowPoints[1] = QPoint(w - 9, h / 2 + 5);
m_arrowPoints[2] = QPoint(w - 9, h / 2 + 25);
}
}
bool arrowUpLeft = (m_index == 0);
painter.drawPolygon(m_arrowPoints, 3);
if(m_isCollapsed)
arrowUpLeft = !arrowUpLeft;
if(orientation() == Qt::Vertical)
{
if(arrowUpLeft)
{
m_arrowPoints[0] = QPoint(w / 2, h - 9);
m_arrowPoints[1] = QPoint(w / 2 - 10, h - 1);
m_arrowPoints[2] = QPoint(w / 2 + 10, h - 1);
}
else
{
m_arrowPoints[0] = QPoint(w / 2, h - 1);
m_arrowPoints[1] = QPoint(w / 2 - 10, h - 9);
m_arrowPoints[2] = QPoint(w / 2 + 10, h - 9);
}
}
else
{
if(arrowUpLeft)
{
m_arrowPoints[0] = QPoint(w - 9, h / 2 + 15);
m_arrowPoints[1] = QPoint(w - 1, h / 2 + 5);
m_arrowPoints[2] = QPoint(w - 1, h / 2 + 20);
}
else
{
m_arrowPoints[0] = QPoint(w - 1, h / 2 + 15);
m_arrowPoints[1] = QPoint(w - 9, h / 2 + 5);
m_arrowPoints[2] = QPoint(w - 9, h / 2 + 25);
}
}
painter.drawPolygon(m_arrowPoints, 3);
}
// draw the bullets
if(orientation() == Qt::Vertical)
{
painter.drawEllipse(QPoint(w / 4 - 10, h - 10), 3, 3);
painter.drawEllipse(QPoint(w / 4, h - 10), 3, 3);
painter.drawEllipse(QPoint(w / 4 + 10, h - 10), 3, 3);
// in the middle if we're not collapsible in one direction
int y = h / 2;
painter.drawEllipse(QPoint(3 * w / 4 - 10, h - 10), 3, 3);
painter.drawEllipse(QPoint(3 * w / 4, h - 10), 3, 3);
painter.drawEllipse(QPoint(3 * w / 4 + 10, h - 10), 3, 3);
// or away from the arrow
if(m_index == 0)
y = 10;
else if(m_index == 1)
y = h - 10;
painter.drawEllipse(QPoint(w / 4 - 10, y), 3, 3);
painter.drawEllipse(QPoint(w / 4, y), 3, 3);
painter.drawEllipse(QPoint(w / 4 + 10, y), 3, 3);
painter.drawEllipse(QPoint(3 * w / 4 - 10, y), 3, 3);
painter.drawEllipse(QPoint(3 * w / 4, y), 3, 3);
painter.drawEllipse(QPoint(3 * w / 4 + 10, y), 3, 3);
}
else
{
painter.drawEllipse(QPoint(w - 10, h / 4 - 10), 3, 3);
painter.drawEllipse(QPoint(w - 10, h / 4), 3, 3);
painter.drawEllipse(QPoint(w - 10, h / 4 + 10), 3, 3);
// in the middle if we're not collapsible in one direction
int x = w / 2;
painter.drawEllipse(QPoint(w - 10, 3 * h / 4 - 10), 3, 3);
painter.drawEllipse(QPoint(w - 10, 3 * h / 4), 3, 3);
painter.drawEllipse(QPoint(w - 10, 3 * h / 4 + 10), 3, 3);
// or away from the arrow
if(m_index == 0)
x = 10;
else if(m_index == 1)
x = w - 10;
painter.drawEllipse(QPoint(x, h / 4 - 10), 3, 3);
painter.drawEllipse(QPoint(x, h / 4), 3, 3);
painter.drawEllipse(QPoint(x, h / 4 + 10), 3, 3);
painter.drawEllipse(QPoint(x, 3 * h / 4 - 10), 3, 3);
painter.drawEllipse(QPoint(x, 3 * h / 4), 3, 3);
painter.drawEllipse(QPoint(x, 3 * h / 4 + 10), 3, 3);
}
}
@@ -159,7 +188,7 @@ RDSplitter::RDSplitter(QWidget *parent) : QSplitter(parent)
void RDSplitter::handleDoubleClicked(int index)
{
if(index < 0 || index >= count())
if(index < 0 || index >= count() || !childrenCollapsible())
return;
RDSplitterHandle *rdHandle = (RDSplitterHandle *)handle(index);
@@ -184,6 +213,9 @@ void RDSplitter::handleDoubleClicked(int index)
void RDSplitter::setHandleCollapsed(int pos, int index)
{
if(!childrenCollapsible())
return;
QList<int> totalSizes = sizes();
RDSplitterHandle *rdHandle = (RDSplitterHandle *)handle(index);
if(totalSizes[index] == 0)
+1
View File
@@ -50,6 +50,7 @@ protected:
virtual void paintEvent(QPaintEvent *event);
virtual void mouseDoubleClickEvent(QMouseEvent *event);
QSplitter *m_parent;
QString m_title;
int m_index;
bool m_isCollapsed;
+245 -37
View File
@@ -32,10 +32,13 @@
#include <QMutexLocker>
#include <QPushButton>
#include <QScrollBar>
#include <QSplitter>
#include <QTimer>
#include <QtMath>
#include "Code/QRDUtils.h"
#include "Code/Resources.h"
#include "Widgets/CollapseGroupBox.h"
#include "Widgets/Extended/RDSplitter.h"
#include "Windows/Dialogs/AxisMappingDialog.h"
#include "ui_BufferViewer.h"
@@ -466,7 +469,9 @@ struct BufferConfiguration
uint32_t numRows = 0, unclampedNumRows = 0;
uint32_t pagingOffset = 0;
uint32_t formatStride = 0;
ShaderConstant fixedVars;
uint32_t repeatStride = 1;
uint32_t repeatOffset = 0;
QString noDraw;
@@ -501,6 +506,10 @@ struct BufferConfiguration
unclampedNumRows = o.unclampedNumRows;
pagingOffset = o.pagingOffset;
fixedVars = o.fixedVars;
repeatStride = o.repeatStride;
repeatOffset = o.repeatOffset;
noDraw = o.noDraw;
noVertices = o.noVertices;
@@ -2052,7 +2061,10 @@ static void UnrollConstant(rdcstr prefix, uint32_t baseOffset, const ShaderConst
}
// struct, expand by members
for(uint32_t a = 0; a < qMax(1U, constant.type.descriptor.elements); a++)
uint32_t arraySize = qMax(1U, constant.type.descriptor.elements);
if(arraySize == ~0U)
arraySize = 1U;
for(uint32_t a = 0; a < arraySize; a++)
{
for(const ShaderConstant &child : constant.type.members)
{
@@ -2272,8 +2284,8 @@ void BufferViewer::SetupRawView()
ui->viewIndex->setVisible(false);
ui->dockarea->setVisible(false);
ui->vsinData->setWindowTitle(tr("Buffer Contents"));
ui->vsinData->setFrameShape(QFrame::NoFrame);
ui->fixedVars->setFrameShape(QFrame::NoFrame);
ui->vsinData->setPinnedColumns(1);
ui->vsinData->setColumnGroupRole(columnGroupRole);
@@ -2293,14 +2305,63 @@ void BufferViewer::SetupRawView()
processFormat(format);
});
QVBoxLayout *vertical = new QVBoxLayout(this);
ui->fixedVars->setColumns({tr("Name"), tr("Value"), tr("Type")});
{
ui->fixedVars->header()->setSectionResizeMode(0, QHeaderView::Interactive);
ui->fixedVars->header()->setSectionResizeMode(1, QHeaderView::Interactive);
ui->fixedVars->header()->setSectionResizeMode(2, QHeaderView::Interactive);
}
vertical->setSpacing(3);
vertical->setContentsMargins(3, 3, 3, 3);
ui->fixedVars->setFont(Formatter::FixedFont());
vertical->addWidget(ui->meshToolbar);
vertical->addWidget(ui->vsinData);
vertical->addWidget(ui->formatSpecifier);
m_FixedGroup = new CollapseGroupBox(this);
m_RepeatedGroup = new CollapseGroupBox(this);
m_RepeatedControlBar = new QFrame(this);
m_RepeatedControlBar->setFrameShape(QFrame::Panel);
m_RepeatedControlBar->setFrameShadow(QFrame::Raised);
QHBoxLayout *controlLayout = new QHBoxLayout(m_RepeatedControlBar);
controlLayout->setSpacing(2);
controlLayout->setContentsMargins(6, 2, 6, 2);
controlLayout->addItem(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum));
QVBoxLayout *fixedLayout = new QVBoxLayout(m_FixedGroup);
fixedLayout->setSpacing(0);
fixedLayout->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *repeatedLayout = new QVBoxLayout(m_RepeatedGroup);
repeatedLayout->setSpacing(3);
repeatedLayout->setContentsMargins(0, 0, 0, 0);
repeatedLayout->addWidget(m_RepeatedControlBar);
m_FixedGroup->setTitle(tr("Fixed SoA data"));
m_RepeatedGroup->setTitle(tr("Repeated AoS values"));
m_VLayout = new QVBoxLayout(this);
m_VLayout->setSpacing(3);
m_VLayout->setContentsMargins(3, 3, 3, 3);
m_OuterSplitter = new RDSplitter(Qt::Vertical, this);
m_OuterSplitter->setHandleWidth(12);
m_OuterSplitter->setChildrenCollapsible(false);
m_InnerSplitter = new RDSplitter(Qt::Vertical, this);
m_InnerSplitter->setHandleWidth(12);
m_InnerSplitter->setChildrenCollapsible(false);
// inner splitter is only used when we have these groups, so we can add these unconditionally
m_InnerSplitter->addWidget(m_FixedGroup);
m_InnerSplitter->addWidget(m_RepeatedGroup);
m_VLayout->addWidget(ui->meshToolbar);
// 0 will be variable, but set it to something here so QSplitter doesn't barf
m_OuterSplitter->insertWidget(0, ui->vsinData);
m_OuterSplitter->insertWidget(1, ui->formatSpecifier);
m_VLayout->addWidget(m_OuterSplitter);
}
void BufferViewer::SetupMeshView()
@@ -2314,6 +2375,8 @@ void BufferViewer::SetupMeshView()
ui->byteRangeLengthLabel->setVisible(false);
byteRangeLength->setVisible(false);
ui->fixedVars->setVisible(false);
ui->resourceDetails->setVisible(false);
ui->formatSpecifier->setVisible(false);
ui->cameraControlsGroup->setVisible(false);
@@ -2670,11 +2733,17 @@ void BufferViewer::OnEventChanged(uint32_t eventId)
else
{
QString errors;
ShaderConstant constant = BufferFormatter::ParseFormatString(m_Format, m_ByteSize, errors);
ShaderConstant repeating;
rdctie(bufdata->vsinConfig.fixedVars, repeating) =
BufferFormatter::ParseFormatString(m_Format, m_ByteSize, errors);
UnrollConstant(constant, bufdata->vsinConfig.columns, bufdata->vsinConfig.props);
if(repeating.type.descriptor.type != VarType::Unknown)
{
bufdata->vsinConfig.repeatStride = repeating.type.descriptor.arrayByteStride;
bufdata->vsinConfig.repeatOffset = repeating.byteOffset;
bufdata->vsinConfig.formatStride = constant.type.descriptor.arrayByteStride;
UnrollConstant(repeating, bufdata->vsinConfig.columns, bufdata->vsinConfig.props);
}
ClearModels();
}
@@ -2735,16 +2804,21 @@ void BufferViewer::OnEventChanged(uint32_t eventId)
buf = new BufferData;
// calculate tight stride
buf->stride = std::max(1U, bufdata->vsinConfig.formatStride);
buf->stride = std::max(1U, bufdata->vsinConfig.repeatStride);
// the "permanent" range starts at ByteOffset and goes for m_ByteSize
uint64_t rangeStart = m_ByteOffset;
uint64_t rangeEnd = m_ByteOffset + m_ByteSize;
// we want to fetch the data for fixed and repeated sections (either of which might be 0)
// but calculate the number of rows etc for the repeated sections based on just the data
// available for it
const uint64_t fixedLength = bufdata->vsinConfig.repeatOffset;
// the "permanent" repeated range starts after the fixed data and goes for m_ByteSize
uint64_t repeatedRangeStart = m_ByteOffset + fixedLength;
uint64_t repeatedRangeEnd = m_ByteOffset + m_ByteSize;
// if the byte size is unbounded, the end is unbounded - fix the potential overflow from
// adding the offset
if(m_ByteSize == UINT64_MAX)
rangeEnd = UINT64_MAX;
repeatedRangeEnd = UINT64_MAX;
// get the underlying buffer length
uint64_t bufferLength = 0;
@@ -2757,34 +2831,46 @@ void BufferViewer::OnEventChanged(uint32_t eventId)
}
// clamp the range to the buffer length, which may end up with it being empty
rangeEnd = qMin(rangeEnd, bufferLength);
rangeStart = qMin(rangeStart, bufferLength);
repeatedRangeEnd = qMin(repeatedRangeEnd, bufferLength);
repeatedRangeStart = qMin(repeatedRangeStart, bufferLength);
// store the number of rows unclamped without the paging window
bufdata->vsinConfig.unclampedNumRows =
uint32_t((rangeEnd - rangeStart + buf->stride - 1) / buf->stride);
uint32_t((repeatedRangeEnd - repeatedRangeStart + buf->stride - 1) / buf->stride);
// advance the range by the paging offset
rangeStart = qMin(rangeEnd, rangeStart + m_PagingByteOffset);
repeatedRangeStart = qMin(repeatedRangeEnd, repeatedRangeStart + m_PagingByteOffset);
// calculate the length clamped to the MaxVisibleRows
uint64_t clampedLength =
qMin(rangeEnd - rangeStart, uint64_t(buf->stride * (MaxVisibleRows + 2)));
const uint64_t clampedRepeatedLength =
qMin(repeatedRangeEnd - repeatedRangeStart, uint64_t(buf->stride * (MaxVisibleRows + 2)));
if(m_IsBuffer)
{
if(clampedLength > 0)
buf->storage = r->GetBufferData(m_BufferID, CurrentByteOffset(), clampedLength);
if(repeatedRangeStart > fixedLength)
{
// if the repeated range subsection we're fetching is paged further in, we still need to
// fetch the fixed data from the 'start'
buf->storage = r->GetBufferData(m_BufferID, m_ByteOffset, fixedLength);
// then append the data from where we're paged to
buf->storage.append(r->GetBufferData(m_BufferID, repeatedRangeStart, clampedRepeatedLength));
}
else
{
// otherwise we can fetch it all at once
buf->storage =
r->GetBufferData(m_BufferID, m_ByteOffset, fixedLength + clampedRepeatedLength);
}
}
else
{
buf->storage = r->GetTextureData(m_BufferID, m_TexSub);
}
uint32_t bufCount = uint32_t(buf->size());
uint32_t repeatedDataAvailable = uint32_t(buf->size() - fixedLength);
bufdata->vsinConfig.pagingOffset = uint32_t(m_PagingByteOffset / buf->stride);
bufdata->vsinConfig.numRows = uint32_t((bufCount + buf->stride - 1) / buf->stride);
bufdata->vsinConfig.numRows = uint32_t((repeatedDataAvailable + buf->stride - 1) / buf->stride);
// ownership passes to model
bufdata->vsinConfig.buffers.push_back(buf);
@@ -2874,6 +2960,37 @@ void BufferViewer::OnEventChanged(uint32_t eventId)
if(!m_MeshView)
{
{
ShaderVariable var = InterpretShaderVar(bufdata->vsinConfig.fixedVars,
bufdata->vsinConfig.buffers[0]->storage.begin(),
bufdata->vsinConfig.buffers[0]->storage.end());
bool wasEmpty = ui->fixedVars->topLevelItemCount() == 0;
RDTreeViewExpansionState state;
ui->fixedVars->saveExpansion(state, 0);
ui->fixedVars->beginUpdate();
ui->fixedVars->clear();
if(!var.members.isEmpty())
UI_AddFixedVariables(ui->fixedVars->invisibleRootItem(), var.members);
ui->fixedVars->endUpdate();
if(wasEmpty)
{
// Expand before resizing so that collapsed data will already be visible when expanded
ui->fixedVars->expandAll();
for(int i = 0; i < ui->fixedVars->header()->count(); i++)
ui->fixedVars->resizeColumnToContents(i);
ui->fixedVars->collapseAll();
}
ui->fixedVars->applyExpansion(state, 0);
}
on_rowOffset_valueChanged(ui->rowOffset->value());
const bool prev = (bufdata->vsinConfig.pagingOffset > 0);
@@ -3005,6 +3122,26 @@ void BufferViewer::setPersistData(const QVariant &persistData)
}
}
void BufferViewer::UI_AddFixedVariables(RDTreeWidgetItem *root, const rdcarray<ShaderVariable> &vars)
{
for(const ShaderVariable &v : vars)
{
RDTreeWidgetItem *n = new RDTreeWidgetItem({v.name, VarString(v), TypeString(v)});
root->addChild(n);
if(v.rows > 1)
{
for(uint32_t i = 0; i < v.rows; i++)
n->addChild(new RDTreeWidgetItem(
{QFormatStr("%1.row%2").arg(v.name).arg(i), RowString(v, i), RowTypeString(v)}));
}
if(!v.members.isEmpty())
UI_AddFixedVariables(n, v.members);
}
}
void BufferViewer::calcBoundingData(CalcBoundingBoxData &bbox)
{
for(size_t stage = 0; stage < ARRAY_COUNT(bbox.input); stage++)
@@ -3219,11 +3356,6 @@ void BufferViewer::UI_ResetArcball()
INVOKE_MEMFN(RT_UpdateAndDisplay);
}
uint64_t BufferViewer::CurrentByteOffset()
{
return m_ByteOffset + m_PagingByteOffset;
}
void BufferViewer::UI_CalculateMeshFormats()
{
if(!m_MeshView)
@@ -3659,6 +3791,8 @@ void BufferViewer::ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId
m_PagingByteOffset = 0;
ui->formatSpecifier->setFormat(format);
processFormat(format);
}
@@ -4178,17 +4312,91 @@ void BufferViewer::processFormat(const QString &format)
BufferConfiguration bufconfig;
ShaderConstant cols = BufferFormatter::ParseFormatString(format, m_ByteSize, errors);
ShaderConstant fixed, repeating;
rdctie(fixed, repeating) = BufferFormatter::ParseFormatString(format, m_ByteSize, errors);
CalcColumnWidth(MaxNumRows(cols));
const bool repeatedVars = repeating.type.descriptor.type != VarType::Unknown;
const bool fixedVars = !fixed.type.members.empty();
if(fixedVars && repeatedVars)
{
if(m_OuterSplitter->widget(0) != m_InnerSplitter)
m_OuterSplitter->replaceWidget(0, m_InnerSplitter);
m_FixedGroup->layout()->addWidget(ui->fixedVars);
m_RepeatedGroup->layout()->addWidget(ui->vsinData);
// row offset should be shown in the repeated control bar, but no separator line is needed
ui->offsetLine->setVisible(false);
ui->rowOffsetLabel->setVisible(true);
ui->rowOffset->setVisible(true);
if(ui->rowOffset->parentWidget() != m_RepeatedControlBar)
{
QHBoxLayout *hbox = qobject_cast<QHBoxLayout *>(m_RepeatedControlBar->layout());
hbox->insertWidget(0, ui->rowOffsetLabel);
hbox->insertWidget(1, ui->rowOffset);
}
ui->fixedVars->setVisible(true);
ui->vsinData->setVisible(true);
m_InnerSplitter->setVisible(true);
}
else if(fixedVars)
{
if(m_OuterSplitter->widget(0) != ui->fixedVars)
m_OuterSplitter->replaceWidget(0, ui->fixedVars);
// row offset should not be shown
ui->offsetLine->setVisible(false);
ui->rowOffsetLabel->setVisible(false);
ui->rowOffset->setVisible(false);
ui->fixedVars->setVisible(true);
ui->vsinData->setVisible(false);
m_InnerSplitter->setVisible(false);
}
else if(repeatedVars)
{
if(m_OuterSplitter->widget(0) != ui->vsinData)
m_OuterSplitter->replaceWidget(0, ui->vsinData);
// row offset should be shown with the other controls
ui->offsetLine->setVisible(true);
ui->rowOffsetLabel->setVisible(true);
ui->rowOffset->setVisible(true);
// insert after the offsetLine
if(ui->rowOffset->parentWidget() != ui->meshToolbar)
{
QHBoxLayout *hbox = qobject_cast<QHBoxLayout *>(ui->meshToolbar->layout());
int i = 0;
for(; i < hbox->count(); i++)
{
if(hbox->itemAt(i)->widget() == ui->offsetLine)
break;
}
i++;
if(i < hbox->count())
{
hbox->insertWidget(i, ui->rowOffset);
hbox->insertWidget(i, ui->rowOffsetLabel);
}
}
ui->fixedVars->setVisible(false);
ui->vsinData->setVisible(true);
m_InnerSplitter->setVisible(false);
}
CalcColumnWidth(MaxNumRows(repeating));
ClearModels();
m_Format = format;
ui->formatSpecifier->setFormat(format);
qulonglong stride = qMax(1U, cols.type.descriptor.arrayByteStride);
qulonglong stride = qMax(1U, repeating.type.descriptor.arrayByteStride);
byteRangeStart->setSingleStep(stride);
byteRangeLength->setSingleStep(stride);
+14 -2
View File
@@ -38,8 +38,11 @@ class RDSpinBox64;
class QItemSelection;
class QMenu;
class QPushButton;
class QVBoxLayout;
class RDTableView;
class RDSplitter;
class BufferItemModel;
class CollapseGroupBox;
class CameraWrapper;
class ArcballWrapper;
class FlycamWrapper;
@@ -175,12 +178,12 @@ private:
void UI_UpdateBoundingBox(const CalcBoundingBoxData &bbox);
void UI_UpdateBoundingBoxLabels(int compCount = 0);
void UI_AddFixedVariables(RDTreeWidgetItem *root, const rdcarray<ShaderVariable> &vars);
void FillScrolls(PopulateBufferData *bufdata);
void UI_ResetArcball();
uint64_t CurrentByteOffset();
// data from raw buffer view
bool m_IsBuffer = true;
QString m_Format;
@@ -219,6 +222,15 @@ private:
RichTextViewDelegate *m_delegate = NULL;
QVBoxLayout *m_VLayout = NULL;
RDSplitter *m_OuterSplitter = NULL;
RDSplitter *m_InnerSplitter = NULL;
CollapseGroupBox *m_FixedGroup = NULL;
CollapseGroupBox *m_RepeatedGroup = NULL;
QFrame *m_RepeatedControlBar = NULL;
QMenu *m_HeaderMenu = NULL;
QAction *m_ResetColumnSel = NULL;
+34 -1
View File
@@ -13,6 +13,34 @@
<property name="windowTitle">
<string>Buffer Viewer</string>
</property>
<widget class="RDTreeWidget" name="fixedVars">
<property name="geometry">
<rect>
<x>510</x>
<y>150</y>
<width>191</width>
<height>71</height>
</rect>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ContiguousSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
<widget class="RDTableView" name="vsinData">
<property name="geometry">
<rect>
@@ -1001,7 +1029,7 @@ Enter 0.0 to use automatic/guessed value derived from data.</string>
<rect>
<x>139</x>
<y>160</y>
<width>521</width>
<width>361</width>
<height>61</height>
</rect>
</property>
@@ -1023,6 +1051,11 @@ Enter 0.0 to use automatic/guessed value derived from data.</string>
<extends>QTableView</extends>
<header>Widgets/Extended/RDTableView.h</header>
</customwidget>
<customwidget>
<class>RDTreeWidget</class>
<extends>QTreeView</extends>
<header>Widgets/Extended/RDTreeWidget.h</header>
</customwidget>
<customwidget>
<class>BufferFormatSpecifier</class>
<extends>QWidget</extends>
@@ -294,7 +294,12 @@ void ConstantBufferPreviewer::processFormat(const QString &format)
{
QString errors;
m_formatOverride = BufferFormatter::ParseFormatString(format, ~0ULL, errors);
ShaderConstant fixed, repeating;
rdctie(fixed, repeating) = BufferFormatter::ParseFormatString(format, ~0ULL, errors);
m_formatOverride = fixed;
// we don't handle true unbounded repeating data here, add it as another single element
if(repeating.type.descriptor.type != VarType::Unknown)
m_formatOverride.type.members.push_back(repeating);
ui->formatSpecifier->setErrors(errors);
}
@@ -2157,8 +2157,8 @@ void D3D11PipelineStateViewer::resource_itemActivated(RDTreeWidgetItem *item, in
if(shaderRes)
{
format = BufferFormatter::GetBufferFormatString(Packing::D3DUAV, *shaderRes,
view.res.viewFormat, offs);
format =
BufferFormatter::GetBufferFormatString(Packing::D3DUAV, *shaderRes, view.res.viewFormat);
if(view.res.bufferFlags & D3DBufferViewFlags::Raw)
format = lit("xint");
@@ -2271,8 +2271,8 @@ void D3D12PipelineStateViewer::resource_itemActivated(RDTreeWidgetItem *item, in
if(shaderRes)
{
format = BufferFormatter::GetBufferFormatString(Packing::D3DUAV, *shaderRes,
view.res.viewFormat, offs);
format =
BufferFormatter::GetBufferFormatString(Packing::D3DUAV, *shaderRes, view.res.viewFormat);
if(view.res.bufferFlags & D3DBufferViewFlags::Raw)
format = lit("xint");
@@ -2317,7 +2317,7 @@ void GLPipelineStateViewer::resource_itemActivated(RDTreeWidgetItem *item, int c
QString format = BufferFormatter::GetBufferFormatString(
BufferFormatter::EstimatePackingRules(shaderRes->variableType.members), *shaderRes,
ResourceFormat(), buf.offset);
ResourceFormat());
if(buf.ID != ResourceId())
{
@@ -3036,8 +3036,7 @@ void VulkanPipelineStateViewer::resource_itemActivated(RDTreeWidgetItem *item, i
: stage->reflection->readOnlyResources[buf.bindPoint];
format = BufferFormatter::GetBufferFormatString(
BufferFormatter::EstimatePackingRules(shaderRes.variableType.members), shaderRes, buf.fmt,
buf.offset);
BufferFormatter::EstimatePackingRules(shaderRes.variableType.members), shaderRes, buf.fmt);
}
if(buf.ID != ResourceId())