From a36516c8a5927c4a7b5460f9a60803ced1964ff2 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 21 Mar 2022 15:57:21 +0000 Subject: [PATCH] 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). --- qrenderdoc/Code/BufferFormatter.cpp | 311 +++++++++++++----- qrenderdoc/Code/QRDUtils.h | 10 +- qrenderdoc/Widgets/Extended/RDSplitter.cpp | 132 +++++--- qrenderdoc/Widgets/Extended/RDSplitter.h | 1 + qrenderdoc/Windows/BufferViewer.cpp | 282 +++++++++++++--- qrenderdoc/Windows/BufferViewer.h | 16 +- qrenderdoc/Windows/BufferViewer.ui | 35 +- .../Windows/ConstantBufferPreviewer.cpp | 7 +- .../D3D11PipelineStateViewer.cpp | 4 +- .../D3D12PipelineStateViewer.cpp | 4 +- .../PipelineState/GLPipelineStateViewer.cpp | 2 +- .../VulkanPipelineStateViewer.cpp | 3 +- 12 files changed, 630 insertions(+), 177 deletions(-) diff --git a/qrenderdoc/Code/BufferFormatter.cpp b/qrenderdoc/Code/BufferFormatter.cpp index 1b74e0bd7..e602ae0b6 100644 --- a/qrenderdoc/Code/BufferFormatter.cpp +++ b/qrenderdoc/Code/BufferFormatter.cpp @@ -365,8 +365,56 @@ Packing::Rules BufferFormatter::EstimatePackingRules(const rdcarray &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 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 "(?[1-9])?" // might be a vector "(?x[1-9])?" // or a matrix "(?\\s+[A-Za-z@_][A-Za-z0-9@_]*)?" // get identifier name - "(?\\s*\\[[0-9]+\\])?" // optional array dimension + "(?\\s*\\[[0-9]*\\])?" // optional array dimension "(\\s*:\\s*" // optional specifier after : "(" // bitfield or semantic "(?[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 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 declaredStructs; - if(m_API == GraphicsAPI::Vulkan || m_API == GraphicsAPI::OpenGL) - { - const rdcarray &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 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 &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 arrayElements; diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index bbcc27951..20375bfae 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -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 ParseFormatString(const QString &formatString, + uint64_t maxLen, QString &errors); + static bool CheckInvalidUnbounded(const ShaderConstant &structDef, QString &errors); + static bool ContainsUnbounded(const rdcarray &members); static Packing::Rules EstimatePackingRules(const rdcarray &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 &members, uint32_t requiredByteStride); diff --git a/qrenderdoc/Widgets/Extended/RDSplitter.cpp b/qrenderdoc/Widgets/Extended/RDSplitter.cpp index ff6174c46..73612d0c4 100644 --- a/qrenderdoc/Widgets/Extended/RDSplitter.cpp +++ b/qrenderdoc/Widgets/Extended/RDSplitter.cpp @@ -28,7 +28,7 @@ #include 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 totalSizes = sizes(); RDSplitterHandle *rdHandle = (RDSplitterHandle *)handle(index); if(totalSizes[index] == 0) diff --git a/qrenderdoc/Widgets/Extended/RDSplitter.h b/qrenderdoc/Widgets/Extended/RDSplitter.h index 3790e7f70..e347c0cc2 100644 --- a/qrenderdoc/Widgets/Extended/RDSplitter.h +++ b/qrenderdoc/Widgets/Extended/RDSplitter.h @@ -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; diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index fc69c2460..5e776077d 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -32,10 +32,13 @@ #include #include #include +#include #include #include #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 &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(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(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); diff --git a/qrenderdoc/Windows/BufferViewer.h b/qrenderdoc/Windows/BufferViewer.h index 182090ddb..fbbb32feb 100644 --- a/qrenderdoc/Windows/BufferViewer.h +++ b/qrenderdoc/Windows/BufferViewer.h @@ -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 &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; diff --git a/qrenderdoc/Windows/BufferViewer.ui b/qrenderdoc/Windows/BufferViewer.ui index 68f168e48..2775af5c4 100644 --- a/qrenderdoc/Windows/BufferViewer.ui +++ b/qrenderdoc/Windows/BufferViewer.ui @@ -13,6 +13,34 @@ Buffer Viewer + + + + 510 + 150 + 191 + 71 + + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::ContiguousSelection + + + QAbstractItemView::ScrollPerPixel + + + true + + + false + + @@ -1001,7 +1029,7 @@ Enter 0.0 to use automatic/guessed value derived from data. 139 160 - 521 + 361 61 @@ -1023,6 +1051,11 @@ Enter 0.0 to use automatic/guessed value derived from data. QTableView
Widgets/Extended/RDTableView.h
+ + RDTreeWidget + QTreeView +
Widgets/Extended/RDTreeWidget.h
+
BufferFormatSpecifier QWidget diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.cpp b/qrenderdoc/Windows/ConstantBufferPreviewer.cpp index f1b156036..53ed9e48e 100644 --- a/qrenderdoc/Windows/ConstantBufferPreviewer.cpp +++ b/qrenderdoc/Windows/ConstantBufferPreviewer.cpp @@ -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); } diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp index 4660db31c..19564ce51 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp @@ -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"); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index b2821b2db..4b1498fa3 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -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"); diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index 4ecc9ed4f..f4c006c4b 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -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()) { diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index 4f4faf49c..123722dfc 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -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())