From aaf19a128cd6d557d448c40aff02d4c5c9ee3fc4 Mon Sep 17 00:00:00 2001 From: Remi Palandri Date: Tue, 13 Jan 2026 15:07:24 +0100 Subject: [PATCH] add metadata support to annotationviewer for buffers --- qrenderdoc/Widgets/AnnotationDisplay.cpp | 166 ++++++++++++++++++++++- qrenderdoc/Widgets/AnnotationDisplay.h | 27 ++++ 2 files changed, 189 insertions(+), 4 deletions(-) diff --git a/qrenderdoc/Widgets/AnnotationDisplay.cpp b/qrenderdoc/Widgets/AnnotationDisplay.cpp index 09d0e6cc1..cb48d4019 100644 --- a/qrenderdoc/Widgets/AnnotationDisplay.cpp +++ b/qrenderdoc/Widgets/AnnotationDisplay.cpp @@ -27,14 +27,21 @@ #include #include #include +#include "Code/Interface/QRDInterface.h" +#include "Code/QRDUtils.h" +#include "Code/Resources.h" +#include "Extended/RDHeaderView.h" AnnotationDisplay::AnnotationDisplay(ICaptureContext &ctx, bool standalone, QWidget *parent) : QFrame(parent), m_Ctx(ctx), m_Standalone(standalone) { m_Tree = new RDTreeWidget(this); + m_Header = new RDHeaderView(Qt::Horizontal, this); + m_Tree->setHeader(m_Header); + m_Tree->setColumns({lit("Key"), tr("Value")}); - m_Tree->header()->resizeSection(0, 150); + m_Header->setColumnStretchHints({1, 4}); m_Tree->setFont(Formatter::PreferredFont()); QVBoxLayout *layout = new QVBoxLayout(this); @@ -53,6 +60,12 @@ AnnotationDisplay::AnnotationDisplay(ICaptureContext &ctx, bool standalone, QWid QObject::connect(m_Tree, &RDTreeWidget::customContextMenu, this, &AnnotationDisplay::customContextMenu); + QObject::connect(m_Tree, &RDTreeWidget::itemClicked, this, &AnnotationDisplay::itemClicked); + QObject::connect(m_Tree, &RDTreeWidget::hoverItemChanged, this, + &AnnotationDisplay::hoverItemChanged); + + m_Tree->viewport()->setAttribute(Qt::WA_Hover); + m_Tree->setMouseTracking(true); if(m_Standalone) m_Ctx.AddCaptureViewer(this); @@ -131,10 +144,52 @@ void AnnotationDisplay::addStructuredChildren(RDTreeWidgetItem *parent, const SD else name = obj->name; - RDTreeWidgetItem *item = new RDTreeWidgetItem({name, QString()}); + RDTreeWidgetItem *item; + if(m_HasGoColumn) + item = new RDTreeWidgetItem({name, QString(), QString()}); + else + item = new RDTreeWidgetItem({name, QString()}); m_Items[obj] = item; - item->setTag(QVariant::fromValue((void *)obj)); + + // Check if this is a viewable resource (buffer, texture, or shader) + if(obj->type.basetype == SDBasic::Resource) + { + ResourceId id = obj->data.basic.id; + const ResourceDescription *resDesc = m_Ctx.GetResource(id); + + if(resDesc && (resDesc->type == ResourceType::Buffer || + resDesc->type == ResourceType::Texture || resDesc->type == ResourceType::Shader)) + { + AnnotationResourceTag tag; + tag.resourceId = id; + tag.resourceType = resDesc->type; + + if(resDesc->type == ResourceType::Buffer) + { + // Look for special child annotations for buffer viewer parameters + const SDObject *offsetChild = obj->FindChildByKeyPath("__offset"); + const SDObject *sizeChild = obj->FindChildByKeyPath("__size"); + const SDObject *formatChild = obj->FindChildByKeyPath("__rd_format"); + + if(offsetChild && (offsetChild->type.basetype == SDBasic::UnsignedInteger || + offsetChild->type.basetype == SDBasic::SignedInteger)) + tag.bufferOffset = offsetChild->data.basic.u; + + if(sizeChild && (sizeChild->type.basetype == SDBasic::UnsignedInteger || + sizeChild->type.basetype == SDBasic::SignedInteger)) + tag.bufferSize = sizeChild->data.basic.u; + + if(formatChild && formatChild->type.basetype == SDBasic::String) + tag.bufferFormat = formatChild->data.str; + } + + item->setTag(QVariant::fromValue(tag)); + + if(m_HasGoColumn) + item->setIcon(2, Icons::action()); + } + } if(obj->type.basetype == SDBasic::Chunk || obj->type.basetype == SDBasic::Struct || obj->type.basetype == SDBasic::Array) @@ -146,6 +201,29 @@ void AnnotationDisplay::addStructuredChildren(RDTreeWidgetItem *parent, const SD } } +bool AnnotationDisplay::hasResourceAnnotations(const SDObject &obj) +{ + if(obj.type.basetype == SDBasic::Resource) + { + const ResourceDescription *resDesc = m_Ctx.GetResource(obj.data.basic.id); + if(resDesc && (resDesc->type == ResourceType::Buffer || + resDesc->type == ResourceType::Texture || resDesc->type == ResourceType::Shader)) + return true; + } + + for(const SDObject *child : obj) + { + if(child->type.flags & SDTypeFlags::Hidden) + continue; + if(child->name.beginsWith("__")) + continue; + if(hasResourceAnnotations(*child)) + return true; + } + + return false; +} + void AnnotationDisplay::setAnnotationObject(const SDObject *annotation) { m_Tree->updateExpansion(m_Expansion, 0); @@ -153,8 +231,23 @@ void AnnotationDisplay::setAnnotationObject(const SDObject *annotation) m_Annotation = annotation; m_Items.clear(); + m_HoveredItem = NULL; m_Tree->invisibleRootItem()->clear(); + // Check if we have any buffer annotations to determine columns + m_HasGoColumn = m_Annotation && hasResourceAnnotations(*m_Annotation); + + if(m_HasGoColumn) + { + m_Tree->setColumns({lit("Key"), tr("Value"), tr("Go")}); + m_Header->setColumnStretchHints({1, 4, -1}); + } + else + { + m_Tree->setColumns({lit("Key"), tr("Value")}); + m_Header->setColumnStretchHints({1, 4}); + } + if(m_Annotation) { m_Tree->beginUpdate(); @@ -168,7 +261,10 @@ void AnnotationDisplay::setAnnotationObject(const SDObject *annotation) void AnnotationDisplay::customContextMenu(QModelIndex index, QMenu *menu) { RDTreeWidgetItem *item = m_Tree->itemForIndex(index); - const SDObject *obj = (const SDObject *)item->tag().value(); + + const SDObject *obj = m_Items.key(item, NULL); + if(!obj) + return; rdcstr path; // don't include the root node, it's not part of the path, so only iterate over nodes that have @@ -194,3 +290,65 @@ void AnnotationDisplay::customContextMenu(QModelIndex index, QMenu *menu) menu->insertAction(sep, showInEventBrowser); } + +void AnnotationDisplay::itemClicked(RDTreeWidgetItem *item, int column) +{ + if(!m_HasGoColumn || column != 2) + return; + + QVariant tag = item->tag(); + + if(!tag.canConvert()) + return; + + AnnotationResourceTag resTag = tag.value(); + + if(resTag.resourceType == ResourceType::Buffer) + { + IBufferViewer *viewer = m_Ctx.ViewBuffer(resTag.bufferOffset, resTag.bufferSize, + resTag.resourceId, resTag.bufferFormat); + m_Ctx.AddDockWindow(viewer->Widget(), DockReference::MainToolArea, NULL); + } + else if(resTag.resourceType == ResourceType::Texture) + { + if(!m_Ctx.HasTextureViewer()) + m_Ctx.ShowTextureViewer(); + ITextureViewer *viewer = m_Ctx.GetTextureViewer(); + viewer->ViewTexture(resTag.resourceId, CompType::Typeless, true); + } + else if(resTag.resourceType == ResourceType::Shader) + { + ResourceId id = resTag.resourceId; + ICaptureContext *ctx = &m_Ctx; + m_Ctx.Replay().AsyncInvoke([this, ctx, id](IReplayController *r) { + rdcarray entries = r->GetShaderEntryPoints(id); + if(entries.isEmpty()) + return; + + const ShaderReflection *refl = r->GetShader(ResourceId(), id, entries[0]); + if(!refl) + return; + + GUIInvoke::call(this, [ctx, refl] { + IShaderViewer *viewer = ctx->ViewShader(refl, ResourceId()); + ctx->AddDockWindow(viewer->Widget(), DockReference::MainToolArea, NULL); + }); + }); + } +} + +void AnnotationDisplay::hoverItemChanged(RDTreeWidgetItem *item) +{ + // Restore normal icon for previously hovered resource item with a Go button + if(m_HoveredItem) + { + m_HoveredItem->setIcon(2, Icons::action()); + } + + // Set hover icon for newly hovered resource item, if it has a valid tag + if(item && item->tag().canConvert() && m_HasGoColumn) + { + m_HoveredItem = item; + m_HoveredItem->setIcon(2, Icons::action_hover()); + } +} diff --git a/qrenderdoc/Widgets/AnnotationDisplay.h b/qrenderdoc/Widgets/AnnotationDisplay.h index b55f1a2f4..9c6381523 100644 --- a/qrenderdoc/Widgets/AnnotationDisplay.h +++ b/qrenderdoc/Widgets/AnnotationDisplay.h @@ -24,8 +24,24 @@ #pragma once +#include "Extended/RDHeaderView.h" #include "Extended/RDTreeWidget.h" +struct AnnotationResourceTag +{ + AnnotationResourceTag() = default; + + ResourceId resourceId; + ResourceType resourceType = ResourceType::Unknown; + + // Buffer-specific fields + uint64_t bufferOffset = 0; + uint64_t bufferSize = UINT64_MAX; + rdcstr bufferFormat; +}; + +Q_DECLARE_METATYPE(AnnotationResourceTag); + // can be used either as an embedded control in the resource inspector, or as a separate panel for // monitoring API events class AnnotationDisplay : public QFrame, public IAnnotationViewer, public ICaptureViewer @@ -50,6 +66,8 @@ public: private slots: void customContextMenu(QModelIndex index, QMenu *menu); + void itemClicked(RDTreeWidgetItem *item, int column); + void hoverItemChanged(RDTreeWidgetItem *item); protected: @@ -58,15 +76,24 @@ private: const SDObject *m_Annotation = NULL; RDTreeWidget *m_Tree; + RDHeaderView *m_Header; RDTreeViewExpansionState m_Expansion; // if this is a standalone viewer or not bool m_Standalone = false; + // whether the Go column is present + bool m_HasGoColumn = false; + + // track hovered item for icon changes + RDTreeWidgetItem *m_HoveredItem = NULL; + QMap m_Items; void addStructuredChildren(RDTreeWidgetItem *parent, const SDObject &parentObj); + bool hasResourceAnnotations(const SDObject &obj); + // either is a non-empty node, or has at least one non-empty child bool shouldBeDisplayed(const SDObject &obj); };