diff --git a/qrenderdoc/Code/QRDUtils.cpp b/qrenderdoc/Code/QRDUtils.cpp index 4dac52181..d4a8fbedd 100644 --- a/qrenderdoc/Code/QRDUtils.cpp +++ b/qrenderdoc/Code/QRDUtils.cpp @@ -1332,6 +1332,60 @@ void CombineUsageEvents(ICaptureContext &ctx, const rdcarray &usage, callback(start, end, us); } +QVariant SDObject2Variant(const SDObject *obj) +{ + QVariant param; + + // we don't identify via the type name as many types could be serialised as a ResourceId - + // e.g. ID3D11Resource* or ID3D11Buffer* which would be the actual typename. We want to preserve + // that for the best raw structured data representation instead of flattening those out to just + // "ResourceId", and we also don't want to store two types ('fake' and 'real'), so instead we + // check the custom string. + if(obj->type.basetype == SDBasic::Resource) + { + param = QVariant::fromValue(obj->data.basic.id); + } + else if(obj->type.flags & SDTypeFlags::NullString) + { + param = lit("NULL"); + } + else if(obj->type.flags & SDTypeFlags::HasCustomString) + { + param = obj->data.str; + } + else + { + switch(obj->type.basetype) + { + case SDBasic::Chunk: param = QVariant(); break; + case SDBasic::Struct: param = QFormatStr("%1()").arg(obj->type.name); break; + case SDBasic::Array: param = QFormatStr("%1[]").arg(obj->type.name); break; + case SDBasic::Null: param = lit("NULL"); break; + case SDBasic::Buffer: param = lit("(%1 bytes)").arg(obj->type.byteSize); break; + case SDBasic::String: + { + QStringList lines = QString(obj->data.str).split(QLatin1Char('\n')); + QString trimmedStr; + for(int i = 0; i < 3 && i < lines.count(); i++) + trimmedStr += lines[i] + QLatin1Char('\n'); + if(lines.count() > 3) + trimmedStr += lit("..."); + param = trimmedStr.trimmed(); + break; + } + case SDBasic::Resource: + case SDBasic::Enum: + case SDBasic::UnsignedInteger: param = Formatter::HumanFormat(obj->data.basic.u); break; + case SDBasic::SignedInteger: param = Formatter::Format(obj->data.basic.i); break; + case SDBasic::Float: param = Formatter::Format(obj->data.basic.d); break; + case SDBasic::Boolean: param = (obj->data.basic.b ? lit("True") : lit("False")); break; + case SDBasic::Character: param = QString(QLatin1Char(obj->data.basic.c)); break; + } + } + + return param; +} + void addStructuredChildren(RDTreeWidgetItem *parent, const SDObject &parentObj) { for(const SDObject *obj : parentObj) @@ -1339,73 +1393,20 @@ void addStructuredChildren(RDTreeWidgetItem *parent, const SDObject &parentObj) if(obj->type.flags & SDTypeFlags::Hidden) continue; - QVariant param; + QVariant name; if(parentObj.type.basetype == SDBasic::Array) - param = QFormatStr("[%1]").arg(parent->childCount()); + name = QFormatStr("[%1]").arg(parent->childCount()); else - param = obj->name; + name = obj->name; - RDTreeWidgetItem *item = new RDTreeWidgetItem({param, QString()}); + RDTreeWidgetItem *item = new RDTreeWidgetItem({name, QString()}); - // we don't identify via the type name as many types could be serialised as a ResourceId - - // e.g. ID3D11Resource* or ID3D11Buffer* which would be the actual typename. We want to preserve - // that for the best raw structured data representation instead of flattening those out to just - // "ResourceId", and we also don't want to store two types ('fake' and 'real'), so instead we - // check the custom string. - if(obj->type.basetype == SDBasic::Resource) - { - ResourceId id; - static_assert(sizeof(id) == sizeof(obj->data.basic.u), "ResourceId is no longer uint64_t!"); - memcpy(&id, &obj->data.basic.u, sizeof(id)); + item->setText(1, SDObject2Variant(obj)); - param = id; - } - else if(obj->type.flags & SDTypeFlags::NullString) - { - param = lit("NULL"); - } - else if(obj->type.flags & SDTypeFlags::HasCustomString) - { - param = obj->data.str; - } - else - { - switch(obj->type.basetype) - { - case SDBasic::Chunk: - case SDBasic::Struct: - param = QFormatStr("%1()").arg(obj->type.name); - addStructuredChildren(item, *obj); - break; - case SDBasic::Array: - param = QFormatStr("%1[]").arg(obj->type.name); - addStructuredChildren(item, *obj); - break; - case SDBasic::Null: param = lit("NULL"); break; - case SDBasic::Buffer: param = lit("(%1 bytes)").arg(obj->type.byteSize); break; - case SDBasic::String: - { - QStringList lines = QString(obj->data.str).split(QLatin1Char('\n')); - QString trimmedStr; - for(int i = 0; i < 3 && i < lines.count(); i++) - trimmedStr += lines[i] + QLatin1Char('\n'); - if(lines.count() > 3) - trimmedStr += lit("..."); - param = trimmedStr.trimmed(); - break; - } - case SDBasic::Resource: - case SDBasic::Enum: - case SDBasic::UnsignedInteger: param = Formatter::HumanFormat(obj->data.basic.u); break; - case SDBasic::SignedInteger: param = Formatter::Format(obj->data.basic.i); break; - case SDBasic::Float: param = Formatter::Format(obj->data.basic.d); break; - case SDBasic::Boolean: param = (obj->data.basic.b ? lit("True") : lit("False")); break; - case SDBasic::Character: param = QString(QLatin1Char(obj->data.basic.c)); break; - } - } - - item->setText(1, param); + if(obj->type.basetype == SDBasic::Chunk || obj->type.basetype == SDBasic::Struct || + obj->type.basetype == SDBasic::Array) + addStructuredChildren(item, *obj); parent->addChild(item); } @@ -2742,3 +2743,297 @@ void UpdateVisibleColumns(rdcstr windowTitle, int columnCount, QHeaderView *head header->moveSection(header->visualIndex(logicalIdx), i); } } + +void StructuredDataItemModel::setObjects(const rdcarray &objs) +{ + emit beginResetModel(); + m_Objects = objs; + emit endResetModel(); +} + +// on 64-bit we've got plenty of bits so we can pack the indices into top/bottom 32-bits. +// on 32-bit we assume there won't be any huge arrays or we'll run out of memory elsewhere probably, +// so we pack 9/21 bits allowing for up to 512 arrays of ~2 million entries each. +// bear in mind 2 bits are reserved for the index tag itself +struct IndexMasks +{ + static constexpr quintptr IndexBits() { return sizeof(void *) == 8 ? 32U : 21U; } + static quintptr GetArrayID(quintptr packed) { return packed >> IndexBits(); } + static quintptr GetIndexInArray(quintptr packed) + { + return packed & ((quintptr(1U) << IndexBits()) - 1); + } + static quintptr Pack(quintptr arrayId, quintptr idxInArray) + { + return (arrayId << IndexBits()) | idxInArray; + } +}; + +StructuredDataItemModel::Index StructuredDataItemModel::decodeIndex(QModelIndex idx) const +{ + // if it's a direct pointer, the low bits will be 0 for alignment. + Index ret; + ret.tag = IndexTag(idx.internalId() & 0x3); + + if(ret.tag == Direct) + { + ret.obj = (SDObject *)idx.internalPointer(); + } + else + { + quintptr packed = idx.internalId() >> 2; + ret.obj = (SDObject *)m_Arrays[IndexMasks::GetArrayID(packed)]; + ret.indexInArray = IndexMasks::GetIndexInArray(packed); + } + + return ret; +} + +quintptr StructuredDataItemModel::encodeIndex(Index idx) const +{ + if(idx.tag == Direct) + return (quintptr)idx.obj; + + int arrayId = m_Arrays.indexOf(idx.obj); + if(arrayId == -1) + { + m_Arrays.push_back(idx.obj); + arrayId = m_Arrays.count() - 1; + } + + quintptr packed = IndexMasks::Pack(arrayId, idx.indexInArray); + return (packed << 2U) | quintptr(idx.tag); +} + +// for large arrays (more than this size) paginate it with pages of this size. +// This is primarily beneficial for lazy arrays to avoid needing to lazily evaluate a huge array. +const int ArrayPageSize = 250; + +bool StructuredDataItemModel::isLargeArray(SDObject *obj) const +{ + return (int)obj->NumChildren() > ArrayPageSize; +} + +QModelIndex StructuredDataItemModel::index(int row, int column, const QModelIndex &parent) const +{ + if(row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent)) + return QModelIndex(); + + SDObject *par = NULL; + + if(parent.isValid()) + { + Index decodedParent = decodeIndex(parent); + + // the children of page nodes are real nodes. We cache the array member's index here too for + // parent() lookups + if(decodedParent.tag == PageNode) + { + int idx = decodedParent.indexInArray + row; + + m_ArrayMembers[decodedParent.obj->GetChild(idx)] = idx; + + return createIndex(row, column, encodeIndex({ArrayMember, decodedParent.obj, idx})); + } + else if(decodedParent.tag == ArrayMember) + { + par = decodedParent.obj->GetChild(decodedParent.indexInArray); + } + else + { + par = decodedParent.obj; + } + + // if this parent node is a large array, the children are page nodes, otherwise the child is a + // direct node + if(isLargeArray(par)) + { + return createIndex(row, column, encodeIndex({PageNode, par, row * ArrayPageSize})); + } + else + { + return createIndex(row, column, encodeIndex({Direct, par->GetChild(row), 0})); + } + } + else + { + return createIndex(row, column, encodeIndex({Direct, m_Objects[row], 0})); + } +} + +QModelIndex StructuredDataItemModel::parent(const QModelIndex &index) const +{ + if(index.internalPointer() == NULL) + return QModelIndex(); + + Index decodedIndex = decodeIndex(index); + + SDObject *obj = NULL; + + // array members have parents that are page nodes + if(decodedIndex.tag == ArrayMember) + { + int pageRow = decodedIndex.indexInArray / ArrayPageSize; + return createIndex(decodedIndex.indexInArray / ArrayPageSize, 0, + encodeIndex({PageNode, decodedIndex.obj, pageRow * ArrayPageSize})); + } + else if(decodedIndex.tag == PageNode) + { + obj = decodedIndex.obj; + } + else + { + obj = decodedIndex.obj->GetParent(); + } + + // need to figure out the index for obj, it could be an array member itself in theory, or it might + // be direct, or it could be a root object + if(obj) + { + SDObject *parent = obj->GetParent(); + + if(parent == NULL) + { + int row = m_Objects.indexOf(obj); + if(row >= 0) + return createIndex(row, 0, obj); + + qCritical() << "Encountered object with no parent that is not a root"; + + return QModelIndex(); + } + + // if the parent is a large array + if(isLargeArray(parent)) + { + // we expect to have set up our member index before this lookup was ever needed + auto it = m_ArrayMembers.find(obj); + if(it == m_ArrayMembers.end()) + { + qCritical() << "Expected member index to be set up, but it is not"; + + return QModelIndex(); + } + + // return the index for this item, with the child index we looked up + return createIndex(it.value(), 0, encodeIndex({ArrayMember, parent, it.value()})); + } + + // search our parent to find out our child index + for(size_t i = 0; i < parent->NumChildren(); i++) + if(parent->GetChild(i) == obj) + return createIndex((int)i, 0, obj); + + return QModelIndex(); + } + + return QModelIndex(); +} + +int StructuredDataItemModel::rowCount(const QModelIndex &parent) const +{ + if(!parent.isValid()) + return m_Objects.count(); + + Index decodedIdx = decodeIndex(parent); + + SDObject *obj = NULL; + + // if this is a page node, it either has PageSize children if it's not the last one, or the + // remainder + if(decodedIdx.tag == PageNode) + { + size_t pageBase = decodedIdx.indexInArray; + + return qMin(ArrayPageSize, int(decodedIdx.obj->NumChildren() - pageBase)); + } + else if(decodedIdx.tag == ArrayMember) + { + obj = decodedIdx.obj->GetChild(decodedIdx.indexInArray); + } + else + { + obj = decodedIdx.obj; + } + + if(obj) + { + if(isLargeArray(obj)) + return (int(obj->NumChildren()) + ArrayPageSize - 1) / ArrayPageSize; + else + return (int)obj->NumChildren(); + } + + return 0; +} + +int StructuredDataItemModel::columnCount(const QModelIndex &parent) const +{ + return m_ColumnNames.count(); +} + +QVariant StructuredDataItemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + if(section < m_ColumnNames.count()) + return m_ColumnNames[section]; + } + + return QVariant(); +} + +Qt::ItemFlags StructuredDataItemModel::flags(const QModelIndex &index) const +{ + if(!index.isValid()) + return 0; + + return QAbstractItemModel::flags(index); +} + +QVariant StructuredDataItemModel::data(const QModelIndex &index, int role) const +{ + if(role != Qt::DisplayRole || index.column() >= columnCount()) + return QVariant(); + + Index decodedIdx = decodeIndex(index); + SDObject *obj = NULL; + + if(decodedIdx.tag == PageNode) + { + if(m_ColumnValues[index.column()] == Name) + { + size_t pageBase = decodedIdx.indexInArray; + size_t pageCount = qMin(ArrayPageSize, int(decodedIdx.obj->NumChildren() - pageBase)); + + return QFormatStr("[%1..%2]").arg(pageBase).arg(pageBase + pageCount - 1); + } + return QVariant(); + } + else if(decodedIdx.tag == ArrayMember) + { + obj = decodedIdx.obj->GetChild(decodedIdx.indexInArray); + } + else + { + obj = decodedIdx.obj; + } + + if(obj) + { + switch(m_ColumnValues[index.column()]) + { + case Name: + if(decodedIdx.tag == ArrayMember) + return QFormatStr("[%1]").arg(decodedIdx.indexInArray); + else if(obj->GetParent() && obj->GetParent()->type.basetype == SDBasic::Array) + return QFormatStr("[%1]").arg(index.row()); + else + return obj->name; + case Value: return SDObject2Variant(obj); + case Type: return obj->type.name; + } + } + + return QVariant(); +} diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index 3a3b9c380..f54789f0d 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -577,6 +577,67 @@ private: QAbstractItemView *m_widget; }; +class StructuredDataItemModel : public QAbstractItemModel +{ +public: + enum SDColumn + { + Name, + Value, + Type, + }; + + void setColumns(QStringList names, rdcarray values) + { + m_ColumnNames = names; + m_ColumnValues = values; + } + + void setObjects(const rdcarray &objs); + StructuredDataItemModel(QWidget *parent) : QAbstractItemModel(parent) {} + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private: + enum IndexTag + { + Direct, + PageNode, + ArrayMember, + }; + + struct Index + { + IndexTag tag; + SDObject *obj; + int indexInArray; + }; + + Index decodeIndex(QModelIndex idx) const; + quintptr encodeIndex(Index idx) const; + bool isLargeArray(SDObject *obj) const; + + rdcarray m_Objects; + QStringList m_ColumnNames; + rdcarray m_ColumnValues; + + // these below members have to be mutable since we update these caches in const functions + + // set of large arrays, so we can refer to them by compressed index instead of needing a full + // pointer. + mutable rdcarray m_Arrays; + // objects that are in large arrays. This map gives us the index they are in their parent. + // This is only used for parent() when the parent is an array member, so we only add into this + // list when creating a child index of such an array member - which only happens when the array + // member is expanded. That keeps to a minimum the number of entries. + mutable QMap m_ArrayMembers; +}; + // helper functions for using a double spinbox for 64-bit integers. We do this because it's // infeasible in Qt to actually derive and create a real 64-bit integer spinbox because critical // functionality depends on deriving QAbstractSpinBoxPrivate which is unavailable. So instead we use diff --git a/qrenderdoc/Windows/ResourceInspector.cpp b/qrenderdoc/Windows/ResourceInspector.cpp index 1b68e6b48..3e24c6a09 100644 --- a/qrenderdoc/Windows/ResourceInspector.cpp +++ b/qrenderdoc/Windows/ResourceInspector.cpp @@ -128,6 +128,9 @@ ResourceInspector::ResourceInspector(ICaptureContext &ctx, QWidget *parent) m_ChunksModel->setColumns({tr("Parameter"), tr("Value")}, {StructuredDataItemModel::Name, StructuredDataItemModel::Value}); + m_delegate = new RichTextViewDelegate(ui->initChunks); + ui->initChunks->setItemDelegate(m_delegate); + ui->initChunks->header()->resizeSection(0, 200); ui->initChunks->setFont(Formatter::PreferredFont()); @@ -308,27 +311,17 @@ void ResourceInspector::Inspect(ResourceId id) } ui->relatedResources->endUpdate(); + rdcarray objs; + for(uint32_t chunk : desc->initialisationChunks) { - RDTreeWidgetItem *root = new RDTreeWidgetItem({QString(), QString()}); - if(chunk < file.chunks.size()) - { - SDChunk *chunkObj = file.chunks[chunk]; - - root->setText(0, chunkObj->name); - - addStructuredChildren(root, *chunkObj); - } + objs.push_back(file.chunks[chunk]); else - { - root->setText(1, tr("Invalid chunk index %1").arg(chunk)); - } - - ui->initChunks->addTopLevelItem(root); - - ui->initChunks->setSelectedItem(root); + qCritical() << "Invalid chunk index" << chunk; } + + m_ChunksModel->setObjects(objs); } else { @@ -376,7 +369,7 @@ void ResourceInspector::OnCaptureClosed() m_ResourceModel->reset(); - ui->initChunks->clear(); + m_ChunksModel->setObjects({}); ui->initChunks->clearInternalExpansions(); ui->relatedResources->clear(); ui->resourceUsage->clear(); diff --git a/qrenderdoc/Windows/ResourceInspector.h b/qrenderdoc/Windows/ResourceInspector.h index e5fc49cb6..753877caa 100644 --- a/qrenderdoc/Windows/ResourceInspector.h +++ b/qrenderdoc/Windows/ResourceInspector.h @@ -36,6 +36,8 @@ class QCollatorSortFilterProxyModel; class RDTreeWidgetItem; class ResourceListItemModel; +class StructuredDataItemModel; +class RichTextViewDelegate; class ResourceInspector : public QFrame, public IResourceInspector, public ICaptureViewer { @@ -87,4 +89,6 @@ private: ResourceListItemModel *m_ResourceModel; int m_ResourceCacheID = -1; QCollatorSortFilterProxyModel *m_FilterModel; + StructuredDataItemModel *m_ChunksModel; + RichTextViewDelegate *m_delegate; }; diff --git a/qrenderdoc/Windows/ResourceInspector.ui b/qrenderdoc/Windows/ResourceInspector.ui index 063a12e3d..8d4826dd1 100644 --- a/qrenderdoc/Windows/ResourceInspector.ui +++ b/qrenderdoc/Windows/ResourceInspector.ui @@ -232,7 +232,7 @@ - + 60 @@ -320,6 +320,11 @@ QLineEdit
Widgets/Extended/RDLineEdit.h
+ + RDTreeView + QTreeView +
Widgets/Extended/RDTreeView.h
+
RDTreeWidget QTreeView