/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2019-2025 Baldur Karlsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ #include "DescriptorViewer.h" #include #include #include "Code/QRDUtils.h" #include "Code/Resources.h" #include "toolwindowmanager/ToolWindowManager.h" #include "ui_DescriptorViewer.h" struct ButtonTag { ButtonTag() = default; ButtonTag(bool buffer, const Descriptor &descriptor) : valid(true), buffer(buffer), descriptor(descriptor) { } ButtonTag(ResourceId heap) : valid(true), buffer(false), heap(heap) {} // all constructe tags compare equal so this value can contain data but still be used to enable buttons bool operator==(const ButtonTag &o) const { return valid && o.valid; } bool operator<(const ButtonTag &o) const { return valid < o.valid; } bool valid = false; bool buffer = false; Descriptor descriptor; ResourceId heap; }; Q_DECLARE_METATYPE(ButtonTag); class DescriptorItemModel : public QAbstractItemModel { public: DescriptorItemModel(ICaptureContext &ctx, DescriptorViewer &view, QObject *parent) : QAbstractItemModel(parent), m_Ctx(ctx), m_View(view) { m_API = m_Ctx.APIProps().pipelineType; } void refresh() { emit beginResetModel(); if(m_View.m_D3D12RootSig.parameters.size() + 1 >= ParameterIndexMask) { qCritical() << "Too many root signature parameters, will be clipped"; for(const D3D12Pipe::RootParam ¶m : m_View.m_D3D12RootSig.parameters) if(param.tableRanges.size() + 1 >= TableIndexMask) qCritical() << "Too many tables in parameter, will be clipped"; } emit endResetModel(); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if(row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount()) return QModelIndex(); // root signature has more levels of nesting so is more complex // // for ease, each (x, y) tuple is a QModelIndex(row=x, column, id=y). Column is omitted as it's // not important. We encode the levels of array indexing in the id assuming reasonable packing. // // For packing: // param is at very most 63 because of root signature limits so it needs 6 bits // tables can be at most 1 million long because of descriptor heap limits // tables could be large but it's more likely to have a few tables that are very large // // With 64-bit ids we have plenty of bits. // For 32-bit we take 1 bit for parameter or sampler, 6 bits for param, 20 bits for descriptor, // and the remaining 5 bits for table index. We don't expect this code to run in 32-bit typically // (or ever) so it's a best-effort. For this case we also set the 'ParameterFlag' id specially to // avoid needing to take more bits. We use 1-based indices to be able to distinguish parent from // child. // // QModelIndex() <= root // (ParametersRootNode, FixedNode) <= "Parameters" // (param, ParameterFlag) <= params[param] // (0, ParameterData | param+1) <= params[param].data[0] // (1..., ParameterData | param+1) <= params[param].data[1]... etc // more for root consts/desc // // (table, ParameterData | param+1) <= param.tables[table] // with table >= fixedDataCount // (0, ParameterData | param+1 | table+1) <= table.data[0] // (1..., ParameterData | param+1 | table+1) <= table.data[1]... // (desc, ParameterData | param+1 | table+1) <= table.descriptor[desc] // (dataIdx, ParameterData | param+1 | table+1 | desc+1) <= desc.data[dataIdx] // // (StaticSamplersRootNode, FixedNode) <= "StaticSamplers" // (sampIdx, StaticSamplerData) <= samplers[sampIdx] // (dataIdx, StaticSamplerData | sampIdx+1) <= samplers[sampIdx].data[dataIdx] // (DescriptorHeapNode, FixedNode) <= "Descriptor Heap 1234" if(m_View.m_D3D12RootSig.resourceId != ResourceId()) { // children of the root are the two fixed nodes if(parent == QModelIndex()) { return createIndex(row, column, FixedNode); } // children of the fixed nodes are parameters or static samplers if(parent.internalId() == FixedNode) { if(parent.row() == ParametersRootNode) return createIndex(row, column, ParameterFlag); if(parent.row() == StaticSamplersRootNode) return createIndex(row, column, StaticSamplerData); // other root entries are descriptor heaps return QModelIndex(); } // children of static samplers just add on their index (+1 to distinguish from the plain node) if(parent.internalId() == StaticSamplerData) { return createIndex(row, column, StaticSamplerData | (parent.row() + 1)); } // other rows that aren't parameter data are static sampler properties themselves and have no children if((parent.internalId() & ParameterData) == 0) return QModelIndex(); // children of a parameter node mask on the index into their id if(parent.internalId() == ParameterFlag) { return createIndex(row, column, Encode({uint8_t(parent.row() + 1), 0, 0})); } RootIdx parentIdx = Decode(parent.internalId()); // should not be possible, the root is ParameterFlag and then after that we encode with // 1-based indexing so the values are not 0. if(parentIdx.parameter == 0) return QModelIndex(); // this is a child of a parameter node, encode the range index from the parent's row if(parentIdx.range == 0 && parentIdx.descriptor == 0) { // the fixed parameters do not have children if(parent.row() < TableParameterFixedRowCount) return QModelIndex(); return createIndex(row, column, Encode({parentIdx.parameter, uint16_t(parent.row() - TableParameterFixedRowCount + 1), 0})); } // this is the child of a table node, encode the descriptor index from the parent's row if(parentIdx.descriptor == 0) { // the fixed parameters do not have children if(parent.row() < RangeFixedRowCount) return QModelIndex(); return createIndex(row, column, Encode({parentIdx.parameter, parentIdx.range, uint32_t(parent.row() - RangeFixedRowCount + 1)})); } // children of descriptors are data entries, and do not have children themselves return QModelIndex(); } // otherwise it's a plain list of descriptors // // QModelIndex() <= root // (descIdx, DescriptorFlag) <= descriptor[descIdx] // (dataIdx, DescriptorDataFlag | descIdx) <= descriptor[descIdx].data[dataIdx] if(parent == QModelIndex()) return createIndex(row, column, DescriptorFlag); if(parent.internalId() & DescriptorFlag) return createIndex(row, column, parent.row() | DescriptorDataFlag); // invalid, this would be a child of the data elements return QModelIndex(); } QModelIndex parent(const QModelIndex &index) const override { if(index == QModelIndex()) return QModelIndex(); if(m_View.m_D3D12RootSig.resourceId != ResourceId()) { quintptr id = index.internalId(); // the fixed nodes are under the root if(id == FixedNode) return QModelIndex(); // parameter nodes have a specific id, they are under the fixed node if(id == ParameterFlag) return createIndex(ParametersRootNode, 0, FixedNode); // a static sampler node, parented under the other fixed node if(id == StaticSamplerData) return createIndex(StaticSamplersRootNode, 0, FixedNode); // other rows that aren't parameter data are static sampler properties and are parented under // their static sampler if((id & ParameterData) == 0) { // this is the row index + 1 uint32_t sampIndex = id & ~StaticSamplerData; if(sampIndex == 0) return QModelIndex(); return createIndex(sampIndex - 1, 0, StaticSamplerData); } // at this point the index should either be a range, a descriptor, or a descriptor // data row. RootIdx decodedIndex = Decode(id); // we basically knock the finest grained index out of the parentIdx and turn it into a row. // The only special case is ranges since their parent needs the magic ParameterFlag id // descriptor data node - parent is the descriptor node if(decodedIndex.descriptor != 0) { int row = decodedIndex.descriptor - 1; decodedIndex.descriptor = 0; return createIndex(RangeFixedRowCount + row, 0, Encode(decodedIndex)); } // descriptor node - parent is the range if(decodedIndex.range != 0) { int row = decodedIndex.range - 1; decodedIndex.range = 0; return createIndex(TableParameterFixedRowCount + row, 0, Encode(decodedIndex)); } // should not be possible here if(decodedIndex.parameter == 0) return QModelIndex(); // range node - parent is the parameter which has a different index int row = decodedIndex.parameter - 1; return createIndex(row, 0, ParameterFlag); } else { // the descriptors are parented directly under the root if(index.internalId() & DescriptorFlag) return QModelIndex(); // the children of the descriptor itself are under the descriptor if(index.internalId() & DescriptorDataFlag) { int row = index.internalId() & ~DescriptorDataFlag; return createIndex(row, 0, DescriptorFlag); } } return QModelIndex(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { if(m_View.m_D3D12RootSig.resourceId != ResourceId()) { // for root signature the root node has 2 children for parameters and static samplers if(parent == QModelIndex()) return FirstHeapRootNode + m_View.m_D3D12Heaps.count(); // those fixed nodes have a simple row count if(parent.internalId() == FixedNode) { if(parent.row() == ParametersRootNode) return m_View.m_D3D12RootSig.parameters.count(); if(parent.row() == StaticSamplersRootNode) return m_View.m_D3D12RootSig.staticSamplers.count(); // other members are descriptor heaps which have no members return 0; } // parameter nodes have a specific id if(parent.internalId() == ParameterFlag) { const D3D12Pipe::RootParam ¶m = m_View.m_D3D12RootSig.parameters[parent.row()]; if(!param.constants.empty()) return ConstParameterFixedRowCount; if(param.descriptor.type != DescriptorType::Unknown) return DescParameterFixedRowCount + rowCount(param.descriptor); return TableParameterFixedRowCount + param.tableRanges.count(); } // static sampler node if(parent.internalId() == StaticSamplerData) { return StaticSamplerFixedRowCount + samplerRowCount(); } // other rows that aren't parameter data are static sampler properties themselves and have no children if((parent.internalId() & ParameterData) == 0) return 0; // at this point the parent node should either be a range, a descriptor, or a descriptor // data row. Parameter node parents are handled above as the magic ParameterFlag id RootIdx parentIdx = Decode(parent.internalId()); // 0 should be impossible, all parent nodes at this level should have at least a parameter encoded if(parentIdx.parameter == 0 || parentIdx.parameter - 1 >= m_View.m_D3D12RootSig.parameters.count()) return 0; const D3D12Pipe::RootParam ¶m = m_View.m_D3D12RootSig.parameters[parentIdx.parameter - 1]; if(parentIdx.range > 0 && parentIdx.range - 1 >= param.tableRanges.count()) return 0; // parameters with no tables don't have any more children if(!param.constants.empty() || param.descriptor.type != DescriptorType::Unknown) return 0; // if range is 0 on the parent's ID then this is a range node, so take the index from the parent's row const D3D12Pipe::RootTableRange &range = param.tableRanges[parentIdx.range == 0 ? parent.row() - TableParameterFixedRowCount : parentIdx.range - 1]; // if this is a range node, parent's range will be 0. We return the number of descriptors (plus fixed rows) if(parentIdx.range == 0) { // fixed rows in range nodes have no children if(parent.row() < TableParameterFixedRowCount) return 0; // Do a clamp here if we have descriptors to display if(!m_View.m_Descriptors.empty()) { uint32_t fullOffset = param.heapByteOffset + range.tableByteOffset; uint32_t maxDescriptors; if(range.category == DescriptorCategory::Sampler) maxDescriptors = uint32_t(m_View.m_SamplerDescriptors.size() - fullOffset); else maxDescriptors = uint32_t(m_View.m_Descriptors.size() - fullOffset); return RangeFixedRowCount + qMin(maxDescriptors, range.count); } // otherwise we'll have no descriptor rows but we will have two extras to show the space and // register that would normally be listed in the descriptor names return RangeFixedRowCount + 2; } // if the *parent* has a descriptor index then this must be a descriptor data row, it has no children. if(parentIdx.descriptor != 0) return 0; // otherwise it is a descriptor node // fixed rows in a range have no children if(parent.row() < RangeFixedRowCount) return 0; if(range.category == DescriptorCategory::Sampler) return samplerRowCount(); // out of bounds descriptor index shouldn't happen as we clamped the count above if(param.heapByteOffset + range.tableByteOffset + parent.row() - RangeFixedRowCount >= m_View.m_Descriptors.size()) return 0; return RootSigDescriptorFixedRows + rowCount(m_View.m_Descriptors[param.heapByteOffset + range.tableByteOffset + parent.row() - RangeFixedRowCount]); } else { if(parent == QModelIndex()) return m_View.m_Descriptors.count(); // the children of the descriptor itself don't have any children if(parent.internalId() & DescriptorDataFlag) return 0; uint32_t sampIndex = parent.row(); if(!m_View.m_DescriptorToSamplerLookup.empty()) sampIndex = m_View.m_DescriptorToSamplerLookup[parent.row()]; if(sampIndex != ~0U && m_View.m_SamplerDescriptors[sampIndex].type == DescriptorType::Sampler) return samplerRowCount(); int ret = rowCount(m_View.m_Descriptors[parent.row()]); if(parent.row() < m_View.m_Locations.count()) ret += DescriptorLocationFixedRowCount; return ret; } } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 2; } Qt::ItemFlags flags(const QModelIndex &index) const override { if(!index.isValid()) return 0; return QAbstractItemModel::flags(index); } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if(orientation == Qt::Horizontal && role == Qt::DisplayRole) { if(section == 0) return tr("Index"); else if(section == 1) return tr("Contents"); } return QVariant(); } int rowCount(const Descriptor &desc, bool includeSampler = true) const { int ret = 1; switch(desc.type) { case DescriptorType::ConstantBuffer: case DescriptorType::Buffer: case DescriptorType::ReadWriteBuffer: case DescriptorType::TypedBuffer: case DescriptorType::ReadWriteTypedBuffer: { // type, resource, offset, size, button to view ret = 5; if(desc.flags != DescriptorFlags::NoFlags) ret++; // format or structure size if(desc.type == DescriptorType::TypedBuffer || desc.type == DescriptorType::ReadWriteTypedBuffer) ret++; else if(desc.elementByteSize != 0) ret++; // counter buffer, offset, value if(desc.secondary != ResourceId()) ret += 3; break; } case DescriptorType::AccelerationStructure: { // type, resource, size ret = 3; break; } case DescriptorType::Image: case DescriptorType::ImageSampler: case DescriptorType::ReadWriteImage: { // type, texture type, resource, format, min LOD, button to view ret = 6; // first/num slices ret++; // first/num mips ret++; // swizzle ret++; if(desc.flags != DescriptorFlags::NoFlags) ret++; if(desc.view != ResourceId()) ret++; if(desc.type == DescriptorType::ImageSampler && includeSampler) ret += samplerRowCount(true); break; } case DescriptorType::Sampler: ret = samplerRowCount(); break; case DescriptorType::Unknown: break; } return ret; } QVariant data(const Descriptor &desc, int row, int col) const { if(row == 0) { if(col == 0) return lit("Type"); if(m_API == GraphicsAPI::Vulkan) { switch(desc.type) { case DescriptorType::ConstantBuffer: return lit("Uniform Buffer"); case DescriptorType::Buffer: // no such type on vulkan case DescriptorType::ReadWriteBuffer: return lit("Storage Buffer"); case DescriptorType::TypedBuffer: return lit("Texel Buffer"); case DescriptorType::ReadWriteTypedBuffer: return lit("Storage Texel Buffer"); case DescriptorType::AccelerationStructure: return lit("Acceleration Structure"); case DescriptorType::Image: return lit("Sampled Image"); case DescriptorType::ImageSampler: return lit("Combined Image/Sampler"); case DescriptorType::ReadWriteImage: return lit("Storage Image"); case DescriptorType::Sampler: return lit("Sampler"); case DescriptorType::Unknown: return lit("Uninitialised"); } } else { switch(desc.type) { case DescriptorType::ConstantBuffer: return lit("Constant Buffer"); case DescriptorType::ImageSampler: // no such type on D3D12 case DescriptorType::Buffer: case DescriptorType::Image: case DescriptorType::TypedBuffer: case DescriptorType::AccelerationStructure: return lit("Shader Resource View"); case DescriptorType::ReadWriteBuffer: case DescriptorType::ReadWriteTypedBuffer: case DescriptorType::ReadWriteImage: return lit("Unordered Resource View"); case DescriptorType::Sampler: return lit("Sampler"); case DescriptorType::Unknown: return lit("Uninitialised"); } } return QVariant(); } switch(desc.type) { case DescriptorType::ConstantBuffer: case DescriptorType::Buffer: case DescriptorType::ReadWriteBuffer: case DescriptorType::TypedBuffer: case DescriptorType::ReadWriteTypedBuffer: { if(row == 1) return col == 0 ? lit("Buffer") : QVariant::fromValue(desc.resource); if(row == 2) return col == 0 ? lit("Byte Offset") : Formatter::HumanFormat(desc.byteOffset, Formatter::OffsetSize); if(row == 3) return col == 0 ? lit("Byte Size") : Formatter::HumanFormat(desc.byteSize, Formatter::OffsetSize); row -= 4; if(desc.flags != DescriptorFlags::NoFlags) { if(row == 0) return col == 0 ? lit("Flags") : ToQStr(desc.flags); row--; } // format or structure size if(desc.type == DescriptorType::TypedBuffer || desc.type == DescriptorType::ReadWriteTypedBuffer) { if(row == 0) return col == 0 ? lit("Format") : QString(desc.format.Name()); row--; } else if(desc.elementByteSize != 0) { if(row == 0) return col == 0 ? lit("Element Size") : Formatter::HumanFormat(desc.elementByteSize, Formatter::OffsetSize); row--; } // counter buffer, offset, value if(desc.secondary != ResourceId()) { if(row == 0) return col == 0 ? lit("Counter Buffer") : QVariant::fromValue(desc.secondary); if(row == 1) return col == 0 ? lit("Counter Byte Offset") : Formatter::HumanFormat(desc.counterByteOffset, Formatter::OffsetSize); if(row == 2) return col == 0 ? lit("Counter Value") : Formatter::Format(desc.bufferStructCount); row -= 3; } if(row == 0) return col == 0 ? lit("Show Contents") : QVariant::fromValue(ButtonTag(true, desc)); break; } case DescriptorType::AccelerationStructure: { if(row == 1) return col == 0 ? lit("Acceleration Structure") : QVariant::fromValue(desc.resource); if(row == 2) return col == 0 ? lit("Byte Size") : Formatter::HumanFormat(desc.byteSize, Formatter::OffsetSize); break; } case DescriptorType::Image: case DescriptorType::ImageSampler: case DescriptorType::ReadWriteImage: { if(row == 1) return col == 0 ? lit("Texture Type") : ToQStr(desc.textureType); if(row == 2) return col == 0 ? lit("Texture") : QVariant::fromValue(desc.resource); if(row == 3) return col == 0 ? lit("Format") : QString(desc.format.Name()); if(row == 4) return col == 0 ? lit("Min LOD") : Formatter::Format(desc.minLODClamp); row -= 5; // first/num slices if(row == 0) return col == 0 ? lit("Slice Range") : QFormatStr("%1 - %2") .arg(desc.firstSlice) .arg(desc.numSlices == UINT16_MAX ? desc.numSlices : desc.firstSlice + desc.numSlices); // first/num mips if(row == 1) return col == 0 ? lit("Mip Range") : QFormatStr("%1 - %2") .arg(desc.firstMip) .arg(desc.numMips == UINT8_MAX ? desc.numMips : desc.firstMip + desc.numMips); // swizzle if(row == 2) return col == 0 ? lit("Swizzle") : QFormatStr("%1%2%3%4") .arg(ToQStr(desc.swizzle.red)) .arg(ToQStr(desc.swizzle.green)) .arg(ToQStr(desc.swizzle.blue)) .arg(ToQStr(desc.swizzle.alpha)); row -= 3; if(desc.flags != DescriptorFlags::NoFlags) { if(row == 0) return col == 0 ? lit("Flags") : ToQStr(desc.flags); row--; } if(desc.view != ResourceId()) { if(row == 0) return col == 0 ? lit("View") : QVariant::fromValue(desc.view); row--; } if(row == 0) return col == 0 ? lit("Show Contents") : QVariant::fromValue(ButtonTag(false, desc)); break; } case DescriptorType::Sampler: break; case DescriptorType::Unknown: break; } return QVariant(); } int samplerRowCount(bool combinedSampler = false) const { // type, address U/V/W, filter min/mag/mip, filter function int ret = 8; // omit the type for combined samplers if(combinedSampler) ret--; // min/max LOD ret++; // mip bias ret++; if(m_API == GraphicsAPI::Vulkan) { // immutable ret++; // object ret++; // seamless and unnormalized ret += 2; // sRGB border ret++; // munged ycbcr stuff ret++; } return ret; } QString filter(FilterMode mode, float aniso) const { QString ret = ToQStr(mode); if(mode == FilterMode::Anisotropic) ret += QFormatStr(" %1x").arg(aniso); return ret; } QString filter(FilterFunction func, CompareFunction compare) const { QString ret = ToQStr(func); if(func == FilterFunction::Comparison) ret += QFormatStr(" %1").arg(ToStr(compare)); return ret; } QVariant data(const SamplerDescriptor &desc, int row, int col, bool combinedSampler = false) const { if(combinedSampler) { // omit the type } else { if(row == 0) return col == 0 ? lit("Type") : ToQStr(desc.type); row--; } if(m_Ctx.APIProps().pipelineType == GraphicsAPI::Vulkan) { if(row == 0) return col == 0 ? lit("Immutable") : Formatter::Format(desc.creationTimeConstant); row--; } if(row == 0) return col == 0 ? lit("U Addressing") : ToQStr(desc.addressU); if(row == 1) return col == 0 ? lit("V Addressing") : ToQStr(desc.addressV); if(row == 2) return col == 0 ? lit("W Addressing") : ToQStr(desc.addressW); if(row == 3) return col == 0 ? lit("Minify") : filter(desc.filter.minify, desc.maxAnisotropy); if(row == 4) return col == 0 ? lit("Magnify") : filter(desc.filter.magnify, desc.maxAnisotropy); if(row == 5) return col == 0 ? lit("Mip") : filter(desc.filter.mip, desc.maxAnisotropy); if(row == 6) return col == 0 ? lit("Filter") : filter(desc.filter.filter, desc.compareFunction); if(row == 7) { QString minLOD = Formatter::Format(desc.minLOD); QString maxLOD = Formatter::Format(desc.maxLOD); if(desc.minLOD == -FLT_MAX) minLOD = lit("0"); if(desc.minLOD == -1000.0) minLOD = lit("VK_LOD_CLAMP_NONE"); if(desc.maxLOD == FLT_MAX) maxLOD = lit("FLT_MAX"); if(desc.maxLOD == 1000.0) maxLOD = lit("VK_LOD_CLAMP_NONE"); return col == 0 ? lit("LOD Range") : QFormatStr("%1 - %2").arg(minLOD).arg(maxLOD); } if(row == 8) return col == 0 ? lit("Mip Bias") : Formatter::Format(desc.mipBias); if(m_Ctx.APIProps().pipelineType == GraphicsAPI::Vulkan) { if(row == 9) return col == 0 ? lit("Sampler") : QVariant::fromValue(desc.object); if(row == 10) return col == 0 ? lit("Seamless Cubemaps") : Formatter::Format(desc.seamlessCubemaps); if(row == 11) return col == 0 ? lit("Unnormalised") : Formatter::Format(desc.unnormalized); if(row == 12) return col == 0 ? lit("sRGB Border") : Formatter::Format(desc.srgbBorder); if(row == 13) { if(col == 0) return lit("yCbCr Sampling"); QString data; if(desc.ycbcrSampler != ResourceId()) { data = m_Ctx.GetResourceName(desc.ycbcrSampler); data += QFormatStr(", %1 %2").arg(ToQStr(desc.ycbcrModel)).arg(ToQStr(desc.ycbcrRange)); data += tr(", Chroma %1 [%2,%3]") .arg(ToQStr(desc.chromaFilter)) .arg(ToQStr(desc.xChromaOffset)) .arg(ToQStr(desc.yChromaOffset)); if(desc.forceExplicitReconstruction) data += tr(" Explicit"); } else { data = lit("N/A"); } return data; } } return QVariant(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if(role != Qt::DisplayRole) return QVariant(); if(index.isValid()) { int row = index.row(); int col = index.column(); quintptr id = index.internalId(); if(m_View.m_D3D12RootSig.resourceId != ResourceId()) { // the fixed nodes are under the root if(id == FixedNode) { if(col == 0) { if(row == ParametersRootNode) return lit("Parameters"); if(row == StaticSamplersRootNode) return lit("Static Samplers"); QVariant ret = QFormatStr("Heap %1").arg(ToQStr(m_View.m_D3D12Heaps[row - FirstHeapRootNode])); RichResourceTextInitialise(ret, &m_Ctx, false); return ret; } if(col == 1 && row >= FirstHeapRootNode) return QVariant::fromValue(ButtonTag(m_View.m_D3D12Heaps[row - FirstHeapRootNode])); return QVariant(); } if(id == ParameterFlag) { if(col == 0) return QFormatStr("Parameter %1").arg(row); return QVariant(); } if(id == StaticSamplerData) { if(col == 0) return QFormatStr("Static Sampler %1").arg(row); return QVariant(); } if((id & ParameterData) == 0) { // this is the index + 1 uint32_t sampIndex = id & ~StaticSamplerData; if(sampIndex == 0) return QVariant(); sampIndex--; const D3D12Pipe::StaticSampler &samp = m_View.m_D3D12RootSig.staticSamplers[sampIndex]; if(row >= StaticSamplerFixedRowCount) return data(samp.descriptor, row - StaticSamplerFixedRowCount, col); if(row == 0) return col == 0 ? lit("Visibility") : ToQStr(samp.visibility); if(row == 1) return col == 0 ? lit("Register Space") : Formatter::Format(samp.space); if(row == 2) return col == 0 ? lit("Register") : Formatter::Format(samp.reg); return QVariant(); } RootIdx decodedIndex = Decode(id); const D3D12Pipe::RootParam ¶m = m_View.m_D3D12RootSig.parameters[decodedIndex.parameter - 1]; if(!param.constants.empty()) { if(row == 0) return col == 0 ? lit("Visibility") : ToQStr(param.visibility); rdcarray words; words.resize(param.constants.byteSize() >> 2); memcpy(words.data(), param.constants.data(), words.byteSize()); if(col == 0) { if(row == 1) return lit("Data (Decimal)"); if(row == 2) return lit("Data (Hexadecimal)"); if(row == 3) return lit("Data (Float)"); } QString data; if(row == 3) { float *f = (float *)words.data(); for(size_t i = 0; i < words.size(); i++) data += Formatter::Format(f[i]) + lit(" "); } else { for(uint32_t w : words) data += Formatter::Format(w, row == 2) + lit(" "); } return data; } if(param.descriptor.type != DescriptorType::Unknown) { if(row >= DescParameterFixedRowCount) return data(param.descriptor, row - DescParameterFixedRowCount, col); if(row == 0) return col == 0 ? lit("Visibility") : ToQStr(param.visibility); if(row == 1) return col == 0 ? lit("Register Space") : Formatter::Format(param.space); if(row == 2) return col == 0 ? lit("Register") : Formatter::Format(param.reg); return QVariant(); } if(decodedIndex.range == 0) { if(row >= TableParameterFixedRowCount) { if(col == 0) return QFormatStr("Range %1").arg(row - TableParameterFixedRowCount); return QVariant(); } if(row == 0) return col == 0 ? lit("Visibility") : ToQStr(param.visibility); if(row == 1) return col == 0 ? lit("Heap") : QVariant::fromValue(param.heap); if(row == 2) return col == 0 ? lit("Table Offset") : ToQStr(param.heapByteOffset); return QVariant(); } const D3D12Pipe::RootTableRange &range = param.tableRanges[decodedIndex.range - 1]; if(decodedIndex.descriptor == 0) { if(row >= RangeFixedRowCount) { if(col == 0) { // with no descriptors, we put the space/register here if(m_View.m_Descriptors.empty()) { if(row == RangeFixedRowCount) return col == 0 ? lit("Register Space") : ToQStr(range.space); return col == 0 ? lit("Base Register") : ToQStr(range.baseRegister); } // otherwise we name all the descriptors QLatin1Char regChar = QLatin1Char('?'); if(range.category == DescriptorCategory::Sampler) regChar = QLatin1Char('s'); if(range.category == DescriptorCategory::ConstantBlock) regChar = QLatin1Char('b'); if(range.category == DescriptorCategory::ReadOnlyResource) regChar = QLatin1Char('t'); if(range.category == DescriptorCategory::ReadWriteResource) regChar = QLatin1Char('u'); return QFormatStr("%1%2, space %3") .arg(regChar) .arg(range.baseRegister + row - RangeFixedRowCount) .arg(range.space); } return QVariant(); } if(row == 0) { if(col == 0) return lit("Range Type"); if(range.category == DescriptorCategory::Sampler) return lit("Sampler"); if(range.category == DescriptorCategory::ConstantBlock) return lit("Constant Buffer"); if(range.category == DescriptorCategory::ReadOnlyResource) return lit("SRV"); if(range.category == DescriptorCategory::ReadWriteResource) return lit("UAV"); return QVariant(); } if(row == 1) { if(col == 0) return lit("Table offset"); if(range.appended) return QFormatStr("D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND (%1)").arg(range.tableByteOffset); return Formatter::HumanFormat(range.tableByteOffset, Formatter::NoFlags); } if(row == 2) return col == 0 ? lit("Descriptor Count") : Formatter::HumanFormat(range.count, Formatter::NoFlags); return QVariant(); } // descriptor node data itself const Descriptor &descriptor = m_View.m_Descriptors[param.heapByteOffset + range.tableByteOffset + decodedIndex.descriptor - 1]; if(row == 0) return col == 0 ? lit("Index in heap") : Formatter::HumanFormat(param.heapByteOffset + range.tableByteOffset + decodedIndex.descriptor - 1, Formatter::NoFlags); return data(descriptor, row - RootSigDescriptorFixedRows, col); } else { // the descriptors are parented directly under the root if(index.internalId() & DescriptorFlag) { if(col != 0) return QVariant(); if(row < m_View.m_Locations.count()) return m_View.m_Locations[row].logicalBindName; else return QFormatStr("Descriptor %1").arg(row); } if(index.internalId() & DescriptorDataFlag) { uint32_t descIndex = index.internalId() & ~DescriptorDataFlag; if(descIndex < m_View.m_Locations.size()) { if(row == 0) return col == 0 ? lit("Shader Mask") : ToQStr(m_View.m_Locations[descIndex].stageMask); row--; } const Descriptor &desc = m_View.m_Descriptors[descIndex]; uint32_t sampIndex = descIndex; if(!m_View.m_DescriptorToSamplerLookup.empty()) sampIndex = m_View.m_DescriptorToSamplerLookup[descIndex]; SamplerDescriptor dummy; const SamplerDescriptor &samp = sampIndex < m_View.m_SamplerDescriptors.size() ? m_View.m_SamplerDescriptors[sampIndex] : dummy; if(desc.type == DescriptorType::Sampler) { return data(samp, row, col); } else if(desc.type == DescriptorType::ImageSampler) { int pureDescriptorRowCount = rowCount(desc, false); if(row >= pureDescriptorRowCount) return data(samp, row - pureDescriptorRowCount, col, true); } return data(desc, row, col); } } } return QVariant(); } private: ICaptureContext &m_Ctx; DescriptorViewer &m_View; GraphicsAPI m_API; #define BITS (QT_POINTER_SIZE * 8) static const int ParametersRootNode = 0; static const int StaticSamplersRootNode = 1; static const int FirstHeapRootNode = 2; // the number of rows in a table parameter before the ranges: visibility, heap, heap offset static const int TableParameterFixedRowCount = 3; // the number of rows in a range before the descriptors: category, table offset, count static const int RangeFixedRowCount = 3; // visibility only static const int DescParameterFixedRowCount = 3; // visibility, and 3 forms of interpretation of constants (float, decimal, hex) static const int ConstParameterFixedRowCount = 4; // 3 for space/reg/visibility, plus sampler properties static const int StaticSamplerFixedRowCount = 3; // one extra row for root-signature based descriptors to give the absolute heap offset static const int RootSigDescriptorFixedRows = 1; // extra row for the descripor visibility from location static const int DescriptorLocationFixedRowCount = 1; // top bit indicates parameter data or static sampler data static const quintptr ParameterData = 1ULL << (BITS - 1); static const quintptr StaticSamplerData = 0; #if BITS == 32 // 32-bit packing: // | 1 bit ParameterData flag | // | 6 bits Parameter Index | // | 5 bits Table Index | // | 20 bits Descriptor Index | static const quintptr ParameterFlag = ~0U; static const quintptr FixedNode = ~0U - 1; static const quintptr ParameterIndexMask = 0x3f; static const quintptr ParameterIndexShift = 25; static const quintptr TableIndexMask = 0x1f; static const quintptr TableIndexShift = 20; static const quintptr DescriptorIndexMask = 0xfffff; #else // 64-bit packing: // | 1 bit ParameterData flag | // | 1 bit Parameter Node flag | // | 1 bit Fixed Node flag | // | 5 bits padding | // | 8-bit Parameter Index | // | 16 bit Table Index | // | 32 bits Descriptor Index | static const quintptr ParameterFlag = 3ULL << (BITS - 2); static const quintptr FixedNode = 1ULL << (BITS - 3); static const quintptr ParameterIndexMask = 0xff; static const quintptr ParameterIndexShift = 48; static const quintptr TableIndexMask = 0xffff; static const quintptr TableIndexShift = 32; static const quintptr DescriptorIndexMask = 0xffffffff; #endif static_assert(ParameterFlag & ParameterData, "ParameterFlag should have ParameterData bit set"); static_assert((DescriptorIndexMask & (ParameterIndexMask << ParameterIndexShift)) == 0, "Mask overlaps"); static_assert((DescriptorIndexMask & (TableIndexMask << TableIndexShift)) == 0, "Mask overlaps"); static_assert(((ParameterIndexMask << ParameterIndexShift) & (TableIndexMask << TableIndexShift)) == 0, "Mask overlaps"); static_assert(quintptr(quintptr(ParameterIndexMask << ParameterIndexShift) >> ParameterIndexShift) == ParameterIndexMask, "Mask is clipped"); static_assert(quintptr(quintptr(TableIndexMask << TableIndexShift) >> TableIndexShift) == TableIndexMask, "Mask is clipped"); struct RootIdx { uint8_t parameter; uint16_t range; uint32_t descriptor; }; static_assert(ParameterIndexMask <= UINT8_MAX, "Parameter mask is too large for decoded storage"); static_assert(TableIndexMask <= UINT16_MAX, "Table mask is too large for decoded storage"); static_assert(DescriptorIndexMask <= UINT32_MAX, "Descriptor mask is too large for decoded storage"); RootIdx Decode(quintptr id) const { return {uint8_t((id >> ParameterIndexShift) & ParameterIndexMask), uint16_t((id >> TableIndexShift) & TableIndexMask), uint32_t(id & DescriptorIndexMask)}; } quintptr Encode(RootIdx idx) const { return ParameterData | (quintptr(idx.parameter & ParameterIndexMask) << ParameterIndexShift) | (quintptr(idx.range & TableIndexMask) << TableIndexShift) | quintptr(idx.descriptor & DescriptorIndexMask); } // simple flags for plain descriptor index static const quintptr DescriptorDataFlag = 1ULL << (BITS - 1); static const quintptr DescriptorFlag = 1ULL << (BITS - 2); #undef BITS }; DescriptorViewer::DescriptorViewer(ICaptureContext &ctx, QWidget *parent) : QFrame(parent), ui(new Ui::DescriptorViewer), m_Ctx(ctx) { ui->setupUi(this); { static bool registered = false; if(!registered) { registered = true; QMetaType::registerComparators(); } } m_Model = new DescriptorItemModel(ctx, *this, this); ui->descriptors->setModel(m_Model); ui->descriptors->setFont(Formatter::PreferredFont()); ui->descriptors->header()->setStretchLastSection(true); ui->descriptors->header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); ui->descriptors->header()->setSectionResizeMode(0, QHeaderView::Interactive); ui->descriptors->header()->setMinimumSectionSize(40); ui->descriptors->header()->resizeSection(0, 150); ui->descriptors->header()->setSectionsMovable(false); ui->descriptors->header()->setCascadingSectionResizes(false); ButtonDelegate *viewDelegate = new ButtonDelegate(Icons::action_hover(), QString(), this); viewDelegate->setVisibleTrigger(Qt::DisplayRole, QVariant::fromValue(ButtonTag(false, Descriptor()))); viewDelegate->setCentred(false); ui->descriptors->setItemDelegate(viewDelegate); QObject::connect(viewDelegate, &ButtonDelegate::messageClicked, [this](const QModelIndex &idx) { ButtonTag tag = idx.data(Qt::DisplayRole).value(); if(tag.heap != ResourceId()) { IDescriptorViewer *viewer = m_Ctx.ViewDescriptorStore(tag.heap); m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); return; } if(tag.descriptor.type == DescriptorType::Unknown) return; if(tag.buffer && tag.descriptor.resource != ResourceId()) { QString format; if(tag.descriptor.type == DescriptorType::TypedBuffer || tag.descriptor.type == DescriptorType::ReadWriteTypedBuffer) format = BufferFormatter::GetBufferFormatString(Packing::C, ResourceId(), ShaderResource(), tag.descriptor.format); IBufferViewer *viewer = m_Ctx.ViewBuffer(tag.descriptor.byteOffset, tag.descriptor.byteSize, tag.descriptor.resource, format); m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); } else { TextureDescription *tex = NULL; CompType typeCast = CompType::Typeless; tex = m_Ctx.GetTexture(tag.descriptor.resource); typeCast = tag.descriptor.format.compType; if(tex) { if(tex->type == TextureType::Buffer) { IBufferViewer *viewer = m_Ctx.ViewTextureAsBuffer( tex->resourceId, Subresource(), BufferFormatter::GetTextureFormatString(*tex)); m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); } else { if(!m_Ctx.HasTextureViewer()) m_Ctx.ShowTextureViewer(); ITextureViewer *viewer = m_Ctx.GetTextureViewer(); viewer->ViewTexture(tex->resourceId, typeCast, true); } } } }); m_Ctx.AddCaptureViewer(this); } DescriptorViewer::~DescriptorViewer() { m_Ctx.RemoveCaptureViewer(this); delete ui; } void DescriptorViewer::ViewDescriptorStore(ResourceId id) { DescriptorStoreDescription *desc = m_Ctx.GetDescriptorStore(id); if(!desc) { qCritical() << "Invalid ID passed to ViewDescriptorStore"; return; } m_DescriptorStore = *desc; setWindowTitle(tr("%1 contents").arg(m_Ctx.GetResourceName(m_DescriptorStore.resourceId))); ui->pipeLabel->setText( tr("The pipeline state viewer shows the current bindings in an easier format.")); // refresh contents for the descriptor store OnEventChanged(m_Ctx.CurEvent()); } void DescriptorViewer::ViewDescriptors(const rdcarray &descriptors, const rdcarray &samplerDescriptors) { m_Descriptors = descriptors; m_SamplerDescriptors = samplerDescriptors; m_Descriptors.resize(qMin(descriptors.size(), samplerDescriptors.size())); m_SamplerDescriptors.resize(qMin(descriptors.size(), samplerDescriptors.size())); setWindowTitle(tr("Descriptors")); ui->pipeLabel->setText(QString()); m_Model->refresh(); } void DescriptorViewer::ViewD3D12State() { m_D3D12Heaps = m_Ctx.CurD3D12PipelineState()->descriptorHeaps; m_D3D12RootSig = m_Ctx.CurD3D12PipelineState()->rootSignature; setWindowTitle( tr("%1 at EID %2").arg(m_Ctx.GetResourceName(m_D3D12RootSig.resourceId)).arg(m_Ctx.CurEvent())); ui->pipeLabel->setText( tr("The pipeline state viewer shows the current bindings in an easier format.\n" "This is a snapshot of the root signature & bound parameters at EID %1.") .arg(m_Ctx.CurEvent())); // fetch the descriptor contents for both heaps m_Ctx.Replay().AsyncInvoke([this](IReplayController *r) { ResourceId resourceHeap, samplerHeap; for(const D3D12Pipe::RootParam ¶m : m_D3D12RootSig.parameters) { for(const D3D12Pipe::RootTableRange &range : param.tableRanges) { if(range.category == DescriptorCategory::Sampler) { if(param.heap != ResourceId()) samplerHeap = param.heap; } else { if(param.heap != ResourceId()) resourceHeap = param.heap; } if(resourceHeap != ResourceId() && samplerHeap != ResourceId()) break; } if(resourceHeap != ResourceId() && samplerHeap != ResourceId()) break; } rdcarray ranges; ranges.resize(1); rdcarray descriptors; rdcarray samplerDescriptors; if(resourceHeap != ResourceId()) { DescriptorStoreDescription *resourceDesc = m_Ctx.GetDescriptorStore(resourceHeap); if(resourceDesc) { ranges[0].count = resourceDesc->descriptorCount; ranges[0].descriptorSize = resourceDesc->descriptorByteSize; ranges[0].offset = resourceDesc->firstDescriptorOffset; // we are interpreting typeless descriptors, assume D3D12 can interpret it ranges[0].type = DescriptorType::Unknown; descriptors = r->GetDescriptors(resourceDesc->resourceId, ranges); } } else if(samplerHeap != ResourceId()) { DescriptorStoreDescription *samplerDesc = m_Ctx.GetDescriptorStore(samplerHeap); if(samplerDesc) { ranges[0].count = samplerDesc->descriptorCount; ranges[0].descriptorSize = samplerDesc->descriptorByteSize; ranges[0].offset = samplerDesc->firstDescriptorOffset; // we are interpreting typeless descriptors, assume D3D12 can interpret it ranges[0].type = DescriptorType::Unknown; samplerDescriptors = r->GetSamplerDescriptors(samplerDesc->resourceId, ranges); } } GUIInvoke::call(this, [this, descriptors = std::move(descriptors), samplerDescriptors = std::move(samplerDescriptors)] { m_Descriptors = std::move(descriptors); m_SamplerDescriptors = std::move(samplerDescriptors); m_DescriptorToSamplerLookup.clear(); m_Locations.clear(); m_Model->refresh(); }); }); } void DescriptorViewer::OnCaptureClosed() { ToolWindowManager::closeToolWindow(this); } void DescriptorViewer::OnCaptureLoaded() { } void DescriptorViewer::OnEventChanged(uint32_t eventId) { // each time, re-fetch the descriptors to get up to date contents if(m_DescriptorStore.resourceId != ResourceId() && m_DescriptorStore.descriptorByteSize != 0) { m_Ctx.Replay().AsyncInvoke([this](IReplayController *r) { uint32_t descSize = m_DescriptorStore.descriptorByteSize; rdcarray ranges; ranges.resize(1); ranges[0].count = m_DescriptorStore.descriptorCount; ranges[0].descriptorSize = descSize; ranges[0].offset = m_DescriptorStore.firstDescriptorOffset; // assume this descriptor store knows its type information and can interpret typeless descriptors ranges[0].type = DescriptorType::Unknown; rdcarray descriptors = r->GetDescriptors(m_DescriptorStore.resourceId, ranges); rdcarray locations = r->GetDescriptorLocations(m_DescriptorStore.resourceId, ranges); // fetch only sampler descriptors that we need rdcarray descriptorToSamplerLookup; descriptorToSamplerLookup.fill(descriptors.size(), ~0u); ranges.clear(); uint32_t idx = 0; for(size_t i = 0; i < descriptors.size(); i++) { if(descriptors[i].type == DescriptorType::Sampler || descriptors[i].type == DescriptorType::ImageSampler) { descriptorToSamplerLookup[i] = idx; idx++; // combine contiguous ranges if(!ranges.empty() && ranges.back().offset + ranges.back().count * descSize == i * descSize && ranges.back().type == descriptors[i].type) { ranges.back().count++; } else { DescriptorRange range; range.offset = m_DescriptorStore.firstDescriptorOffset + uint32_t(i * descSize); range.descriptorSize = descSize; range.count = 1; range.type = descriptors[i].type; ranges.push_back(range); } } } rdcarray samplerDescriptors = r->GetSamplerDescriptors(m_DescriptorStore.resourceId, ranges); GUIInvoke::call(this, [this, descriptors = std::move(descriptors), locations = std::move(locations), descriptorToSamplerLookup = std::move(descriptorToSamplerLookup), samplerDescriptors = std::move(samplerDescriptors)] { m_Descriptors = std::move(descriptors); m_Locations = std::move(locations); m_SamplerDescriptors = std::move(samplerDescriptors); m_DescriptorToSamplerLookup = std::move(descriptorToSamplerLookup); RDTreeViewExpansionState state; ui->descriptors->saveExpansion(state, 0); m_Model->refresh(); ui->descriptors->applyExpansion(state, 0); }); }); } } void DescriptorViewer::on_pipeButton_clicked() { m_Ctx.ShowPipelineViewer(); }