From 317c670fd52687b8b0339ca2ef28a4310f6ca38c Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 21 Mar 2024 17:19:48 +0000 Subject: [PATCH] Update GL pipeline state viewer to use new descriptor access --- qrenderdoc/Widgets/Extended/RDTreeWidget.h | 2 + .../D3D11PipelineStateViewer.cpp | 20 - .../PipelineState/GLPipelineStateViewer.cpp | 1810 +++++++++-------- .../PipelineState/GLPipelineStateViewer.h | 37 +- .../PipelineState/PipelineStateViewer.cpp | 15 + .../PipelineState/PipelineStateViewer.h | 9 + 6 files changed, 1064 insertions(+), 829 deletions(-) diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.h b/qrenderdoc/Widgets/Extended/RDTreeWidget.h index de09296e7..6d9863d23 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.h +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.h @@ -73,11 +73,13 @@ public: m_bold = bold; dataChanged(0, Qt::FontRole); } + inline bool bold() const { return m_bold; } inline void setItalic(bool italic) { m_italic = italic; dataChanged(0, Qt::FontRole); } + inline bool italic() const { return m_italic; } inline void setTreeColor(QColor col) { m_treeCol = col; } inline void setBackgroundColor(QColor background) { setBackground(QBrush(background)); } inline void setForegroundColor(QColor foreground) { setForeground(QBrush(foreground)); } diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp index e44ee800c..285814664 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp @@ -72,26 +72,6 @@ struct D3D11ViewTag Q_DECLARE_METATYPE(D3D11ViewTag); -struct ScopedTreeUpdater -{ - ScopedTreeUpdater(RDTreeWidget *widget) : m_Widget(widget) - { - vs = m_Widget->verticalScrollBar()->value(); - m_Widget->beginUpdate(); - m_Widget->clear(); - } - - ~ScopedTreeUpdater() - { - m_Widget->clearSelection(); - m_Widget->endUpdate(); - m_Widget->verticalScrollBar()->setValue(vs); - } - - RDTreeWidget *m_Widget; - int vs; -}; - D3D11PipelineStateViewer::D3D11PipelineStateViewer(ICaptureContext &ctx, PipelineStateViewer &common, QWidget *parent) : QFrame(parent), ui(new Ui::D3D11PipelineStateViewer), m_Ctx(ctx), m_Common(common) diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index 9cc9a0d72..2203f66a7 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -51,24 +51,30 @@ struct GLVBIBTag Q_DECLARE_METATYPE(GLVBIBTag); +struct GLReadOnlyTag +{ + GLReadOnlyTag() = default; + GLReadOnlyTag(uint32_t reg, ResourceId id) : reg(reg), ID(id) {} + uint32_t reg = 0; + ResourceId ID; +}; + +Q_DECLARE_METATYPE(GLReadOnlyTag); + struct GLReadWriteTag { - GLReadWriteTag() + GLReadWriteTag() = default; + GLReadWriteTag(GLReadWriteType readWriteType, uint32_t index, uint32_t reg, ResourceId id, + uint64_t offs, uint64_t sz) + : readWriteType(readWriteType), rwIndex(index), reg(reg), ID(id), offset(offs), size(sz) { - bindPoint = 0; - offset = size = 0; } - GLReadWriteTag(int32_t b, ResourceId id, uint64_t offs, uint64_t sz) - { - bindPoint = b; - ID = id; - offset = offs; - size = sz; - } - int32_t bindPoint; + GLReadWriteType readWriteType = GLReadWriteType::Atomic; + uint32_t rwIndex = 0; + uint32_t reg = 0; ResourceId ID; - uint64_t offset; - uint64_t size; + uint64_t offset = 0; + uint64_t size = 0; }; Q_DECLARE_METATYPE(GLReadWriteTag); @@ -472,7 +478,32 @@ void GLPipelineStateViewer::OnCaptureClosed() void GLPipelineStateViewer::OnEventChanged(uint32_t eventId) { - setState(); + m_Ctx.Replay().AsyncInvoke([this](IReplayController *r) { + const GLPipe::State *state = r->GetGLPipelineState(); + ResourceId descriptorStore = state->descriptorStore; + DescriptorRange range; + range.offset = 0; + range.descriptorSize = state->descriptorByteSize; + range.count = state->descriptorCount; + + rdcarray ranges = {range}; + + rdcarray locations = + r->GetDescriptorLocations(descriptorStore, ranges); + rdcarray descriptors = r->GetDescriptors(descriptorStore, ranges); + rdcarray samplerDescriptors = + r->GetSamplerDescriptors(descriptorStore, ranges); + + // we only write to m_Locations etc on the GUI thread so we know there's no race here. + GUIInvoke::call(this, + [this, locations = std::move(locations), descriptors = std::move(descriptors), + samplerDescriptors = std::move(samplerDescriptors)]() { + m_Locations = locations; + m_Descriptors = descriptors; + m_SamplerDescriptors = samplerDescriptors; + setState(); + }); + }); } void GLPipelineStateViewer::SelectPipelineStage(PipelineStage stage) @@ -491,15 +522,16 @@ ResourceId GLPipelineStateViewer::GetResource(RDTreeWidgetItem *item) ui->vsUBOs, ui->tcsUBOs, ui->tesUBOs, ui->gsUBOs, ui->fsUBOs, ui->csUBOs, }; - if(tag.canConvert()) - { - return tag.value(); - } - else if(tag.canConvert()) + if(tag.canConvert()) { GLVBIBTag buf = tag.value(); return buf.id; } + else if(tag.canConvert()) + { + GLReadOnlyTag ro = tag.value(); + return ro.ID; + } else if(tag.canConvert()) { GLReadWriteTag rw = tag.value(); @@ -517,7 +549,7 @@ ResourceId GLPipelineStateViewer::GetResource(RDTreeWidgetItem *item) int cb = tag.value(); - return m_Ctx.CurPipelineState().GetConstantBuffer(stage->stage, cb, 0).resourceId; + return m_Ctx.CurPipelineState().GetConstantBlockDescriptor(stage->stage, cb, 0).descriptor.resource; } return ResourceId(); @@ -533,6 +565,11 @@ void GLPipelineStateViewer::on_showEmpty_toggled(bool checked) setState(); } +bool GLPipelineStateViewer::isInactiveRow(RDTreeWidgetItem *node) +{ + return node->italic(); +} + void GLPipelineStateViewer::setInactiveRow(RDTreeWidgetItem *node) { node->setItalic(true); @@ -546,17 +583,18 @@ void GLPipelineStateViewer::setEmptyRow(RDTreeWidgetItem *node) void GLPipelineStateViewer::setViewDetails(RDTreeWidgetItem *node, TextureDescription *tex, uint32_t firstMip, uint32_t numMips, uint32_t firstSlice, - uint32_t numSlices, const GLPipe::Texture *texBinding) + uint32_t numSlices, + const GLPipe::TextureCompleteness *texCompleteness) { QString text; - if(texBinding) + if(texCompleteness) { - if(!texBinding->completeStatus.isEmpty()) - text += tr("The texture is incomplete:\n%1\n\n").arg(texBinding->completeStatus); + if(!texCompleteness->completeStatus.isEmpty()) + text += tr("The texture is incomplete:\n%1\n\n").arg(texCompleteness->completeStatus); - if(!texBinding->typeConflict.isEmpty()) - text += tr("Multiple conflicting bindings:\n%1\n\n").arg(texBinding->typeConflict); + if(!texCompleteness->typeConflict.isEmpty()) + text += tr("Multiple conflicting bindings:\n%1\n\n").arg(texCompleteness->typeConflict); } if(tex) @@ -595,6 +633,453 @@ void GLPipelineStateViewer::setViewDetails(RDTreeWidgetItem *node, TextureDescri } } +void GLPipelineStateViewer::addImageSamplerRow(const Descriptor &descriptor, + const SamplerDescriptor &samplerDescriptor, + uint32_t reg, const ShaderResource *shaderTex, + const ShaderSampler *shaderSamp, bool usedSlot, + const GLPipe::TextureCompleteness *texCompleteness, + RDTreeWidgetItem *textures, RDTreeWidgetItem *samplers) +{ + bool filledSlot = (descriptor.resource != ResourceId()); + + if(showNode(usedSlot, filledSlot)) + { + // only show one empty node per reg at most, but prioritise used slots over unused, and filled + // over empty. Any tie-breaks we just pick an arbitrary one this can only happen if at least one + // of 'show unused' or 'show empty' is enabled + + for(int i = 0; i < textures->childCount(); i++) + { + GLReadOnlyTag existing = textures->child(i)->tag().value(); + + // if it's a different reg, ignore of course! + if(existing.reg != reg) + continue; + + // existing one is empty, just overwrite it no matter what + if(existing.ID == ResourceId()) + { + delete textures->takeChild(i); + delete samplers->takeChild(i); + // we assume there's only ever one duplicate at once + break; + } + + // existing one is non-empty but we are, abort! + if(existing.ID != ResourceId() && !filledSlot) + return; + + // existing one is unused, ours is. Using + if(isInactiveRow(textures->child(i)) && usedSlot) + { + delete textures->takeChild(i); + delete samplers->takeChild(i); + // we assume there's only ever one duplicate at once + break; + } + + // existing one is used but we aren't + if(!isInactiveRow(textures->child(i)) && !usedSlot) + return; + } + + // do texture + { + QString slotname = QString::number(reg); + + if(texCompleteness && !texCompleteness->typeConflict.empty()) + slotname += tr(": "); + else if(shaderTex && !shaderTex->name.empty()) + slotname += lit(": ") + shaderTex->name; + + uint32_t w = 1, h = 1, d = 1; + uint32_t a = 1; + QString format = lit("Unknown"); + QString typeName = lit("Unknown"); + + if(!filledSlot) + { + format = lit("-"); + typeName = lit("-"); + w = h = d = a = 0; + } + + TextureDescription *tex = m_Ctx.GetTexture(descriptor.resource); + + if(tex) + { + w = tex->width; + h = tex->height; + d = tex->depth; + a = tex->arraysize; + format = tex->format.Name(); + typeName = ToQStr(tex->type); + + if(tex->format.type == ResourceFormatType::D16S8 || + tex->format.type == ResourceFormatType::D24S8 || + tex->format.type == ResourceFormatType::D32S8) + { + if(descriptor.format.compType == CompType::Depth) + format += tr(" Depth-Read"); + else if(descriptor.format.compType == CompType::UInt) + format += tr(" Stencil-Read"); + } + else if(descriptor.swizzle.red != TextureSwizzle::Red || + descriptor.swizzle.green != TextureSwizzle::Green || + descriptor.swizzle.blue != TextureSwizzle::Blue || + descriptor.swizzle.alpha != TextureSwizzle::Alpha) + { + format += tr(" swizzle[%1%2%3%4]") + .arg(ToQStr(descriptor.swizzle.red)) + .arg(ToQStr(descriptor.swizzle.green)) + .arg(ToQStr(descriptor.swizzle.blue)) + .arg(ToQStr(descriptor.swizzle.alpha)); + } + } + + RDTreeWidgetItem *node = NULL; + + if(texCompleteness && !texCompleteness->typeConflict.empty()) + { + node = new RDTreeWidgetItem({slotname, tr("Conflicting bindings"), lit("-"), lit("-"), + lit("-"), lit("-"), lit("-"), lit("-"), QString()}); + + setViewDetails(node, NULL, 0, 0, 0, ~0U, texCompleteness); + } + else + { + node = new RDTreeWidgetItem( + {slotname, descriptor.resource, typeName, w, h, d, a, format, QString()}); + + if(tex) + setViewDetails(node, tex, descriptor.firstMip, descriptor.numMips, 0, ~0U, texCompleteness); + } + + node->setTag(QVariant::fromValue(GLReadOnlyTag(reg, descriptor.resource))); + + if(!filledSlot) + setEmptyRow(node); + + if(texCompleteness) + setEmptyRow(node); + + if(!usedSlot) + setInactiveRow(node); + + textures->addChild(node); + } + + // do sampler + { + QString slotname = QString::number(reg); + + if(shaderTex && !shaderTex->name.empty()) + slotname += lit(": ") + shaderTex->name; + + QString borderColor = QFormatStr("%1, %2, %3, %4") + .arg(samplerDescriptor.borderColorValue.floatValue[0]) + .arg(samplerDescriptor.borderColorValue.floatValue[1]) + .arg(samplerDescriptor.borderColorValue.floatValue[2]) + .arg(samplerDescriptor.borderColorValue.floatValue[3]); + + QString addressing; + + QString addPrefix; + QString addVal; + + QString addr[] = {ToQStr(samplerDescriptor.addressU, GraphicsAPI::OpenGL), + ToQStr(samplerDescriptor.addressV, GraphicsAPI::OpenGL), + ToQStr(samplerDescriptor.addressW, GraphicsAPI::OpenGL)}; + + // arrange like either STR: WRAP or ST: WRAP, R: CLAMP + for(int a = 0; a < 3; a++) + { + const QString str[] = {lit("S"), lit("T"), lit("R")}; + QString prefix = str[a]; + + if(a == 0 || addr[a] == addr[a - 1]) + { + addPrefix += prefix; + } + else + { + addressing += QFormatStr("%1: %2, ").arg(addPrefix).arg(addVal); + + addPrefix = prefix; + } + addVal = addr[a]; + } + + addressing += addPrefix + lit(": ") + addVal; + + if(samplerDescriptor.UseBorder()) + addressing += QFormatStr("<%1>").arg(borderColor); + + if(descriptor.textureType == TextureType::TextureCube || + descriptor.textureType == TextureType::TextureCubeArray) + { + addressing += samplerDescriptor.seamlessCubemaps ? tr(" Seamless") : tr(" Non-Seamless"); + } + + QString filter = ToQStr(samplerDescriptor.filter); + + if(samplerDescriptor.maxAnisotropy > 1) + filter += lit(" Aniso%1x").arg(samplerDescriptor.maxAnisotropy); + + if(samplerDescriptor.filter.filter == FilterFunction::Comparison) + filter += QFormatStr(" (%1)").arg(ToQStr(samplerDescriptor.compareFunction)); + else if(samplerDescriptor.filter.filter != FilterFunction::Normal) + filter += QFormatStr(" (%1)").arg(ToQStr(samplerDescriptor.filter.filter)); + + RDTreeWidgetItem *node = new RDTreeWidgetItem({ + slotname, + samplerDescriptor.object != ResourceId() ? samplerDescriptor.object : descriptor.resource, + addressing, + filter, + QFormatStr("%1 - %2") + .arg(samplerDescriptor.minLOD == -FLT_MAX ? lit("0") + : QString::number(samplerDescriptor.minLOD)) + .arg(samplerDescriptor.maxLOD == FLT_MAX ? lit("FLT_MAX") + : QString::number(samplerDescriptor.maxLOD)), + samplerDescriptor.mipBias, + }); + + node->setTag(QVariant::fromValue(GLReadOnlyTag(reg, descriptor.resource))); + + if(!filledSlot) + setEmptyRow(node); + + if(!usedSlot) + setInactiveRow(node); + + samplers->addChild(node); + } + } +} + +void GLPipelineStateViewer::addUBORow(const Descriptor &descriptor, uint32_t reg, uint32_t index, + const ConstantBlock *shaderBind, bool usedSlot, + RDTreeWidget *ubos) +{ + bool filledSlot = + ((shaderBind && !shaderBind->bufferBacked) || descriptor.resource != ResourceId()); + + if(showNode(usedSlot, filledSlot)) + { + ulong offset = 0; + ulong length = 0; + int numvars = shaderBind ? shaderBind->variables.count() : 0; + ulong byteSize = shaderBind ? (ulong)shaderBind->byteSize : 0; + + QString name; + QString sizestr = tr("%1 Variables").arg(numvars); + QString byterange; + + if(!filledSlot) + { + name = tr("Empty"); + length = 0; + } + + QString slotname = QString::number(reg); + + if(shaderBind && !shaderBind->name.empty()) + slotname += lit(": ") + shaderBind->name; + + offset = descriptor.byteOffset; + length = descriptor.byteSize; + + BufferDescription *buf = m_Ctx.GetBuffer(descriptor.resource); + if(buf) + { + if(length == 0) + length = buf->length; + } + + if(length == byteSize) + sizestr = tr("%1 Variables, %2 bytes") + .arg(numvars) + .arg(Formatter::HumanFormat(length, Formatter::OffsetSize)); + else + sizestr = tr("%1 Variables, %2 bytes needed, %3 provided") + .arg(numvars) + .arg(Formatter::HumanFormat(byteSize, Formatter::OffsetSize)) + .arg(Formatter::HumanFormat(length, Formatter::OffsetSize)); + + if(length < byteSize) + filledSlot = false; + + byterange = QFormatStr("%1 - %2") + .arg(Formatter::HumanFormat(offset, Formatter::OffsetSize)) + .arg(Formatter::HumanFormat(offset + length, Formatter::OffsetSize)); + + RDTreeWidgetItem *node; + if(shaderBind && !shaderBind->bufferBacked) + { + node = new RDTreeWidgetItem( + {tr("Uniforms"), QString(), QString(), tr("%1 Variables").arg(numvars), QString()}); + } + else + { + node = new RDTreeWidgetItem({slotname, descriptor.resource, byterange, sizestr, QString()}); + } + + node->setTag(QVariant::fromValue(index)); + + if(!filledSlot) + setEmptyRow(node); + + if(!usedSlot) + setInactiveRow(node); + + ubos->addTopLevelItem(node); + } +} + +void GLPipelineStateViewer::addReadWriteRow(const Descriptor &descriptor, uint32_t reg, + uint32_t index, const ShaderResource *shaderBind, + bool usedSlot, + const GLPipe::TextureCompleteness *texCompleteness, + RDTreeWidgetItem *readwrites) +{ + bool filledSlot = descriptor.resource != ResourceId(); + + if(showNode(usedSlot, filledSlot)) + { + GLReadWriteType readWriteType = GLReadWriteType::Image; + if(descriptor.type == DescriptorType::ReadWriteBuffer) + readWriteType = GLReadWriteType::SSBO; + + if(shaderBind) + readWriteType = GetGLReadWriteType(*shaderBind); + + // only show one empty node per reg at most, but prioritise used slots over unused, and filled + // over empty. Any tie-breaks we just pick an arbitrary one this can only happen if at least one + // of 'show unused' or 'show empty' is enabled + + for(int i = 0; i < readwrites->childCount(); i++) + { + GLReadWriteTag existing = readwrites->child(i)->tag().value(); + + // if it's a different reg, ignore of course! + if(existing.reg != reg || existing.readWriteType != readWriteType) + continue; + + // existing one is empty, just overwrite it no matter what + if(existing.ID == ResourceId()) + { + delete readwrites->takeChild(i); + // we assume there's only ever one duplicate at once + break; + } + + // existing one is non-empty but we are, abort! + if(existing.ID != ResourceId() && !filledSlot) + return; + + // existing one is unused, ours is + if(isInactiveRow(readwrites->child(i)) && usedSlot) + { + delete readwrites->takeChild(i); + // we assume there's only ever one duplicate at once + break; + } + + // existing one is used but we aren't + if(!isInactiveRow(readwrites->child(i)) && !usedSlot) + return; + } + + QString binding = readWriteType == GLReadWriteType::Image ? tr("Image") + : readWriteType == GLReadWriteType::Atomic ? tr("Atomic") + : readWriteType == GLReadWriteType::SSBO ? tr("SSBO") + : tr("Unknown"); + + QString slotname = QString::number(reg); + + if(shaderBind && !shaderBind->name.empty()) + slotname += lit(": ") + shaderBind->name; + + QString dimensions; + QString format = descriptor.format.Name(); + QString access = tr("Read/Write"); + if(descriptor.flags & DescriptorFlags::ReadOnlyAccess) + access = tr("Read-Only"); + if(descriptor.flags & DescriptorFlags::WriteOnlyAccess) + access = tr("Write-Only"); + + uint64_t offset = 0; + uint64_t length = 0; + + TextureDescription *tex = m_Ctx.GetTexture(descriptor.resource); + + if(tex) + { + if(tex->dimension == 1) + { + if(tex->arraysize > 1) + dimensions = QFormatStr("%1[%2]").arg(tex->width).arg(tex->arraysize); + else + dimensions = QFormatStr("%1").arg(tex->width); + } + else if(tex->dimension == 2) + { + if(tex->arraysize > 1) + dimensions = QFormatStr("%1x%2[%3]").arg(tex->width).arg(tex->height).arg(tex->arraysize); + else + dimensions = QFormatStr("%1x%2").arg(tex->width).arg(tex->height); + } + else if(tex->dimension == 3) + { + dimensions = QFormatStr("%1x%2x%3").arg(tex->width).arg(tex->height).arg(tex->depth); + } + } + + BufferDescription *buf = m_Ctx.GetBuffer(descriptor.resource); + + if(buf) + { + length = buf->length; + if(descriptor.byteSize > 0) + { + offset = descriptor.byteOffset; + length = descriptor.byteSize; + } + + if(offset > 0) + dimensions = tr("%1 bytes at offset %2 bytes").arg(length).arg(offset); + else + dimensions = tr("%1 bytes").arg(length); + + format = lit("-"); + } + + if(!filledSlot) + { + dimensions = lit("-"); + access = lit("-"); + } + + RDTreeWidgetItem *node = new RDTreeWidgetItem( + {binding, slotname, descriptor.resource, dimensions, format, access, QString()}); + + node->setTag(QVariant::fromValue( + GLReadWriteTag(readWriteType, index, reg, descriptor.resource, offset, length))); + + if(tex) + setViewDetails(node, tex, descriptor.firstMip, descriptor.numMips, descriptor.firstSlice, + descriptor.numSlices, texCompleteness); + + if(!filledSlot) + setEmptyRow(node); + + if(!usedSlot) + setInactiveRow(node); + + readwrites->addChild(node); + } +} + bool GLPipelineStateViewer::showNode(bool usedSlot, bool filledSlot) { const bool showUnused = ui->showUnused->isChecked(); @@ -754,12 +1239,9 @@ void GLPipelineStateViewer::clearState() } void GLPipelineStateViewer::setShaderState(const GLPipe::Shader &stage, RDLabel *shader, - RDTreeWidget *textures, RDTreeWidget *samplers, - RDTreeWidget *ubos, RDTreeWidget *subs, - RDTreeWidget *readwrites) + RDTreeWidget *subs) { ShaderReflection *shaderDetails = stage.reflection; - const ShaderBindpointMapping &mapping = stage.bindpointMapping; const GLPipe::State &state = *m_Ctx.CurGLPipelineState(); if(stage.shaderResourceId == ResourceId()) @@ -778,361 +1260,7 @@ void GLPipelineStateViewer::setShaderState(const GLPipe::Shader &stage, RDLabel shader->setText(shText); } - int vs = 0; - int vs2 = 0; - - // simultaneous update of resources and samplers - vs = textures->verticalScrollBar()->value(); - textures->beginUpdate(); - textures->clear(); - vs2 = samplers->verticalScrollBar()->value(); - samplers->beginUpdate(); - samplers->clear(); - - for(int i = 0; i < state.textures.count(); i++) - { - const GLPipe::Texture &r = state.textures[i]; - const GLPipe::Sampler &s = state.samplers[i]; - - const ShaderResource *shaderInput = NULL; - const Bindpoint *map = NULL; - - if(shaderDetails) - { - for(const ShaderResource &bind : shaderDetails->readOnlyResources) - { - if(bind.isReadOnly && mapping.readOnlyResources[bind.bindPoint].bind == i) - { - shaderInput = &bind; - map = &mapping.readOnlyResources[bind.bindPoint]; - } - } - } - - bool filledSlot = (r.resourceId != ResourceId()); - bool usedSlot = (shaderInput && map && map->used); - - if(showNode(usedSlot, filledSlot)) - { - // do texture - { - QString slotname = QString::number(i); - - if(!r.typeConflict.empty()) - slotname += tr(": "); - else if(shaderInput && !shaderInput->name.empty()) - slotname += lit(": ") + shaderInput->name; - - uint32_t w = 1, h = 1, d = 1; - uint32_t a = 1; - QString format = lit("Unknown"); - QString typeName = lit("Unknown"); - - if(!filledSlot) - { - format = lit("-"); - typeName = lit("-"); - w = h = d = a = 0; - } - - TextureDescription *tex = m_Ctx.GetTexture(r.resourceId); - - if(tex) - { - w = tex->width; - h = tex->height; - d = tex->depth; - a = tex->arraysize; - format = tex->format.Name(); - typeName = ToQStr(tex->type); - - if(tex->format.type == ResourceFormatType::D16S8 || - tex->format.type == ResourceFormatType::D24S8 || - tex->format.type == ResourceFormatType::D32S8) - { - if(r.depthReadChannel == 0) - format += tr(" Depth-Read"); - else if(r.depthReadChannel == 1) - format += tr(" Stencil-Read"); - } - else if(r.swizzle.red != TextureSwizzle::Red || r.swizzle.green != TextureSwizzle::Green || - r.swizzle.blue != TextureSwizzle::Blue || r.swizzle.alpha != TextureSwizzle::Alpha) - { - format += tr(" swizzle[%1%2%3%4]") - .arg(ToQStr(r.swizzle.red)) - .arg(ToQStr(r.swizzle.green)) - .arg(ToQStr(r.swizzle.blue)) - .arg(ToQStr(r.swizzle.alpha)); - } - } - - RDTreeWidgetItem *node = NULL; - - if(!r.typeConflict.empty()) - { - node = new RDTreeWidgetItem({slotname, tr("Conflicting bindings"), lit("-"), lit("-"), - lit("-"), lit("-"), lit("-"), lit("-"), QString()}); - - setViewDetails(node, NULL, 0, 0, 0, ~0U, &r); - } - else - { - node = - new RDTreeWidgetItem({slotname, r.resourceId, typeName, w, h, d, a, format, QString()}); - - node->setTag(QVariant::fromValue(r.resourceId)); - - if(tex) - setViewDetails(node, tex, r.firstMip, r.numMips, 0, ~0U, &r); - } - - if(!filledSlot) - setEmptyRow(node); - - if(!r.completeStatus.isEmpty() || !r.typeConflict.isEmpty()) - setEmptyRow(node); - - if(!usedSlot) - setInactiveRow(node); - - textures->addTopLevelItem(node); - } - - // do sampler - { - QString slotname = QString::number(i); - - if(shaderInput && !shaderInput->name.empty()) - slotname += lit(": ") + shaderInput->name; - - QString borderColor = QFormatStr("%1, %2, %3, %4") - .arg(s.borderColor[0]) - .arg(s.borderColor[1]) - .arg(s.borderColor[2]) - .arg(s.borderColor[3]); - - QString addressing; - - QString addPrefix; - QString addVal; - - QString addr[] = {ToQStr(s.addressS, GraphicsAPI::OpenGL), - ToQStr(s.addressT, GraphicsAPI::OpenGL), - ToQStr(s.addressR, GraphicsAPI::OpenGL)}; - - // arrange like either STR: WRAP or ST: WRAP, R: CLAMP - for(int a = 0; a < 3; a++) - { - const QString str[] = {lit("S"), lit("T"), lit("R")}; - QString prefix = str[a]; - - if(a == 0 || addr[a] == addr[a - 1]) - { - addPrefix += prefix; - } - else - { - addressing += QFormatStr("%1: %2, ").arg(addPrefix).arg(addVal); - - addPrefix = prefix; - } - addVal = addr[a]; - } - - addressing += addPrefix + lit(": ") + addVal; - - if(s.UseBorder()) - addressing += QFormatStr("<%1>").arg(borderColor); - - if(r.type == TextureType::TextureCube || r.type == TextureType::TextureCubeArray) - { - addressing += s.seamlessCubeMap ? tr(" Seamless") : tr(" Non-Seamless"); - } - - QString filter = ToQStr(s.filter); - - if(s.maxAnisotropy > 1) - filter += lit(" Aniso%1x").arg(s.maxAnisotropy); - - if(s.filter.filter == FilterFunction::Comparison) - filter += QFormatStr(" (%1)").arg(ToQStr(s.compareFunction)); - else if(s.filter.filter != FilterFunction::Normal) - filter += QFormatStr(" (%1)").arg(ToQStr(s.filter.filter)); - - RDTreeWidgetItem *node = new RDTreeWidgetItem({ - slotname, - s.resourceId != ResourceId() ? s.resourceId : r.resourceId, - addressing, - filter, - QFormatStr("%1 - %2") - .arg(s.minLOD == -FLT_MAX ? lit("0") : QString::number(s.minLOD)) - .arg(s.maxLOD == FLT_MAX ? lit("FLT_MAX") : QString::number(s.maxLOD)), - s.mipLODBias, - }); - - if(!filledSlot) - setEmptyRow(node); - - if(!usedSlot) - setInactiveRow(node); - - samplers->addTopLevelItem(node); - } - } - } - - samplers->clearSelection(); - samplers->endUpdate(); - samplers->verticalScrollBar()->setValue(vs2); - textures->clearSelection(); - textures->endUpdate(); - textures->verticalScrollBar()->setValue(vs); - - vs = ubos->verticalScrollBar()->value(); - ubos->beginUpdate(); - ubos->clear(); - - // see if there's a global UBO, if so display it first - if(shaderDetails) - { - const ConstantBlock *shaderCBuf = NULL; - const Bindpoint *map = NULL; - - int idx = 0; - - for(const ConstantBlock &bind : shaderDetails->constantBlocks) - { - if(!bind.bufferBacked) - { - shaderCBuf = &bind; - map = &mapping.constantBlocks[bind.bindPoint]; - break; - } - - idx++; - } - - if(shaderCBuf && map) - { - bool filledSlot = true; - bool usedSlot = map->used; - - if(showNode(usedSlot, filledSlot)) - { - QString sizestr = tr("%1 Variables").arg(shaderCBuf->variables.count()); - - RDTreeWidgetItem *node = - new RDTreeWidgetItem({tr("Uniforms"), QString(), QString(), sizestr, QString()}); - - node->setTag(QVariant::fromValue(idx)); - - if(!usedSlot) - setInactiveRow(node); - - ubos->addTopLevelItem(node); - } - } - } - - for(int i = 0; i < state.uniformBuffers.count(); i++) - { - const GLPipe::Buffer &b = state.uniformBuffers[i]; - - const ConstantBlock *shaderCBuf = NULL; - const Bindpoint *map = NULL; - - int idx = 0; - - if(shaderDetails) - { - for(const ConstantBlock &bind : shaderDetails->constantBlocks) - { - if(bind.bufferBacked && mapping.constantBlocks[bind.bindPoint].bind == i) - { - shaderCBuf = &bind; - map = &mapping.constantBlocks[bind.bindPoint]; - - // if this one is used, break immediately. Otherwise keep going to see if we find one that - // is used - if(map->used) - break; - } - - idx++; - } - } - - bool filledSlot = ((shaderCBuf && !shaderCBuf->bufferBacked) || b.resourceId != ResourceId()); - bool usedSlot = (shaderCBuf && map && map->used); - - if(showNode(usedSlot, filledSlot)) - { - ulong offset = 0; - ulong length = 0; - int numvars = shaderCBuf ? shaderCBuf->variables.count() : 0; - ulong byteSize = shaderCBuf ? (ulong)shaderCBuf->byteSize : 0; - - QString name; - QString sizestr = tr("%1 Variables").arg(numvars); - QString byterange; - - if(!filledSlot) - { - name = tr("Empty"); - length = 0; - } - - QString slotname = QString::number(i); - - if(shaderCBuf && !shaderCBuf->name.empty()) - slotname += lit(": ") + shaderCBuf->name; - - offset = b.byteOffset; - length = b.byteSize; - - BufferDescription *buf = m_Ctx.GetBuffer(b.resourceId); - if(buf) - { - if(length == 0) - length = buf->length; - } - - if(length == byteSize) - sizestr = tr("%1 Variables, %2 bytes") - .arg(numvars) - .arg(Formatter::HumanFormat(length, Formatter::OffsetSize)); - else - sizestr = tr("%1 Variables, %2 bytes needed, %3 provided") - .arg(numvars) - .arg(Formatter::HumanFormat(byteSize, Formatter::OffsetSize)) - .arg(Formatter::HumanFormat(length, Formatter::OffsetSize)); - - if(length < byteSize) - filledSlot = false; - - byterange = QFormatStr("%1 - %2") - .arg(Formatter::HumanFormat(offset, Formatter::OffsetSize)) - .arg(Formatter::HumanFormat(offset + length, Formatter::OffsetSize)); - - RDTreeWidgetItem *node = - new RDTreeWidgetItem({slotname, b.resourceId, byterange, sizestr, QString()}); - - node->setTag(QVariant::fromValue(idx)); - - if(!filledSlot) - setEmptyRow(node); - - if(!usedSlot) - setInactiveRow(node); - - ubos->addTopLevelItem(node); - } - } - ubos->clearSelection(); - ubos->endUpdate(); - ubos->verticalScrollBar()->setValue(vs); - - vs = subs->verticalScrollBar()->value(); + int vs = subs->verticalScrollBar()->value(); subs->beginUpdate(); subs->clear(); for(int i = 0; i < stage.subroutines.count(); i++) @@ -1142,155 +1270,6 @@ void GLPipelineStateViewer::setShaderState(const GLPipe::Shader &stage, RDLabel subs->verticalScrollBar()->setValue(vs); subs->parentWidget()->setVisible(!stage.subroutines.empty()); - - vs = readwrites->verticalScrollBar()->value(); - readwrites->beginUpdate(); - readwrites->clear(); - - rdcarray rw; - if(shaderDetails) - rw = shaderDetails->readWriteResources; - - // on GL read-write resources come from multiple namespaces so it's valid to have binding X for - // several. This makes it hard to do as we do above, iterate over every bind from 0 to max and - // find any matching shader bind. Instead we just sort by the bindpoint which we know internally - // in GL we normalised/sorted. The order of multiple elements on bindpoint 0 is undefined. - std::sort(rw.begin(), rw.end(), [](const ShaderResource &a, const ShaderResource &b) { - return a.bindPoint < b.bindPoint; - }); - - for(int i = 0; i < rw.count(); i++) - { - const ShaderResource &res = rw[i]; - const Bindpoint &bind = stage.bindpointMapping.readWriteResources[res.bindPoint]; - int bindPoint = bind.bind; - - GLReadWriteType readWriteType = GetGLReadWriteType(res); - - const GLPipe::Buffer *bf = NULL; - const GLPipe::ImageLoadStore *im = NULL; - ResourceId id; - - if(readWriteType == GLReadWriteType::Image && bindPoint >= 0 && bindPoint < state.images.count()) - { - im = &state.images[bindPoint]; - id = state.images[bindPoint].resourceId; - } - - if(readWriteType == GLReadWriteType::Atomic && bindPoint >= 0 && - bindPoint < state.atomicBuffers.count()) - { - bf = &state.atomicBuffers[bindPoint]; - id = state.atomicBuffers[bindPoint].resourceId; - } - - if(readWriteType == GLReadWriteType::SSBO && bindPoint >= 0 && - bindPoint < state.shaderStorageBuffers.count()) - { - bf = &state.shaderStorageBuffers[bindPoint]; - id = state.shaderStorageBuffers[bindPoint].resourceId; - } - - bool filledSlot = id != ResourceId(); - bool usedSlot = bind.used; - - if(showNode(usedSlot, filledSlot)) - { - QString binding = readWriteType == GLReadWriteType::Image ? tr("Image") - : readWriteType == GLReadWriteType::Atomic ? tr("Atomic") - : readWriteType == GLReadWriteType::SSBO ? tr("SSBO") - : tr("Unknown"); - - QString slotname = QFormatStr("%1: %2").arg(bindPoint).arg(res.name); - QString dimensions; - QString format = lit("-"); - QString access = tr("Read/Write"); - if(im) - { - if(im->readAllowed && !im->writeAllowed) - access = tr("Read-Only"); - if(!im->readAllowed && im->writeAllowed) - access = tr("Write-Only"); - format = im->imageFormat.Name(); - } - - QVariant tag; - - TextureDescription *tex = m_Ctx.GetTexture(id); - - if(tex) - { - if(tex->dimension == 1) - { - if(tex->arraysize > 1) - dimensions = QFormatStr("%1[%2]").arg(tex->width).arg(tex->arraysize); - else - dimensions = QFormatStr("%1").arg(tex->width); - } - else if(tex->dimension == 2) - { - if(tex->arraysize > 1) - dimensions = QFormatStr("%1x%2[%3]").arg(tex->width).arg(tex->height).arg(tex->arraysize); - else - dimensions = QFormatStr("%1x%2").arg(tex->width).arg(tex->height); - } - else if(tex->dimension == 3) - { - dimensions = QFormatStr("%1x%2x%3").arg(tex->width).arg(tex->height).arg(tex->depth); - } - - tag = QVariant::fromValue(id); - } - - BufferDescription *buf = m_Ctx.GetBuffer(id); - - if(buf) - { - uint64_t offset = 0; - uint64_t length = buf->length; - if(bf && bf->byteSize > 0) - { - offset = bf->byteOffset; - length = bf->byteSize; - } - - if(offset > 0) - dimensions = tr("%1 bytes at offset %2 bytes").arg(length).arg(offset); - else - dimensions = tr("%1 bytes").arg(length); - - tag = QVariant::fromValue(GLReadWriteTag(res.bindPoint, id, offset, length)); - } - - if(!filledSlot) - { - dimensions = lit("-"); - access = lit("-"); - } - - RDTreeWidgetItem *node = - new RDTreeWidgetItem({binding, slotname, id, dimensions, format, access, QString()}); - - node->setTag(tag); - - if(im && tex) - setViewDetails(node, tex, im->mipLevel, 1, im->layered ? 0 : im->slice, - im->layered ? ~0U : 1U); - - if(!filledSlot) - setEmptyRow(node); - - if(!usedSlot) - setInactiveRow(node); - - readwrites->addTopLevelItem(node); - } - } - readwrites->clearSelection(); - readwrites->endUpdate(); - readwrites->verticalScrollBar()->setValue(vs); - - readwrites->parentWidget()->setVisible(readwrites->invisibleRootItem()->childCount() > 0); } QString GLPipelineStateViewer::MakeGenericValueString(uint32_t compCount, CompType compType, @@ -1329,7 +1308,7 @@ QString GLPipelineStateViewer::MakeGenericValueString(uint32_t compCount, CompTy } } -GLPipelineStateViewer::GLReadWriteType GLPipelineStateViewer::GetGLReadWriteType(ShaderResource res) +GLReadWriteType GLPipelineStateViewer::GetGLReadWriteType(ShaderResource res) { GLReadWriteType ret = GLReadWriteType::Image; @@ -1619,18 +1598,264 @@ void GLPipelineStateViewer::setState() ui->viBuffers->endUpdate(); ui->viBuffers->verticalScrollBar()->setValue(vs); - setShaderState(state.vertexShader, ui->vsShader, ui->vsTextures, ui->vsSamplers, ui->vsUBOs, - ui->vsSubroutines, ui->vsReadWrite); - setShaderState(state.geometryShader, ui->gsShader, ui->gsTextures, ui->gsSamplers, ui->gsUBOs, - ui->gsSubroutines, ui->gsReadWrite); - setShaderState(state.tessControlShader, ui->tcsShader, ui->tcsTextures, ui->tcsSamplers, - ui->tcsUBOs, ui->tcsSubroutines, ui->tcsReadWrite); - setShaderState(state.tessEvalShader, ui->tesShader, ui->tesTextures, ui->tesSamplers, ui->tesUBOs, - ui->tesSubroutines, ui->tesReadWrite); - setShaderState(state.fragmentShader, ui->fsShader, ui->fsTextures, ui->fsSamplers, ui->fsUBOs, - ui->fsSubroutines, ui->fsReadWrite); - setShaderState(state.computeShader, ui->csShader, ui->csTextures, ui->csSamplers, ui->csUBOs, - ui->csSubroutines, ui->csReadWrite); + { + ScopedTreeUpdater restorers[] = { + // VS + ui->vsTextures, + ui->vsSamplers, + ui->vsUBOs, + ui->vsSubroutines, + ui->vsReadWrite, + // GS + ui->gsTextures, + ui->gsSamplers, + ui->gsUBOs, + ui->gsSubroutines, + ui->gsReadWrite, + // tcs + ui->tcsTextures, + ui->tcsSamplers, + ui->tcsUBOs, + ui->tcsSubroutines, + ui->tcsReadWrite, + // tes + ui->tesTextures, + ui->tesSamplers, + ui->tesUBOs, + ui->tesSubroutines, + ui->tesReadWrite, + // fs + ui->fsTextures, + ui->fsSamplers, + ui->fsUBOs, + ui->fsSubroutines, + ui->fsReadWrite, + // CS + ui->csTextures, + ui->csSamplers, + ui->csUBOs, + ui->csSubroutines, + ui->csReadWrite, + }; + + const ShaderReflection *shaderRefls[NumShaderStages]; + + RDTreeWidget *ubos[] = { + ui->vsUBOs, ui->tcsUBOs, ui->tesUBOs, ui->gsUBOs, ui->fsUBOs, ui->csUBOs, + }; + + RDTreeWidgetItem textures[6]; + RDTreeWidgetItem samplers[6]; + RDTreeWidgetItem readwrites[6]; + + for(ShaderStage stage : values()) + shaderRefls[(uint32_t)stage] = m_Ctx.CurPipelineState().GetShaderReflection(stage); + + for(uint32_t i = 0; i < m_Locations.size(); i++) + { + // locations are not stage specific + uint32_t reg = m_Locations[i].fixedBindNumber; + + bool usedSlot = false; + + // look for any accesses that use this descriptor, we generally expect only one per stage + // so if multiple exist then we'll pick the first one (somewhat arbitrarily). We could add + // duplicates here if we wanted now that we have the information + DescriptorAccess stageAccesses[NumShaderStages]; + for(const DescriptorAccess &access : m_Ctx.CurPipelineState().GetDescriptorAccess()) + { + if(access.byteOffset == i * state.descriptorByteSize) + { + if(stageAccesses[(uint32_t)access.stage].type == DescriptorType::Unknown || + stageAccesses[(uint32_t)access.stage].staticallyUnused) + stageAccesses[(uint32_t)access.stage] = access; + } + } + + const GLPipe::TextureCompleteness *texCompleteness = NULL; + for(const GLPipe::TextureCompleteness &comp : state.textureCompleteness) + { + // GL descriptors are laid out linearly so we can identify the offset of the current + // descriptor without having to store it + if(comp.descriptorByteOffset == i * state.descriptorByteSize) + { + texCompleteness = ∁ + break; + } + } + + if(m_Locations[i].category == DescriptorCategory::ConstantBlock) + { + for(ShaderStage stage : values()) + { + if((uint32_t)stage >= ARRAY_COUNT(ubos)) + continue; + + const ShaderReflection *refl = shaderRefls[(uint32_t)stage]; + const ConstantBlock *shaderBind = NULL; + + const DescriptorAccess &access = stageAccesses[(uint32_t)stage]; + usedSlot = !access.staticallyUnused && access.type != DescriptorType::Unknown; + + if(refl && access.type != DescriptorType::Unknown) + shaderBind = &refl->constantBlocks[access.index]; + + addUBORow(m_Descriptors[i], reg, access.index, shaderBind, usedSlot, ubos[(uint32_t)stage]); + } + } + else if(m_Locations[i].category == DescriptorCategory::ReadOnlyResource) + { + // look for any shaders that use this binding + for(ShaderStage stage : values()) + { + if((uint32_t)stage >= ARRAY_COUNT(textures)) + continue; + + const ShaderReflection *refl = shaderRefls[(uint32_t)stage]; + + const ShaderSampler *shaderSamp = NULL; + const ShaderResource *shaderTex = NULL; + + const DescriptorAccess &access = stageAccesses[(uint32_t)stage]; + usedSlot = !access.staticallyUnused && access.type != DescriptorType::Unknown; + + if(refl && access.type != DescriptorType::Unknown) + { + shaderTex = &refl->readOnlyResources[access.index]; + shaderSamp = &refl->samplers[access.index]; + } + + addImageSamplerRow(m_Descriptors[i], m_SamplerDescriptors[i], reg, shaderTex, shaderSamp, + usedSlot, texCompleteness, &textures[(uint32_t)stage], + &samplers[(uint32_t)stage]); + } + } + else if(m_Locations[i].category == DescriptorCategory::ReadWriteResource) + { + // look for any shaders that use this binding + for(ShaderStage stage : values()) + { + if((uint32_t)stage >= ARRAY_COUNT(readwrites)) + continue; + + const ShaderReflection *refl = shaderRefls[(uint32_t)stage]; + + const ShaderResource *shaderBind = NULL; + + const DescriptorAccess &access = stageAccesses[(uint32_t)stage]; + usedSlot = !access.staticallyUnused && access.type != DescriptorType::Unknown; + + if(refl && access.type != DescriptorType::Unknown) + shaderBind = &refl->readWriteResources[access.index]; + + addReadWriteRow(m_Descriptors[i], reg, access.index, shaderBind, usedSlot, + texCompleteness, &readwrites[(uint32_t)stage]); + } + } + } + + RDTreeWidget *textureWidgets[] = { + ui->vsTextures, ui->tcsTextures, ui->tesTextures, + ui->gsTextures, ui->fsTextures, ui->csTextures, + }; + + RDTreeWidget *samplerWidgets[] = { + ui->vsSamplers, ui->tcsSamplers, ui->tesSamplers, + ui->gsSamplers, ui->fsSamplers, ui->csSamplers, + }; + + RDTreeWidget *readwriteWidgets[] = { + ui->vsReadWrite, ui->tcsReadWrite, ui->tesReadWrite, + ui->gsReadWrite, ui->fsReadWrite, ui->csReadWrite, + }; + + // sort all entries by register, so that e.g. we don't display 2D textures before 3D textures + // even if their locations all come together. + for(size_t i = 0; i < ARRAY_COUNT(textures); i++) + { + rdcarray items; + while(textures[i].childCount()) + items.push_back(textures[i].takeChild(textures[i].childCount() - 1)); + + std::sort(items.begin(), items.end(), [](RDTreeWidgetItem *a, RDTreeWidgetItem *b) { + GLReadOnlyTag a_tag = a->tag().value(); + GLReadOnlyTag b_tag = b->tag().value(); + + return a_tag.reg < b_tag.reg; + }); + + for(RDTreeWidgetItem *item : items) + textureWidgets[i]->addTopLevelItem(item); + } + + for(size_t i = 0; i < ARRAY_COUNT(samplers); i++) + { + rdcarray items; + while(samplers[i].childCount()) + items.push_back(samplers[i].takeChild(samplers[i].childCount() - 1)); + + std::sort(items.begin(), items.end(), [](RDTreeWidgetItem *a, RDTreeWidgetItem *b) { + GLReadOnlyTag a_tag = a->tag().value(); + GLReadOnlyTag b_tag = b->tag().value(); + + return a_tag.reg < b_tag.reg; + }); + + for(RDTreeWidgetItem *item : items) + samplerWidgets[i]->addTopLevelItem(item); + } + + for(size_t i = 0; i < ARRAY_COUNT(readwrites); i++) + { + rdcarray items; + while(readwrites[i].childCount()) + items.push_back(readwrites[i].takeChild(readwrites[i].childCount() - 1)); + + std::sort(items.begin(), items.end(), [](RDTreeWidgetItem *a, RDTreeWidgetItem *b) { + GLReadWriteTag a_tag = a->tag().value(); + GLReadWriteTag b_tag = b->tag().value(); + + // sort by read-write type first (atomics, then SSBOs, then images) + if(a_tag.readWriteType != b_tag.readWriteType) + return a_tag.readWriteType < b_tag.readWriteType; + + // then by register + return a_tag.reg < b_tag.reg; + }); + + for(RDTreeWidgetItem *item : items) + readwriteWidgets[i]->addTopLevelItem(item); + } + + // UBOs don't have to be sorted because there's only one type there, the locations are already + // in order + + setShaderState(state.vertexShader, ui->vsShader, ui->vsSubroutines); + setShaderState(state.geometryShader, ui->gsShader, ui->gsSubroutines); + setShaderState(state.tessControlShader, ui->tcsShader, ui->tcsSubroutines); + setShaderState(state.tessEvalShader, ui->tesShader, ui->tesSubroutines); + setShaderState(state.fragmentShader, ui->fsShader, ui->fsSubroutines); + setShaderState(state.computeShader, ui->csShader, ui->csSubroutines); + + ui->vsReadWrite->parentWidget()->setVisible(ui->vsReadWrite->topLevelItemCount() > 0 && + shaderRefls[0] && + shaderRefls[0]->readWriteResources.count() > 0); + ui->tcsReadWrite->parentWidget()->setVisible(ui->tcsReadWrite->topLevelItemCount() > 0 && + shaderRefls[1] && + shaderRefls[1]->readWriteResources.count() > 0); + ui->tesReadWrite->parentWidget()->setVisible(ui->tesReadWrite->topLevelItemCount() > 0 && + shaderRefls[2] && + shaderRefls[2]->readWriteResources.count() > 0); + ui->gsReadWrite->parentWidget()->setVisible(ui->gsReadWrite->topLevelItemCount() > 0 && + shaderRefls[3] && + shaderRefls[3]->readWriteResources.count() > 0); + ui->fsReadWrite->parentWidget()->setVisible(ui->fsReadWrite->topLevelItemCount() > 0 && + shaderRefls[4] && + shaderRefls[4]->readWriteResources.count() > 0); + ui->csReadWrite->parentWidget()->setVisible(ui->csReadWrite->topLevelItemCount() > 0 && + shaderRefls[5] && + shaderRefls[5]->readWriteResources.count() > 0); + } QToolButton *shaderButtons[] = { ui->vsShaderViewButton, ui->tcsShaderViewButton, ui->tesShaderViewButton, @@ -2367,9 +2592,11 @@ void GLPipelineStateViewer::resource_itemActivated(RDTreeWidgetItem *item, int c QVariant tag = item->tag(); - if(tag.canConvert()) + if(tag.canConvert()) { - TextureDescription *tex = m_Ctx.GetTexture(tag.value()); + GLReadOnlyTag ro = tag.value(); + + TextureDescription *tex = m_Ctx.GetTexture(ro.ID); if(tex) { @@ -2387,36 +2614,53 @@ void GLPipelineStateViewer::resource_itemActivated(RDTreeWidgetItem *item, int c ITextureViewer *viewer = m_Ctx.GetTextureViewer(); viewer->ViewTexture(tex->resourceId, CompType::Typeless, true); } - - return; } } else if(tag.canConvert()) { - GLReadWriteTag buf = tag.value(); + GLReadWriteTag rw = tag.value(); const ShaderResource *shaderRes = NULL; - for(const ShaderResource &res : stage->reflection->readWriteResources) - { - if(res.bindPoint == buf.bindPoint) - { - shaderRes = &res; - break; - } - } + if(rw.rwIndex < stage->reflection->readWriteResources.size()) + shaderRes = &stage->reflection->readWriteResources[rw.rwIndex]; if(!shaderRes) return; + if(shaderRes->isTexture) + { + TextureDescription *tex = m_Ctx.GetTexture(rw.ID); + + 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, CompType::Typeless, true); + } + } + + return; + } + QString format = BufferFormatter::GetBufferFormatString( BufferFormatter::EstimatePackingRules(stage->shaderResourceId, shaderRes->variableType.members), stage->shaderResourceId, *shaderRes, ResourceFormat()); - if(buf.ID != ResourceId()) + if(rw.ID != ResourceId()) { - IBufferViewer *viewer = m_Ctx.ViewBuffer(buf.offset, buf.size, buf.ID, format); + IBufferViewer *viewer = m_Ctx.ViewBuffer(rw.offset, rw.size, rw.ID, format); m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); } @@ -2778,7 +3022,6 @@ void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, const GLPipe::Shad { const GLPipe::State &pipe = *m_Ctx.CurGLPipelineState(); ShaderReflection *shaderDetails = sh.reflection; - const ShaderBindpointMapping &mapping = sh.bindpointMapping; { xml.writeStartElement(tr("h3")); @@ -2836,177 +3079,187 @@ void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, const GLPipe::Shad QList readwriteRows; QList subRows; - for(int i = 0; i < pipe.textures.count(); i++) - { - const GLPipe::Texture &r = pipe.textures[i]; - const GLPipe::Sampler &s = pipe.samplers[i]; - - const ShaderResource *shaderInput = NULL; - const Bindpoint *map = NULL; - - if(shaderDetails) - { - for(const ShaderResource &bind : shaderDetails->readOnlyResources) - { - if(bind.isReadOnly && mapping.readOnlyResources[bind.bindPoint].bind == i) - { - shaderInput = &bind; - map = &mapping.readOnlyResources[bind.bindPoint]; - } - } - } - - bool filledSlot = (r.resourceId != ResourceId()); - bool usedSlot = (shaderInput && map->used); - - if(shaderInput) - { - // do texture - { - QString slotname = QString::number(i); - - if(!shaderInput->name.isEmpty()) - slotname += QFormatStr(": %1").arg(shaderInput->name); - - uint32_t w = 1, h = 1, d = 1; - uint32_t a = 1; - QString format = tr("Unknown"); - QString name = m_Ctx.GetResourceName(r.resourceId); - QString typeName = tr("Unknown"); - - if(!filledSlot) - { - name = tr("Empty"); - format = lit("-"); - typeName = lit("-"); - w = h = d = a = 0; - } - - TextureDescription *tex = m_Ctx.GetTexture(r.resourceId); - if(tex) - { - w = tex->width; - h = tex->height; - d = tex->depth; - a = tex->arraysize; - format = tex->format.Name(); - typeName = ToQStr(tex->type); - - if(tex->format.type == ResourceFormatType::D16S8 || - tex->format.type == ResourceFormatType::D24S8 || - tex->format.type == ResourceFormatType::D32S8) - { - if(r.depthReadChannel == 0) - format += tr(" Depth-Repipead"); - else if(r.depthReadChannel == 1) - format += tr(" Stencil-Read"); - } - else if(r.swizzle.red != TextureSwizzle::Red || r.swizzle.green != TextureSwizzle::Green || - r.swizzle.blue != TextureSwizzle::Blue || r.swizzle.alpha != TextureSwizzle::Alpha) - { - format += QFormatStr(" swizzle[%1%2%3%4]") - .arg(ToQStr(r.swizzle.red)) - .arg(ToQStr(r.swizzle.green)) - .arg(ToQStr(r.swizzle.blue)) - .arg(ToQStr(r.swizzle.alpha)); - } - } - - textureRows.push_back({slotname, name, typeName, w, h, d, a, format, r.firstMip, r.numMips}); - } - - // do sampler - { - QString slotname = QString::number(i); - - if(!shaderInput->name.isEmpty()) - slotname += QFormatStr(": %1").arg(shaderInput->name); - - QString borderColor = QFormatStr("%1, %2, %3, %4") - .arg(s.borderColor[0]) - .arg(s.borderColor[1]) - .arg(s.borderColor[2]) - .arg(s.borderColor[3]); - - QString addressing; - - QString addPrefix; - QString addVal; - - QString addr[] = {ToQStr(s.addressS, GraphicsAPI::OpenGL), - ToQStr(s.addressT, GraphicsAPI::OpenGL), - ToQStr(s.addressR, GraphicsAPI::OpenGL)}; - - // arrange like either STR: WRAP or ST: WRAP, R: CLAMP - for(int a = 0; a < 3; a++) - { - const QString str[] = {lit("S"), lit("T"), lit("R")}; - QString prefix = str[a]; - - if(a == 0 || addr[a] == addr[a - 1]) - { - addPrefix += prefix; - } - else - { - addressing += QFormatStr("%1: %2, ").arg(addPrefix).arg(addVal); - - addPrefix = prefix; - } - addVal = addr[a]; - } - - addressing += addPrefix + lit(": ") + addVal; - - if(s.UseBorder()) - addressing += QFormatStr("<%1>").arg(borderColor); - - if(r.type == TextureType::TextureCube || r.type == TextureType::TextureCubeArray) - { - addressing += s.seamlessCubeMap ? tr(" Seamless") : tr(" Non-Seamless"); - } - - QString filter = ToQStr(s.filter); - - if(s.maxAnisotropy > 1) - filter += tr(" Aniso%1x").arg(s.maxAnisotropy); - - if(s.filter.filter == FilterFunction::Comparison) - filter += QFormatStr(" %1").arg(ToQStr(s.compareFunction)); - else if(s.filter.filter != FilterFunction::Normal) - filter += QFormatStr(" (%1)").arg(ToQStr(s.filter.filter)); - - samplerRows.push_back( - {slotname, addressing, filter, - QFormatStr("%1 - %2") - .arg(s.minLOD == -FLT_MAX ? lit("0") : QString::number(s.minLOD)) - .arg(s.maxLOD == FLT_MAX ? lit("FLT_MAX") : QString::number(s.maxLOD)), - s.mipLODBias}); - } - } - } - if(shaderDetails) { - uint32_t i = 0; - for(const ConstantBlock &shaderCBuf : shaderDetails->constantBlocks) + for(const DescriptorAccess &access : m_Ctx.CurPipelineState().GetDescriptorAccess()) { - int bindPoint = mapping.constantBlocks[i].bind; + // filter only to accesses from this stage + if(access.stage != sh.stage) + continue; - const GLPipe::Buffer *b = NULL; + const ShaderResource *shaderTex = NULL; + const ShaderSampler *shaderSamp = NULL; + const ConstantBlock *shaderUBO = NULL; + const ShaderResource *shaderRW = NULL; - if(bindPoint >= 0 && bindPoint < pipe.uniformBuffers.count()) - b = &pipe.uniformBuffers[bindPoint]; + if(CategoryForDescriptorType(access.type) == DescriptorCategory::ReadOnlyResource) + { + if(access.index < shaderDetails->readOnlyResources.size()) + shaderTex = &shaderDetails->readOnlyResources[access.index]; - bool filledSlot = !shaderCBuf.bufferBacked || (b && b->resourceId != ResourceId()); - bool usedSlot = mapping.constantBlocks[i].used; + if(access.index < shaderDetails->samplers.size()) + shaderSamp = &shaderDetails->samplers[access.index]; + } + else if(CategoryForDescriptorType(access.type) == DescriptorCategory::ConstantBlock) + { + if(access.index < shaderDetails->constantBlocks.size()) + shaderUBO = &shaderDetails->constantBlocks[access.index]; + } + else if(CategoryForDescriptorType(access.type) == DescriptorCategory::ReadWriteResource) + { + if(access.index < shaderDetails->readWriteResources.size()) + shaderRW = &shaderDetails->readWriteResources[access.index]; + } - // show if + // locations and descriptors are flat indexed since we grabbed them all + const DescriptorLogicalLocation &loc = m_Locations[access.byteOffset / pipe.descriptorByteSize]; + const Descriptor &descriptor = m_Descriptors[access.byteOffset / pipe.descriptorByteSize]; + const SamplerDescriptor &samplerDescriptor = + m_SamplerDescriptors[access.byteOffset / pipe.descriptorByteSize]; + + bool filledSlot = (descriptor.resource != ResourceId()); + + if(shaderTex) + { + // do texture + { + QString slotname = QString::number(loc.fixedBindNumber); + + if(!shaderTex->name.isEmpty()) + slotname += QFormatStr(": %1").arg(shaderTex->name); + + uint32_t w = 1, h = 1, d = 1; + uint32_t a = 1; + QString format = tr("Unknown"); + QString name = m_Ctx.GetResourceName(descriptor.resource); + QString typeName = tr("Unknown"); + + if(!filledSlot) + { + name = tr("Empty"); + format = lit("-"); + typeName = lit("-"); + w = h = d = a = 0; + } + + TextureDescription *tex = m_Ctx.GetTexture(descriptor.resource); + if(tex) + { + w = tex->width; + h = tex->height; + d = tex->depth; + a = tex->arraysize; + format = tex->format.Name(); + typeName = ToQStr(tex->type); + + if(tex->format.type == ResourceFormatType::D16S8 || + tex->format.type == ResourceFormatType::D24S8 || + tex->format.type == ResourceFormatType::D32S8) + { + if(descriptor.format.compType == CompType::Depth) + format += tr(" Depth-Read"); + else if(descriptor.format.compType == CompType::UInt) + format += tr(" Stencil-Read"); + } + else if(descriptor.swizzle.red != TextureSwizzle::Red || + descriptor.swizzle.green != TextureSwizzle::Green || + descriptor.swizzle.blue != TextureSwizzle::Blue || + descriptor.swizzle.alpha != TextureSwizzle::Alpha) + { + format += QFormatStr(" swizzle[%1%2%3%4]") + .arg(ToQStr(descriptor.swizzle.red)) + .arg(ToQStr(descriptor.swizzle.green)) + .arg(ToQStr(descriptor.swizzle.blue)) + .arg(ToQStr(descriptor.swizzle.alpha)); + } + } + + textureRows.push_back({slotname, name, typeName, w, h, d, a, format, descriptor.firstMip, + descriptor.numMips}); + } + + // do sampler + { + QString slotname = QString::number(loc.fixedBindNumber); + + if(shaderSamp && !shaderSamp->name.isEmpty()) + slotname += QFormatStr(": %1").arg(shaderSamp->name); + else if(!shaderTex->name.isEmpty()) + slotname += QFormatStr(": %1").arg(shaderTex->name); + + QString borderColor = QFormatStr("%1, %2, %3, %4") + .arg(samplerDescriptor.borderColorValue.floatValue[0]) + .arg(samplerDescriptor.borderColorValue.floatValue[1]) + .arg(samplerDescriptor.borderColorValue.floatValue[2]) + .arg(samplerDescriptor.borderColorValue.floatValue[3]); + + QString addressing; + + QString addPrefix; + QString addVal; + + QString addr[] = {ToQStr(samplerDescriptor.addressU, GraphicsAPI::OpenGL), + ToQStr(samplerDescriptor.addressV, GraphicsAPI::OpenGL), + ToQStr(samplerDescriptor.addressW, GraphicsAPI::OpenGL)}; + + // arrange like either STR: WRAP or ST: WRAP, R: CLAMP + for(int a = 0; a < 3; a++) + { + const QString str[] = {lit("S"), lit("T"), lit("R")}; + QString prefix = str[a]; + + if(a == 0 || addr[a] == addr[a - 1]) + { + addPrefix += prefix; + } + else + { + addressing += QFormatStr("%1: %2, ").arg(addPrefix).arg(addVal); + + addPrefix = prefix; + } + addVal = addr[a]; + } + + addressing += addPrefix + lit(": ") + addVal; + + if(samplerDescriptor.UseBorder()) + addressing += QFormatStr("<%1>").arg(borderColor); + + if(descriptor.textureType == TextureType::TextureCube || + descriptor.textureType == TextureType::TextureCubeArray) + { + addressing += samplerDescriptor.seamlessCubemaps ? tr(" Seamless") : tr(" Non-Seamless"); + } + + QString filter = ToQStr(samplerDescriptor.filter); + + if(samplerDescriptor.maxAnisotropy > 1) + filter += tr(" Aniso%1x").arg(samplerDescriptor.maxAnisotropy); + + if(samplerDescriptor.filter.filter == FilterFunction::Comparison) + filter += QFormatStr(" %1").arg(ToQStr(samplerDescriptor.compareFunction)); + else if(samplerDescriptor.filter.filter != FilterFunction::Normal) + filter += QFormatStr(" (%1)").arg(ToQStr(samplerDescriptor.filter.filter)); + + samplerRows.push_back({slotname, addressing, filter, + QFormatStr("%1 - %2") + .arg(samplerDescriptor.minLOD == -FLT_MAX + ? lit("0") + : QString::number(samplerDescriptor.minLOD)) + .arg(samplerDescriptor.maxLOD == FLT_MAX + ? lit("FLT_MAX") + : QString::number(samplerDescriptor.maxLOD)), + samplerDescriptor.mipBias}); + } + } + + if(shaderUBO) { uint64_t offset = 0; uint64_t length = 0; - int numvars = shaderCBuf.variables.count(); - uint64_t byteSize = shaderCBuf.byteSize; + int numvars = shaderUBO->variables.count(); + uint64_t byteSize = shaderUBO->byteSize; QString slotname = tr("Uniforms"); QString name = tr("Empty"); @@ -3016,15 +3269,14 @@ void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, const GLPipe::Shad if(!filledSlot) length = 0; - if(b) { - slotname = QFormatStr("%1: %2").arg(bindPoint).arg(shaderCBuf.name); - offset = b->byteOffset; - length = b->byteSize; + slotname = QFormatStr("%1: %2").arg(loc.fixedBindNumber).arg(shaderUBO->name); + offset = descriptor.byteOffset; + length = descriptor.byteSize; - name = m_Ctx.GetResourceName(b->resourceId); + name = m_Ctx.GetResourceName(descriptor.resource); - BufferDescription *buf = m_Ctx.GetBuffer(b->resourceId); + BufferDescription *buf = m_Ctx.GetBuffer(descriptor.resource); if(buf && length == 0) length = buf->length; @@ -3039,79 +3291,28 @@ void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, const GLPipe::Shad cbufferRows.push_back({slotname, name, byterange, sizestr}); } - i++; - } - } - { - uint32_t i = 0; - for(uint32_t subval : sh.subroutines) - { - subRows.push_back({i, subval}); - - i++; - } - } - - if(shaderDetails) - { - uint32_t i = 0; - for(const ShaderResource &res : shaderDetails->readWriteResources) - { - int bindPoint = mapping.readWriteResources[i].bind; - - GLReadWriteType readWriteType = GetGLReadWriteType(res); - - const GLPipe::Buffer *bf = NULL; - const GLPipe::ImageLoadStore *im = NULL; - ResourceId id; - - if(readWriteType == GLReadWriteType::Image && bindPoint >= 0 && bindPoint < pipe.images.count()) + if(shaderRW) { - im = &pipe.images[bindPoint]; - id = pipe.images[bindPoint].resourceId; - } + GLReadWriteType readWriteType = GetGLReadWriteType(*shaderRW); - if(readWriteType == GLReadWriteType::Atomic && bindPoint >= 0 && - bindPoint < pipe.atomicBuffers.count()) - { - bf = &pipe.atomicBuffers[bindPoint]; - id = pipe.atomicBuffers[bindPoint].resourceId; - } - - if(readWriteType == GLReadWriteType::SSBO && bindPoint >= 0 && - bindPoint < pipe.shaderStorageBuffers.count()) - { - bf = &pipe.shaderStorageBuffers[bindPoint]; - id = pipe.shaderStorageBuffers[bindPoint].resourceId; - } - - bool filledSlot = id != ResourceId(); - bool usedSlot = mapping.readWriteResources[i].used; - - // show if - { QString binding = readWriteType == GLReadWriteType::Image ? tr("Image") : readWriteType == GLReadWriteType::Atomic ? tr("Atomic") : readWriteType == GLReadWriteType::SSBO ? tr("SSBO") : tr("Unknown"); - QString slotname = QFormatStr("%1: %2").arg(bindPoint).arg(res.name); - QString name = m_Ctx.GetResourceName(id); + QString slotname = QFormatStr("%1: %2").arg(loc.fixedBindNumber).arg(shaderRW->name); + QString name = m_Ctx.GetResourceName(descriptor.resource); QString dimensions; - QString format = lit("-"); - QString access = tr("Read/Write"); - if(im) - { - if(im->readAllowed && !im->writeAllowed) - access = tr("Read-Only"); - if(!im->readAllowed && im->writeAllowed) - access = tr("Write-Only"); - format = im->imageFormat.Name(); - } + QString format = descriptor.format.Name(); + QString rwAccessType = tr("Read/Write"); + if(descriptor.flags & DescriptorFlags::ReadOnlyAccess) + rwAccessType = tr("Read-Only"); + if(descriptor.flags & DescriptorFlags::WriteOnlyAccess) + rwAccessType = tr("Write-Only"); // check to see if it's a texture - TextureDescription *tex = m_Ctx.GetTexture(id); + TextureDescription *tex = m_Ctx.GetTexture(descriptor.resource); if(tex) { if(tex->dimension == 1) @@ -3136,32 +3337,43 @@ void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, const GLPipe::Shad } // if not a texture, it must be a buffer - BufferDescription *buf = m_Ctx.GetBuffer(id); + BufferDescription *buf = m_Ctx.GetBuffer(descriptor.resource); if(buf) { uint64_t offset = 0; uint64_t length = buf->length; - if(bf && bf->byteSize > 0) + if(descriptor.byteSize > 0) { - offset = bf->byteOffset; - length = bf->byteSize; + offset = descriptor.byteOffset; + length = descriptor.byteSize; } if(offset > 0) dimensions = tr("%1 bytes at offset %2 bytes").arg(length).arg(offset); else dimensions = tr("%1 bytes").arg(length); + + format = lit("-"); } if(!filledSlot) { name = tr("Empty"); - dimensions = tr("-"); - access = tr("-"); + dimensions = lit("-"); + rwAccessType = lit("-"); } - readwriteRows.push_back({binding, slotname, name, dimensions, format, access}); + readwriteRows.push_back({binding, slotname, name, dimensions, format, rwAccessType}); } + } + } + + { + uint32_t i = 0; + for(uint32_t subval : sh.subroutines) + { + subRows.push_back({i, subval}); + i++; } } diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h index 3c6fe30cc..b667e0656 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h @@ -39,6 +39,13 @@ class RDTreeWidget; class RDTreeWidgetItem; class PipelineStateViewer; +enum class GLReadWriteType +{ + Atomic, + SSBO, + Image, +}; + class GLPipelineStateViewer : public QFrame, public ICaptureViewer { Q_OBJECT @@ -81,24 +88,30 @@ private: ICaptureContext &m_Ctx; PipelineStateViewer &m_Common; - enum class GLReadWriteType - { - Atomic, - SSBO, - Image, - }; - QString MakeGenericValueString(uint32_t compCount, CompType compType, const GLPipe::VertexAttribute &val); GLReadWriteType GetGLReadWriteType(ShaderResource res); - void setShaderState(const GLPipe::Shader &stage, RDLabel *shader, RDTreeWidget *tex, - RDTreeWidget *samp, RDTreeWidget *ubo, RDTreeWidget *sub, RDTreeWidget *rw); + void setShaderState(const GLPipe::Shader &stage, RDLabel *shader, RDTreeWidget *sub); + + void addUBORow(const Descriptor &descriptor, uint32_t reg, uint32_t index, + const ConstantBlock *shaderBind, bool usedSlot, RDTreeWidget *ubos); + void addImageSamplerRow(const Descriptor &descriptor, const SamplerDescriptor &samplerDescriptor, + uint32_t reg, const ShaderResource *shaderTex, + const ShaderSampler *shaderSamp, bool usedSlot, + const GLPipe::TextureCompleteness *texCompleteness, + RDTreeWidgetItem *textures, RDTreeWidgetItem *samplers); + void addReadWriteRow(const Descriptor &descriptor, uint32_t reg, uint32_t index, + const ShaderResource *shaderBind, bool usedSlot, + const GLPipe::TextureCompleteness *texCompleteness, + RDTreeWidgetItem *readwrites); + void clearShaderState(RDLabel *shader, RDTreeWidget *tex, RDTreeWidget *samp, RDTreeWidget *ubo, RDTreeWidget *sub, RDTreeWidget *rw); void setState(); void clearState(); + bool isInactiveRow(RDTreeWidgetItem *node); void setInactiveRow(RDTreeWidgetItem *node); void setEmptyRow(RDTreeWidgetItem *node); void highlightIABind(int slot); @@ -110,7 +123,7 @@ private: void setViewDetails(RDTreeWidgetItem *node, TextureDescription *tex, uint32_t firstMip, uint32_t numMips, uint32_t firstSlice, uint32_t numSlices, - const GLPipe::Texture *texBinding = NULL); + const GLPipe::TextureCompleteness *texCompleteness = NULL); void exportHTML(QXmlStreamWriter &xml, const GLPipe::VertexInput &vtx); void exportHTML(QXmlStreamWriter &xml, const GLPipe::Shader &sh); @@ -118,6 +131,10 @@ private: void exportHTML(QXmlStreamWriter &xml, const GLPipe::Rasterizer &rs); void exportHTML(QXmlStreamWriter &xml, const GLPipe::FrameBuffer &fb); + rdcarray m_Locations; + rdcarray m_Descriptors; + rdcarray m_SamplerDescriptors; + // keep track of the VB nodes (we want to be able to highlight them easily on hover) QList m_VBNodes; // list of empty VB nodes that shouldn't be highlighted on hover diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index b5cab6fea..031927a2d 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -1546,3 +1547,17 @@ void PipelineStateViewer::SelectPipelineStage(PipelineStage stage) else if(m_Vulkan) m_Vulkan->SelectPipelineStage(stage); } + +ScopedTreeUpdater::ScopedTreeUpdater(RDTreeWidget *widget) : m_Widget(widget) +{ + vs = m_Widget->verticalScrollBar()->value(); + m_Widget->beginUpdate(); + m_Widget->clear(); +} + +ScopedTreeUpdater::~ScopedTreeUpdater() +{ + m_Widget->clearSelection(); + m_Widget->endUpdate(); + m_Widget->verticalScrollBar()->setValue(vs); +} diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h index 9cdd78516..ec8e20c42 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h @@ -50,6 +50,15 @@ class VulkanPipelineStateViewer; class PipelineStateViewer; +struct ScopedTreeUpdater +{ + ScopedTreeUpdater(RDTreeWidget *widget); + ~ScopedTreeUpdater(); + + RDTreeWidget *m_Widget; + int vs; +}; + class RDPreviewTooltip : public QFrame, public ITreeViewTipDisplay { private: