mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-13 11:07:08 +00:00
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.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<ResourceId>();
|
||||
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,
|
||||
|
||||
@@ -275,6 +275,8 @@ private:
|
||||
rdcarray<BoundResourceArray> &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<EventUsage> &usage);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<OutputPair> m_Thumbnails;
|
||||
rdcarray<rdcpair<uint64_t, uint64_t>> 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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user