From d11da2f3a1bc509acaf1f55aafaeff2463b992c2 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 9 Nov 2022 14:14:24 +0000 Subject: [PATCH] Refactor texture viewer thumbnails to not use swapchains * There seems to be a significant slowdown when using real swapchains, both for creation and display. Since thumbnails don't update often (only on event change, or if the panel is resized which is not a regular occurence) counterintuitively it's better to render and readback the image offscreen and re-upload it on the CPU. --- qrenderdoc/Widgets/ResourcePreview.cpp | 66 ++++++++++++- qrenderdoc/Widgets/ResourcePreview.h | 13 +++ qrenderdoc/Windows/TextureViewer.cpp | 62 ++++++++---- qrenderdoc/Windows/TextureViewer.h | 2 + renderdoc/api/replay/renderdoc_replay.h | 20 ++++ renderdoc/replay/replay_controller.h | 6 ++ renderdoc/replay/replay_output.cpp | 122 ++++++++++++++++++++++++ 7 files changed, 269 insertions(+), 22 deletions(-) diff --git a/qrenderdoc/Widgets/ResourcePreview.cpp b/qrenderdoc/Widgets/ResourcePreview.cpp index 52691ee6e..e8af91553 100644 --- a/qrenderdoc/Widgets/ResourcePreview.cpp +++ b/qrenderdoc/Widgets/ResourcePreview.cpp @@ -35,6 +35,29 @@ ResourcePreview::ResourcePreview(ICaptureContext &c, IReplayOutput *output, QWid ui->thumbnail->SetContext(c); ui->thumbnail->SetOutput(output); + Initialise(); +} + +ResourcePreview::ResourcePreview(bool, QWidget *parent) + : QFrame(parent), ui(new Ui::ResourcePreview) +{ + ui->setupUi(this); + + delete ui->thumbnail; + ui->thumbnail = NULL; + m_ManualThumbnail = new RDLabel(); + m_ManualThumbnail->setGeometry(QRect(0, 0, 16, 16)); + m_ManualThumbnail->setScaledContents(true); + m_ManualThumbnail->setSizePolicy( + QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); + + ui->gridLayout->addWidget(m_ManualThumbnail, 0, 0, 1, 2); + + Initialise(); +} + +void ResourcePreview::Initialise() +{ setBackgroundRole(QPalette::Background); setForegroundRole(QPalette::Foreground); @@ -57,12 +80,19 @@ ResourcePreview::ResourcePreview(ICaptureContext &c, IReplayOutput *output, QWid ui->descriptionLabel->setForegroundRole(QPalette::Foreground); ui->descriptionLabel->setFont(Formatter::PreferredFont()); - QObject::connect(ui->thumbnail, &CustomPaintWidget::clicked, this, &ResourcePreview::clickEvent); + if(ui->thumbnail) + QObject::connect(ui->thumbnail, &CustomPaintWidget::clicked, this, &ResourcePreview::clickEvent); + else if(m_ManualThumbnail) + QObject::connect(m_ManualThumbnail, &RDLabel::clicked, this, &ResourcePreview::clickEvent); QObject::connect(ui->slotLabel, &RDLabel::clicked, this, &ResourcePreview::clickEvent); QObject::connect(ui->descriptionLabel, &RDLabel::clicked, this, &ResourcePreview::clickEvent); - QObject::connect(ui->thumbnail, &CustomPaintWidget::doubleClicked, this, - &ResourcePreview::doubleClickEvent); + if(ui->thumbnail) + QObject::connect(ui->thumbnail, &CustomPaintWidget::doubleClicked, this, + &ResourcePreview::doubleClickEvent); + else if(m_ManualThumbnail) + QObject::connect(m_ManualThumbnail, &RDLabel::doubleClicked, this, + &ResourcePreview::doubleClickEvent); QObject::connect(ui->slotLabel, &RDLabel::doubleClicked, this, &ResourcePreview::doubleClickEvent); QObject::connect(ui->descriptionLabel, &RDLabel::doubleClicked, this, &ResourcePreview::doubleClickEvent); @@ -110,5 +140,33 @@ void ResourcePreview::setSelected(bool sel) WindowingData ResourcePreview::GetWidgetWindowingData() { - return ui->thumbnail->GetWidgetWindowingData(); + if(ui->thumbnail) + return ui->thumbnail->GetWidgetWindowingData(); + return {}; +} + +QSize ResourcePreview::GetThumbSize() +{ + if(ui->thumbnail) + return ui->thumbnail->size(); + + if(m_ManualThumbnail) + return m_ManualThumbnail->size(); + + return {}; +} + +void ResourcePreview::UpdateThumb(QSize s, const bytebuf &imgData) +{ + if(!m_ManualThumbnail) + return; + + QImage thumb(imgData.data(), s.width(), s.height(), s.width() * 3, QImage::Format_RGB888); + m_ManualThumbnail->setPixmap(QPixmap::fromImage(thumb)); +} + +void ResourcePreview::resizeEvent(QResizeEvent *e) +{ + emit resized(this); + QFrame::resizeEvent(e); } diff --git a/qrenderdoc/Widgets/ResourcePreview.h b/qrenderdoc/Widgets/ResourcePreview.h index 01a7dd400..d8f9400bf 100644 --- a/qrenderdoc/Widgets/ResourcePreview.h +++ b/qrenderdoc/Widgets/ResourcePreview.h @@ -34,6 +34,7 @@ class ResourcePreview; struct IReplayOutput; struct ICaptureContext; +class RDLabel; class ResourcePreview : public QFrame { @@ -41,11 +42,17 @@ class ResourcePreview : public QFrame public: explicit ResourcePreview(ICaptureContext &c, IReplayOutput *output, QWidget *parent = 0); + // create a manually rendered preview + explicit ResourcePreview(bool, QWidget *parent = 0); + + void Initialise(); + ~ResourcePreview(); signals: void clicked(QMouseEvent *e); void doubleClicked(QMouseEvent *e); + void resized(ResourcePreview *prev); public: void setSlotName(const QString &n); @@ -55,6 +62,8 @@ public: void doubleClickEvent(QMouseEvent *e); WindowingData GetWidgetWindowingData(); + QSize GetThumbSize(); + void UpdateThumb(QSize s, const bytebuf &imgData); void setActive(bool b) { @@ -68,8 +77,12 @@ public: void setSelected(bool sel); private: + virtual void resizeEvent(QResizeEvent *event); + Ui::ResourcePreview *ui; + RDLabel *m_ManualThumbnail = NULL; + bool m_Active; bool m_Selected = false; }; diff --git a/qrenderdoc/Windows/TextureViewer.cpp b/qrenderdoc/Windows/TextureViewer.cpp index ea512add4..bf0b3e020 100644 --- a/qrenderdoc/Windows/TextureViewer.cpp +++ b/qrenderdoc/Windows/TextureViewer.cpp @@ -1981,10 +1981,11 @@ void TextureViewer::textureTab_Closing(int index) ResourcePreview *TextureViewer::UI_CreateThumbnail(ThumbnailStrip *strip) { - ResourcePreview *prev = new ResourcePreview(m_Ctx, m_Output); + ResourcePreview *prev = new ResourcePreview(true); QObject::connect(prev, &ResourcePreview::clicked, this, &TextureViewer::thumb_clicked); QObject::connect(prev, &ResourcePreview::doubleClicked, this, &TextureViewer::thumb_doubleClicked); + QObject::connect(prev, &ResourcePreview::resized, this, &TextureViewer::UI_PreviewResized); prev->setActive(false); strip->addThumb(prev); @@ -2366,6 +2367,7 @@ void TextureViewer::InitResourcePreview(ResourcePreview *prev, BoundResource res Following &follow, const QString &bindName, const QString &slotName) { + Subresource sub = {0, 0, ~0U}; if(res.resourceId != ResourceId() || force) { QString fullname = bindName; @@ -2380,8 +2382,6 @@ void TextureViewer::InitResourcePreview(ResourcePreview *prev, BoundResource res prev->setResourceName(fullname); - WindowingData winData = prev->GetWidgetWindowingData(); - prev->setProperty("f", QVariant::fromValue(follow)); prev->setSlotName(slotName); prev->setActive(true); @@ -2389,20 +2389,15 @@ void TextureViewer::InitResourcePreview(ResourcePreview *prev, BoundResource res if(m_Ctx.GetTexture(res.resourceId)) { - m_Ctx.Replay().AsyncInvoke([this, winData, res](IReplayController *) { - Subresource sub = {0, 0, ~0U}; - if(res.firstMip >= 0) - sub.mip = res.firstMip; - if(res.firstSlice >= 0) - sub.slice = res.firstSlice; - m_Output->AddThumbnail(winData, res.resourceId, sub, res.typeCast); - }); + if(res.firstMip >= 0) + sub.mip = res.firstMip; + if(res.firstSlice >= 0) + sub.slice = res.firstSlice; } else { - m_Ctx.Replay().AsyncInvoke([this, winData](IReplayController *) { - m_Output->AddThumbnail(winData, ResourceId(), {0, 0, ~0U}, CompType::Typeless); - }); + res.resourceId = ResourceId(); + res.typeCast = CompType::Typeless; } } else if(m_Following == follow) @@ -2411,17 +2406,48 @@ void TextureViewer::InitResourcePreview(ResourcePreview *prev, BoundResource res prev->setActive(true); prev->setSelected(true); - WindowingData winData = prev->GetWidgetWindowingData(); - m_Ctx.Replay().AsyncInvoke([this, winData](IReplayController *) { - m_Output->AddThumbnail(winData, ResourceId(), {0, 0, ~0U}, CompType::Typeless); - }); + res.resourceId = ResourceId(); + res.typeCast = CompType::Typeless; } else { prev->setResourceName(QString()); prev->setActive(false); prev->setSelected(false); + + return; } + + prev->setProperty("id", QVariant::fromValue(res.resourceId)); + prev->setProperty("mip", sub.mip); + prev->setProperty("slice", sub.slice); + prev->setProperty("cast", uint32_t(res.typeCast)); + + GUIInvoke::call(this, [this, prev] { UI_PreviewResized(prev); }); +} + +void TextureViewer::UI_PreviewResized(ResourcePreview *prev) +{ + QSize s = prev->GetThumbSize(); + + ResourceId id = prev->property("id").value(); + Subresource sub = {0, 0, ~0U}; + sub.mip = prev->property("mip").toUInt(); + sub.slice = prev->property("slice").toUInt(); + CompType typeCast = (CompType)prev->property("cast").toUInt(); + + m_Ctx.Replay().AsyncInvoke(lit("preview%1").arg((qulonglong)(void *)prev), + [this, prev, s, sub, id, typeCast](IReplayController *) { + bytebuf data = + m_Output->DrawThumbnail(s.width(), s.height(), id, sub, typeCast); + // new and swap to move the data into the lambda + bytebuf *copy = new bytebuf; + copy->swap(data); + GUIInvoke::call(this, [prev, s, copy]() { + prev->UpdateThumb(s, *copy); + delete copy; + }); + }); } void TextureViewer::InitStageResourcePreviews(ShaderStage stage, diff --git a/qrenderdoc/Windows/TextureViewer.h b/qrenderdoc/Windows/TextureViewer.h index e49617fe9..c40eed19b 100644 --- a/qrenderdoc/Windows/TextureViewer.h +++ b/qrenderdoc/Windows/TextureViewer.h @@ -275,6 +275,8 @@ private: rdcarray &ResList, ThumbnailStrip *prevs, int &prevIndex, bool copy, bool rw); + void UI_PreviewResized(ResourcePreview *prev); + void AddResourceUsageEntry(QMenu &menu, uint32_t start, uint32_t end, ResourceUsage usage); void OpenResourceContextMenu(ResourceId id, bool input, const rdcarray &usage); diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 53b5949e3..abf2bdd31 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -292,6 +292,26 @@ Should only be called for texture outputs. virtual ResultDetails AddThumbnail(WindowingData window, ResourceId textureId, const Subresource &sub, CompType typeCast) = 0; + DOCUMENT(R"(Draws a thumbnail for a particular texture with sensible defaults and returns an RGBA8 +byte buffer for display. This does not render to a window but internally to a texture which is read +back from the GPU. + +Should only be called for texture outputs. + +:param int width: The width of the desired thumbnail. +:param int height: The height of the desired thumbnail. +:param ResourceId textureId: The texture ID to display in the thumbnail preview. +:param Subresource sub: The subresource within this texture to use. +:param CompType typeCast: If possible interpret the texture with this type instead of its normal + type. If set to :data:`CompType.Typeless` then no cast is applied, otherwise where allowed the + texture data will be reinterpreted - e.g. from unsigned integers to floats, or to unsigned + normalised values. +:return: A buffer with the thumbnail RGBA8 data if successful, or empty if something went wrong. +:rtype: bytes +)"); + virtual bytebuf DrawThumbnail(int32_t width, int32_t height, ResourceId textureId, + const Subresource &sub, CompType typeCast) = 0; + DOCUMENT(R"(Render to the window handle specified when the output was created. This will also render any thumbnails and the pixel context, if enabled. diff --git a/renderdoc/replay/replay_controller.h b/renderdoc/replay/replay_controller.h index ca1fbe823..d88e657db 100644 --- a/renderdoc/replay/replay_controller.h +++ b/renderdoc/replay/replay_controller.h @@ -48,6 +48,8 @@ public: void ClearThumbnails(); ResultDetails AddThumbnail(WindowingData window, ResourceId texID, const Subresource &sub, CompType typeCast); + bytebuf DrawThumbnail(int32_t width, int32_t height, ResourceId textureId, const Subresource &sub, + CompType typeCast); void Display(); @@ -103,6 +105,10 @@ private: ResourceId m_CustomShaderResourceId; rdcarray m_Thumbnails; + rdcarray> m_ThumbnailGenerators; + // keep 8 generators to avoid churn, but most thumbnails should be the same size so this means + // during resize we don't create and destroy too many + static const size_t MaxThumbnailGenerators = 8; float m_ContextX; float m_ContextY; diff --git a/renderdoc/replay/replay_output.cpp b/renderdoc/replay/replay_output.cpp index ea32a551e..c6db6a6d6 100644 --- a/renderdoc/replay/replay_output.cpp +++ b/renderdoc/replay/replay_output.cpp @@ -350,6 +350,9 @@ void ReplayOutput::ClearThumbnails() for(size_t i = 0; i < m_Thumbnails.size(); i++) m_pDevice->DestroyOutputWindow(m_Thumbnails[i].outputID); + for(auto it = m_ThumbnailGenerators.begin(); it != m_ThumbnailGenerators.end(); ++it) + m_pDevice->DestroyOutputWindow(it->second); + m_Thumbnails.clear(); } @@ -371,6 +374,125 @@ ResultDetails ReplayOutput::SetPixelContext(WindowingData window) return RDResult(); } +bytebuf ReplayOutput::DrawThumbnail(int32_t width, int32_t height, ResourceId textureId, + const Subresource &sub, CompType typeCast) +{ + bytebuf ret; + + uint64_t key = (uint64_t(width) << 32) | height; + int idx = -1; + uint64_t outputID = 0; + for(int i = 0; i < m_ThumbnailGenerators.count(); i++) + { + if(m_ThumbnailGenerators[i].first == key) + { + idx = i; + outputID = m_ThumbnailGenerators[i].second; + break; + } + } + + if(idx < 0) + { + // resize oldest generator if we have hit the max + if(m_ThumbnailGenerators.size() == MaxThumbnailGenerators) + { + outputID = m_ThumbnailGenerators.back().second; + m_pDevice->SetOutputWindowDimensions(outputID, width, height); + m_ThumbnailGenerators.pop_back(); + } + else + { + outputID = m_pDevice->MakeOutputWindow(CreateHeadlessWindowingData(width, height), false); + } + } + else + { + // remove the found one, so it can get inserted into the front + m_ThumbnailGenerators.erase(idx); + } + // make this the most recent generator, so the oldest one will be kicked out + m_ThumbnailGenerators.insert(0, {key, outputID}); + + bool depthMode = false; + + for(size_t t = 0; t < m_pController->m_Textures.size(); t++) + { + if(m_pController->m_Textures[t].resourceId == textureId) + { + depthMode = (m_pController->m_Textures[t].creationFlags & TextureCategory::DepthTarget) || + (m_pController->m_Textures[t].format.compType == CompType::Depth); + break; + } + } + + if(textureId == ResourceId()) + { + m_pDevice->BindOutputWindow(outputID, false); + + FloatVector dark = RenderDoc::Inst().DarkCheckerboardColor(); + FloatVector light = RenderDoc::Inst().LightCheckerboardColor(); + + FloatVector dark2; + dark2.x = light.x; + dark2.y = dark.y; + dark2.z = dark.z; + dark2.w = 1.0f; + FloatVector light2; + light2.x = dark.x; + light2.y = light.y; + light2.z = light.z; + light2.w = 1.0f; + m_pDevice->RenderCheckerboard(dark2, light2); + m_pController->FatalErrorCheck(); + + m_pDevice->FlipOutputWindow(outputID); + m_pController->FatalErrorCheck(); + } + else + { + m_pDevice->BindOutputWindow(outputID, false); + m_pDevice->ClearOutputWindowColor(outputID, FloatVector()); + + TextureDisplay disp; + + disp.red = disp.green = disp.blue = true; + disp.alpha = false; + disp.hdrMultiplier = -1.0f; + disp.linearDisplayAsGamma = true; + disp.flipY = false; + disp.subresource = sub; + disp.subresource.sample = 0; + disp.customShaderId = ResourceId(); + disp.resourceId = m_pDevice->GetLiveID(textureId); + disp.typeCast = typeCast; + disp.scale = -1.0f; + disp.rangeMin = 0.0f; + disp.rangeMax = 1.0f; + disp.xOffset = 0.0f; + disp.yOffset = 0.0f; + disp.rawOutput = false; + disp.overlay = DebugOverlay::NoOverlay; + + if(typeCast == CompType::SNorm) + disp.rangeMin = -1.0f; + + if(depthMode) + disp.green = disp.blue = false; + + m_pDevice->RenderTexture(disp); + m_pController->FatalErrorCheck(); + + m_pDevice->FlipOutputWindow(outputID); + m_pController->FatalErrorCheck(); + } + + m_pDevice->GetOutputWindowData(outputID, ret); + m_pController->FatalErrorCheck(); + + return ret; +} + ResultDetails ReplayOutput::AddThumbnail(WindowingData window, ResourceId texID, const Subresource &sub, CompType typeCast) {