diff --git a/qrenderdoc/Code/BufferFormatter.cpp b/qrenderdoc/Code/BufferFormatter.cpp index e602ae0b6..78eaed928 100644 --- a/qrenderdoc/Code/BufferFormatter.cpp +++ b/qrenderdoc/Code/BufferFormatter.cpp @@ -34,6 +34,8 @@ struct StructFormatData uint32_t offset = 0; uint32_t alignment = 0; uint32_t paddedStride = 0; + bool singleDef = false; + bool singleMember = false; }; GraphicsAPI BufferFormatter::m_API; @@ -390,8 +392,7 @@ bool BufferFormatter::CheckInvalidUnbounded(const ShaderConstant &structDef, QSt { errors = tr("%1 in %2 can't be unbounded when not the last member.\n") .arg(structDef.type.members[i].name) - .arg(structDef.type.descriptor.name.empty() ? "implicit_root" - : structDef.type.descriptor.name); + .arg(structDef.type.descriptor.name); return false; } @@ -400,8 +401,7 @@ bool BufferFormatter::CheckInvalidUnbounded(const ShaderConstant &structDef, QSt { errors = tr("%1 in %2 can't have unbounded array in a child.\n") .arg(structDef.type.members[i].name) - .arg(structDef.type.descriptor.name.empty() ? "implicit_root" - : structDef.type.descriptor.name); + .arg(structDef.type.descriptor.name); return false; } @@ -412,9 +412,8 @@ bool BufferFormatter::CheckInvalidUnbounded(const ShaderConstant &structDef, QSt return true; } -rdcpair BufferFormatter::ParseFormatString(const QString &formatString, - uint64_t maxLen, - QString &errors) +rdcpair BufferFormatter::ParseFormatString( + const QString &formatString, uint64_t maxLen, bool cbuffer, QString &errors) { StructFormatData root; StructFormatData *cur = &root; @@ -707,6 +706,10 @@ rdcpair BufferFormatter::ParseFormatString(const { cur->paddedStride = annot.param.toUInt(); } + else if(annot.name == lit("single") || annot.name == lit("fixed")) + { + cur->singleDef = true; + } else { errors = tr("Unrecognised annotation on struct definition: %1\n").arg(annot.name); @@ -716,6 +719,9 @@ rdcpair BufferFormatter::ParseFormatString(const } annotations.clear(); + + if(!success) + break; } else { @@ -737,6 +743,9 @@ rdcpair BufferFormatter::ParseFormatString(const annotations.clear(); + if(!success) + break; + QString baseType = match.captured(4); if(baseType.isEmpty()) @@ -806,6 +815,9 @@ rdcpair BufferFormatter::ParseFormatString(const annotations.clear(); + if(!success) + break; + cur->structDef.type.members.push_back(el); continue; @@ -835,9 +847,21 @@ rdcpair BufferFormatter::ParseFormatString(const annotations.clear(); + if(!success) + break; + continue; } + if(cur->singleMember) + { + errors = + tr("[[single]] can only be used if there is only one variable in the root.\n" + "Consider wrapping the variables in a struct and marking it as [[single]].\n"); + success = false; + break; + } + QRegularExpressionMatch structMatch = structUseRegex.match(line); bool isPadding = false; @@ -848,6 +872,13 @@ rdcpair BufferFormatter::ParseFormatString(const bool isPointer = !structMatch.captured(2).trimmed().isEmpty(); + if(structContext.singleDef) + { + errors = tr("[[single]] annotated structs cannot be used, only defined.\n"); + success = false; + break; + } + QString varName = structMatch.captured(3).trimmed(); if(varName.isEmpty()) @@ -864,6 +895,27 @@ rdcpair BufferFormatter::ParseFormatString(const { isPadding = true; } + else if(annot.name == lit("single") || annot.name == lit("fixed")) + { + if(cur != &root) + { + errors = tr("[[single]] can only be used on variables in the root\n"); + success = false; + break; + } + else if(!cur->structDef.type.members.empty()) + { + errors = + tr("[[single]] can only be used if there is only one variable in the root.\n" + "Consider wrapping the variables in a struct and marking it as [[single]].\n"); + success = false; + break; + } + else + { + cur->singleMember = true; + } + } else { errors = tr("Unrecognised annotation on variable: %1\n").arg(annot.name); @@ -872,6 +924,9 @@ rdcpair BufferFormatter::ParseFormatString(const } } + if(!success) + break; + annotations.clear(); QString arrayDim = structMatch.captured(4).trimmed(); @@ -887,6 +942,13 @@ rdcpair BufferFormatter::ParseFormatString(const arrayCount = 1; } + if(cur->singleMember && arrayCount == ~0U) + { + errors = tr("[[single]] can't be used on unbounded arrays.\n"); + success = false; + break; + } + QString bitfield = structMatch.captured(6).trimmed(); if(isPointer) @@ -1340,6 +1402,27 @@ rdcpair BufferFormatter::ParseFormatString(const { isPadding = true; } + else if(annot.name == lit("single") || annot.name == lit("fixed")) + { + if(cur != &root) + { + errors = tr("[[single]] can only be used on variables in the root\n"); + success = false; + break; + } + else if(!cur->structDef.type.members.empty()) + { + errors = + tr("[[single]] can only be used if there is only one variable in the root.\n" + "Consider wrapping the variables in a struct and marking it as [[single]].\n"); + success = false; + break; + } + else + { + cur->singleMember = true; + } + } else { errors = tr("Unrecognised annotation on variable: %1\n").arg(annot.name); @@ -1564,36 +1647,6 @@ rdcpair BufferFormatter::ParseFormatString(const if(success && root.structDef.type.members.isEmpty() && !lastStruct.isEmpty()) root = structelems[lastStruct]; - // on D3D it's not possible to have anything but AoS, no preamble, so always consider the root - // member to be the struct definition of an array, as long as we're declaring a UAV - if(IsD3D(m_API) && pack != Packing::D3DCB) - { - // if there's already only one root member just make it infinite - if(root.structDef.type.members.size() == 1) - { - root.structDef.type.members[0].type.descriptor.elements = ~0U; - } - else if(root.structDef.type.members.empty()) - { - // do nothing, we'll hit the invalid failure case below - } - else - { - // otherwise wrap a struct around the members, to be the infinite AoS - rdcarray inners; - inners.swap(root.structDef.type.members); - - ShaderConstant el; - el.byteOffset = 0; - el.type.descriptor.type = VarType::Struct; - el.type.descriptor.elements = ~0U; - el.type.descriptor.arrayByteStride = root.structDef.type.descriptor.arrayByteStride; - - root.structDef.type.members.push_back(el); - inners.swap(root.structDef.type.members[0].type.members); - } - } - root.structDef.type.descriptor.arrayByteStride = AlignUp(root.offset, GetAlignment(pack, root.structDef)); @@ -1610,11 +1663,7 @@ rdcpair BufferFormatter::ParseFormatString(const // check that unbounded arrays are only the last member of each struct. Doing this separately // makes the below check easier since we only have to consider last members if(!CheckInvalidUnbounded(root.structDef, errors)) - { - if(IsD3D(m_API) && pack != Packing::D3DCB) - errors += tr("D3D structured buffers always define an unbounded array.\n"); success = false; - } } // we only allow one 'infinite' array. You can't have an infinite member inside an already @@ -1660,8 +1709,6 @@ rdcpair BufferFormatter::ParseFormatString(const errors = tr("Can't have unbounded %1[] as child of an unbounded %2[].\n") .arg(iter->name) .arg(infiniteArrayName); - if(IsD3D(m_API) && pack != Packing::D3DCB) - errors += tr("D3D structured buffers always define an unbounded array.\n"); break; } @@ -1680,6 +1727,54 @@ rdcpair BufferFormatter::ParseFormatString(const } } + // on D3D if we have an unbounded array it *must* be the root element, as D3D does not support + // some fixed elements before it, structured buffers are strictly just an AoS. + // we do allow specifying cbuffers which are all fixed and not unbounded, so we just check to see + // that if there is an unbounded array that it's the root + if( + // on D3D + IsD3D(m_API) && + // if the parsing worked + success && !root.structDef.type.members.empty() && + // if we have an unbounded array somewhere (we know there's only one, from above) + ContainsUnbounded(root.structDef.type.members) && + // it must be in the root and it must be alone with no siblings + !(root.structDef.type.members.size() == 1 && + root.structDef.type.members[0].type.descriptor.elements == ~0U)) + { + errors += tr("On D3D an unbounded array must be only be used alone as the root element.\n"); + success = false; + } + + // when not viewing a cbuffer, if the root hasn't been explicitly marked as a single struct and we + // don't have an unbounded array then consider it an AoS definition in all other cases as that is + // very likely what the user expects + if(success && !root.structDef.type.members.empty() && + !ContainsUnbounded(root.structDef.type.members) && !root.singleMember && !root.singleDef && + !cbuffer) + { + // if there's already only one root member just make it infinite + if(root.structDef.type.members.size() == 1) + { + root.structDef.type.members[0].type.descriptor.elements = ~0U; + } + else + { + // otherwise wrap a struct around the members, to be the infinite AoS + rdcarray inners; + inners.swap(root.structDef.type.members); + + ShaderConstant el; + el.byteOffset = 0; + el.type.descriptor.type = VarType::Struct; + el.type.descriptor.elements = ~0U; + el.type.descriptor.arrayByteStride = root.structDef.type.descriptor.arrayByteStride; + + root.structDef.type.members.push_back(el); + inners.swap(root.structDef.type.members[0].type.members); + } + } + if(!success || root.structDef.type.members.isEmpty()) { root.structDef.type.members.clear(); @@ -1881,7 +1976,8 @@ QString BufferFormatter::GetBufferFormatString(Packing::Rules pack, const Shader QList declaredStructs; format = DeclareStruct(pack, declaredStructs, structName, res.variableType.members, 0, QString()); - format = QFormatStr("%1\n\n%2").arg(DeclarePacking(pack)).arg(format); + format = + QFormatStr("%1\n\n%2\n\n%3 buffer[];").arg(DeclarePacking(pack)).arg(format).arg(structName); } else { diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 85d48c635..f61ad55d4 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -41,7 +41,6 @@ #include "Windows/APIInspector.h" #include "Windows/BufferViewer.h" #include "Windows/CommentView.h" -#include "Windows/ConstantBufferPreviewer.h" #include "Windows/DebugMessageView.h" #include "Windows/Dialogs/CaptureDialog.h" #include "Windows/Dialogs/CrashDialog.h" @@ -2597,14 +2596,17 @@ IBufferViewer *CaptureContext::ViewTextureAsBuffer(ResourceId id, const Subresou return viewer; } -IConstantBufferPreviewer *CaptureContext::ViewConstantBuffer(ShaderStage stage, uint32_t slot, - uint32_t idx) +IBufferViewer *CaptureContext::ViewConstantBuffer(ShaderStage stage, uint32_t slot, uint32_t idx) { - ConstantBufferPreviewer *existing = ConstantBufferPreviewer::has(stage, slot, idx); + BufferViewer *existing = BufferViewer::HasCBufferView(stage, slot, idx); if(existing != NULL) return existing; - return new ConstantBufferPreviewer(*this, stage, slot, idx, m_MainWindow); + BufferViewer *viewer = new BufferViewer(*this, false, m_MainWindow); + + viewer->ViewCBuffer(stage, slot, idx); + + return viewer; } IPixelHistoryView *CaptureContext::ViewPixelHistory(ResourceId texID, uint32_t x, uint32_t y, @@ -2756,11 +2758,13 @@ void CaptureContext::AddDockWindow(QWidget *newWindow, DockReference ref, QWidge if(ref == DockReference::TransientPopupArea) { - if(qobject_cast(newWindow)) + BufferViewer *buf = qobject_cast(newWindow); + + if(buf && buf->IsCBufferView()) { ToolWindowManager *manager = ToolWindowManager::managerOf(refWindow); - ConstantBufferPreviewer *cb = manager->findChild(); + BufferViewer *cb = BufferViewer::GetFirstCBufferView(buf); if(cb) { manager->addToolWindow(newWindow, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 44f03f5c5..551ef7f7e 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -267,8 +267,7 @@ public: IBufferViewer *ViewTextureAsBuffer(ResourceId id, const Subresource &sub, const rdcstr &format = "") override; - IConstantBufferPreviewer *ViewConstantBuffer(ShaderStage stage, uint32_t slot, - uint32_t idx) override; + IBufferViewer *ViewConstantBuffer(ShaderStage stage, uint32_t slot, uint32_t idx) override; IPixelHistoryView *ViewPixelHistory(ResourceId texID, uint32_t x, uint32_t y, const TextureDisplay &display) override; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 29ec903a8..855030bec 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -1251,25 +1251,6 @@ protected: DECLARE_REFLECTION_STRUCT(IShaderMessageViewer); -DOCUMENT("A constant buffer preview window."); -struct IConstantBufferPreviewer -{ - DOCUMENT(R"(Retrieves the PySide2 QWidget for this :class:`ConstantBufferPreviewer` if PySide2 is available, or otherwise -returns a unique opaque pointer that can be passed back to any RenderDoc functions expecting a -QWidget. - -:return: Return the widget handle, either a PySide2 handle or an opaque handle. -:rtype: QWidget -)"); - virtual QWidget *Widget() = 0; - -protected: - IConstantBufferPreviewer() = default; - ~IConstantBufferPreviewer() = default; -}; - -DECLARE_REFLECTION_STRUCT(IConstantBufferPreviewer); - DOCUMENT("A pixel history window."); struct IPixelHistoryView { @@ -2593,18 +2574,17 @@ bytes. virtual IBufferViewer *ViewTextureAsBuffer(ResourceId id, const Subresource &sub, const rdcstr &format = "") = 0; - DOCUMENT(R"(Show a new :class:`ConstantBufferPreviewer` window, showing a read-only view of a the + DOCUMENT(R"(Show a new :class:`BufferViewer` window, showing a read-only view of a the variables in a constant buffer with their values. :param renderdoc.ShaderStage stage: The stage that the constant buffer is bound to. :param int slot: The index in the shader's constant buffer list to look up. :param int idx: For APIs that support arrayed resource binds, the index in the constant buffer array. -:return: The new :class:`ConstantBufferPreviewer` window opened, but not shown. -:rtype: ConstantBufferPreviewer +:return: The new :class:`BufferViewer` window opened, but not shown. +:rtype: BufferViewer )"); - virtual IConstantBufferPreviewer *ViewConstantBuffer(ShaderStage stage, uint32_t slot, - uint32_t idx) = 0; + virtual IBufferViewer *ViewConstantBuffer(ShaderStage stage, uint32_t slot, uint32_t idx) = 0; DOCUMENT(R"(Show a new :class:`PixelHistoryView` window, showing the results from a pixel history operation. diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index 46dd0f8ee..3e99b04aa 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -201,7 +201,8 @@ public: static void Init(GraphicsAPI api) { m_API = api; } static rdcpair ParseFormatString(const QString &formatString, - uint64_t maxLen, QString &errors); + uint64_t maxLen, bool cbuffer, + QString &errors); static uint32_t GetVarSize(const ShaderConstant &var); static Packing::Rules EstimatePackingRules(const rdcarray &members); diff --git a/qrenderdoc/Widgets/BufferFormatSpecifier.cpp b/qrenderdoc/Widgets/BufferFormatSpecifier.cpp index 04aecf83f..42c773ebc 100644 --- a/qrenderdoc/Widgets/BufferFormatSpecifier.cpp +++ b/qrenderdoc/Widgets/BufferFormatSpecifier.cpp @@ -52,6 +52,11 @@ void BufferFormatSpecifier::setContext(ICaptureContext *ctx) showHelp(m_Ctx->Config().BufferFormatter_ShowHelp); } +void BufferFormatSpecifier::setTitle(QString title) +{ + ui->formatGroup->setTitle(title); +} + void BufferFormatSpecifier::toggleHelp() { ui->helpText->setVisible(!ui->helpText->isVisible()); diff --git a/qrenderdoc/Widgets/BufferFormatSpecifier.h b/qrenderdoc/Widgets/BufferFormatSpecifier.h index 98cdfd139..1f529de1a 100644 --- a/qrenderdoc/Widgets/BufferFormatSpecifier.h +++ b/qrenderdoc/Widgets/BufferFormatSpecifier.h @@ -42,6 +42,7 @@ public: ~BufferFormatSpecifier(); void setContext(ICaptureContext *ctx); + void setTitle(QString title); signals: void processFormat(const QString &format); diff --git a/qrenderdoc/Widgets/Extended/RDHeaderView.cpp b/qrenderdoc/Widgets/Extended/RDHeaderView.cpp index 2f2b3bdac..e427b896d 100644 --- a/qrenderdoc/Widgets/Extended/RDHeaderView.cpp +++ b/qrenderdoc/Widgets/Extended/RDHeaderView.cpp @@ -122,6 +122,9 @@ void RDHeaderView::cacheSections() QAbstractItemModel *m = this->model(); + if(!m) + return; + int oldCount = m_sections.count(); m_sections.resize(m->columnCount()); diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index c3dfb7c2c..5029c15f3 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -38,6 +38,7 @@ #include "Code/QRDUtils.h" #include "Code/Resources.h" #include "Widgets/CollapseGroupBox.h" +#include "Widgets/Extended/RDLabel.h" #include "Widgets/Extended/RDSplitter.h" #include "Windows/Dialogs/AxisMappingDialog.h" #include "ui_BufferViewer.h" @@ -470,6 +471,7 @@ struct BufferConfiguration uint32_t pagingOffset = 0; ShaderConstant fixedVars; + rdcarray evalVars; uint32_t repeatStride = 1; uint32_t repeatOffset = 0; @@ -507,6 +509,7 @@ struct BufferConfiguration pagingOffset = o.pagingOffset; fixedVars = o.fixedVars; + evalVars = o.evalVars; repeatStride = o.repeatStride; repeatOffset = o.repeatOffset; @@ -1431,6 +1434,8 @@ struct PopulateBufferData int vsoutVert; int gsoutVert; + CBufferData cb; + QString highlightNames[6]; BufferConfiguration vsinConfig, vsoutConfig, gsoutConfig; @@ -2081,6 +2086,8 @@ static void UnrollConstant(const ShaderConstant &constant, rdcarray BufferViewer::m_CBufferViews; + BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) : QFrame(parent), ui(new Ui::BufferViewer), m_Ctx(ctx) { @@ -2215,6 +2222,8 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) // wireframe only available on solid shaded options ui->wireframeRender->setEnabled(false); + ui->setFormat->setVisible(false); + ui->fovGuess->setValue(90.0); on_controlType_currentIndexChanged(0); @@ -2341,6 +2350,16 @@ void BufferViewer::SetupRawView() controlLayout->setSpacing(2); controlLayout->setContentsMargins(6, 2, 6, 2); + m_RepeatedOffset = new RDLabel(this); + + QFrame *line = new QFrame(this); + line->setFrameShape(QFrame::VLine); + line->setFrameShadow(QFrame::Sunken); + + controlLayout->addWidget(line); + + controlLayout->addWidget(m_RepeatedOffset); + controlLayout->addItem(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); QVBoxLayout *fixedLayout = new QVBoxLayout(m_FixedGroup); @@ -2349,7 +2368,7 @@ void BufferViewer::SetupRawView() QVBoxLayout *repeatedLayout = new QVBoxLayout(m_RepeatedGroup); repeatedLayout->setSpacing(3); - repeatedLayout->setContentsMargins(0, 0, 0, 0); + repeatedLayout->setContentsMargins(2, 0, 0, 0); repeatedLayout->addWidget(m_RepeatedControlBar); @@ -2368,6 +2387,8 @@ void BufferViewer::SetupRawView() m_InnerSplitter->setHandleWidth(12); m_InnerSplitter->setChildrenCollapsible(false); + m_InnerSplitter->setVisible(false); + // inner splitter is only used when we have these groups, so we can add these unconditionally m_InnerSplitter->addWidget(m_FixedGroup); m_InnerSplitter->addWidget(m_RepeatedGroup); @@ -2663,6 +2684,8 @@ BufferViewer::~BufferViewer() m_Ctx.RemoveCaptureViewer(this); delete ui; + + m_CBufferViews.removeOne(this); } void BufferViewer::OnCaptureLoaded() @@ -2741,8 +2764,6 @@ void BufferViewer::OnEventChanged(uint32_t eventId) bufdata->highlightNames[4] = m_ModelGSOut->posName(); bufdata->highlightNames[5] = m_ModelGSOut->secondaryName(); - updateWindowTitle(); - const ActionDescription *action = m_Ctx.CurAction(); configureDrawRange(); @@ -2793,10 +2814,50 @@ void BufferViewer::OnEventChanged(uint32_t eventId) } else { + // update with the current cbuffer for the current slot + if(IsCBufferView()) + { + BoundCBuffer cb = m_Ctx.CurPipelineState().GetConstantBuffer( + m_CBufferSlot.stage, m_CBufferSlot.slot, m_CBufferSlot.arrayIdx); + m_BufferID = cb.resourceId; + m_ByteOffset = cb.byteOffset; + m_ByteSize = cb.byteSize; + + const ShaderReflection *reflection = + m_Ctx.CurPipelineState().GetShaderReflection(m_CBufferSlot.stage); + bufdata->cb.valid = + (reflection != NULL && m_CBufferSlot.slot < reflection->constantBlocks.size()); + if(bufdata->cb.valid) + bufdata->cb.bufferBacked = reflection->constantBlocks[m_CBufferSlot.slot].bufferBacked; + + ui->setFormat->setEnabled(bufdata->cb.bufferBacked); + if(ui->setFormat->isEnabled()) + ui->setFormat->setToolTip(tr("Specify a custom format for this constant buffer")); + else + ui->setFormat->setToolTip(tr("Cannot specify custom format without backing memory")); + + bufdata->cb.pipe = m_CBufferSlot.stage == ShaderStage::Compute + ? m_Ctx.CurPipelineState().GetComputePipelineObject() + : m_Ctx.CurPipelineState().GetGraphicsPipelineObject(); + bufdata->cb.shader = m_Ctx.CurPipelineState().GetShader(m_CBufferSlot.stage); + bufdata->cb.entryPoint = m_Ctx.CurPipelineState().GetShaderEntryPoint(m_CBufferSlot.stage); + bufdata->cb.inlinedata = cb.inlineData; + + if(m_Format.isEmpty()) + { + // stage, slot, and array index are all invariant when viewing a constant buffer + // ee only need to use the actual bound shader as a key. + RDTreeViewExpansionState &prevShaderExpansionState = + ui->fixedVars->getInternalExpansion(qHash(ToQStr(m_CurCBuffer.shader))); + + ui->fixedVars->saveExpansion(prevShaderExpansionState, 0); + } + } + QString errors; ShaderConstant repeating; rdctie(bufdata->vsinConfig.fixedVars, repeating) = - BufferFormatter::ParseFormatString(m_Format, m_ByteSize, errors); + BufferFormatter::ParseFormatString(m_Format, m_ByteSize, IsCBufferView(), errors); if(repeating.type.descriptor.type != VarType::Unknown) { @@ -2809,6 +2870,8 @@ void BufferViewer::OnEventChanged(uint32_t eventId) ClearModels(); } + updateWindowTitle(); + bufdata->vsinConfig.curInstance = bufdata->vsoutConfig.curInstance = bufdata->gsoutConfig.curInstance = m_Config.curInstance; bufdata->vsinConfig.curView = bufdata->vsoutConfig.curView = bufdata->gsoutConfig.curView = @@ -2820,6 +2883,8 @@ void BufferViewer::OnEventChanged(uint32_t eventId) bufdata->vsinConfig.baseVertex = action ? action->baseVertex : 0; + ui->formatSpecifier->setEnabled(!IsCBufferView() || bufdata->cb.bufferBacked); + ui->instance->setEnabled(action && (action->flags & ActionFlags::Instanced)); if(!ui->instance->isEnabled()) ui->instance->setValue(0); @@ -2908,7 +2973,11 @@ void BufferViewer::OnEventChanged(uint32_t eventId) if(m_IsBuffer) { - if(repeatedRangeStart > fixedLength) + if(m_BufferID == ResourceId()) + { + buf->storage = bufdata->cb.inlinedata; + } + else if(repeatedRangeStart > fixedLength) { // if the repeated range subsection we're fetching is paged further in, we still need to // fetch the fixed data from the 'start' @@ -2943,6 +3012,17 @@ void BufferViewer::OnEventChanged(uint32_t eventId) } } + // for cbuffers, if the format is empty or if we're not buffer-backed, we evaluate variables + // here and don't use the format override with a fetched buffer + if((m_Format.isEmpty() || !bufdata->cb.bufferBacked) && IsCBufferView()) + { + // only fetch the cbuffer constants if this binding is currently valid + if(bufdata->cb.valid) + bufdata->vsinConfig.evalVars = r->GetCBufferVariableContents( + bufdata->cb.pipe, bufdata->cb.shader, m_CBufferSlot.stage, bufdata->cb.entryPoint, + m_CBufferSlot.slot, m_BufferID, m_ByteOffset, m_ByteSize); + } + GUIInvoke::call(this, [this, bufdata]() { if(bufdata->sequence != m_Sequence) return; @@ -2954,6 +3034,8 @@ void BufferViewer::OnEventChanged(uint32_t eventId) m_PostVS = bufdata->postVS; m_PostGS = bufdata->postGS; + m_CurCBuffer = bufdata->cb; + // if we didn't have a position column selected before, or the name has changed, re-guess if(m_ModelVSIn->posColumn() == -1 || bufdata->highlightNames[0] != bufdata->vsinConfig.columnName(m_ModelVSIn->posColumn())) @@ -3021,10 +3103,24 @@ void BufferViewer::OnEventChanged(uint32_t eventId) if(!m_MeshView) { + m_RepeatedOffset->setText( + tr("Starting at: %1 bytes").arg(m_ByteOffset + bufdata->vsinConfig.repeatOffset)); + { - ShaderVariable var = InterpretShaderVar(bufdata->vsinConfig.fixedVars, - bufdata->vsinConfig.buffers[0]->storage.begin(), - bufdata->vsinConfig.buffers[0]->storage.end()); + rdcarray vars; + + if((m_BufferID == ResourceId() && m_CurCBuffer.inlinedata.empty()) || m_Format.isEmpty()) + { + vars = bufdata->vsinConfig.evalVars; + } + else + { + ShaderVariable var = InterpretShaderVar(bufdata->vsinConfig.fixedVars, + bufdata->vsinConfig.buffers[0]->storage.begin(), + bufdata->vsinConfig.buffers[0]->storage.end()); + + vars.swap(var.members); + } bool wasEmpty = ui->fixedVars->topLevelItemCount() == 0; @@ -3035,8 +3131,8 @@ void BufferViewer::OnEventChanged(uint32_t eventId) ui->fixedVars->clear(); - if(!var.members.isEmpty()) - UI_AddFixedVariables(ui->fixedVars->invisibleRootItem(), var.members); + if(!vars.isEmpty()) + UI_AddFixedVariables(ui->fixedVars->invisibleRootItem(), vars); ui->fixedVars->endUpdate(); @@ -3049,7 +3145,14 @@ void BufferViewer::OnEventChanged(uint32_t eventId) ui->fixedVars->collapseAll(); } - ui->fixedVars->applyExpansion(state, 0); + // if we have saved expansion state for the new shader, apply it, otherwise apply the + // previous one to get any overlap (e.g. two different shaders with very similar or + // identical constants) + if(ui->fixedVars->hasInternalExpansion(qHash(ToQStr(m_CurCBuffer.shader)))) + ui->fixedVars->applyExpansion( + ui->fixedVars->getInternalExpansion(qHash(ToQStr(m_CurCBuffer.shader))), 0); + else + ui->fixedVars->applyExpansion(state, 0); } on_rowOffset_valueChanged(ui->rowOffset->value()); @@ -3857,6 +3960,59 @@ void BufferViewer::ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId processFormat(format); } +BufferViewer *BufferViewer::HasCBufferView(ShaderStage stage, uint32_t slot, uint32_t idx) +{ + CBufferSlot cbuffer = {stage, slot, idx}; + + for(BufferViewer *c : m_CBufferViews) + { + if(c->m_CBufferSlot == cbuffer) + return c; + } + + return NULL; +} + +BufferViewer *BufferViewer::GetFirstCBufferView(BufferViewer *exclude) +{ + for(BufferViewer *b : m_CBufferViews) + { + if(b != exclude) + return b; + } + + return NULL; +} + +void BufferViewer::ViewCBuffer(const ShaderStage stage, uint32_t slot, uint32_t idx) +{ + if(!m_Ctx.IsCaptureLoaded()) + return; + + m_IsBuffer = true; + m_ByteOffset = 0; + m_ByteSize = UINT64_MAX; + m_BufferID = ResourceId(); + m_CBufferSlot = {stage, slot, idx}; + m_TexSub = {0, 0, 0}; + + updateWindowTitle(); + + m_ObjectByteSize = 0; + m_PagingByteOffset = 0; + + // enable the button to toggle on formatting, so we can pre-fill with a sensible format when it's + // enabled + ui->setFormat->setVisible(true); + + ui->formatSpecifier->setFormat(QString()); + ui->formatSpecifier->setVisible(false); + + processFormat(QString()); + + m_CBufferViews.push_back(this); +} + void BufferViewer::ViewTexture(ResourceId id, const Subresource &sub, const rdcstr &format) { if(!m_Ctx.IsCaptureLoaded()) @@ -3920,11 +4076,66 @@ bool BufferViewer::eventFilter(QObject *watched, QEvent *event) void BufferViewer::updateWindowTitle() { if(!m_MeshView) - setWindowTitle(m_Ctx.GetResourceName(m_BufferID) + lit(" - Contents")); + { + if(IsCBufferView()) + { + QString bufName; + + const ShaderReflection *reflection = + m_Ctx.CurPipelineState().GetShaderReflection(m_CBufferSlot.stage); + + int32_t bindPoint = -1; + if(reflection != NULL) + { + if(m_CBufferSlot.slot < reflection->constantBlocks.size() && + !reflection->constantBlocks[m_CBufferSlot.slot].name.isEmpty()) + { + bufName = QFormatStr("<%1>").arg(reflection->constantBlocks[m_CBufferSlot.slot].name); + bindPoint = reflection->constantBlocks[m_CBufferSlot.slot].bindPoint; + } + } + + if(bufName.isEmpty()) + { + if(m_BufferID != ResourceId()) + bufName = m_Ctx.GetResourceName(m_BufferID); + else + bufName = tr("Unbound"); + } + + const ShaderBindpointMapping &mapping = + m_Ctx.CurPipelineState().GetBindpointMapping(m_CBufferSlot.stage); + + uint32_t arraySize = ~0U; + if(bindPoint >= 0 && bindPoint < mapping.constantBlocks.count()) + arraySize = mapping.constantBlocks[bindPoint].arraySize; + + GraphicsAPI pipeType = m_Ctx.APIProps().pipelineType; + + QString title = QFormatStr("%1 %2 %3") + .arg(ToQStr(m_CBufferSlot.stage, pipeType)) + .arg(IsD3D(pipeType) ? lit("CB") : lit("UBO")) + .arg(m_CBufferSlot.slot); + + if(m_Ctx.CurPipelineState().SupportsResourceArrays() && arraySize > 1) + title += QFormatStr("[%1]").arg(m_CBufferSlot.arrayIdx); + + title += QFormatStr(" - %1").arg(bufName); + + setWindowTitle(title); + } + else + { + setWindowTitle(m_Ctx.GetResourceName(m_BufferID) + lit(" - Contents")); + } + } } void BufferViewer::on_resourceDetails_clicked() { + if(m_BufferID == ResourceId()) + return; + if(!m_Ctx.HasResourceInspector()) m_Ctx.ShowResourceInspector(); @@ -4362,6 +4573,40 @@ void BufferViewer::on_axisMappingButton_clicked() showAxisMappingDialog(); } +void BufferViewer::on_setFormat_toggled(bool checked) +{ + if(!checked) + { + ui->formatSpecifier->setVisible(false); + + processFormat(QString()); + return; + } + + ui->formatSpecifier->setVisible(true); + + const ShaderReflection *reflection = + m_Ctx.CurPipelineState().GetShaderReflection(m_CBufferSlot.stage); + + if(m_CBufferSlot.slot >= reflection->constantBlocks.size()) + { + ui->formatSpecifier->setVisible(false); + + processFormat(QString()); + return; + } + + if(IsD3D(m_Ctx.APIProps().pipelineType)) + ui->formatSpecifier->setFormat(BufferFormatter::DeclareStruct( + Packing::D3DCB, reflection->constantBlocks[m_CBufferSlot.slot].name, + reflection->constantBlocks[m_CBufferSlot.slot].variables, 0)); + else + ui->formatSpecifier->setFormat(BufferFormatter::DeclareStruct( + BufferFormatter::EstimatePackingRules(reflection->constantBlocks[m_CBufferSlot.slot].variables), + reflection->constantBlocks[m_CBufferSlot.slot].name, + reflection->constantBlocks[m_CBufferSlot.slot].variables, 0)); +} + void BufferViewer::processFormat(const QString &format) { QString errors; @@ -4375,7 +4620,18 @@ void BufferViewer::processFormat(const QString &format) BufferConfiguration bufconfig; ShaderConstant fixed, repeating; - rdctie(fixed, repeating) = BufferFormatter::ParseFormatString(format, m_ByteSize, errors); + + if(IsCBufferView() && format.isEmpty()) + { + // insert a dummy member so we get identified as plain fixed vars - we will automatically + // evaluate ignoring the format + fixed.type.members.push_back(ShaderConstant()); + } + else + { + rdctie(fixed, repeating) = + BufferFormatter::ParseFormatString(format, m_ByteSize, IsCBufferView(), errors); + } const bool repeatedVars = repeating.type.descriptor.type != VarType::Unknown; const bool fixedVars = !fixed.type.members.empty(); @@ -4467,16 +4723,33 @@ void BufferViewer::processFormat(const QString &format) m_Format = format; - qulonglong stride = qMax(1U, repeating.type.descriptor.arrayByteStride); + if(IsCBufferView()) + { + ui->byteRangeLine->setVisible(false); + ui->byteRangeStartLabel->setVisible(false); + byteRangeStart->setVisible(false); + ui->byteRangeLengthLabel->setVisible(false); + byteRangeLength->setVisible(false); + GraphicsAPI pipeType = m_Ctx.APIProps().pipelineType; - byteRangeStart->setSingleStep(stride); - byteRangeLength->setSingleStep(stride); + if(IsD3D(pipeType)) + ui->formatSpecifier->setTitle(tr("Constant Buffer Custom Format")); + else + ui->formatSpecifier->setTitle(tr("Uniform Buffer Custom Format")); + } + else + { + qulonglong stride = qMax(1U, repeating.type.descriptor.arrayByteStride); - byteRangeStart->setMaximum((qulonglong)m_ObjectByteSize); - byteRangeLength->setMaximum((qulonglong)m_ObjectByteSize); + byteRangeStart->setSingleStep(stride); + byteRangeLength->setSingleStep(stride); - byteRangeStart->setValue(m_ByteOffset); - byteRangeLength->setValue(m_ByteSize); + byteRangeStart->setMaximum((qulonglong)m_ObjectByteSize); + byteRangeLength->setMaximum((qulonglong)m_ObjectByteSize); + + byteRangeStart->setValue(m_ByteOffset); + byteRangeLength->setValue(m_ByteSize); + } ui->formatSpecifier->setErrors(errors); @@ -4527,7 +4800,7 @@ void BufferViewer::updateExportActionNames() } m_ExportCSV->setEnabled(true); - m_ExportBytes->setEnabled(true); + m_ExportBytes->setEnabled(m_BufferID != ResourceId()); if(m_MeshView) { diff --git a/qrenderdoc/Windows/BufferViewer.h b/qrenderdoc/Windows/BufferViewer.h index 6f7eda583..e9ffa6778 100644 --- a/qrenderdoc/Windows/BufferViewer.h +++ b/qrenderdoc/Windows/BufferViewer.h @@ -39,6 +39,7 @@ class QItemSelection; class QMenu; class QPushButton; class QVBoxLayout; +class RDLabel; class RDTableView; class RDSplitter; class BufferItemModel; @@ -72,6 +73,16 @@ struct BBoxData } bounds[3]; }; +struct CBufferData +{ + bool valid = false; + bool bufferBacked = false; + ResourceId pipe; + ResourceId shader; + rdcstr entryPoint; + bytebuf inlinedata; +}; + class BufferViewer : public QFrame, public IBufferViewer, public ICaptureViewer { Q_OBJECT @@ -82,7 +93,11 @@ public: explicit BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent = 0); ~BufferViewer(); + static BufferViewer *HasCBufferView(ShaderStage stage, uint32_t slot, uint32_t idx); + static BufferViewer *GetFirstCBufferView(BufferViewer *exclude); + bool IsCBufferView() const { return m_CBufferSlot.stage != ShaderStage::Count; } void ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id, const rdcstr &format = ""); + void ViewCBuffer(const ShaderStage stage, uint32_t slot, uint32_t idx); void ViewTexture(ResourceId id, const Subresource &sub, const rdcstr &format = ""); // IBufferViewer @@ -123,6 +138,7 @@ private slots: void on_byteRangeLength_valueChanged(double value); void on_axisMappingCombo_currentIndexChanged(int index); void on_axisMappingButton_clicked(); + void on_setFormat_toggled(bool checked); // manual slots void render_mouseMove(QMouseEvent *e); @@ -197,6 +213,22 @@ private: uint64_t m_ByteSize = UINT64_MAX; ResourceId m_BufferID; + struct CBufferSlot + { + ShaderStage stage; + uint32_t slot; + uint32_t arrayIdx; + + bool operator==(const CBufferSlot &c) const + { + return stage == c.stage && slot == c.slot && arrayIdx == c.arrayIdx; + } + } m_CBufferSlot = {ShaderStage::Count, 0, 0}; + + CBufferData m_CurCBuffer; + + static QList m_CBufferViews; + CameraWrapper *m_CurrentCamera = NULL; ArcballWrapper *m_Arcball = NULL; FlycamWrapper *m_Flycam = NULL; @@ -235,6 +267,8 @@ private: QFrame *m_RepeatedControlBar = NULL; + RDLabel *m_RepeatedOffset = NULL; + QMenu *m_HeaderMenu = NULL; QAction *m_ResetColumnSel = NULL; diff --git a/qrenderdoc/Windows/BufferViewer.ui b/qrenderdoc/Windows/BufferViewer.ui index 2775af5c4..8dd47056e 100644 --- a/qrenderdoc/Windows/BufferViewer.ui +++ b/qrenderdoc/Windows/BufferViewer.ui @@ -875,6 +875,22 @@ Enter 0.0 to use automatic/guessed value derived from data. + + + + {} + + + true + + + Qt::ToolButtonTextOnly + + + true + + + diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.cpp b/qrenderdoc/Windows/ConstantBufferPreviewer.cpp deleted file mode 100644 index 53ed9e48e..000000000 --- a/qrenderdoc/Windows/ConstantBufferPreviewer.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/****************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2019-2022 Baldur Karlsson - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - ******************************************************************************/ - -#include "ConstantBufferPreviewer.h" -#include -#include -#include "Code/QRDUtils.h" -#include "toolwindowmanager/ToolWindowManager.h" -#include "ui_ConstantBufferPreviewer.h" - -QList ConstantBufferPreviewer::m_Previews; - -ConstantBufferPreviewer::ConstantBufferPreviewer(ICaptureContext &ctx, const ShaderStage stage, - uint32_t slot, uint32_t idx, QWidget *parent) - : QFrame(parent), ui(new Ui::ConstantBufferPreviewer), m_Ctx(ctx) -{ - ui->setupUi(this); - - m_stage = stage; - m_slot = slot; - m_arrayIdx = idx; - - QObject::connect(ui->formatSpecifier, &BufferFormatSpecifier::processFormat, this, - &ConstantBufferPreviewer::processFormat); - - ui->formatSpecifier->setContext(&m_Ctx); - - ui->splitter->setCollapsible(1, true); - ui->splitter->setSizes({1, 0}); - ui->splitter->handle(1)->setEnabled(false); - - ui->variables->setColumns({tr("Name"), tr("Value"), tr("Type")}); - { - ui->variables->header()->setSectionResizeMode(0, QHeaderView::Interactive); - ui->variables->header()->setSectionResizeMode(1, QHeaderView::Interactive); - ui->variables->header()->setSectionResizeMode(2, QHeaderView::Interactive); - } - - ui->variables->setFont(Formatter::FixedFont()); - - m_Previews.push_back(this); - m_Ctx.AddCaptureViewer(this); -} - -ConstantBufferPreviewer::~ConstantBufferPreviewer() -{ - m_Ctx.RemoveCaptureViewer(this); - m_Previews.removeOne(this); - delete ui; -} - -ConstantBufferPreviewer *ConstantBufferPreviewer::has(ShaderStage stage, uint32_t slot, uint32_t idx) -{ - for(ConstantBufferPreviewer *c : m_Previews) - { - if(c->m_stage == stage && c->m_slot == slot && c->m_arrayIdx == idx) - return c; - } - - return NULL; -} - -void ConstantBufferPreviewer::OnCaptureLoaded() -{ - Reset(); -} - -void ConstantBufferPreviewer::OnCaptureClosed() -{ - Reset(); - - ToolWindowManager::closeToolWindow(this); -} - -void ConstantBufferPreviewer::Reset() -{ - ui->variables->clear(); - ui->variables->clearInternalExpansions(); - - ui->saveCSV->setEnabled(false); -} - -void ConstantBufferPreviewer::OnEventChanged(uint32_t eventId) -{ - BoundCBuffer cb = m_Ctx.CurPipelineState().GetConstantBuffer(m_stage, m_slot, m_arrayIdx); - m_cbuffer = cb.resourceId; - uint64_t offset = cb.byteOffset; - uint64_t size = cb.byteSize; - bytebuf inlineData = cb.inlineData; - - ResourceId prevShader = m_shader; - - m_pipe = m_stage == ShaderStage::Compute ? m_Ctx.CurPipelineState().GetComputePipelineObject() - : m_Ctx.CurPipelineState().GetGraphicsPipelineObject(); - m_shader = m_Ctx.CurPipelineState().GetShader(m_stage); - rdcstr entryPoint = m_Ctx.CurPipelineState().GetShaderEntryPoint(m_stage); - const ShaderReflection *reflection = m_Ctx.CurPipelineState().GetShaderReflection(m_stage); - - bool wasEmpty = ui->variables->topLevelItemCount() == 0; - - updateLabels(); - - if(reflection == NULL || m_slot >= reflection->constantBlocks.size()) - { - // save expansion before clearing - if(m_formatOverride.type.members.empty()) - { - RDTreeViewExpansionState &prevShaderExpansionState = - ui->variables->getInternalExpansion(qHash(ToQStr(prevShader))); - ui->variables->saveExpansion(prevShaderExpansionState, 0); - } - - setVariables({}); - return; - } - - ui->setFormat->setEnabled(reflection->constantBlocks[m_slot].bufferBacked); - - if(!m_formatOverride.type.members.empty() && reflection->constantBlocks[m_slot].bufferBacked) - { - if(!inlineData.empty() && m_cbuffer == ResourceId()) - { - setVariablesPreserveExpansion(applyFormatOverride(inlineData), wasEmpty); - } - else - { - m_Ctx.Replay().AsyncInvoke([this, offset, size, wasEmpty](IReplayController *r) { - bytebuf data; - - if(size > 0 && m_cbuffer != ResourceId()) - data = r->GetBufferData(m_cbuffer, offset, size); - - rdcarray vars = applyFormatOverride(data); - - GUIInvoke::call(this, - [this, vars, wasEmpty] { setVariablesPreserveExpansion(vars, wasEmpty); }); - }); - } - } - else - { - m_Ctx.Replay().AsyncInvoke( - [this, prevShader, entryPoint, offset, size, wasEmpty](IReplayController *r) { - rdcarray vars = r->GetCBufferVariableContents( - m_pipe, m_shader, m_stage, entryPoint, m_slot, m_cbuffer, offset, size); - GUIInvoke::call(this, [this, prevShader, vars, wasEmpty] { - - RDTreeViewExpansionState &prevShaderExpansionState = - ui->variables->getInternalExpansion(qHash(ToQStr(prevShader))); - - // stage, slot, and array index are all invariant across a given ConstantBufferPreviewer - // instance. We only need to use the actual bound shader as a key. - ui->variables->saveExpansion(prevShaderExpansionState, 0); - - setVariables(vars); - if(wasEmpty) - { - // Expand before resizing so that collapsed data will already be visible when expanded - ui->variables->expandAll(); - for(int i = 0; i < 3; i++) - ui->variables->resizeColumnToContents(i); - ui->variables->collapseAll(); - } - - // if we have saved expansion state for the new shader, apply it, otherwise apply the - // previous one to get any overlap (e.g. two different shaders with very similar or - // identical constants) - if(ui->variables->hasInternalExpansion(qHash(ToQStr(m_shader)))) - ui->variables->applyExpansion( - ui->variables->getInternalExpansion(qHash(ToQStr(m_shader))), 0); - else - ui->variables->applyExpansion(prevShaderExpansionState, 0); - }); - }); - } -} - -void ConstantBufferPreviewer::on_setFormat_toggled(bool checked) -{ - if(!checked) - { - ui->splitter->setCollapsible(1, true); - ui->splitter->setSizes({1, 0}); - ui->splitter->handle(1)->setEnabled(false); - - processFormat(QString()); - return; - } - - ui->splitter->setCollapsible(1, false); - ui->splitter->setSizes({1, 1}); - ui->splitter->handle(1)->setEnabled(true); - - const ShaderReflection *reflection = m_Ctx.CurPipelineState().GetShaderReflection(m_stage); - - if(IsD3D(m_Ctx.APIProps().pipelineType)) - ui->formatSpecifier->setFormat( - BufferFormatter::DeclareStruct(Packing::D3DCB, reflection->constantBlocks[m_slot].name, - reflection->constantBlocks[m_slot].variables, 0)); - else - ui->formatSpecifier->setFormat(BufferFormatter::DeclareStruct( - BufferFormatter::EstimatePackingRules(reflection->constantBlocks[m_slot].variables), - reflection->constantBlocks[m_slot].name, reflection->constantBlocks[m_slot].variables, 0)); -} - -void ConstantBufferPreviewer::on_resourceDetails_clicked() -{ - if(!m_Ctx.HasResourceInspector()) - m_Ctx.ShowResourceInspector(); - - m_Ctx.GetResourceInspector()->Inspect(m_cbuffer); - - ToolWindowManager::raiseToolWindow(m_Ctx.GetResourceInspector()->Widget()); -} - -void ConstantBufferPreviewer::on_saveCSV_clicked() -{ - QString filename = RDDialog::getSaveFileName(this, tr("Export buffer data as CSV"), QString(), - tr("CSV Files (*.csv)")); - - if(!filename.isEmpty()) - { - QDir dirinfo = QFileInfo(filename).dir(); - if(dirinfo.exists()) - { - QFile f(filename, this); - if(f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) - { - QTextStream ts(&f); - - ts << tr("Name,Value,Type\n"); - - for(int i = 0; i < ui->variables->topLevelItemCount(); i++) - exportCSV(ts, QString(), ui->variables->topLevelItem(i)); - - return; - } - - RDDialog::critical( - this, tr("Error exporting buffer data"), - tr("Couldn't open path %1 for write.\n%2").arg(filename).arg(f.errorString())); - } - else - { - RDDialog::critical(this, tr("Invalid directory"), - tr("Cannot find target directory to save to")); - } - } -} - -void ConstantBufferPreviewer::exportCSV(QTextStream &ts, const QString &prefix, RDTreeWidgetItem *item) -{ - if(item->childCount() == 0) - { - ts << QFormatStr("%1,\"%2\",%3\n").arg(item->text(0)).arg(item->text(1)).arg(item->text(2)); - } - else - { - ts << QFormatStr("%1,,%2\n").arg(item->text(0)).arg(item->text(2)); - for(int i = 0; i < item->childCount(); i++) - exportCSV(ts, item->text(0) + lit("."), item->child(i)); - } -} - -void ConstantBufferPreviewer::processFormat(const QString &format) -{ - if(format.isEmpty()) - { - m_formatOverride = ShaderConstant(); - ui->formatSpecifier->setErrors(QString()); - } - else - { - QString errors; - - ShaderConstant fixed, repeating; - rdctie(fixed, repeating) = BufferFormatter::ParseFormatString(format, ~0ULL, errors); - m_formatOverride = fixed; - // we don't handle true unbounded repeating data here, add it as another single element - if(repeating.type.descriptor.type != VarType::Unknown) - m_formatOverride.type.members.push_back(repeating); - ui->formatSpecifier->setErrors(errors); - } - - OnEventChanged(m_Ctx.CurEvent()); -} - -void ConstantBufferPreviewer::addVariables(RDTreeWidgetItem *root, - const rdcarray &vars) -{ - for(const ShaderVariable &v : vars) - { - RDTreeWidgetItem *n = new RDTreeWidgetItem({v.name, VarString(v), TypeString(v)}); - - root->addChild(n); - - if(v.rows > 1) - { - for(uint32_t i = 0; i < v.rows; i++) - n->addChild(new RDTreeWidgetItem( - {QFormatStr("%1.row%2").arg(v.name).arg(i), RowString(v, i), RowTypeString(v)})); - } - - if(!v.members.isEmpty()) - addVariables(n, v.members); - } -} - -void ConstantBufferPreviewer::setVariables(const rdcarray &vars) -{ - ui->variables->beginUpdate(); - - ui->variables->clear(); - - ui->saveCSV->setEnabled(false); - - if(!vars.isEmpty()) - { - addVariables(ui->variables->invisibleRootItem(), vars); - ui->saveCSV->setEnabled(true); - } - - ui->variables->endUpdate(); -} - -void ConstantBufferPreviewer::setVariablesPreserveExpansion(const rdcarray &vars, - bool wasEmpty) -{ - RDTreeViewExpansionState state; - ui->variables->saveExpansion(state, 0); - - setVariables(vars); - - if(wasEmpty) - { - // Expand before resizing so that collapsed data will already be visible when expanded - ui->variables->expandAll(); - for(int i = 0; i < 3; i++) - ui->variables->resizeColumnToContents(i); - ui->variables->collapseAll(); - } - - ui->variables->applyExpansion(state, 0); -} - -void ConstantBufferPreviewer::updateLabels() -{ - QString bufName = m_Ctx.GetResourceName(m_cbuffer); - - const ShaderReflection *reflection = m_Ctx.CurPipelineState().GetShaderReflection(m_stage); - - if(reflection != NULL) - { - if(m_Ctx.IsAutogeneratedName(m_cbuffer) && m_slot < reflection->constantBlocks.size() && - !reflection->constantBlocks[m_slot].name.isEmpty()) - bufName = QFormatStr("<%1>").arg(reflection->constantBlocks[m_slot].name); - } - - ui->nameLabel->setText(bufName); - - GraphicsAPI pipeType = m_Ctx.APIProps().pipelineType; - - QString title = QFormatStr("%1 %2 %3") - .arg(ToQStr(m_stage, pipeType)) - .arg(IsD3D(pipeType) ? lit("CB") : lit("UBO")) - .arg(m_slot); - - if(m_Ctx.CurPipelineState().SupportsResourceArrays()) - title += QFormatStr(" [%1]").arg(m_arrayIdx); - - ui->slotLabel->setText(title); - setWindowTitle(title); -} - -rdcarray ConstantBufferPreviewer::applyFormatOverride(const bytebuf &bytes) -{ - ShaderVariable var = InterpretShaderVar(m_formatOverride, bytes.begin(), bytes.end()); - - return var.members; -} diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.h b/qrenderdoc/Windows/ConstantBufferPreviewer.h deleted file mode 100644 index 42533ab30..000000000 --- a/qrenderdoc/Windows/ConstantBufferPreviewer.h +++ /dev/null @@ -1,94 +0,0 @@ -/****************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2019-2022 Baldur Karlsson - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - ******************************************************************************/ - -#pragma once - -#include -#include "Code/Interface/QRDInterface.h" - -namespace Ui -{ -class ConstantBufferPreviewer; -} - -class QTextStream; -class RDTreeWidgetItem; -struct BufferFormatter; - -class ConstantBufferPreviewer : public QFrame, public IConstantBufferPreviewer, public ICaptureViewer -{ - Q_OBJECT - -public: - explicit ConstantBufferPreviewer(ICaptureContext &ctx, const ShaderStage stage, uint32_t slot, - uint32_t idx, QWidget *parent = 0); - ~ConstantBufferPreviewer(); - - static ConstantBufferPreviewer *has(ShaderStage stage, uint32_t slot, uint32_t idx); - - // IConstantBufferPreviewer - QWidget *Widget() override { return this; } - // ICaptureViewer - void OnCaptureLoaded() override; - void OnCaptureClosed() override; - - void OnSelectedEventChanged(uint32_t eventId) override {} - void OnEventChanged(uint32_t eventId) override; - -private slots: - // automatic slots - void on_setFormat_toggled(bool checked); - void on_saveCSV_clicked(); - void on_resourceDetails_clicked(); - - // manual slots - void processFormat(const QString &format); - -private: - void Reset(); - - Ui::ConstantBufferPreviewer *ui; - ICaptureContext &m_Ctx; - - ResourceId m_cbuffer; - ResourceId m_pipe; - ResourceId m_shader; - ShaderStage m_stage = ShaderStage::Vertex; - uint32_t m_slot = 0; - uint32_t m_arrayIdx = 0; - - void exportCSV(QTextStream &ts, const QString &prefix, RDTreeWidgetItem *item); - - rdcarray applyFormatOverride(const bytebuf &data); - - void addVariables(RDTreeWidgetItem *root, const rdcarray &vars); - void setVariables(const rdcarray &vars); - void setVariablesPreserveExpansion(const rdcarray &vars, bool wasEmpty); - - void updateLabels(); - - static QList m_Previews; - - ShaderConstant m_formatOverride; -}; diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.ui b/qrenderdoc/Windows/ConstantBufferPreviewer.ui deleted file mode 100644 index 0607cd1b0..000000000 --- a/qrenderdoc/Windows/ConstantBufferPreviewer.ui +++ /dev/null @@ -1,249 +0,0 @@ - - - ConstantBufferPreviewer - - - - 0 - 0 - 400 - 300 - - - - Constant Buffer - - - - 0 - - - 3 - - - 3 - - - 3 - - - 3 - - - - - Qt::Vertical - - - false - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 28 - - - - QFrame::Panel - - - QFrame::Raised - - - - 2 - - - 6 - - - 2 - - - 6 - - - 2 - - - - - Subresource - - - - - - - Qt::Vertical - - - - - - - Mip - - - - - - - Qt::Vertical - - - - - - - {} - - - true - - - Qt::ToolButtonTextOnly - - - true - - - - - - - Qt::Vertical - - - - - - - Export to CSV - - - - - - - :/save.png:/save.png - - - true - - - - - - - Open the currently visible buffer's resource details in the resource inspector - - - - :/link.png:/link.png - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - QFrame::Panel - - - QFrame::Plain - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::ContiguousSelection - - - QAbstractItemView::ScrollPerPixel - - - true - - - false - - - - - - - - - - - - - RDTreeWidget - QTreeView -
Widgets/Extended/RDTreeWidget.h
-
- - BufferFormatSpecifier - QWidget -
Widgets/BufferFormatSpecifier.h
- 1 -
-
- - - - -
diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp index 19564ce51..663837fd7 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp @@ -2210,7 +2210,7 @@ void D3D11PipelineStateViewer::cbuffer_itemActivated(RDTreeWidgetItem *item, int return; } - IConstantBufferPreviewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cbufIdx, 0); + IBufferViewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cbufIdx, 0); m_Ctx.AddDockWindow(prev->Widget(), DockReference::TransientPopupArea, this, 0.3f); } diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index 4b1498fa3..56c80ae5d 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -2314,7 +2314,7 @@ void D3D12PipelineStateViewer::cbuffer_itemActivated(RDTreeWidgetItem *item, int return; } - IConstantBufferPreviewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cb.idx, cb.arrayIdx); + IBufferViewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cb.idx, cb.arrayIdx); m_Ctx.AddDockWindow(prev->Widget(), DockReference::TransientPopupArea, this, 0.3f); } diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index f4c006c4b..eb8e9068b 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -2342,7 +2342,7 @@ void GLPipelineStateViewer::ubo_itemActivated(RDTreeWidgetItem *item, int column int cb = tag.value(); - IConstantBufferPreviewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cb, 0); + IBufferViewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cb, 0); m_Ctx.AddDockWindow(prev->Widget(), DockReference::TransientPopupArea, this, 0.3f); } diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index 123722dfc..49d6aa236 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -3109,7 +3109,7 @@ void VulkanPipelineStateViewer::ubo_itemActivated(RDTreeWidgetItem *item, int co return; } - IConstantBufferPreviewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cb.slotIdx, cb.arrayIdx); + IBufferViewer *prev = m_Ctx.ViewConstantBuffer(stage->stage, cb.slotIdx, cb.arrayIdx); m_Ctx.AddDockWindow(prev->Widget(), DockReference::TransientPopupArea, this, 0.3f); } diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 46edc0181..5d7818a47 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -789,11 +789,9 @@ struct CaptureContextInvoker : ObjectForwarder return InvokeRetFunction(&ICaptureContext::ViewTextureAsBuffer, id, sub, format); } - virtual IConstantBufferPreviewer *ViewConstantBuffer(ShaderStage stage, uint32_t slot, - uint32_t idx) override + virtual IBufferViewer *ViewConstantBuffer(ShaderStage stage, uint32_t slot, uint32_t idx) override { - return InvokeRetFunction(&ICaptureContext::ViewConstantBuffer, - stage, slot, idx); + return InvokeRetFunction(&ICaptureContext::ViewConstantBuffer, stage, slot, idx); } virtual IPixelHistoryView *ViewPixelHistory(ResourceId texID, uint32_t x, uint32_t y, diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 40d269ec9..dc2de5342 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -218,7 +218,6 @@ SOURCES += Code/qrenderdoc.cpp \ Windows/PipelineState/GLPipelineStateViewer.cpp \ Widgets/Extended/RDTreeView.cpp \ Widgets/Extended/RDTreeWidget.cpp \ - Windows/ConstantBufferPreviewer.cpp \ Widgets/BufferFormatSpecifier.cpp \ Windows/BufferViewer.cpp \ Widgets/Extended/RDTableView.cpp \ @@ -304,7 +303,6 @@ HEADERS += Code/CaptureContext.h \ Windows/PipelineState/GLPipelineStateViewer.h \ Widgets/Extended/RDTreeView.h \ Widgets/Extended/RDTreeWidget.h \ - Windows/ConstantBufferPreviewer.h \ Widgets/BufferFormatSpecifier.h \ Windows/BufferViewer.h \ Widgets/Extended/RDTableView.h \ @@ -353,7 +351,6 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/PipelineState/D3D11PipelineStateViewer.ui \ Windows/PipelineState/D3D12PipelineStateViewer.ui \ Windows/PipelineState/GLPipelineStateViewer.ui \ - Windows/ConstantBufferPreviewer.ui \ Widgets/BufferFormatSpecifier.ui \ Widgets/ComputeDebugSelector.ui \ Windows/BufferViewer.ui \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index e50669409..41bcba3ed 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -623,7 +623,6 @@ - @@ -746,7 +745,6 @@ - @@ -964,7 +962,6 @@ - @@ -1207,12 +1204,6 @@ MOC %(Filename).h $(IntDir)generated\moc_%(Filename).cpp - - %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) - "$(QtBinDir)\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" - MOC %(Filename).h - $(IntDir)generated\moc_%(Filename).cpp - %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) "$(QtBinDir)\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" @@ -1508,12 +1499,6 @@ UIC %(Filename).ui $(IntDir)generated\ui_%(Filename).h - - %(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs) - "$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" - UIC %(Filename).ui - $(IntDir)generated\ui_%(Filename).h - %(Fullpath);$(QtBinDir)\uic.exe;%(AdditionalInputs) "$(QtBinDir)\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index e1112c090..0f8d601be 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -186,9 +186,6 @@ Widgets\Extended - - Windows - Widgets @@ -417,9 +414,6 @@ Generated Files - - Generated Files - Generated Files @@ -983,9 +977,6 @@ Generated Files - - Generated Files - Generated Files @@ -1184,9 +1175,6 @@ Windows - - Windows - Windows @@ -1274,9 +1262,6 @@ Windows - - Windows - Windows diff --git a/renderdoc/api/replay/pipestate.inl b/renderdoc/api/replay/pipestate.inl index 65bf09d19..a0b2b0c17 100644 --- a/renderdoc/api/replay/pipestate.inl +++ b/renderdoc/api/replay/pipestate.inl @@ -1013,7 +1013,16 @@ BoundCBuffer PipeState::GetConstantBuffer(ShaderStage stage, uint32_t BufIdx, ui const Bindpoint &bind = s.bindpointMapping.constantBlocks[s.reflection->constantBlocks[BufIdx].bindPoint]; - if(s.reflection->constantBlocks[BufIdx].bufferBacked == false) + const VKPipe::BindingElement *descriptorBind = NULL; + if(bind.bindset < pipe.descriptorSets.count() && + bind.bind < pipe.descriptorSets[bind.bindset].bindings.count() && + ArrayIdx < pipe.descriptorSets[bind.bindset].bindings[bind.bind].binds.size()) + { + descriptorBind = &pipe.descriptorSets[bind.bindset].bindings[bind.bind].binds[ArrayIdx]; + } + + if(s.reflection->constantBlocks[BufIdx].bufferBacked == false || + (descriptorBind && descriptorBind->inlineBlock)) { if(s.reflection->constantBlocks[BufIdx].compileConstants) { @@ -1035,11 +1044,8 @@ BoundCBuffer PipeState::GetConstantBuffer(ShaderStage stage, uint32_t BufIdx, ui src = &pipe.descriptorSets[bind.bindset].inlineData; - const VKPipe::BindingElement &descriptorBind = - pipe.descriptorSets[bind.bindset].bindings[bind.bind].binds[ArrayIdx]; - - ret.byteOffset = descriptorBind.byteOffset; - ret.byteSize = descriptorBind.byteSize; + ret.byteOffset = descriptorBind->byteOffset; + ret.byteSize = descriptorBind->byteSize; } else { @@ -1089,17 +1095,12 @@ BoundCBuffer PipeState::GetConstantBuffer(ShaderStage stage, uint32_t BufIdx, ui return ret; } - if(bind.bindset >= pipe.descriptorSets.count() || - bind.bind >= pipe.descriptorSets[bind.bindset].bindings.count() || - ArrayIdx >= pipe.descriptorSets[bind.bindset].bindings[bind.bind].binds.size()) + if(descriptorBind == NULL) return BoundCBuffer(); - const VKPipe::BindingElement &descriptorBind = - pipe.descriptorSets[bind.bindset].bindings[bind.bind].binds[ArrayIdx]; - - ret.resourceId = descriptorBind.resourceResourceId; - ret.byteOffset = descriptorBind.byteOffset; - ret.byteSize = descriptorBind.byteSize; + ret.resourceId = descriptorBind->resourceResourceId; + ret.byteOffset = descriptorBind->byteOffset; + ret.byteSize = descriptorBind->byteSize; } } }