From 9d3f7169edeaa2e1436d9bd702f8dc0b899cd795 Mon Sep 17 00:00:00 2001 From: baldurk Date: Tue, 20 Apr 2021 13:06:41 +0100 Subject: [PATCH] Add thumbnails to texture tooltips in pipeline state view * If there would be no tooltip otherwise, it just shows the thumbnail. Otherwise any tooltip text (like view parameters or image layout) is displayed below the thumbnail --- qrenderdoc/Widgets/Extended/RDTreeView.cpp | 21 +- qrenderdoc/Widgets/Extended/RDTreeView.h | 10 +- qrenderdoc/Widgets/Extended/RDTreeWidget.cpp | 7 + qrenderdoc/Widgets/Extended/RDTreeWidget.h | 2 + .../PipelineState/PipelineStateViewer.cpp | 205 ++++++++++++++++++ .../PipelineState/PipelineStateViewer.h | 38 ++++ .../PipelineState/PipelineStateViewer.ui | 17 ++ 7 files changed, 288 insertions(+), 12 deletions(-) diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.cpp b/qrenderdoc/Widgets/Extended/RDTreeView.cpp index 321c1eab2..6a7a8c19f 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeView.cpp @@ -132,19 +132,23 @@ RDTipLabel::RDTipLabel(QWidget *listener) : QLabel(NULL), mouseListener(listener setWindowOpacity(opacity / 255.0); } -QSize RDTipLabel::getSizeForTip(QString text) +QSize RDTipLabel::configureTip(QWidget *, QModelIndex, QString text) { setText(text); - return sizeHint(); + return minimumSizeHint(); } -void RDTipLabel::showTip(QPoint pos, QString text) +void RDTipLabel::showTip(QPoint pos) { move(pos); - setText(text); show(); } +bool RDTipLabel::forceTip(QWidget *widget, QModelIndex idx) +{ + return false; +} + void RDTipLabel::paintEvent(QPaintEvent *ev) { QStylePainter p(this); @@ -234,7 +238,7 @@ void RDTreeView::mouseMoveEvent(QMouseEvent *e) { QString tooltip = m_currentHoverIndex.data(Qt::ToolTipRole).toString(); - if(!tooltip.isEmpty()) + if(!tooltip.isEmpty() || m_Tooltip->forceTip(this, m_currentHoverIndex)) { // We don't use QToolTip since we have a custom tooltip for showing elided results, and we // use that for consistency. This also makes it easier to slot in a custom tooltip widget @@ -248,7 +252,7 @@ void RDTreeView::mouseMoveEvent(QMouseEvent *e) // start with the tooltip placed bottom-right of the cursor, as the default QRect tooltipRect; tooltipRect.setTopLeft(p + cursorSize); - tooltipRect.setSize(m_Tooltip->getSizeForTip(tooltip)); + tooltipRect.setSize(m_Tooltip->configureTip(this, m_currentHoverIndex, tooltip)); // clip by the available geometry in x if(tooltipRect.right() > screenAvailGeom.right()) @@ -259,7 +263,7 @@ void RDTreeView::mouseMoveEvent(QMouseEvent *e) if(tooltipRect.bottom() > screenAvailGeom.bottom()) tooltipRect.moveBottom(p.y() - cursorSize.y()); - m_Tooltip->showTip(tooltipRect.topLeft(), tooltip); + m_Tooltip->showTip(tooltipRect.topLeft()); m_CurrentTooltipElided = false; } } @@ -414,7 +418,8 @@ bool RDTreeView::viewportEvent(QEvent *event) // need to use a custom label tooltip since the QToolTip freaks out as we're placing it // underneath the cursor instead of next to it (so that the tooltip lines up over the // row) - m_Tooltip->showTip(viewport()->mapToGlobal(option.rect.topLeft()), fullText); + m_Tooltip->configureTip(this, index, fullText); + m_Tooltip->showTip(viewport()->mapToGlobal(option.rect.topLeft())); m_CurrentTooltipElided = true; } } diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.h b/qrenderdoc/Widgets/Extended/RDTreeView.h index e36fd2d95..7d7c827c2 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.h +++ b/qrenderdoc/Widgets/Extended/RDTreeView.h @@ -51,8 +51,9 @@ struct ITreeViewTipDisplay { public: virtual void hideTip() = 0; - virtual QSize getSizeForTip(QString text) = 0; - virtual void showTip(QPoint pos, QString text) = 0; + virtual QSize configureTip(QWidget *widget, QModelIndex idx, QString text) = 0; + virtual void showTip(QPoint pos) = 0; + virtual bool forceTip(QWidget *widget, QModelIndex idx) = 0; }; class RDTipLabel : public QLabel, public ITreeViewTipDisplay @@ -66,8 +67,9 @@ public: explicit RDTipLabel(QWidget *listener = NULL); void hideTip() { hide(); } - QSize getSizeForTip(QString text); - void showTip(QPoint pos, QString text); + QSize configureTip(QWidget *widget, QModelIndex idx, QString text); + void showTip(QPoint pos); + bool forceTip(QWidget *widget, QModelIndex idx); protected: void paintEvent(QPaintEvent *); diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp index d41b2c10e..25654f267 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp @@ -668,6 +668,13 @@ QAbstractItemDelegate *RDTreeWidget::itemDelegate() const return m_userDelegate; } +RDTreeWidgetItem *RDTreeWidget::itemForIndex(QModelIndex idx) const +{ + if(idx.model() == m_model) + return m_model->itemForIndex(idx); + return NULL; +} + void RDTreeWidget::copyItem(QPoint pos, RDTreeWidgetItem *item) { copyIndex(pos, m_model->indexForItem(item, 0)); diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.h b/qrenderdoc/Widgets/Extended/RDTreeWidget.h index b5a188e72..c207947e8 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.h +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.h @@ -238,6 +238,8 @@ public: void setItemDelegate(QAbstractItemDelegate *delegate); QAbstractItemDelegate *itemDelegate() const; + RDTreeWidgetItem *itemForIndex(QModelIndex idx) const; + void copyItem(QPoint pos, RDTreeWidgetItem *item); void setColumns(const QStringList &columns); diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index 98b823fea..89c9aae2b 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -80,11 +81,122 @@ static uint32_t byteSize(const ResourceFormat &fmt) return fmt.compByteWidth * fmt.compCount; } +RDPreviewTooltip::RDPreviewTooltip(PipelineStateViewer *parent, CustomPaintWidget *thumbnail, + ICaptureContext &ctx) + : QFrame(parent), m_Ctx(ctx) +{ + int margin = style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, NULL, this); + int opacity = style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, NULL, this); + + pipe = parent; + + setWindowFlags(Qt::ToolTip); + setAttribute(Qt::WA_TransparentForMouseEvents); + setForegroundRole(QPalette::ToolTipText); + setBackgroundRole(QPalette::ToolTipBase); + setFrameStyle(QFrame::NoFrame); + setWindowOpacity(opacity / 255.0); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + QHBoxLayout *hbox = new QHBoxLayout; + QVBoxLayout *vbox = new QVBoxLayout; + label = new QLabel(this); + + label->setMargin(margin + 1); + label->setAlignment(Qt::AlignLeft); + label->setIndent(1); + + title = new QLabel(this); + title->setMargin(margin + 1); + title->setAlignment(Qt::AlignLeft); + title->setIndent(1); + + setLayout(vbox); + vbox->addWidget(title); + vbox->addLayout(hbox); + + hbox->addWidget(thumbnail); + hbox->addStretch(); + + vbox->addWidget(label); +} + +void RDPreviewTooltip::hideTip() +{ + hide(); +} + +QSize RDPreviewTooltip::configureTip(QWidget *widget, QModelIndex idx, QString text) +{ + ResourceId id = pipe->updateThumbnail(widget, idx); + if(id != ResourceId()) + { + title->setText(m_Ctx.GetResourceName(id)); + title->show(); + } + else + { + title->hide(); + } + label->setText(text); + label->setVisible(!text.isEmpty()); + layout()->update(); + layout()->activate(); + return minimumSizeHint(); +} + +void RDPreviewTooltip::showTip(QPoint pos) +{ + move(pos); + resize(minimumSize()); + show(); +} + +bool RDPreviewTooltip::forceTip(QWidget *widget, QModelIndex idx) +{ + return pipe->hasThumbnail(widget, idx); +} + +void RDPreviewTooltip::paintEvent(QPaintEvent *ev) +{ + QStylePainter p(this); + QStyleOptionFrame opt; + opt.init(this); + p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); + p.end(); + + QWidget::paintEvent(ev); +} + +void RDPreviewTooltip::resizeEvent(QResizeEvent *e) +{ + QStyleHintReturnMask frameMask; + QStyleOption option; + option.init(this); + if(style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask)) + setMask(frameMask.region); + + QWidget::resizeEvent(e); +} + PipelineStateViewer::PipelineStateViewer(ICaptureContext &ctx, QWidget *parent) : QFrame(parent), ui(new Ui::PipelineStateViewer), m_Ctx(ctx) { ui->setupUi(this); + ui->thumbnail->SetContext(m_Ctx); + ui->layout->removeWidget(ui->thumbnail); + + m_Tooltip = new RDPreviewTooltip(this, ui->thumbnail, m_Ctx); + + QColor c = palette().color(QPalette::ToolTipBase).toRgb(); + + m_TexDisplay.backgroundColor = FloatVector(); + m_TexDisplay.backgroundColor.w = 1.0f; + + // auto-fit and center scale + m_TexDisplay.scale = -1.0f; + m_D3D11 = NULL; m_D3D12 = NULL; m_GL = NULL; @@ -105,6 +217,8 @@ PipelineStateViewer::~PipelineStateViewer() m_Ctx.BuiltinWindowClosed(this); m_Ctx.RemoveCaptureViewer(this); + delete m_Tooltip; + delete ui; } @@ -121,6 +235,24 @@ void PipelineStateViewer::OnCaptureLoaded() if(m_Current) m_Current->OnCaptureLoaded(); + + WindowingData thumbData = ui->thumbnail->GetWidgetWindowingData(); + + m_Ctx.Replay().BlockInvoke([thumbData, this](IReplayController *r) { + m_Output = r->CreateOutput(thumbData, ReplayOutputType::Texture); + + ui->thumbnail->SetOutput(m_Output); + + RT_UpdateAndDisplay(r); + }); +} + +void PipelineStateViewer::RT_UpdateAndDisplay(IReplayController *r) +{ + if(m_Output != NULL) + m_Output->SetTextureDisplay(m_TexDisplay); + + GUIInvoke::call(this, [this]() { ui->thumbnail->update(); }); } void PipelineStateViewer::OnCaptureClosed() @@ -1057,6 +1189,77 @@ void PipelineStateViewer::ShowResourceContextMenu(RDTreeWidget *widget, const QP RDDialog::show(&contextMenu, widget->viewport()->mapToGlobal(pos)); } +ResourceId PipelineStateViewer::updateThumbnail(QWidget *widget, QModelIndex idx) +{ + ResourceId id; + + RDTreeWidget *treeWidget = qobject_cast(widget); + if(treeWidget) + { + RDTreeWidgetItem *item = treeWidget->itemForIndex(idx); + + if(item) + { + if(m_D3D11) + id = m_D3D11->GetResource(item); + else if(m_D3D12) + id = m_D3D12->GetResource(item); + else if(m_GL) + id = m_GL->GetResource(item); + else if(m_Vulkan) + id = m_Vulkan->GetResource(item); + } + + TextureDescription *tex = m_Ctx.GetTexture(id); + + if(tex) + { + m_TexDisplay.resourceId = id; + INVOKE_MEMFN(RT_UpdateAndDisplay); + + float aspect = (float)tex->width / (float)qMax(1U, tex->height); + + // keep height fixed at 100, and make width match the aspect ratio of the texture - up to 21:9 + // ratio + ui->thumbnail->setFixedSize((int)qBound(100.0f, aspect * 100.0f, (21.0f / 9.0f) * 100.0f), 100); + } + else + { + ui->thumbnail->setFixedSize(0, 0); + } + } + + return id; +} + +bool PipelineStateViewer::hasThumbnail(QWidget *widget, QModelIndex idx) +{ + RDTreeWidget *treeWidget = qobject_cast(widget); + if(treeWidget) + { + ResourceId id; + + RDTreeWidgetItem *item = treeWidget->itemForIndex(idx); + + if(item) + { + if(m_D3D11) + id = m_D3D11->GetResource(item); + else if(m_D3D12) + id = m_D3D12->GetResource(item); + else if(m_GL) + id = m_GL->GetResource(item); + else if(m_Vulkan) + id = m_Vulkan->GetResource(item); + } + + if(id != ResourceId() && m_Ctx.GetTexture(id)) + return true; + } + + return false; +} + void PipelineStateViewer::SetupResourceView(RDTreeWidget *widget) { auto handler = [this, widget](const QPoint &pos) { @@ -1091,6 +1294,8 @@ void PipelineStateViewer::SetupResourceView(RDTreeWidget *widget) widget->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(widget, &RDTreeWidget::customContextMenuRequested, handler); + + widget->setCustomTooltip(m_Tooltip); } QString PipelineStateViewer::GetVBufferFormatString(uint32_t slot) diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h index d82dfc391..51079aab3 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h @@ -27,6 +27,7 @@ #include #include #include "Code/Interface/QRDInterface.h" +#include "Widgets/Extended/RDTreeView.h" namespace Ui { @@ -40,12 +41,39 @@ class QMenu; class RDLabel; class RDTreeWidgetItem; class RDTreeWidget; +class CustomPaintWidget; class D3D11PipelineStateViewer; class D3D12PipelineStateViewer; class GLPipelineStateViewer; class VulkanPipelineStateViewer; +class PipelineStateViewer; + +class RDPreviewTooltip : public QFrame, public ITreeViewTipDisplay +{ +private: + Q_OBJECT + + PipelineStateViewer *pipe = NULL; + QLabel *title = NULL; + QLabel *label = NULL; + ICaptureContext &m_Ctx; + +public: + explicit RDPreviewTooltip(PipelineStateViewer *parent, CustomPaintWidget *thumbnail, + ICaptureContext &ctx); + + void hideTip(); + QSize configureTip(QWidget *widget, QModelIndex idx, QString text); + void showTip(QPoint pos); + bool forceTip(QWidget *widget, QModelIndex idx); + +protected: + void paintEvent(QPaintEvent *); + void resizeEvent(QResizeEvent *); +}; + class PipelineStateViewer : public QFrame, public IPipelineStateViewer, public ICaptureViewer { Q_OBJECT @@ -84,6 +112,9 @@ public: void setTopologyDiagram(QLabel *diagram, Topology topo); void setMeshViewPixmap(RDLabel *meshView); + ResourceId updateThumbnail(QWidget *widget, QModelIndex idx); + bool hasThumbnail(QWidget *widget, QModelIndex idx); + QXmlStreamWriter *beginHTMLExport(); void exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols, const QList &rows); @@ -101,6 +132,13 @@ private: QMenu *editMenus[6] = {}; + RDPreviewTooltip *m_Tooltip = NULL; + + TextureDisplay m_TexDisplay; + IReplayOutput *m_Output = NULL; + + void RT_UpdateAndDisplay(IReplayController *r); + void AddResourceUsageEntry(QMenu &menu, uint32_t start, uint32_t end, ResourceUsage usage); void ShowResourceContextMenu(RDTreeWidget *widget, const QPoint &pos, ResourceId id, const rdcarray &usage); diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.ui index 59bf84763..a519fadae 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.ui @@ -29,8 +29,25 @@ 3 + + + + + 0 + 0 + + + + + + + CustomPaintWidget + QWidget +
Widgets/CustomPaintWidget.h
+
+