From 1194531fc519b60adddc9826cb2e641d9045dc07 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 10 Apr 2024 14:08:01 +0100 Subject: [PATCH] Add a descriptor viewer for showing all of a heap, root sig, or set --- qrenderdoc/Code/BufferFormatter.cpp | 2 +- qrenderdoc/Code/CaptureContext.cpp | 20 + qrenderdoc/Code/CaptureContext.h | 3 + qrenderdoc/Code/Interface/QRDInterface.h | 28 + qrenderdoc/Code/QRDUtils.cpp | 128 ++ qrenderdoc/Code/QRDUtils.h | 41 + qrenderdoc/Windows/DescriptorViewer.cpp | 1565 +++++++++++++++++ qrenderdoc/Windows/DescriptorViewer.h | 83 + qrenderdoc/Windows/DescriptorViewer.ui | 142 ++ .../D3D12PipelineStateViewer.cpp | 34 + .../PipelineState/D3D12PipelineStateViewer.h | 1 + .../PipelineState/D3D12PipelineStateViewer.ui | 200 ++- .../VulkanPipelineStateViewer.cpp | 152 +- .../PipelineState/VulkanPipelineStateViewer.h | 7 +- .../VulkanPipelineStateViewer.ui | 616 +++++++ qrenderdoc/Windows/PythonShell.cpp | 11 + qrenderdoc/Windows/ResourceInspector.cpp | 10 +- qrenderdoc/Windows/ShaderMessageViewer.cpp | 102 +- qrenderdoc/Windows/ShaderMessageViewer.h | 22 +- qrenderdoc/qrenderdoc.pro | 3 + qrenderdoc/qrenderdoc_local.vcxproj | 15 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 15 + 22 files changed, 3043 insertions(+), 157 deletions(-) create mode 100644 qrenderdoc/Windows/DescriptorViewer.cpp create mode 100644 qrenderdoc/Windows/DescriptorViewer.h create mode 100644 qrenderdoc/Windows/DescriptorViewer.ui diff --git a/qrenderdoc/Code/BufferFormatter.cpp b/qrenderdoc/Code/BufferFormatter.cpp index 638d5c4c2..7fedf379f 100644 --- a/qrenderdoc/Code/BufferFormatter.cpp +++ b/qrenderdoc/Code/BufferFormatter.cpp @@ -2410,7 +2410,7 @@ QString BufferFormatter::GetBufferFormatString(Packing::Rules pack, ResourceId s } else { - const auto &desc = res.variableType; + const ShaderConstantType &desc = res.variableType; if(viewFormat.type == ResourceFormatType::Undefined) { diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index b896d6874..748570d7a 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -42,6 +42,7 @@ #include "Windows/BufferViewer.h" #include "Windows/CommentView.h" #include "Windows/DebugMessageView.h" +#include "Windows/DescriptorViewer.h" #include "Windows/Dialogs/CaptureDialog.h" #include "Windows/Dialogs/CrashDialog.h" #include "Windows/Dialogs/LiveCapture.h" @@ -2589,6 +2590,25 @@ IShaderMessageViewer *CaptureContext::ViewShaderMessages(ShaderStageMask stages) return new ShaderMessageViewer(*this, stages, m_MainWindow); } +IDescriptorViewer *CaptureContext::ViewDescriptorStore(ResourceId id) +{ + DescriptorViewer *viewer = new DescriptorViewer(*this, m_MainWindow); + + viewer->ViewDescriptorStore(id); + + return viewer; +} + +IDescriptorViewer *CaptureContext::ViewDescriptors(const rdcarray &descriptors, + const rdcarray &samplerDescriptors) +{ + DescriptorViewer *viewer = new DescriptorViewer(*this, m_MainWindow); + + viewer->ViewDescriptors(descriptors, samplerDescriptors); + + return viewer; +} + IBufferViewer *CaptureContext::ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const rdcstr &format) { diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 381fbe1a1..7541de474 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -266,6 +266,9 @@ public: IShaderMessageViewer *ViewShaderMessages(ShaderStageMask stages) override; + IDescriptorViewer *ViewDescriptorStore(ResourceId id) override; + IDescriptorViewer *ViewDescriptors(const rdcarray &descriptors, + const rdcarray &samplerDescriptors) override; IBufferViewer *ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const rdcstr &format = "") override; IBufferViewer *ViewTextureAsBuffer(ResourceId id, const Subresource &sub, diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index e8d62b1b6..5494eef2e 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -2608,6 +2608,34 @@ through the execution of a given shader. )"); virtual IShaderMessageViewer *ViewShaderMessages(ShaderStageMask stages) = 0; + DOCUMENT(R"(Show a new :class:`DescriptorViewer` window, showing the full raw contents of a +descriptor store. + +:param renderdoc.ResourceId id: The ID of the descriptor store to fetch data from. +:return: The new :class:`DescriptorViewer` window opened, but not shown. +:rtype: DescriptorViewer +)"); + virtual IDescriptorViewer *ViewDescriptorStore(ResourceId id) = 0; + + DOCUMENT(R"(Show a new :class:`DescriptorViewer` window, showing contents of an arbitrary list of +descriptors. + +The descriptor lists should be in parallel, with identical sizes. If a non-sampler descriptor is +to be displayed, the corresponding sampler descriptor should be uninitialised and vice-versa. If +the lists are not the same length, only indices up to the minimum of the two lengths will be used. + +This function should not be used to view the entirety of a descriptor store - in that case the +:func:`ViewDescriptorStore` function will be more efficient. + +:param List[renderdoc.Descriptor] descriptors: The list of descriptors to process and show. +:param List[renderdoc.Descriptor] samplerDescriptors: The list of sampler descriptors to process and + show. +:return: The new :class:`DescriptorViewer` window opened, but not shown. +:rtype: DescriptorViewer +)"); + virtual IDescriptorViewer *ViewDescriptors(const rdcarray &descriptors, + const rdcarray &samplerDescriptors) = 0; + DOCUMENT(R"(Show a new :class:`BufferViewer` window, showing a read-only view of buffer data. :param int byteOffset: The offset in bytes to the start of the buffer data to show. diff --git a/qrenderdoc/Code/QRDUtils.cpp b/qrenderdoc/Code/QRDUtils.cpp index fb4eaaddc..88bc2a542 100644 --- a/qrenderdoc/Code/QRDUtils.cpp +++ b/qrenderdoc/Code/QRDUtils.cpp @@ -1365,6 +1365,134 @@ bool RichTextViewDelegate::linkHover(QMouseEvent *e, const QFont &font, const QM return false; } +ButtonDelegate::ButtonDelegate(const QIcon &icon, QString text, QWidget *parent) + : m_Icon(icon), m_Text(text), QStyledItemDelegate(parent) +{ +} + +void ButtonDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if(m_VisibleRole != -1 && index.data(m_VisibleRole) != m_VisibleValue) + return QStyledItemDelegate::paint(painter, option, index); + + // draw the background to get selection etc + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &option, painter); + + QStyleOptionButton button; + + button.text = m_Text; + button.icon = m_Icon; + + if(m_EnableRole == -1 || index.data(m_EnableRole) == m_EnableValue) + button.state = QStyle::State_Enabled; + + if(m_ClickedIndex == index) + button.state |= QStyle::State_Sunken; + + QSize sz = + QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &button, option.decorationSize); + + button.rect = getButtonRect(option.rect, sz); + button.iconSize = option.decorationSize; + + QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter); +} + +QSize ButtonDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if(m_VisibleRole != -1 && index.data(m_VisibleRole) != m_VisibleValue) + return QStyledItemDelegate::sizeHint(option, index); + + QStyleOptionButton button; + button.text = m_Text; + button.icon = m_Icon; + button.state = QStyle::State_Enabled; + + return QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &button, + option.decorationSize); +} + +bool ButtonDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if(m_VisibleRole != -1 && index.data(m_VisibleRole) != m_VisibleValue) + return QStyledItemDelegate::editorEvent(event, model, option, index); + + if(event->type() == QEvent::MouseButtonPress) + { + QMouseEvent *e = (QMouseEvent *)event; + + QPoint p = e->pos(); + + QSize sz = sizeHint(option, index); + QRect rect = getButtonRect(option.rect, sz); + + if(rect.contains(p) && (m_EnableRole == -1 || index.data(m_EnableRole) == m_EnableValue)) + { + m_ClickedIndex = index; + return true; + } + } + else if(event->type() == QEvent::MouseMove) + { + QMouseEvent *e = (QMouseEvent *)event; + + if(m_ClickedIndex != index || (e->buttons() & Qt::LeftButton) == 0) + { + m_ClickedIndex = QModelIndex(); + } + else + { + QPoint p = e->pos(); + + QSize sz = sizeHint(option, index); + QRect rect = getButtonRect(option.rect, sz); + + if(!rect.contains(p)) + { + m_ClickedIndex = QModelIndex(); + } + } + } + else if(event->type() == QEvent::MouseButtonRelease) + { + if(m_ClickedIndex == index && index != QModelIndex()) + { + m_ClickedIndex = QModelIndex(); + + QMouseEvent *e = (QMouseEvent *)event; + + QPoint p = e->pos(); + + QSize sz = sizeHint(option, index); + QRect rect = getButtonRect(option.rect, sz); + + if(rect.contains(p)) + { + emit messageClicked(index); + return true; + } + } + } + + return QStyledItemDelegate::editorEvent(event, model, option, index); +} + +QRect ButtonDelegate::getButtonRect(const QRect boundsRect, const QSize sz) const +{ + QRect rect = boundsRect; + rect.setWidth(qMin(rect.width(), sz.width())); + rect.setHeight(qMin(rect.height(), sz.height())); + if(m_Centered) + rect.moveLeft(rect.center().x() - rect.width() / 2); + rect.moveTop(rect.center().y() - rect.height() / 2); + // clip if the rounding from centering caused us to go out of bounds + rect.setTop(qMax(rect.top(), boundsRect.top())); + rect.setLeft(qMax(rect.left(), boundsRect.left())); + return rect; +} + #include "renderdoc_tostr.inl" QString ToQStr(const ResourceUsage usage, const GraphicsAPI apitype) diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index 9176dd489..9a3acd5c3 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -766,6 +766,47 @@ private: QAbstractItemView *m_widget; }; +class ButtonDelegate : public QStyledItemDelegate +{ +private: + Q_OBJECT + + QModelIndex m_ClickedIndex; + QIcon m_Icon; + QString m_Text; + bool m_Centered = true; + + int m_EnableRole = -1; + QVariant m_EnableValue; + + int m_VisibleRole = -1; + QVariant m_VisibleValue; + + QRect getButtonRect(const QRect boundsRect, const QSize sz) const; + +public: + ButtonDelegate(const QIcon &icon, QString text, QWidget *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, + const QModelIndex &index) override; + + void setVisibleTrigger(int role, QVariant value) + { + m_VisibleRole = role; + m_VisibleValue = value; + } + void setEnableTrigger(int role, QVariant value) + { + m_EnableRole = role; + m_EnableValue = value; + } + void setCentred(bool centered) { m_Centered = centered; } +signals: + void messageClicked(const QModelIndex &index); +}; + class StructuredDataItemModel : public QAbstractItemModel { public: diff --git a/qrenderdoc/Windows/DescriptorViewer.cpp b/qrenderdoc/Windows/DescriptorViewer.cpp new file mode 100644 index 000000000..954f10b1b --- /dev/null +++ b/qrenderdoc/Windows/DescriptorViewer.cpp @@ -0,0 +1,1565 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2024 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::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::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: 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::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 == UINT_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 == UINT_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); + + 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 = 1; + // 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; + 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; + 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_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; + + 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().count++; + } + else + { + DescriptorRange range; + range.offset = uint32_t(i * descSize); + range.descriptorSize = descSize; + range.count = 1; + 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(); +} diff --git a/qrenderdoc/Windows/DescriptorViewer.h b/qrenderdoc/Windows/DescriptorViewer.h new file mode 100644 index 000000000..802b487c6 --- /dev/null +++ b/qrenderdoc/Windows/DescriptorViewer.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2024 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. + ******************************************************************************/ + +#pragma once + +#include +#include "Code/Interface/QRDInterface.h" + +namespace Ui +{ +class DescriptorViewer; +} + +class DescriptorItemModel; + +class DescriptorViewer : public QFrame, public IDescriptorViewer, public ICaptureViewer +{ + Q_OBJECT + +public: + explicit DescriptorViewer(ICaptureContext &ctx, QWidget *parent = 0); + ~DescriptorViewer(); + + void ViewDescriptorStore(ResourceId id); + void ViewDescriptors(const rdcarray &descriptors, + const rdcarray &samplerDescriptors); + void ViewD3D12State(); + + // IDescriptorViewer + QWidget *Widget() override { return this; } + // ICaptureViewer + void OnCaptureLoaded() override; + void OnCaptureClosed() override; + void OnSelectedEventChanged(uint32_t eventId) override {} + void OnEventChanged(uint32_t eventId) override; + +private slots: + void on_pipeButton_clicked(); + +private: + Ui::DescriptorViewer *ui; + ICaptureContext &m_Ctx; + + DescriptorStoreDescription m_DescriptorStore; + + rdcarray m_Descriptors; + rdcarray m_SamplerDescriptors; + rdcarray m_Locations; + + // the descriptors array is always full (we don't worry about the overallocation for only-samplers), + // but if we fetched these ourselves we will have fetched samplers sparsely only when necessary. + // This array is the same size as m_Descriptors in that case containing the lookup indices in the samplers array + rdcarray m_DescriptorToSamplerLookup; + + rdcarray m_D3D12Heaps; + D3D12Pipe::RootSignature m_D3D12RootSig; + + // make the model a friend for simplicity + friend class DescriptorItemModel; + + DescriptorItemModel *m_Model; +}; diff --git a/qrenderdoc/Windows/DescriptorViewer.ui b/qrenderdoc/Windows/DescriptorViewer.ui new file mode 100644 index 000000000..c4010fdec --- /dev/null +++ b/qrenderdoc/Windows/DescriptorViewer.ui @@ -0,0 +1,142 @@ + + + DescriptorViewer + + + + 0 + 0 + 400 + 300 + + + + Descriptor Viewer + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 5 + + + 0 + + + 2 + + + 6 + + + 2 + + + + + Open Pipeline State + + + + :/action.png:/action.png + + + Open the pipeline state viewer to more easily show the current bindings. + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFrame::Box + + + QFrame::Plain + + + 1 + + + Qt::ScrollBarAlwaysOn + + + QAbstractItemView::NoEditTriggers + + + 12 + + + true + + + true + + + true + + + false + + + false + + + false + + + + + + + + RDTreeView + QTreeWidget +
Widgets/Extended/RDTreeView.h
+
+
+ + +
diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index 12afcecb6..07f90124b 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -31,6 +31,7 @@ #include "Code/Resources.h" #include "Widgets/ComputeDebugSelector.h" #include "Widgets/Extended/RDHeaderView.h" +#include "Windows/DescriptorViewer.h" #include "flowlayout/FlowLayout.h" #include "toolwindowmanager/ToolWindowManager.h" #include "PipelineStateViewer.h" @@ -114,6 +115,11 @@ D3D12PipelineStateViewer::D3D12PipelineStateViewer(ICaptureContext &ctx, ui->psRootSig, ui->csRootSig, ui->asRootSig, ui->msRootSig, }; + QToolButton *sigButtons[] = { + ui->vsRootSigButton, ui->hsRootSigButton, ui->dsRootSigButton, ui->gsRootSigButton, + ui->psRootSigButton, ui->csRootSigButton, ui->asRootSigButton, ui->msRootSigButton, + }; + QToolButton *viewButtons[] = { ui->vsShaderViewButton, ui->hsShaderViewButton, ui->dsShaderViewButton, ui->gsShaderViewButton, ui->psShaderViewButton, ui->csShaderViewButton, @@ -172,6 +178,9 @@ D3D12PipelineStateViewer::D3D12PipelineStateViewer(ICaptureContext &ctx, for(QToolButton *b : viewButtons) QObject::connect(b, &QToolButton::clicked, this, &D3D12PipelineStateViewer::shaderView_clicked); + for(QToolButton *b : sigButtons) + QObject::connect(b, &QToolButton::clicked, this, &D3D12PipelineStateViewer::rootSigView_clicked); + for(RDLabel *b : shaderLabels) { b->setAutoFillBackground(true); @@ -1086,6 +1095,14 @@ void D3D12PipelineStateViewer::clearState() for(QToolButton *b : shaderButtons) b->setEnabled(false); + QToolButton *sigButtons[] = { + ui->vsRootSigButton, ui->hsRootSigButton, ui->dsRootSigButton, ui->gsRootSigButton, + ui->psRootSigButton, ui->csRootSigButton, ui->asRootSigButton, ui->msRootSigButton, + }; + + for(QToolButton *b : sigButtons) + b->setEnabled(false); + const QPixmap &tick = Pixmaps::tick(this); const QPixmap &cross = Pixmaps::cross(this); @@ -1883,6 +1900,14 @@ void D3D12PipelineStateViewer::setState() m_Common.SetupShaderEditButton(b, state.pipelineResourceId, stage->resourceId, stage->reflection); } + QToolButton *sigButtons[] = { + ui->vsRootSigButton, ui->hsRootSigButton, ui->dsRootSigButton, ui->gsRootSigButton, + ui->psRootSigButton, ui->csRootSigButton, ui->asRootSigButton, ui->msRootSigButton, + }; + + for(QToolButton *b : sigButtons) + b->setEnabled(state.rootSignature.resourceId != ResourceId()); + bool streamoutSet = false; vs = ui->gsStreamOut->verticalScrollBar()->value(); ui->gsStreamOut->beginUpdate(); @@ -2537,6 +2562,15 @@ void D3D12PipelineStateViewer::shaderView_clicked() m_Ctx.AddDockWindow(shad->Widget(), DockReference::AddTo, this); } +void D3D12PipelineStateViewer::rootSigView_clicked() +{ + DescriptorViewer *view = (DescriptorViewer *)m_Ctx.ViewDescriptors({}, {}); + + view->ViewD3D12State(); + + m_Ctx.AddDockWindow(view->Widget(), DockReference::AddTo, this); +} + void D3D12PipelineStateViewer::shaderSave_clicked() { const D3D12Pipe::Shader *stage = stageForSender(qobject_cast(QObject::sender())); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h index c22ee1e2a..0f2bf7a95 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h @@ -74,6 +74,7 @@ private slots: // manual slots void shaderView_clicked(); + void rootSigView_clicked(); void shaderSave_clicked(); void resource_itemActivated(RDTreeWidgetItem *item, int column); void cbuffer_itemActivated(RDTreeWidgetItem *item, int column); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui index df4ab30ef..4634623bd 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui @@ -506,7 +506,7 @@ - Shader + Root Signature && Shader @@ -525,6 +525,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -912,7 +935,7 @@ - Shader + Root Signature && Shader @@ -931,6 +954,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -1318,7 +1364,7 @@ - Shader + Root Signature && Shader @@ -1337,6 +1383,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -1724,7 +1793,7 @@ - Shader + Root Signature && Shader @@ -1743,6 +1812,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -2750,7 +2842,7 @@ - Shader + Root Signature && Shader @@ -2769,6 +2861,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -3684,7 +3799,7 @@ - Shader + Root Signature && Shader @@ -3715,6 +3830,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -4122,7 +4260,7 @@ - Shader + Root Signature && Shader @@ -4153,6 +4291,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -4543,7 +4704,7 @@ - Shader + Root Signature && Shader @@ -4574,6 +4735,29 @@ + + + + PointingHandCursor + + + Open Root Signature View + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index da64d15ef..92697e717 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -145,6 +145,11 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, ui->tesShader, ui->gsShader, ui->fsShader, ui->csShader, }; + RDLabel *pipeLayoutLabels[] = { + ui->tsPipeLayout, ui->msPipeLayout, ui->vsPipeLayout, ui->tcsPipeLayout, + ui->tesPipeLayout, ui->gsPipeLayout, ui->fsPipeLayout, ui->csPipeLayout, + }; + QToolButton *viewButtons[] = { ui->tsShaderViewButton, ui->msShaderViewButton, ui->vsShaderViewButton, ui->tcsShaderViewButton, ui->tesShaderViewButton, ui->gsShaderViewButton, @@ -184,6 +189,11 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, ui->tesUBOs, ui->gsUBOs, ui->fsUBOs, ui->csUBOs, }; + RDTreeWidget *descSets[] = { + ui->tsDescSets, ui->msDescSets, ui->vsDescSets, ui->tcsDescSets, + ui->tesDescSets, ui->gsDescSets, ui->fsDescSets, ui->csDescSets, + }; + // setup FlowLayout for CS shader group, with debugging controls { QLayout *oldLayout = ui->csShaderGroup->layout(); @@ -210,8 +220,28 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, b->setBackgroundRole(QPalette::ToolTipBase); b->setForegroundRole(QPalette::ToolTipText); b->setMinimumSizeHint(QSize(250, 0)); + b->setFont(Formatter::PreferredFont()); } + for(RDLabel *b : pipeLayoutLabels) + { + b->setAutoFillBackground(true); + b->setBackgroundRole(QPalette::ToolTipBase); + b->setForegroundRole(QPalette::ToolTipText); + b->setMinimumSizeHint(QSize(250, ui->vsShaderViewButton->minimumSizeHint().height())); + b->setFont(Formatter::PreferredFont()); + } + + // collapse the descriptor groups by default + ui->vsDescGroup->setCollapsed(true); + ui->tcsDescGroup->setCollapsed(true); + ui->tesDescGroup->setCollapsed(true); + ui->gsDescGroup->setCollapsed(true); + ui->fsDescGroup->setCollapsed(true); + ui->csDescGroup->setCollapsed(true); + ui->tsDescGroup->setCollapsed(true); + ui->msDescGroup->setCollapsed(true); + QObject::connect(m_ComputeDebugSelector, &ComputeDebugSelector::beginDebug, this, &VulkanPipelineStateViewer::computeDebugSelector_beginDebug); @@ -251,6 +281,10 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, QObject::connect(ubo, &RDTreeWidget::itemActivated, this, &VulkanPipelineStateViewer::ubo_itemActivated); + for(RDTreeWidget *desc : descSets) + QObject::connect(desc, &RDTreeWidget::itemActivated, this, + &VulkanPipelineStateViewer::descSet_itemActivated); + { QMenu *extensionsMenu = new QMenu(this); @@ -328,6 +362,23 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, m_Common.SetupResourceView(ubo); } + for(RDTreeWidget *desc : descSets) + { + RDHeaderView *header = new RDHeaderView(Qt::Horizontal, this); + desc->setHeader(header); + + desc->setColumns({tr("Index"), tr("Layout"), tr("Bound Set"), tr("Go")}); + header->setColumnStretchHints({-1, 4, 4, -1}); + + desc->setHoverIconColumn(3, action, action_hover); + desc->setClearSelectionOnFocusLoss(true); + desc->setInstantTooltips(true); + + m_Common.SetupResourceView(desc); + } + + ui->vsDescGroupVLayout->activate(); + { RDHeaderView *header = new RDHeaderView(Qt::Horizontal, this); ui->xfbBuffers->setHeader(header); @@ -441,28 +492,20 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, ui->viAttrs->setFont(Formatter::PreferredFont()); ui->viBuffers->setFont(Formatter::PreferredFont()); - ui->tsShader->setFont(Formatter::PreferredFont()); ui->tsResources->setFont(Formatter::PreferredFont()); ui->tsUBOs->setFont(Formatter::PreferredFont()); - ui->msShader->setFont(Formatter::PreferredFont()); ui->msResources->setFont(Formatter::PreferredFont()); ui->msUBOs->setFont(Formatter::PreferredFont()); - ui->vsShader->setFont(Formatter::PreferredFont()); ui->vsResources->setFont(Formatter::PreferredFont()); ui->vsUBOs->setFont(Formatter::PreferredFont()); - ui->gsShader->setFont(Formatter::PreferredFont()); ui->gsResources->setFont(Formatter::PreferredFont()); ui->gsUBOs->setFont(Formatter::PreferredFont()); - ui->tcsShader->setFont(Formatter::PreferredFont()); ui->tcsResources->setFont(Formatter::PreferredFont()); ui->tcsUBOs->setFont(Formatter::PreferredFont()); - ui->tesShader->setFont(Formatter::PreferredFont()); ui->tesResources->setFont(Formatter::PreferredFont()); ui->tesUBOs->setFont(Formatter::PreferredFont()); - ui->fsShader->setFont(Formatter::PreferredFont()); ui->fsResources->setFont(Formatter::PreferredFont()); ui->fsUBOs->setFont(Formatter::PreferredFont()); - ui->csShader->setFont(Formatter::PreferredFont()); ui->csResources->setFont(Formatter::PreferredFont()); ui->csUBOs->setFont(Formatter::PreferredFont()); ui->xfbBuffers->setFont(Formatter::PreferredFont()); @@ -895,12 +938,15 @@ void VulkanPipelineStateViewer::setNewMeshPipeFlow() ui->pipeFlow->setIsolatedStage(5); // compute shader isolated } -void VulkanPipelineStateViewer::clearShaderState(RDLabel *shader, RDTreeWidget *resources, - RDTreeWidget *cbuffers) +void VulkanPipelineStateViewer::clearShaderState(RDLabel *shader, RDLabel *pipeLayout, + RDTreeWidget *resources, RDTreeWidget *cbuffers, + RDTreeWidget *descSets) { + pipeLayout->setText(tr("Pipeline Layout")); shader->setText(QFormatStr("%1: %1").arg(ToQStr(ResourceId()))); resources->clear(); cbuffers->clear(); + descSets->clear(); } void VulkanPipelineStateViewer::clearState() @@ -917,14 +963,14 @@ void VulkanPipelineStateViewer::clearState() ui->primRestart->setVisible(false); ui->topologyDiagram->setPixmap(QPixmap()); - clearShaderState(ui->tsShader, ui->tsResources, ui->tsUBOs); - clearShaderState(ui->msShader, ui->msResources, ui->msUBOs); - clearShaderState(ui->vsShader, ui->vsResources, ui->vsUBOs); - clearShaderState(ui->tcsShader, ui->tcsResources, ui->tcsUBOs); - clearShaderState(ui->tesShader, ui->tesResources, ui->tesUBOs); - clearShaderState(ui->gsShader, ui->gsResources, ui->gsUBOs); - clearShaderState(ui->fsShader, ui->fsResources, ui->fsUBOs); - clearShaderState(ui->csShader, ui->csResources, ui->csUBOs); + clearShaderState(ui->tsShader, ui->tsPipeLayout, ui->tsResources, ui->tsUBOs, ui->tsDescSets); + clearShaderState(ui->msShader, ui->msPipeLayout, ui->msResources, ui->msUBOs, ui->msDescSets); + clearShaderState(ui->vsShader, ui->vsPipeLayout, ui->vsResources, ui->vsUBOs, ui->vsDescSets); + clearShaderState(ui->tcsShader, ui->tcsPipeLayout, ui->tcsResources, ui->tcsUBOs, ui->tcsDescSets); + clearShaderState(ui->tesShader, ui->tesPipeLayout, ui->tesResources, ui->tesUBOs, ui->tesDescSets); + clearShaderState(ui->gsShader, ui->gsPipeLayout, ui->gsResources, ui->gsUBOs, ui->gsDescSets); + clearShaderState(ui->fsShader, ui->fsPipeLayout, ui->fsResources, ui->fsUBOs, ui->fsDescSets); + clearShaderState(ui->csShader, ui->csPipeLayout, ui->csResources, ui->csUBOs, ui->csDescSets); ui->xfbBuffers->clear(); @@ -1618,7 +1664,8 @@ void VulkanPipelineStateViewer::addConstantBlockRow(const ConstantBlock *cblock, } void VulkanPipelineStateViewer::setShaderState(const VKPipe::Pipeline &pipe, - const VKPipe::Shader &stage, RDLabel *shader) + const VKPipe::Shader &stage, RDLabel *shader, + RDLabel *pipeLayout, RDTreeWidget *descSets) { ShaderReflection *shaderDetails = stage.reflection; @@ -1643,6 +1690,31 @@ void VulkanPipelineStateViewer::setShaderState(const VKPipe::Pipeline &pipe, shText += tr(" (Subgroup size %1)").arg(stage.requiredSubgroupSize); shader->setText(shText); + + if(pipe.pipelineComputeLayoutResourceId != ResourceId()) + { + pipeLayout->setText(tr("Pipeline Layout: %1").arg(ToQStr(pipe.pipelineComputeLayoutResourceId))); + } + else if(pipe.pipelinePreRastLayoutResourceId == pipe.pipelineFragmentLayoutResourceId) + { + pipeLayout->setText(tr("Pipeline Layout: %1").arg(ToQStr(pipe.pipelineFragmentLayoutResourceId))); + } + else + { + pipeLayout->setText(tr("Pipeline Layouts: %1 and %2") + .arg(ToQStr(pipe.pipelinePreRastLayoutResourceId)) + .arg(ToQStr(pipe.pipelineFragmentLayoutResourceId))); + } + + descSets->clear(); + for(uint32_t i = 0; i < pipe.descriptorSets.size(); i++) + { + RDTreeWidgetItem *item = + new RDTreeWidgetItem({i, pipe.descriptorSets[i].layoutResourceId, + pipe.descriptorSets[i].descriptorSetResourceId, QString()}); + item->setTag(i); + descSets->addTopLevelItem(item); + } } void VulkanPipelineStateViewer::setState() @@ -1727,8 +1799,8 @@ void VulkanPipelineStateViewer::setState() if(m_MeshPipe) { - setShaderState(state.graphics, state.taskShader, ui->tsShader); - setShaderState(state.graphics, state.meshShader, ui->msShader); + setShaderState(state.graphics, state.taskShader, ui->tsShader, ui->tsPipeLayout, ui->tsDescSets); + setShaderState(state.graphics, state.meshShader, ui->msShader, ui->msPipeLayout, ui->msDescSets); if(state.meshShader.reflection) ui->msTopology->setText(ToQStr(state.meshShader.reflection->outputTopology)); @@ -1998,14 +2070,19 @@ void VulkanPipelineStateViewer::setState() ui->viBuffers->endUpdate(); ui->viBuffers->verticalScrollBar()->setValue(vs); - setShaderState(state.graphics, state.vertexShader, ui->vsShader); - setShaderState(state.graphics, state.geometryShader, ui->gsShader); - setShaderState(state.graphics, state.tessControlShader, ui->tcsShader); - setShaderState(state.graphics, state.tessEvalShader, ui->tesShader); + setShaderState(state.graphics, state.vertexShader, ui->vsShader, ui->vsPipeLayout, + ui->vsDescSets); + setShaderState(state.graphics, state.geometryShader, ui->gsShader, ui->gsPipeLayout, + ui->gsDescSets); + setShaderState(state.graphics, state.tessControlShader, ui->tcsShader, ui->tcsPipeLayout, + ui->tcsDescSets); + setShaderState(state.graphics, state.tessEvalShader, ui->tesShader, ui->tesPipeLayout, + ui->tesDescSets); } - setShaderState(state.graphics, state.fragmentShader, ui->fsShader); - setShaderState(state.compute, state.computeShader, ui->csShader); + setShaderState(state.graphics, state.fragmentShader, ui->fsShader, ui->fsPipeLayout, + ui->fsDescSets); + setShaderState(state.compute, state.computeShader, ui->csShader, ui->csPipeLayout, ui->csDescSets); // fill in descriptor access { @@ -3016,6 +3093,27 @@ void VulkanPipelineStateViewer::ubo_itemActivated(RDTreeWidgetItem *item, int co m_Ctx.AddDockWindow(prev->Widget(), DockReference::TransientPopupArea, this, 0.3f); } +void VulkanPipelineStateViewer::descSet_itemActivated(RDTreeWidgetItem *item, int column) +{ + const VKPipe::Shader *stage = stageForSender(item->treeWidget()); + + if(stage == NULL) + return; + + int index = item->tag().toInt(); + + const rdcarray &descSets = + stage->stage == ShaderStage::Compute ? m_Ctx.CurVulkanPipelineState()->compute.descriptorSets + : m_Ctx.CurVulkanPipelineState()->graphics.descriptorSets; + + if(index < descSets.count()) + { + IDescriptorViewer *viewer = m_Ctx.ViewDescriptorStore(descSets[index].descriptorSetResourceId); + + m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); + } +} + void VulkanPipelineStateViewer::on_viAttrs_itemActivated(RDTreeWidgetItem *item, int column) { on_meshView_clicked(); diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h index 419ce4aa5..dbaf59d1d 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h @@ -86,6 +86,7 @@ private slots: void resource_itemActivated(RDTreeWidgetItem *item, int column); void resource_hoverItemChanged(RDTreeWidgetItem *hover); void ubo_itemActivated(RDTreeWidgetItem *item, int column); + void descSet_itemActivated(RDTreeWidgetItem *item, int column); void vertex_leave(QEvent *e); void on_computeDebugSelector_clicked(); @@ -111,8 +112,10 @@ private: void addConstantBlockRow(const ConstantBlock *cblock, const UsedDescriptor &used, uint32_t dynamicOffset, RDTreeWidget *ubos); - void setShaderState(const VKPipe::Pipeline &pipe, const VKPipe::Shader &stage, RDLabel *shader); - void clearShaderState(RDLabel *shader, RDTreeWidget *res, RDTreeWidget *ubo); + void setShaderState(const VKPipe::Pipeline &pipe, const VKPipe::Shader &stage, RDLabel *shader, + RDLabel *pipeLayout, RDTreeWidget *descSets); + void clearShaderState(RDLabel *shader, RDLabel *pipeLayout, RDTreeWidget *resources, + RDTreeWidget *cbuffers, RDTreeWidget *descSets); void setState(); void clearState(); diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui index 9c8fcf17d..74a9b5f3e 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui @@ -633,6 +633,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + @@ -945,6 +1022,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + @@ -1257,6 +1411,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + @@ -1569,6 +1800,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + @@ -3004,6 +3312,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + @@ -3895,6 +4280,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + @@ -4282,6 +4744,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + @@ -4628,6 +5167,83 @@ + + + + + 0 + 0 + + + + Descriptor Sets + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + 0 + 1 + + + + QFrame::Box + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + true + + + false + + + + + + diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index a6200b9cf..7522495d2 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -835,6 +835,17 @@ struct CaptureContextInvoker : ObjectForwarder return InvokeRetFunction(&ICaptureContext::ViewShaderMessages, stages); } + virtual IDescriptorViewer *ViewDescriptorStore(ResourceId id) override + { + return InvokeRetFunction(&ICaptureContext::ViewDescriptorStore, id); + } + virtual IDescriptorViewer *ViewDescriptors(const rdcarray &descriptors, + const rdcarray &samplerDescriptors) override + { + return InvokeRetFunction(&ICaptureContext::ViewDescriptors, descriptors, + samplerDescriptors); + } + virtual IBufferViewer *ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const rdcstr &format = "") override { diff --git a/qrenderdoc/Windows/ResourceInspector.cpp b/qrenderdoc/Windows/ResourceInspector.cpp index 9e873e1a1..4794c1cdd 100644 --- a/qrenderdoc/Windows/ResourceInspector.cpp +++ b/qrenderdoc/Windows/ResourceInspector.cpp @@ -262,7 +262,8 @@ void ResourceInspector::Inspect(ResourceId id) m_Resource = id; - ui->viewContents->setVisible(m_Ctx.GetTexture(id) || m_Ctx.GetBuffer(id)); + ui->viewContents->setVisible(m_Ctx.GetTexture(id) || m_Ctx.GetBuffer(id) || + m_Ctx.GetDescriptorStore(id)); m_Entries.clear(); @@ -568,6 +569,7 @@ void ResourceInspector::on_viewContents_clicked() { TextureDescription *tex = m_Ctx.GetTexture(m_Resource); BufferDescription *buf = m_Ctx.GetBuffer(m_Resource); + DescriptorStoreDescription *desc = m_Ctx.GetDescriptorStore(m_Resource); if(tex) { @@ -592,6 +594,12 @@ void ResourceInspector::on_viewContents_clicked() m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); } + else if(desc) + { + IDescriptorViewer *viewer = m_Ctx.ViewDescriptorStore(desc->resourceId); + + m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); + } else if(!m_Entries.isEmpty()) { ShaderEntryPoint entry = m_Entries[0]; diff --git a/qrenderdoc/Windows/ShaderMessageViewer.cpp b/qrenderdoc/Windows/ShaderMessageViewer.cpp index 026f8a786..ab8413954 100644 --- a/qrenderdoc/Windows/ShaderMessageViewer.cpp +++ b/qrenderdoc/Windows/ShaderMessageViewer.cpp @@ -37,102 +37,6 @@ static const int debuggableRole = Qt::UserRole + 1; static const int gotoableRole = Qt::UserRole + 2; -ButtonDelegate::ButtonDelegate(const QIcon &icon, int enableRole, QWidget *parent) - : m_Icon(icon), m_EnableRole(enableRole), QStyledItemDelegate(parent) -{ -} - -void ButtonDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - // draw the background to get selection etc - QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &option, painter); - - QStyleOptionButton button; - - QSize sz = sizeHint(option, index); - button.rect = option.rect; - button.rect.setLeft(button.rect.center().x() - sz.width() / 2); - button.rect.setTop(button.rect.center().y() - sz.height() / 2); - button.rect.setSize(sz); - button.icon = m_Icon; - button.iconSize = sz; - - if(m_EnableRole == 0 || index.data(m_EnableRole).toBool()) - button.state = QStyle::State_Enabled; - - if(m_ClickedIndex == index) - button.state |= QStyle::State_Sunken; - - QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter); -} - -QSize ButtonDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QStyleOptionButton button; - button.icon = m_Icon; - button.state = QStyle::State_Enabled; - - return QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &button, - option.decorationSize); -} - -bool ButtonDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) -{ - if(event->type() == QEvent::MouseButtonPress) - { - if(m_EnableRole == 0 || index.data(m_EnableRole).toBool()) - m_ClickedIndex = index; - } - else if(event->type() == QEvent::MouseMove) - { - QMouseEvent *e = (QMouseEvent *)event; - - if(m_ClickedIndex != index || (e->buttons() & Qt::LeftButton) == 0) - { - m_ClickedIndex = QModelIndex(); - } - else - { - QPoint p = e->pos(); - - QSize sz = option.decorationSize; - QRect rect = option.rect; - rect.setLeft(rect.center().x() - sz.width() / 2); - rect.setTop(rect.center().y() - sz.height() / 2); - rect.setSize(sz); - - if(!rect.contains(p)) - { - m_ClickedIndex = QModelIndex(); - } - } - } - else if(event->type() == QEvent::MouseButtonRelease) - { - if(m_ClickedIndex == index && index != QModelIndex()) - { - m_ClickedIndex = QModelIndex(); - - QMouseEvent *e = (QMouseEvent *)event; - - QPoint p = e->pos(); - - QSize sz = option.decorationSize; - QRect rect = option.rect; - rect.setLeft(rect.center().x() - sz.width() / 2); - rect.setTop(rect.center().y() - sz.height() / 2); - rect.setSize(sz); - - if(rect.contains(p)) - emit messageClicked(index); - } - } - - return true; -} - ShaderMessageViewer::ShaderMessageViewer(ICaptureContext &ctx, ShaderStageMask stages, QWidget *parent) : QFrame(parent), ui(new Ui::ShaderMessageViewer), m_Ctx(ctx) { @@ -211,7 +115,8 @@ ShaderMessageViewer::ShaderMessageViewer(ICaptureContext &ctx, ShaderStageMask s int sortColumn = 0; - m_debugDelegate = new ButtonDelegate(Icons::wrench(), debuggableRole, this); + m_debugDelegate = new ButtonDelegate(Icons::wrench(), QString(), this); + m_debugDelegate->setEnableTrigger(debuggableRole, true); if(m_Action && (m_Action->flags & ActionFlags::Dispatch)) { @@ -248,7 +153,8 @@ ShaderMessageViewer::ShaderMessageViewer(ICaptureContext &ctx, ShaderStageMask s m_LayoutStage = ShaderStage::Vertex; } - m_gotoDelegate = new ButtonDelegate(Icons::find(), gotoableRole, this); + m_gotoDelegate = new ButtonDelegate(Icons::find(), QString(), this); + m_gotoDelegate->setEnableTrigger(gotoableRole, true); ui->messages->setItemDelegateForColumn(0, m_debugDelegate); ui->messages->setItemDelegateForColumn(1, m_gotoDelegate); diff --git a/qrenderdoc/Windows/ShaderMessageViewer.h b/qrenderdoc/Windows/ShaderMessageViewer.h index e73e097ee..60cd3b885 100644 --- a/qrenderdoc/Windows/ShaderMessageViewer.h +++ b/qrenderdoc/Windows/ShaderMessageViewer.h @@ -28,31 +28,13 @@ #include #include "Code/Interface/QRDInterface.h" +class ButtonDelegate; + namespace Ui { class ShaderMessageViewer; } -class ButtonDelegate : public QStyledItemDelegate -{ -private: - Q_OBJECT - - QModelIndex m_ClickedIndex; - QIcon m_Icon; - int m_EnableRole; - -public: - ButtonDelegate(const QIcon &icon, int enableRole, QWidget *parent); - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, - const QModelIndex &index) override; -signals: - void messageClicked(const QModelIndex &index); -}; - class ShaderMessageViewer : public QFrame, public IShaderMessageViewer, public ICaptureViewer { Q_OBJECT diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 55bac9f4b..e4dd356db 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -190,6 +190,7 @@ SOURCES += Code/qrenderdoc.cpp \ Windows/TextureViewer.cpp \ Windows/ShaderViewer.cpp \ Windows/ShaderMessageViewer.cpp \ + Windows/DescriptorViewer.cpp \ Widgets/Extended/RDLineEdit.cpp \ Widgets/Extended/RDTextEdit.cpp \ Widgets/Extended/RDLabel.cpp \ @@ -275,6 +276,7 @@ HEADERS += Code/CaptureContext.h \ Windows/TextureViewer.h \ Windows/ShaderViewer.h \ Windows/ShaderMessageViewer.h \ + Windows/DescriptorViewer.h \ Widgets/Extended/RDLineEdit.h \ Widgets/Extended/RDTextEdit.h \ Widgets/Extended/RDLabel.h \ @@ -356,6 +358,7 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/BufferViewer.ui \ Windows/ShaderViewer.ui \ Windows/ShaderMessageViewer.ui \ + Windows/DescriptorViewer.ui \ Windows/DebugMessageView.ui \ Windows/LogView.ui \ Windows/CommentView.ui \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 7a7927d60..239bb5174 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -628,6 +628,7 @@ + @@ -747,6 +748,7 @@ + @@ -968,6 +970,7 @@ + @@ -1223,6 +1226,12 @@ MOC %(Filename).h $(IntDir)generated\moc_%(Filename).cpp + + %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) + "$(QtBinDir)\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" + MOC %(Filename).h + $(IntDir)generated\moc_%(Filename).cpp + %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) "$(QtBinDir)\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" @@ -1518,6 +1527,12 @@ UIC %(Filename).ui $(IntDir)generated\ui_%(Filename).h + + %(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs) + "$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" + UIC %(Filename).ui + $(IntDir)generated\ui_%(Filename).h + %(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs) "$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index d669a7451..119abf2b0 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -378,6 +378,9 @@ Windows + + Windows + Windows @@ -432,6 +435,9 @@ Generated Files + + Generated Files + Generated Files @@ -992,6 +998,9 @@ Generated Files + + Generated Files + Generated Files @@ -1190,6 +1199,9 @@ Windows + + Windows + Windows @@ -1277,6 +1289,9 @@ Windows + + Windows + Windows