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())