diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index 514085999..c1e6e1b3e 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -370,6 +370,17 @@ struct ToStr return "Unknown"; } + static std::string Get(const QualityHint &el) + { + switch(el) + { + case QualityHint::DontCare: return "Don't Care"; + case QualityHint::Nicest: return "Nicest"; + case QualityHint::Fastest: return "Fastest"; + } + return "Unknown"; + } + static std::string Get(const TextureFilter &el) { std::string filter = ""; @@ -401,6 +412,26 @@ struct ToStr return filter; } + static std::string Get(const D3DBufferViewFlags &el) + { + std::string ret; + + if(el == D3DBufferViewFlags::NoFlags) + return ""; + + if(el & D3DBufferViewFlags::Raw) + ret += " | Raw"; + if(el & D3DBufferViewFlags::Append) + ret += " | Append"; + if(el & D3DBufferViewFlags::Counter) + ret += " | Counter"; + + if(!ret.empty()) + ret = ret.substr(3); + + return ret; + } + static std::string Get(const TextureDim &el) { switch(el) diff --git a/qrenderdoc/Widgets/PipelineFlowChart.h b/qrenderdoc/Widgets/PipelineFlowChart.h index 03dc60e75..9c19bbadb 100644 --- a/qrenderdoc/Widgets/PipelineFlowChart.h +++ b/qrenderdoc/Widgets/PipelineFlowChart.h @@ -34,6 +34,8 @@ public: explicit PipelineFlowChart(QWidget *parent = 0); ~PipelineFlowChart(); + const QStringList &stageAbbreviations() { return m_StageAbbrevs; } + const QStringList &stageNames() { return m_StageNames; } void setStages(const QStringList &abbrevs, const QStringList &names); void setStageName(int index, const QString &abbrev, const QString &name); void setIsolatedStage(int index); diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp index defd7b30a..99576571a 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "Code/Resources.h" #include "PipelineStateViewer.h" @@ -1034,7 +1035,7 @@ void D3D11PipelineStateViewer::setShaderState(const D3D11Pipe::Shader &stage, QL if(!filledSlot) { - name = lit("Empty"); + name = tr("Empty"); length = 0; } @@ -2294,8 +2295,793 @@ void D3D11PipelineStateViewer::shaderSave_clicked() m_Common.SaveShaderFile(shaderDetails); } +QVariantList D3D11PipelineStateViewer::exportViewHTML(D3D11Pipe::View &view, int i, + ShaderReflection *refl, + const QString &extraParams) +{ + const ShaderResource *shaderInput = NULL; + + bool rw = false; + + if(refl) + { + for(const ShaderResource &bind : refl->ReadOnlyResources) + { + if(!bind.IsSampler && bind.bindPoint == i) + { + shaderInput = &bind; + break; + } + } + for(const ShaderResource &bind : refl->ReadWriteResources) + { + if(bind.bindPoint == i) + { + shaderInput = &bind; + rw = true; + break; + } + } + } + + QString name = tr("Empty"); + QString typeName = tr("Unknown"); + QString format = tr("Unknown"); + uint64_t w = 1; + uint32_t h = 1, d = 1; + uint32_t a = 0; + + QString viewFormat = ToQStr(view.Format.strname); + + TextureDescription *tex = m_Ctx.GetTexture(view.Resource); + BufferDescription *buf = m_Ctx.GetBuffer(view.Resource); + + QString viewParams; + + // check to see if it's a texture + if(tex) + { + w = tex->width; + h = tex->height; + d = tex->depth; + a = tex->arraysize; + format = ToQStr(tex->format.strname); + name = ToQStr(tex->name); + typeName = ToQStr(tex->resType); + + if(tex->mips > 1) + viewParams = tr("Highest Mip: %1, Num Mips: %2").arg(view.HighestMip).arg(view.NumMipLevels); + + if(tex->arraysize > 1) + { + if(!viewParams.isEmpty()) + viewParams += lit(", "); + viewParams += + tr("First Slice: %1, Array Size: %2").arg(view.FirstArraySlice).arg(view.ArraySize); + } + } + + // if not a texture, it must be a buffer + if(buf) + { + w = buf->length; + h = 0; + d = 0; + a = 0; + format = ToQStr(view.Format.strname); + name = ToQStr(buf->name); + typeName = lit("Buffer"); + + if(view.Flags & D3DBufferViewFlags::Raw) + { + typeName = rw ? lit("RWByteAddressBuffer") : lit("ByteAddressBuffer"); + } + else if(view.ElementSize > 0) + { + // for structured buffers, display how many 'elements' there are in the buffer + typeName = QFormatStr("%1[%2]") + .arg(rw ? lit("RWStructuredBuffer") : lit("StructuredBuffer")) + .arg(buf->length / view.ElementSize); + } + + if(view.Flags & D3DBufferViewFlags::Append || view.Flags & D3DBufferViewFlags::Counter) + { + typeName += tr(" (Count: %1)").arg(view.BufferStructCount); + } + + if(shaderInput && !shaderInput->IsTexture) + { + if(view.Format.compType == CompType::Typeless) + { + if(shaderInput->variableType.members.count > 0) + viewFormat = format = lit("struct ") + ToQStr(shaderInput->variableType.descriptor.name); + else + viewFormat = format = ToQStr(shaderInput->variableType.descriptor.name); + } + else + { + format = ToQStr(view.Format.strname); + } + } + + viewParams = tr("First Element: %1, Num Elements %2, Flags %3") + .arg(view.FirstElement) + .arg(view.NumElements) + .arg(ToQStr(view.Flags)); + } + + if(viewParams.isEmpty()) + viewParams = extraParams; + else + viewParams += lit(", ") + extraParams; + + return {i, name, ToQStr(view.Type), typeName, w, h, d, a, viewFormat, format, viewParams}; +} + +void D3D11PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D11Pipe::IA &ia) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Input Layouts")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D11Pipe::Layout &l : ia.layouts) + { + rows.push_back({i, ToQStr(l.SemanticName), l.SemanticIndex, ToQStr(l.Format.strname), + l.InputSlot, l.ByteOffset, (bool)l.PerInstance, l.InstanceDataStepRate}); + + i++; + } + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Semantic Name"), tr("Semantic Index"), tr("Format"), tr("Input Slot"), + tr("Byte Offset"), tr("Per Instance"), tr("Instance Data Step Rate")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Vertex Buffers")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D11Pipe::VB &vb : ia.vbuffers) + { + QString name = tr("Buffer %1").arg(ToQStr(vb.Buffer)); + uint64_t length = 0; + + if(vb.Buffer == ResourceId()) + { + continue; + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(vb.Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + rows.push_back({i, name, vb.Stride, vb.Offset, length}); + + i++; + } + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Buffer"), tr("Stride"), tr("Offset"), tr("Byte Length")}, rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Index Buffer")); + xml.writeEndElement(); + + QString name = tr("Buffer %1").arg(ToQStr(ia.ibuffer.Buffer)); + uint64_t length = 0; + + if(ia.ibuffer.Buffer == ResourceId()) + { + name = tr("Empty"); + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(ia.ibuffer.Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + QString ifmt = lit("UNKNOWN"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 2) + ifmt = lit("R16_UINT"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 4) + ifmt = lit("R32_UINT"); + + m_Common.exportHTMLTable(xml, {tr("Buffer"), tr("Format"), tr("Offset"), tr("Byte Length")}, + {name, ifmt, ia.ibuffer.Offset, length}); + } + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Primitive Topology")}, {ToQStr(m_Ctx.CurDrawcall()->topology)}); +} + +void D3D11PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D11Pipe::Shader &sh) +{ + ShaderReflection *shaderDetails = sh.ShaderDetails; + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Shader")); + xml.writeEndElement(); + + QString shadername = tr("Unknown"); + + if(sh.Object == ResourceId()) + shadername = tr("Unbound"); + else + shadername = ToQStr(sh.name); + + if(shaderDetails && shaderDetails->DebugInfo.entryFunc.count > 0 && + shaderDetails->DebugInfo.files.count > 0) + { + QString shaderfn; + + int entryFile = shaderDetails->DebugInfo.entryFile; + if(entryFile < 0 || entryFile >= shaderDetails->DebugInfo.files.count) + entryFile = 0; + + shaderfn = QFileInfo(ToQStr(shaderDetails->DebugInfo.files[entryFile].first)).fileName(); + + shadername = + QFormatStr("%1() - %2").arg(ToQStr(shaderDetails->DebugInfo.entryFunc)).arg(shaderfn); + } + + xml.writeStartElement(lit("p")); + xml.writeCharacters(shadername); + xml.writeEndElement(); + + if(sh.Object == ResourceId()) + return; + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Resources")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < sh.SRVs.count; i++) + { + if(sh.SRVs[i].Object == ResourceId()) + continue; + + rows.push_back(exportViewHTML(sh.SRVs[i], i, shaderDetails, QString())); + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Name"), tr("View Type"), tr("Resource Type"), + tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters"), + }, + rows); + } + + if(sh.stage == ShaderStage::Compute) + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Unordered Access Views")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < sh.UAVs.count; i++) + { + if(sh.UAVs[i].Object == ResourceId()) + continue; + + rows.push_back(exportViewHTML(sh.UAVs[i], i, shaderDetails, QString())); + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Name"), tr("View Type"), tr("Resource Type"), + tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters"), + }, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Samplers")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < sh.Samplers.count; i++) + { + const D3D11Pipe::Sampler &s = sh.Samplers[i]; + + if(s.Samp == ResourceId()) + continue; + + 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.AddressU), ToQStr(s.AddressV), ToQStr(s.AddressW)}; + + // arrange like either UVW: WRAP or UV: WRAP, W: CLAMP + for(int a = 0; a < 3; a++) + { + const QString str[] = {lit("U"), lit("V"), lit("W")}; + 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; + + rows.push_back({i, addressing, borderColor, ToQStr(s.Comparison), ToQStr(s.Filter), + s.MaxAniso, s.MinLOD == -FLT_MAX ? lit("0") : QString::number(s.MinLOD), + s.MaxLOD == FLT_MAX ? lit("FLT_MAX") : QString::number(s.MaxLOD), s.MipLODBias}); + } + + m_Common.exportHTMLTable( + xml, + { + tr("Slot"), tr("Addressing"), tr("Border Colour"), tr("Comparison"), tr("Filter"), + tr("Max Anisotropy"), tr("Min LOD"), tr("Max LOD"), tr("Mip Bias"), + }, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Constant Buffers")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < sh.ConstantBuffers.count; i++) + { + ConstantBlock *shaderCBuf = NULL; + + if(sh.ConstantBuffers[i].Buffer == ResourceId()) + continue; + + if(shaderDetails && i < shaderDetails->ConstantBlocks.count && + shaderDetails->ConstantBlocks[i].name.count > 0) + shaderCBuf = &shaderDetails->ConstantBlocks[i]; + + QString name = tr("Constant Buffer %1").arg(ToQStr(sh.ConstantBuffers[i].Buffer)); + uint64_t length = 1; + int numvars = shaderCBuf ? shaderCBuf->variables.count : 0; + uint32_t byteSize = shaderCBuf ? shaderCBuf->byteSize : 0; + + if(sh.ConstantBuffers[i].Buffer == ResourceId()) + { + name = tr("Empty"); + length = 0; + } + + BufferDescription *buf = m_Ctx.GetBuffer(sh.ConstantBuffers[i].Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + + rows.push_back({i, name, sh.ConstantBuffers[i].VecOffset, sh.ConstantBuffers[i].VecCount, + numvars, byteSize, length}); + } + + m_Common.exportHTMLTable(xml, + {tr("Slot"), tr("Buffer"), tr("Vector Offset"), tr("Vector Count"), + tr("Number of Variables"), tr("Bytes Needed"), tr("Bytes Provided")}, + rows); + } + + if(sh.ClassInstances.count > 0) + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Class Instances")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < sh.ClassInstances.count; i++) + { + QString interfaceName = tr("Interface %1").arg(i); + + if(sh.ShaderDetails && i < sh.ShaderDetails->Interfaces.count) + interfaceName = ToQStr(sh.ShaderDetails->Interfaces[i]); + + rows.push_back({i, interfaceName, ToQStr(sh.ClassInstances[i])}); + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Interface Name"), tr("Instance Name"), + }, + rows); + } +} + +void D3D11PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D11Pipe::SO &so) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Stream Out Targets")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D11Pipe::SOBind &o : so.Outputs) + { + QString name = tr("Buffer %1").arg(ToQStr(o.Buffer)); + uint64_t length = 0; + + if(o.Buffer == ResourceId()) + { + name = tr("Empty"); + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(o.Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + rows.push_back({i, name, o.Offset, length}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("Buffer"), tr("Offset"), tr("Byte Length")}, rows); + } +} + +void D3D11PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D11Pipe::Rasterizer &rs) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("States")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Fill Mode"), tr("Cull Mode"), tr("Front CCW")}, + {ToQStr(rs.m_State.fillMode), ToQStr(rs.m_State.cullMode), + rs.m_State.FrontCCW ? tr("Yes") : tr("No")}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Scissor Enable"), tr("Line AA Enable"), tr("Multisample Enable"), + tr("Forced Sample Count"), tr("Conservative Raster")}, + {rs.m_State.ScissorEnable ? tr("Yes") : tr("No"), + rs.m_State.AntialiasedLineEnable ? tr("Yes") : tr("No"), + rs.m_State.MultisampleEnable ? tr("Yes") : tr("No"), rs.m_State.ForcedSampleCount, + rs.m_State.ConservativeRasterization ? tr("Yes") : tr("No")}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Depth Clip"), tr("Depth Bias"), tr("Depth Bias Clamp"), tr("Slope Scaled Bias")}, + {rs.m_State.DepthClip ? tr("Yes") : tr("No"), rs.m_State.DepthBias, + Formatter::Format(rs.m_State.DepthBiasClamp), + Formatter::Format(rs.m_State.SlopeScaledDepthBias)}); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Viewports")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D11Pipe::Viewport &v : rs.Viewports) + { + if(v.Width == v.Height && v.Width == 0 && v.Height == 0) + continue; + + rows.push_back({i, v.X, v.Y, v.Width, v.Height, v.MinDepth, v.MaxDepth}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("X"), tr("Y"), tr("Width"), tr("Height"), + tr("Min Depth"), tr("Max Depth")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Scissors")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D11Pipe::Scissor &s : rs.Scissors) + { + if(s.right == 0 && s.bottom == 0) + continue; + + rows.push_back({i, s.left, s.top, s.right - s.left, s.bottom - s.top}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("X"), tr("Y"), tr("Width"), tr("Height")}, rows); + } +} + +void D3D11PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D11Pipe::OM &om) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Blend State")); + xml.writeEndElement(); + + QString blendFactor = QFormatStr("%1, %2, %3, %4") + .arg(om.m_BlendState.BlendFactor[0], 0, 'f', 2) + .arg(om.m_BlendState.BlendFactor[1], 0, 'f', 2) + .arg(om.m_BlendState.BlendFactor[2], 0, 'f', 2) + .arg(om.m_BlendState.BlendFactor[3], 0, 'f', 2); + + m_Common.exportHTMLTable(xml, {tr("Independent Blend Enable"), tr("Alpha to Coverage"), + tr("Sample Mask"), tr("Blend Factor")}, + { + om.m_BlendState.IndependentBlend ? tr("Yes") : tr("No"), + om.m_BlendState.AlphaToCoverage ? tr("Yes") : tr("No"), + Formatter::Format(om.m_BlendState.SampleMask, true), blendFactor, + }); + + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Target Blends")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D11Pipe::Blend &b : om.m_BlendState.Blends) + { + if(i >= om.RenderTargets.count) + continue; + + QString mask = QFormatStr("%1%2%3%4") + .arg((b.WriteMask & 0x1) == 0 ? lit("_") : lit("R")) + .arg((b.WriteMask & 0x2) == 0 ? lit("_") : lit("G")) + .arg((b.WriteMask & 0x4) == 0 ? lit("_") : lit("B")) + .arg((b.WriteMask & 0x8) == 0 ? lit("_") : lit("A")); + + rows.push_back({i, b.Enabled ? tr("Yes") : tr("No"), b.LogicEnabled ? tr("Yes") : tr("No"), + ToQStr(b.m_Blend.Source), ToQStr(b.m_Blend.Destination), + ToQStr(b.m_Blend.Operation), ToQStr(b.m_AlphaBlend.Source), + ToQStr(b.m_AlphaBlend.Destination), ToQStr(b.m_AlphaBlend.Operation), + ToQStr(b.Logic), mask}); + + i++; + } + + m_Common.exportHTMLTable( + xml, + { + tr("Slot"), tr("Blend Enable"), tr("Logic Enable"), tr("Blend Source"), + tr("Blend Destination"), tr("Blend Operation"), tr("Alpha Blend Source"), + tr("Alpha Blend Destination"), tr("Alpha Blend Operation"), tr("Logic Operation"), + tr("Write Mask"), + }, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Depth State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Depth Test Enable"), tr("Depth Writes Enable"), tr("Depth Function")}, + {om.m_State.DepthEnable ? tr("Yes") : tr("No"), + om.m_State.DepthWrites ? tr("Yes") : tr("No"), ToQStr(om.m_State.DepthFunc)}); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Stencil State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Stencil Test Enable"), tr("Stencil Read Mask"), tr("Stencil Write Mask")}, + {om.m_State.StencilEnable ? tr("Yes") : tr("No"), + Formatter::Format(om.m_State.StencilReadMask, true), + Formatter::Format(om.m_State.StencilWriteMask, true)}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Face"), tr("Function"), tr("Pass Operation"), tr("Fail Operation"), + tr("Depth Fail Operation")}, + { + {tr("Front"), ToQStr(om.m_State.m_FrontFace.Func), ToQStr(om.m_State.m_FrontFace.PassOp), + ToQStr(om.m_State.m_FrontFace.FailOp), ToQStr(om.m_State.m_FrontFace.DepthFailOp)}, + {tr("Back"), ToQStr(om.m_State.m_BackFace.Func), ToQStr(om.m_State.m_BackFace.PassOp), + ToQStr(om.m_State.m_BackFace.FailOp), ToQStr(om.m_State.m_BackFace.DepthFailOp)}, + }); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Render targets")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < om.RenderTargets.count; i++) + { + if(om.RenderTargets[i].Object == ResourceId()) + continue; + + rows.push_back(exportViewHTML(om.RenderTargets[i], i, NULL, QString())); + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Name"), tr("View Type"), tr("Resource Type"), + tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters"), + }, + rows); + } + + if(om.UAVs.count > 0 && om.UAVs[0].Object != ResourceId()) + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Unordered Access Views")); + xml.writeEndElement(); + + QList rows; + + uint32_t i = 0; + + for(; i < om.UAVStartSlot; i++) + rows.push_back({i, tr("Empty"), QString(), QString(), QString(), QString(), 0, 0, 0, 0, + QString(), QString(), QString()}); + + for(; i < (uint32_t)om.RenderTargets.count; i++) + { + if(om.UAVs[i - om.UAVStartSlot].Object == ResourceId()) + continue; + + rows.push_back(exportViewHTML(om.UAVs[i - om.UAVStartSlot], i, + m_Ctx.CurD3D11PipelineState().m_PS.ShaderDetails, QString())); + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Name"), tr("View Type"), tr("Resource Type"), + tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters"), + }, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Depth target")); + xml.writeEndElement(); + + QList rows; + + QString extra; + + if(om.DepthReadOnly && om.StencilReadOnly) + extra = tr("Depth & Stencil Read-Only"); + else if(om.DepthReadOnly) + extra = tr("Depth Read-Only"); + else if(om.StencilReadOnly) + extra = tr("Stencil Read-Only"); + + rows.push_back(exportViewHTML(om.DepthTarget, 0, NULL, extra)); + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Name"), tr("View Type"), tr("Resource Type"), + tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters"), + }, + rows); + } +} + void D3D11PipelineStateViewer::on_exportHTML_clicked() { + QXmlStreamWriter *xmlptr = m_Common.beginHTMLExport(); + + if(xmlptr) + { + QXmlStreamWriter &xml = *xmlptr; + + const QStringList &stageNames = ui->pipeFlow->stageNames(); + const QStringList &stageAbbrevs = ui->pipeFlow->stageAbbreviations(); + + int stage = 0; + for(const QString &sn : stageNames) + { + xml.writeStartElement(lit("div")); + xml.writeStartElement(lit("a")); + xml.writeAttribute(lit("name"), stageAbbrevs[stage]); + xml.writeEndElement(); + xml.writeEndElement(); + + xml.writeStartElement(lit("div")); + xml.writeAttribute(lit("class"), lit("stage")); + + xml.writeStartElement(lit("h1")); + xml.writeCharacters(sn); + xml.writeEndElement(); + + switch(stage) + { + case 0: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_IA); break; + case 1: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_VS); break; + case 2: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_HS); break; + case 3: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_DS); break; + case 4: + exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_GS); + exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_SO); + break; + case 5: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_RS); break; + case 6: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_PS); break; + case 7: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_OM); break; + case 8: exportHTML(xml, m_Ctx.CurD3D11PipelineState().m_CS); break; + } + + xml.writeEndElement(); + + stage++; + } + + m_Common.endHTMLExport(xmlptr); + } } void D3D11PipelineStateViewer::on_meshView_clicked() diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h index 088b79724..a3dca6ded 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h @@ -32,6 +32,7 @@ namespace Ui class D3D11PipelineStateViewer; } +class QXmlStreamWriter; class RDTreeWidget; class RDTreeWidgetItem; struct D3D11ViewTag; @@ -92,6 +93,14 @@ private: void setState(); void clearState(); + QVariantList exportViewHTML(D3D11Pipe::View &view, int i, ShaderReflection *refl, + const QString &extraParams); + void exportHTML(QXmlStreamWriter &xml, D3D11Pipe::IA &ia); + void exportHTML(QXmlStreamWriter &xml, D3D11Pipe::Shader &sh); + void exportHTML(QXmlStreamWriter &xml, D3D11Pipe::SO &so); + void exportHTML(QXmlStreamWriter &xml, D3D11Pipe::Rasterizer &rs); + void exportHTML(QXmlStreamWriter &xml, D3D11Pipe::OM &om); + void setInactiveRow(RDTreeWidgetItem *node); void setEmptyRow(RDTreeWidgetItem *node); void highlightIABind(int slot); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index 96cc43202..d944d6657 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "Code/Resources.h" #include "PipelineStateViewer.h" @@ -939,9 +940,8 @@ void D3D12PipelineStateViewer::setShaderState(const D3D12Pipe::Shader &stage, QL shader->setText( QFormatStr("%1 - %2").arg(ToQStr(state.name)).arg(m_Ctx.CurPipelineState().Abbrev(stage.stage))); else - shader->setText(QFormatStr("%1 - %2 Shader") - .arg(ToQStr(state.name)) - .arg(ToQStr(stage.stage, GraphicsAPI::D3D12))); + shader->setText( + tr("%1 - %2 Shader").arg(ToQStr(state.name)).arg(ToQStr(stage.stage, GraphicsAPI::D3D12))); if(shaderDetails && !shaderDetails->DebugInfo.entryFunc.empty() && !shaderDetails->DebugInfo.files.empty()) @@ -1184,7 +1184,7 @@ void D3D12PipelineStateViewer::setShaderState(const D3D12Pipe::Shader &stage, QL bytesize = uint32_t(b.RootValues.count * 4); if(!filledSlot) - name = lit("Empty"); + name = tr("Empty"); BufferDescription *buf = m_Ctx.GetBuffer(b.Buffer); @@ -2181,8 +2181,930 @@ void D3D12PipelineStateViewer::shaderSave_clicked() m_Common.SaveShaderFile(shaderDetails); } +QVariantList D3D12PipelineStateViewer::exportViewHTML(const D3D12Pipe::View &view, bool rw, + const ShaderResource *shaderInput, + const QString &extraParams) +{ + QString name = tr("Empty"); + QString typeName = tr("Unknown"); + QString format = tr("Unknown"); + uint64_t w = 1; + uint32_t h = 1, d = 1; + uint32_t a = 0; + + QString viewFormat = ToQStr(view.Format.strname); + + TextureDescription *tex = m_Ctx.GetTexture(view.Resource); + BufferDescription *buf = m_Ctx.GetBuffer(view.Resource); + + QString viewParams; + + // check to see if it's a texture + if(tex) + { + w = tex->width; + h = tex->height; + d = tex->depth; + a = tex->arraysize; + format = ToQStr(tex->format.strname); + name = ToQStr(tex->name); + typeName = ToQStr(tex->resType); + + if(view.swizzle[0] != TextureSwizzle::Red || view.swizzle[1] != TextureSwizzle::Green || + view.swizzle[2] != TextureSwizzle::Blue || view.swizzle[3] != TextureSwizzle::Alpha) + { + format += tr(" swizzle[%1%2%3%4]") + .arg(ToQStr(view.swizzle[0])) + .arg(ToQStr(view.swizzle[1])) + .arg(ToQStr(view.swizzle[2])) + .arg(ToQStr(view.swizzle[3])); + } + + if(tex->mips > 1) + viewParams = tr("Highest Mip: %1, Num Mips: %2").arg(view.HighestMip).arg(view.NumMipLevels); + + if(tex->arraysize > 1) + { + if(!viewParams.isEmpty()) + viewParams += lit(", "); + viewParams += + tr("First Slice: %1, Array Size: %2").arg(view.FirstArraySlice).arg(view.ArraySize); + } + + if(view.MinLODClamp > 0.0f) + { + if(!viewParams.isEmpty()) + viewParams += lit(", "); + viewParams += tr("MinLODClamp: %1").arg(view.MinLODClamp); + } + } + + // if not a texture, it must be a buffer + if(buf) + { + w = buf->length; + h = 0; + d = 0; + a = 0; + format = ToQStr(view.Format.strname); + name = ToQStr(buf->name); + typeName = lit("Buffer"); + + if(view.BufferFlags & D3DBufferViewFlags::Raw) + { + typeName = rw ? lit("RWByteAddressBuffer") : lit("ByteAddressBuffer"); + } + else if(view.ElementSize > 0) + { + // for structured buffers, display how many 'elements' there are in the buffer + typeName = QFormatStr("%1[%2]") + .arg(rw ? lit("RWStructuredBuffer") : lit("StructuredBuffer")) + .arg(buf->length / view.ElementSize); + } + + if(view.BufferFlags & D3DBufferViewFlags::Append || view.BufferFlags & D3DBufferViewFlags::Counter) + { + typeName += tr(" (Count: %1)").arg(view.BufferStructCount); + } + + if(shaderInput && !shaderInput->IsTexture) + { + if(view.Format.compType == CompType::Typeless) + { + if(shaderInput->variableType.members.count > 0) + viewFormat = format = lit("struct ") + ToQStr(shaderInput->variableType.descriptor.name); + else + viewFormat = format = ToQStr(shaderInput->variableType.descriptor.name); + } + else + { + format = ToQStr(view.Format.strname); + } + } + + viewParams = tr("First Element: %1, Num Elements %2, Flags %3") + .arg(view.FirstElement) + .arg(view.NumElements) + .arg(ToQStr(view.BufferFlags)); + + if(view.CounterResource != ResourceId()) + { + QString counterName = tr("Buffer %1").arg(ToQStr(view.CounterResource)); + BufferDescription *counterBuf = m_Ctx.GetBuffer(view.CounterResource); + if(counterBuf) + counterName = ToQStr(counterBuf->name); + viewParams += tr(", Counter in %1 at %2 bytes").arg(counterName).arg(view.CounterByteOffset); + } + } + + if(viewParams.isEmpty()) + viewParams = extraParams; + else + viewParams += lit(", ") + extraParams; + + return {name, ToQStr(view.Type), typeName, w, h, d, a, viewFormat, format, viewParams}; +} + +void D3D12PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D12Pipe::IA &ia) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Input Layouts")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D12Pipe::Layout &l : ia.layouts) + { + rows.push_back({i, ToQStr(l.SemanticName), l.SemanticIndex, ToQStr(l.Format.strname), + l.InputSlot, l.ByteOffset, (bool)l.PerInstance, l.InstanceDataStepRate}); + + i++; + } + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Semantic Name"), tr("Semantic Index"), tr("Format"), tr("Input Slot"), + tr("Byte Offset"), tr("Per Instance"), tr("Instance Data Step Rate")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Vertex Buffers")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D12Pipe::VB &vb : ia.vbuffers) + { + QString name = tr("Buffer %1").arg(ToQStr(vb.Buffer)); + uint64_t length = 0; + + if(vb.Buffer == ResourceId()) + { + continue; + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(vb.Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + length = qMin(length, (uint64_t)vb.Size); + + rows.push_back({i, name, vb.Stride, vb.Offset, length}); + + i++; + } + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Buffer"), tr("Stride"), tr("Offset"), tr("Byte Length")}, rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Index Buffer")); + xml.writeEndElement(); + + QString name = tr("Buffer %1").arg(ToQStr(ia.ibuffer.Buffer)); + uint64_t length = 0; + + if(ia.ibuffer.Buffer == ResourceId()) + { + name = tr("Empty"); + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(ia.ibuffer.Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + length = qMin(length, (uint64_t)ia.ibuffer.Size); + + QString ifmt = lit("UNKNOWN"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 2) + ifmt = lit("R16_UINT"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 4) + ifmt = lit("R32_UINT"); + + m_Common.exportHTMLTable(xml, {tr("Buffer"), tr("Format"), tr("Offset"), tr("Byte Length")}, + {name, ifmt, ia.ibuffer.Offset, length}); + } + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Primitive Topology")}, {ToQStr(m_Ctx.CurDrawcall()->topology)}); +} + +void D3D12PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D12Pipe::Shader &sh) +{ + ShaderReflection *shaderDetails = sh.ShaderDetails; + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Shader")); + xml.writeEndElement(); + + QString shadername = tr("Unknown"); + + const D3D12Pipe::State &state = m_Ctx.CurD3D12PipelineState(); + + if(sh.Object == ResourceId()) + shadername = tr("Unbound"); + else if(state.customName) + shadername = + QFormatStr("%1 - %2").arg(ToQStr(state.name)).arg(m_Ctx.CurPipelineState().Abbrev(sh.stage)); + else + shadername = + tr("%1 - %2 Shader").arg(ToQStr(state.name)).arg(ToQStr(sh.stage, GraphicsAPI::D3D12)); + + if(shaderDetails && !shaderDetails->DebugInfo.entryFunc.empty() && + !shaderDetails->DebugInfo.files.empty()) + { + QString shaderfn; + + int entryFile = shaderDetails->DebugInfo.entryFile; + if(entryFile < 0 || entryFile >= shaderDetails->DebugInfo.files.count) + entryFile = 0; + + shaderfn = QFileInfo(ToQStr(shaderDetails->DebugInfo.files[entryFile].first)).fileName(); + + shadername = + QFormatStr("%1() - %2").arg(ToQStr(shaderDetails->DebugInfo.entryFunc)).arg(shaderfn); + } + + xml.writeStartElement(lit("p")); + xml.writeCharacters(shadername); + xml.writeEndElement(); + + if(sh.Object == ResourceId()) + return; + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Shader Resource Views")); + xml.writeEndElement(); + + QList rows; + + for(int space = 0; space < sh.Spaces.count; space++) + { + for(int reg = 0; reg < sh.Spaces[space].SRVs.count; reg++) + { + const D3D12Pipe::View &v = sh.Spaces[space].SRVs[reg]; + + // consider this register to not exist - it's in a gap defined by sparse root signature + // elements + if(v.RootElement == ~0U) + continue; + + const ShaderResource *shaderInput = NULL; + + if(sh.ShaderDetails) + { + for(int i = 0; i < sh.BindpointMapping.ReadOnlyResources.count; i++) + { + const BindpointMap &b = sh.BindpointMapping.ReadOnlyResources[i]; + const ShaderResource &res = sh.ShaderDetails->ReadOnlyResources[i]; + + bool regMatch = b.bind == reg; + + // handle unbounded arrays specially. It's illegal to have an unbounded array with + // anything after it + if(b.bind <= reg) + regMatch = (b.arraySize == ~0U) || (b.bind + (int)b.arraySize > reg); + + if(b.bindset == space && regMatch && !res.IsReadOnly && !res.IsSampler) + { + shaderInput = &res; + break; + } + } + } + + QString rootel = v.Immediate ? tr("#%1 Direct").arg(v.RootElement) + : tr("#%1 Table[%2]").arg(v.RootElement).arg(v.TableIndex); + + QVariantList row = exportViewHTML(v, false, shaderInput, QString()); + + row.push_front(reg); + row.push_front(space); + row.push_front(rootel); + + rows.push_back(row); + } + } + + m_Common.exportHTMLTable( + xml, {tr("Root Sig El"), tr("Space"), tr("Register"), tr("Resource"), tr("View Type"), + tr("Resource Type"), tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Unordered Access Views")); + xml.writeEndElement(); + + QList rows; + + for(int space = 0; space < sh.Spaces.count; space++) + { + for(int reg = 0; reg < sh.Spaces[space].UAVs.count; reg++) + { + const D3D12Pipe::View &v = sh.Spaces[space].UAVs[reg]; + + // consider this register to not exist - it's in a gap defined by sparse root signature + // elements + if(v.RootElement == ~0U) + continue; + + const ShaderResource *shaderInput = NULL; + + if(sh.ShaderDetails) + { + for(int i = 0; i < sh.BindpointMapping.ReadOnlyResources.count; i++) + { + const BindpointMap &b = sh.BindpointMapping.ReadOnlyResources[i]; + const ShaderResource &res = sh.ShaderDetails->ReadOnlyResources[i]; + + bool regMatch = b.bind == reg; + + // handle unbounded arrays specially. It's illegal to have an unbounded array with + // anything after it + if(b.bind <= reg) + regMatch = (b.arraySize == ~0U) || (b.bind + (int)b.arraySize > reg); + + if(b.bindset == space && regMatch && !res.IsReadOnly && !res.IsSampler) + { + shaderInput = &res; + break; + } + } + } + + QString rootel = v.Immediate ? tr("#%1 Direct").arg(v.RootElement) + : tr("#%1 Table[%2]").arg(v.RootElement).arg(v.TableIndex); + + QVariantList row = exportViewHTML(v, true, shaderInput, QString()); + + row.push_front(reg); + row.push_front(space); + row.push_front(rootel); + + rows.push_back(row); + } + } + + m_Common.exportHTMLTable( + xml, {tr("Root Sig El"), tr("Space"), tr("Register"), tr("Resource"), tr("View Type"), + tr("Resource Type"), tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Samplers")); + xml.writeEndElement(); + + QList rows; + + for(int space = 0; space < sh.Spaces.count; space++) + { + for(int reg = 0; reg < sh.Spaces[space].Samplers.count; reg++) + { + const D3D12Pipe::Sampler &s = sh.Spaces[space].Samplers[reg]; + + // consider this register to not exist - it's in a gap defined by sparse root signature + // elements + if(s.RootElement == ~0U) + continue; + + const ShaderResource *shaderInput = NULL; + + if(sh.ShaderDetails) + { + for(int i = 0; i < sh.BindpointMapping.ReadOnlyResources.count; i++) + { + const BindpointMap &b = sh.BindpointMapping.ReadOnlyResources[i]; + const ShaderResource &res = sh.ShaderDetails->ReadOnlyResources[i]; + + bool regMatch = b.bind == reg; + + // handle unbounded arrays specially. It's illegal to have an unbounded array with + // anything after it + if(b.bind <= reg) + regMatch = (b.arraySize == ~0U) || (b.bind + (int)b.arraySize > reg); + + if(b.bindset == space && regMatch && res.IsSampler) + { + shaderInput = &res; + break; + } + } + } + + QString rootel = s.Immediate ? tr("#%1 Static").arg(s.RootElement) + : tr("#%1 Table[%2]").arg(s.RootElement).arg(s.TableIndex); + + { + QString regname = QString::number(reg); + + if(shaderInput && !shaderInput->name.empty()) + regname += lit(": ") + ToQStr(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.AddressU), ToQStr(s.AddressV), ToQStr(s.AddressW)}; + + // arrange like either UVW: WRAP or UV: WRAP, W: CLAMP + for(int a = 0; a < 3; a++) + { + const QString str[] = {lit("U"), lit("V"), lit("W")}; + 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); + + QString filter = ToQStr(s.Filter); + + if(s.MaxAniso > 1) + filter += QFormatStr(" %1x").arg(s.MaxAniso); + + if(s.Filter.func == FilterFunc::Comparison) + filter += QFormatStr(" (%1)").arg(ToQStr(s.Comparison)); + else if(s.Filter.func != FilterFunc::Normal) + filter += QFormatStr(" (%1)").arg(ToQStr(s.Filter.func)); + + rows.push_back({rootel, space, regname, 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}); + } + } + } + + m_Common.exportHTMLTable(xml, {tr("Root Sig El"), tr("Space"), tr("Register"), tr("Addressing"), + tr("Filter"), tr("LOD Clamp"), tr("LOD Bias")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Constant Buffers")); + xml.writeEndElement(); + + QList rows; + + for(int space = 0; space < sh.Spaces.count; space++) + { + for(int reg = 0; reg < sh.Spaces[space].ConstantBuffers.count; reg++) + { + const D3D12Pipe::CBuffer &b = sh.Spaces[space].ConstantBuffers[reg]; + + const ConstantBlock *shaderCBuf = NULL; + + if(sh.ShaderDetails) + { + for(int i = 0; i < sh.BindpointMapping.ConstantBlocks.count; i++) + { + const BindpointMap &bm = sh.BindpointMapping.ConstantBlocks[i]; + const ConstantBlock &res = sh.ShaderDetails->ConstantBlocks[i]; + + bool regMatch = bm.bind == reg; + + // handle unbounded arrays specially. It's illegal to have an unbounded array with + // anything after it + if(bm.bind <= reg) + regMatch = (bm.arraySize == ~0U) || (bm.bind + (int)bm.arraySize > reg); + + if(bm.bindset == space && regMatch) + { + shaderCBuf = &res; + break; + } + } + } + + QString rootel; + + if(b.Immediate) + { + if(!b.RootValues.empty()) + rootel = tr("#%1 Consts").arg(b.RootElement); + else + rootel = tr("#%1 Direct").arg(b.RootElement); + } + else + { + rootel = tr("#%1 Table[%2]").arg(b.RootElement).arg(b.TableIndex); + } + + { + QString name = tr("Constant Buffer %1").arg(ToQStr(b.Buffer)); + uint64_t length = b.ByteSize; + uint64_t offset = b.Offset; + int numvars = shaderCBuf ? shaderCBuf->variables.count : 0; + uint32_t bytesize = shaderCBuf ? shaderCBuf->byteSize : 0; + + if(b.Immediate && !b.RootValues.empty()) + bytesize = uint32_t(b.RootValues.count * 4); + + BufferDescription *buf = m_Ctx.GetBuffer(b.Buffer); + + if(buf) + name = ToQStr(buf->name); + else + name = tr("Empty"); + + QString regname = QString::number(reg); + + if(shaderCBuf && !shaderCBuf->name.empty()) + regname += lit(": ") + ToQStr(shaderCBuf->name); + + length = qMin(length, (uint64_t)bytesize); + + rows.push_back({rootel, space, regname, name, offset, length, numvars}); + } + } + } + + m_Common.exportHTMLTable(xml, + {tr("Root Signature Index"), tr("Space"), tr("Register"), tr("Buffer"), + tr("Byte Offset"), tr("Byte Size"), tr("Number of Variables")}, + rows); + } +} + +void D3D12PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D12Pipe::Streamout &so) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Stream Out Targets")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D12Pipe::SOBind &o : so.Outputs) + { + QString name = tr("Buffer %1").arg(ToQStr(o.Buffer)); + uint64_t length = 0; + QString counterName = tr("Buffer %1").arg(ToQStr(o.WrittenCountBuffer)); + uint64_t counterLength = 0; + + if(o.Buffer == ResourceId()) + { + name = tr("Empty"); + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(o.Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + if(o.WrittenCountBuffer == ResourceId()) + { + counterName = tr("Empty"); + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(o.WrittenCountBuffer); + if(buf) + { + counterName = ToQStr(buf->name); + counterLength = buf->length; + } + } + + length = qMin(length, o.Size); + + rows.push_back({i, name, o.Offset, length, counterName, o.WrittenCountOffset, counterLength}); + + i++; + } + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Buffer"), tr("Offset"), tr("Byte Length"), tr("Counter Buffer"), + tr("Counter Offset"), tr("Counter Byte Length")}, + rows); + } +} + +void D3D12PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D12Pipe::Rasterizer &rs) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("States")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Fill Mode"), tr("Cull Mode"), tr("Front CCW")}, + {ToQStr(rs.m_State.fillMode), ToQStr(rs.m_State.cullMode), + rs.m_State.FrontCCW ? tr("Yes") : tr("No")}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Line AA Enable"), tr("Multisample Enable"), tr("Forced Sample Count"), + tr("Conservative Raster"), tr("Sample Mask")}, + {rs.m_State.AntialiasedLineEnable ? tr("Yes") : tr("No"), + rs.m_State.MultisampleEnable ? tr("Yes") : tr("No"), rs.m_State.ForcedSampleCount, + rs.m_State.ConservativeRasterization ? tr("Yes") : tr("No"), + Formatter::Format(rs.SampleMask, true)}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Depth Clip"), tr("Depth Bias"), tr("Depth Bias Clamp"), tr("Slope Scaled Bias")}, + {rs.m_State.DepthClip ? tr("Yes") : tr("No"), rs.m_State.DepthBias, + Formatter::Format(rs.m_State.DepthBiasClamp), + Formatter::Format(rs.m_State.SlopeScaledDepthBias)}); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Viewports")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D12Pipe::Viewport &v : rs.Viewports) + { + if(v.Width == v.Height && v.Width == 0 && v.Height == 0) + continue; + + rows.push_back({i, v.X, v.Y, v.Width, v.Height, v.MinDepth, v.MaxDepth}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("X"), tr("Y"), tr("Width"), tr("Height"), + tr("Min Depth"), tr("Max Depth")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Scissors")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D12Pipe::Scissor &s : rs.Scissors) + { + if(s.right == 0 && s.bottom == 0) + continue; + + rows.push_back({i, s.left, s.top, s.right - s.left, s.bottom - s.top}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("X"), tr("Y"), tr("Width"), tr("Height")}, rows); + } +} + +void D3D12PipelineStateViewer::exportHTML(QXmlStreamWriter &xml, D3D12Pipe::OM &om) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Blend State")); + xml.writeEndElement(); + + QString blendFactor = QFormatStr("%1, %2, %3, %4") + .arg(om.m_BlendState.BlendFactor[0], 0, 'f', 2) + .arg(om.m_BlendState.BlendFactor[1], 0, 'f', 2) + .arg(om.m_BlendState.BlendFactor[2], 0, 'f', 2) + .arg(om.m_BlendState.BlendFactor[3], 0, 'f', 2); + + m_Common.exportHTMLTable(xml, {tr("Independent Blend Enable"), tr("Alpha to Coverage"), + tr("Blend Factor"), tr("Multisampling Rate")}, + {om.m_BlendState.IndependentBlend ? tr("Yes") : tr("No"), + om.m_BlendState.AlphaToCoverage ? tr("Yes") : tr("No"), blendFactor, + tr("%1x %2 qual").arg(om.multiSampleCount).arg(om.multiSampleQuality)}); + + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Target Blends")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const D3D12Pipe::Blend &b : om.m_BlendState.Blends) + { + if(i >= om.RenderTargets.count) + continue; + + QString mask = QFormatStr("%1%2%3%4") + .arg((b.WriteMask & 0x1) == 0 ? lit("_") : lit("R")) + .arg((b.WriteMask & 0x2) == 0 ? lit("_") : lit("G")) + .arg((b.WriteMask & 0x4) == 0 ? lit("_") : lit("B")) + .arg((b.WriteMask & 0x8) == 0 ? lit("_") : lit("A")); + + rows.push_back({i, b.Enabled ? tr("Yes") : tr("No"), b.LogicEnabled ? tr("Yes") : tr("No"), + ToQStr(b.m_Blend.Source), ToQStr(b.m_Blend.Destination), + ToQStr(b.m_Blend.Operation), ToQStr(b.m_AlphaBlend.Source), + ToQStr(b.m_AlphaBlend.Destination), ToQStr(b.m_AlphaBlend.Operation), + ToQStr(b.Logic), mask}); + + i++; + } + + m_Common.exportHTMLTable( + xml, + { + tr("Slot"), tr("Blend Enable"), tr("Logic Enable"), tr("Blend Source"), + tr("Blend Destination"), tr("Blend Operation"), tr("Alpha Blend Source"), + tr("Alpha Blend Destination"), tr("Alpha Blend Operation"), tr("Logic Operation"), + tr("Write Mask"), + }, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Depth State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Depth Test Enable"), tr("Depth Writes Enable"), tr("Depth Function")}, + {om.m_State.DepthEnable ? tr("Yes") : tr("No"), + om.m_State.DepthWrites ? tr("Yes") : tr("No"), ToQStr(om.m_State.DepthFunc)}); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Stencil State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Stencil Test Enable"), tr("Stencil Read Mask"), tr("Stencil Write Mask")}, + {om.m_State.StencilEnable ? tr("Yes") : tr("No"), + Formatter::Format(om.m_State.StencilReadMask, true), + Formatter::Format(om.m_State.StencilWriteMask, true)}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Face"), tr("Function"), tr("Pass Operation"), tr("Fail Operation"), + tr("Depth Fail Operation")}, + { + {tr("Front"), ToQStr(om.m_State.m_FrontFace.Func), ToQStr(om.m_State.m_FrontFace.PassOp), + ToQStr(om.m_State.m_FrontFace.FailOp), ToQStr(om.m_State.m_FrontFace.DepthFailOp)}, + {tr("Back"), ToQStr(om.m_State.m_BackFace.Func), ToQStr(om.m_State.m_BackFace.PassOp), + ToQStr(om.m_State.m_BackFace.FailOp), ToQStr(om.m_State.m_BackFace.DepthFailOp)}, + }); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Render targets")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < om.RenderTargets.count; i++) + { + if(om.RenderTargets[i].Resource == ResourceId()) + continue; + + QVariantList row = exportViewHTML(om.RenderTargets[i], false, NULL, QString()); + row.push_front(i); + + rows.push_back(row); + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Name"), tr("View Type"), tr("Resource Type"), + tr("Width"), tr("Height"), tr("Depth"), tr("Array Size"), + tr("View Format"), tr("Resource Format"), tr("View Parameters"), + }, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Depth target")); + xml.writeEndElement(); + + QString extra; + + if(om.DepthReadOnly && om.StencilReadOnly) + extra = tr("Depth & Stencil Read-Only"); + else if(om.DepthReadOnly) + extra = tr("Depth Read-Only"); + else if(om.StencilReadOnly) + extra = tr("Stencil Read-Only"); + + m_Common.exportHTMLTable(xml, + { + tr("Name"), tr("View Type"), tr("Resource Type"), tr("Width"), + tr("Height"), tr("Depth"), tr("Array Size"), tr("View Format"), + tr("Resource Format"), tr("View Parameters"), + }, + {exportViewHTML(om.DepthTarget, false, NULL, extra)}); + } +} + void D3D12PipelineStateViewer::on_exportHTML_clicked() { + QXmlStreamWriter *xmlptr = m_Common.beginHTMLExport(); + + if(xmlptr) + { + QXmlStreamWriter &xml = *xmlptr; + + const QStringList &stageNames = ui->pipeFlow->stageNames(); + const QStringList &stageAbbrevs = ui->pipeFlow->stageAbbreviations(); + + int stage = 0; + for(const QString &sn : stageNames) + { + xml.writeStartElement(lit("div")); + xml.writeStartElement(lit("a")); + xml.writeAttribute(lit("name"), stageAbbrevs[stage]); + xml.writeEndElement(); + xml.writeEndElement(); + + xml.writeStartElement(lit("div")); + xml.writeAttribute(lit("class"), lit("stage")); + + xml.writeStartElement(lit("h1")); + xml.writeCharacters(sn); + xml.writeEndElement(); + + switch(stage) + { + case 0: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_IA); break; + case 1: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_VS); break; + case 2: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_HS); break; + case 3: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_DS); break; + case 4: + exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_GS); + exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_SO); + break; + case 5: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_RS); break; + case 6: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_PS); break; + case 7: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_OM); break; + case 8: exportHTML(xml, m_Ctx.CurD3D12PipelineState().m_CS); break; + } + + xml.writeEndElement(); + + stage++; + } + + m_Common.endHTMLExport(xmlptr); + } } void D3D12PipelineStateViewer::on_meshView_clicked() diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h index 02e558b39..e1f50740e 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h @@ -32,6 +32,8 @@ namespace Ui class D3D12PipelineStateViewer; } +class QXmlStreamWriter; + class RDTreeWidget; class RDTreeWidgetItem; struct D3D12ViewTag; @@ -105,6 +107,14 @@ private: bool showNode(bool usedSlot, bool filledSlot); + QVariantList exportViewHTML(const D3D12Pipe::View &view, bool rw, + const ShaderResource *shaderInput, const QString &extraParams); + void exportHTML(QXmlStreamWriter &xml, D3D12Pipe::IA &ia); + void exportHTML(QXmlStreamWriter &xml, D3D12Pipe::Shader &sh); + void exportHTML(QXmlStreamWriter &xml, D3D12Pipe::Streamout &so); + void exportHTML(QXmlStreamWriter &xml, D3D12Pipe::Rasterizer &rs); + void exportHTML(QXmlStreamWriter &xml, D3D12Pipe::OM &om); + // keep track of the VB nodes (we want to be able to highlight them easily on hover) QList m_VBNodes; }; diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index 7b26e43be..c85c1bb2f 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "Code/Resources.h" #include "PipelineStateViewer.h" @@ -679,7 +680,7 @@ void GLPipelineStateViewer::setShaderState(const GLPipe::Shader &stage, QLabel * if(!filledSlot) { - name = lit("Empty"); + name = tr("Empty"); format = lit("-"); typeName = lit("-"); w = h = d = a = 0; @@ -2304,8 +2305,1050 @@ void GLPipelineStateViewer::shaderSave_clicked() m_Common.SaveShaderFile(shaderDetails); } +void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, GLPipe::VertexInput &vtx) +{ + const GLPipe::State &pipe = m_Ctx.CurGLPipelineState(); + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Vertex Attributes")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const GLPipe::VertexAttribute &a : vtx.attributes) + { + QString generic; + if(!a.Enabled) + generic = MakeGenericValueString(a.Format.compCount, a.Format.compType, a); + rows.push_back( + {i, (bool)a.Enabled, a.BufferSlot, ToQStr(a.Format.strname), a.RelativeOffset, generic}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("Enabled"), tr("Vertex Buffer Slot"), + tr("Format"), tr("Relative Offset"), tr("Generic Value")}, + rows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Vertex Buffers")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const GLPipe::VB &vb : vtx.vbuffers) + { + QString name = tr("Buffer %1").arg(ToQStr(vb.Buffer)); + uint64_t length = 0; + + if(vb.Buffer == ResourceId()) + { + continue; + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(vb.Buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + rows.push_back({i, name, vb.Stride, vb.Offset, vb.Divisor, length}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("Buffer"), tr("Stride"), tr("Offset"), + tr("Instance Divisor"), tr("Byte Length")}, + rows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Index Buffer")); + xml.writeEndElement(); + + QString name = tr("Buffer %1").arg(ToQStr(vtx.ibuffer)); + uint64_t length = 0; + + if(vtx.ibuffer == ResourceId()) + { + name = tr("Empty"); + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(vtx.ibuffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + QString ifmt = lit("UNKNOWN"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 2) + ifmt = lit("R16_UINT"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 4) + ifmt = lit("R32_UINT"); + + m_Common.exportHTMLTable(xml, {tr("Buffer"), tr("Format"), tr("Byte Length")}, + {name, ifmt, length}); + } + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Primitive Topology")}, {ToQStr(m_Ctx.CurDrawcall()->topology)}); + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("States")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Primitive Restart"), tr("Restart Index"), tr("Provoking Vertex Last")}, + {(bool)vtx.primitiveRestart, vtx.restartIndex, + vtx.provokingVertexLast ? tr("Yes") : tr("No")}); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Rasterizer Discard"), tr("Clip Origin Lower Left"), tr("Clip Space Z")}, + {pipe.m_VtxProcess.discard ? tr("Yes") : tr("No"), + pipe.m_VtxProcess.clipOriginLowerLeft ? tr("Yes") : tr("No"), + pipe.m_VtxProcess.clipNegativeOneToOne ? tr("-1 to 1") : tr("0 to 1")}); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + QList clipPlaneRows; + + for(int i = 0; i < 8; i++) + clipPlaneRows.push_back({i, pipe.m_VtxProcess.clipPlanes[i] ? tr("Yes") : tr("No")}); + + m_Common.exportHTMLTable(xml, + { + tr("User Clip Plane"), tr("Enabled"), + }, + clipPlaneRows); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, + { + tr("Default Inner Tessellation Level"), tr("Default Outer Tessellation level"), + }, + { + QFormatStr("%1, %2") + .arg(pipe.m_VtxProcess.defaultInnerLevel[0]) + .arg(pipe.m_VtxProcess.defaultInnerLevel[1]), + + QFormatStr("%1, %2, %3, %4") + .arg(pipe.m_VtxProcess.defaultOuterLevel[0]) + .arg(pipe.m_VtxProcess.defaultOuterLevel[1]) + .arg(pipe.m_VtxProcess.defaultOuterLevel[2]) + .arg(pipe.m_VtxProcess.defaultOuterLevel[3]), + }); + } +} + +void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, GLPipe::Shader &sh) +{ + const GLPipe::State &pipe = m_Ctx.CurGLPipelineState(); + ShaderReflection *shaderDetails = sh.ShaderDetails; + const ShaderBindpointMapping &mapping = sh.BindpointMapping; + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Shader")); + xml.writeEndElement(); + + QString shadername = tr("Unknown"); + + if(sh.Object == ResourceId()) + shadername = tr("Unbound"); + else + shadername = ToQStr(sh.ShaderName); + + if(sh.Object == ResourceId()) + { + shadername = tr("Unbound"); + } + else + { + QString shname = tr("%1 Shader").arg(ToQStr(sh.stage, GraphicsAPI::OpenGL)); + + if(!sh.customShaderName && !sh.customProgramName && !sh.customPipelineName) + { + shadername = QFormatStr("%1 %2").arg(shname).arg(ToQStr(sh.Object)); + } + else + { + if(sh.customShaderName) + shname = ToQStr(sh.ShaderName); + + if(sh.customProgramName) + shname = QFormatStr("%1 - %2").arg(ToQStr(sh.ProgramName)).arg(shname); + + if(sh.customPipelineName && sh.PipelineActive) + shname = QFormatStr("%1 - %2").arg(ToQStr(sh.PipelineName)).arg(shname); + + shadername = shname; + } + } + + xml.writeStartElement(tr("p")); + xml.writeCharacters(shadername); + xml.writeEndElement(); + + if(sh.Object == ResourceId()) + return; + } + + QList textureRows; + QList samplerRows; + QList cbufferRows; + 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 BindpointMap *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.Resource != ResourceId()); + bool usedSlot = (shaderInput && map->used); + + if(shaderInput) + { + // do texture + { + QString slotname = QString::number(i); + + if(shaderInput && shaderInput->name.count > 0) + slotname += QFormatStr(": %1").arg(ToQStr(shaderInput->name)); + + uint32_t w = 1, h = 1, d = 1; + uint32_t a = 1; + QString format = tr("Unknown"); + QString name = tr("Shader Resource %1").arg(ToQStr(r.Resource)); + QString typeName = tr("Unknown"); + + if(!filledSlot) + { + name = tr("Empty"); + format = lit("-"); + typeName = lit("-"); + w = h = d = a = 0; + } + + TextureDescription *tex = m_Ctx.GetTexture(r.Resource); + if(tex) + { + w = tex->width; + h = tex->height; + d = tex->depth; + a = tex->arraysize; + format = ToQStr(tex->format.strname); + name = ToQStr(tex->name); + typeName = ToQStr(tex->resType); + + if(tex->format.special && (tex->format.specialFormat == SpecialFormat::D16S8 || + tex->format.specialFormat == SpecialFormat::D24S8 || + tex->format.specialFormat == SpecialFormat::D32S8)) + { + if(r.DepthReadChannel == 0) + format += tr(" Depth-Repipead"); + else if(r.DepthReadChannel == 1) + format += tr(" Stencil-Read"); + } + else if(r.Swizzle[0] != TextureSwizzle::Red || r.Swizzle[1] != TextureSwizzle::Green || + r.Swizzle[2] != TextureSwizzle::Blue || r.Swizzle[3] != TextureSwizzle::Alpha) + { + format += QFormatStr(" swizzle[%1%2%3%4]") + .arg(ToQStr(r.Swizzle[0])) + .arg(ToQStr(r.Swizzle[1])) + .arg(ToQStr(r.Swizzle[2])) + .arg(ToQStr(r.Swizzle[3])); + } + } + + textureRows.push_back({slotname, name, typeName, w, h, d, a, format}); + } + + // do sampler + { + QString slotname = QString::number(i); + + if(shaderInput && shaderInput->name.count > 0) + slotname += QFormatStr(": %1").arg(ToQStr(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), ToQStr(s.AddressT), ToQStr(s.AddressR)}; + + // 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.ResType == TextureDim::TextureCube || r.ResType == TextureDim::TextureCubeArray) + { + addressing += s.SeamlessCube ? tr(" Seamless") : tr(" Non-Seamless"); + } + + QString filter = ToQStr(s.Filter); + + if(s.MaxAniso > 1) + filter += tr(" Aniso%1x").arg(s.MaxAniso); + + if(s.Filter.func == FilterFunc::Comparison) + filter += QFormatStr(" %1").arg(ToQStr(s.Comparison)); + else if(s.Filter.func != FilterFunc::Normal) + filter += QFormatStr(" (%1)").arg(ToQStr(s.Filter.func)); + + 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) + { + int bindPoint = mapping.ConstantBlocks[i].bind; + + const GLPipe::Buffer *b = NULL; + + if(bindPoint >= 0 && bindPoint < pipe.UniformBuffers.count) + b = &pipe.UniformBuffers[bindPoint]; + + bool filledSlot = !shaderCBuf.bufferBacked || (b && b->Resource != ResourceId()); + bool usedSlot = mapping.ConstantBlocks[i].used; + + // show if + { + uint64_t offset = 0; + uint64_t length = 0; + int numvars = shaderCBuf.variables.count; + uint64_t byteSize = shaderCBuf.byteSize; + + QString slotname = tr("Uniforms"); + QString name; + QString sizestr = tr("%1 Variables").arg(numvars); + QString byterange; + + if(!filledSlot) + { + name = tr("Empty"); + length = 0; + } + + if(b) + { + slotname = QFormatStr("%1: %2").arg(bindPoint).arg(ToQStr(shaderCBuf.name)); + name = tr("UBO %1").arg(ToQStr(b->Resource)); + offset = b->Offset; + length = b->Size; + + BufferDescription *buf = m_Ctx.GetBuffer(b->Resource); + if(buf) + { + name = ToQStr(buf->name); + if(length == 0) + length = buf->length; + } + + if(length == byteSize) + sizestr = tr("%1 Variables, %2 bytes").arg(numvars).arg(length); + else + sizestr = + tr("%1 Variables, %2 bytes needed, %3 provided").arg(numvars).arg(byteSize).arg(length); + + byterange = QFormatStr("%1 - %2").arg(offset).arg(offset + length); + } + + 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) + { + im = &pipe.Images[bindPoint]; + id = pipe.Images[bindPoint].Resource; + } + + if(readWriteType == GLReadWriteType::Atomic && bindPoint >= 0 && + bindPoint < pipe.AtomicBuffers.count) + { + bf = &pipe.AtomicBuffers[bindPoint]; + id = pipe.AtomicBuffers[bindPoint].Resource; + } + + if(readWriteType == GLReadWriteType::SSBO && bindPoint >= 0 && + bindPoint < pipe.ShaderStorageBuffers.count) + { + bf = &pipe.ShaderStorageBuffers[bindPoint]; + id = pipe.ShaderStorageBuffers[bindPoint].Resource; + } + + 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(ToQStr(res.name)); + QString 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 = ToQStr(im->Format.strname); + } + + // check to see if it's a texture + 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); + } + + name = ToQStr(tex->name); + } + + // if not a texture, it must be a buffer + BufferDescription *buf = m_Ctx.GetBuffer(id); + if(buf) + { + uint64_t offset = 0; + uint64_t length = buf->length; + if(bf && bf->Size > 0) + { + offset = bf->Offset; + length = bf->Size; + } + + if(offset > 0) + dimensions = tr("%1 bytes at offset %2 bytes").arg(length).arg(offset); + else + dimensions = tr("%1 bytes").arg(length); + + name = ToQStr(buf->name); + } + + if(!filledSlot) + { + name = tr("Empty"); + dimensions = tr("-"); + access = tr("-"); + } + + readwriteRows.push_back({binding, slotname, name, dimensions, format, access}); + } + i++; + } + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Textures")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("Name"), tr("Type"), tr("Width"), tr("Height"), + tr("Depth"), tr("Array Size"), tr("Format")}, + textureRows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Samplers")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Addressing"), tr("Filtering"), tr("LOD Clamping"), tr("LOD Bias")}, + samplerRows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Uniform Buffers")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("Name"), tr("Byte Range"), tr("Size")}, + cbufferRows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Subroutines")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Index"), tr("Value")}, subRows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Read-write resources")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, + { + tr("Binding"), tr("Resource"), tr("Name"), tr("Dimensions"), tr("Format"), tr("Access"), + }, + readwriteRows); + } +} + +void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, GLPipe::Feedback &xfb) +{ + const GLPipe::State &pipe = m_Ctx.CurGLPipelineState(); + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("States")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Active"), tr("Paused")}, + {xfb.Active ? tr("Yes") : tr("No"), xfb.Paused ? tr("Yes") : tr("No")}); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Transform Feedback Targets")); + xml.writeEndElement(); + + QList rows; + + for(size_t i = 0; i < ARRAY_COUNT(xfb.BufferBinding); i++) + { + QString name = tr("Buffer %1").arg(ToQStr(xfb.BufferBinding[i])); + uint64_t length = 0; + + if(xfb.BufferBinding[i] == ResourceId()) + { + name = tr("Empty"); + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(xfb.BufferBinding[i]); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + rows.push_back({i, name, xfb.Offset[i], xfb.Size[i], length}); + } + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Buffer"), tr("Offset"), tr("Binding size"), tr("Buffer byte Length")}, + rows); + } +} + +void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, GLPipe::Rasterizer &rs) +{ + const GLPipe::State &pipe = m_Ctx.CurGLPipelineState(); + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Rasterizer")); + xml.writeEndElement(); + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("States")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Fill Mode"), tr("Cull Mode"), tr("Front CCW")}, + {ToQStr(rs.m_State.fillMode), ToQStr(rs.m_State.cullMode), + rs.m_State.FrontCCW ? tr("Yes") : tr("No")}); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Multisample Enable"), tr("Sample Shading"), tr("Sample Mask"), + tr("Sample Coverage"), tr("Sample Coverage Invert"), tr("Alpha to Coverage"), + tr("Alpha to One"), tr("Min Sample Shading Rate")}, + { + rs.m_State.MultisampleEnable ? tr("Yes") : tr("No"), + rs.m_State.SampleShading ? tr("Yes") : tr("No"), + rs.m_State.SampleMask ? Formatter::Format(rs.m_State.SampleMaskValue, true) : tr("No"), + rs.m_State.SampleCoverage ? QString::number(rs.m_State.SampleCoverageValue) : tr("No"), + rs.m_State.SampleCoverageInvert ? tr("Yes") : tr("No"), + rs.m_State.SampleAlphaToCoverage ? tr("Yes") : tr("No"), + rs.m_State.SampleAlphaToOne ? tr("Yes") : tr("No"), + Formatter::Format(rs.m_State.MinSampleShadingRate), + }); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, + { + tr("Programmable Point Size"), tr("Fixed Point Size"), tr("Line Width"), + tr("Point Fade Threshold"), tr("Point Origin Upper Left"), + }, + { + rs.m_State.ProgrammablePointSize ? tr("Yes") : tr("No"), + Formatter::Format(rs.m_State.PointSize), Formatter::Format(rs.m_State.LineWidth), + Formatter::Format(rs.m_State.PointFadeThreshold), + rs.m_State.PointOriginUpperLeft ? tr("Yes") : tr("No"), + }); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Depth Clamp"), tr("Depth Bias"), tr("Offset Clamp"), tr("Slope Scaled Bias")}, + {rs.m_State.DepthClamp ? tr("Yes") : tr("No"), rs.m_State.DepthBias, + Formatter::Format(rs.m_State.OffsetClamp), + Formatter::Format(rs.m_State.SlopeScaledDepthBias)}); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Hints")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, + { + tr("Derivatives"), tr("Line Smooth"), tr("Poly Smooth"), tr("Tex Compression"), + }, + { + ToQStr(pipe.m_Hints.Derivatives), + pipe.m_Hints.LineSmoothEnabled ? ToQStr(pipe.m_Hints.LineSmooth) : tr("Disabled"), + pipe.m_Hints.PolySmoothEnabled ? ToQStr(pipe.m_Hints.PolySmooth) : tr("Disabled"), + ToQStr(pipe.m_Hints.TexCompression), + }); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Viewports")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const GLPipe::Viewport &v : rs.Viewports) + { + rows.push_back({i, v.Left, v.Bottom, v.Width, v.Height, v.MinDepth, v.MaxDepth}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("Left"), tr("Bottom"), tr("Width"), tr("Height"), + tr("Min Depth"), tr("Max Depth")}, + rows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Scissors")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const GLPipe::Scissor &s : rs.Scissors) + { + rows.push_back({i, (bool)s.Enabled, s.Left, s.Bottom, s.Width, s.Height}); + + i++; + } + + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Enabled"), tr("Left"), tr("Bottom"), tr("Width"), tr("Height")}, rows); + } +} + +void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, GLPipe::FrameBuffer &fb) +{ + const GLPipe::State &pipe = m_Ctx.CurGLPipelineState(); + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Blend State")); + xml.writeEndElement(); + + QString blendFactor = QFormatStr("%1, %2, %3, %4") + .arg(fb.m_Blending.BlendFactor[0], 0, 'f', 2) + .arg(fb.m_Blending.BlendFactor[1], 0, 'f', 2) + .arg(fb.m_Blending.BlendFactor[2], 0, 'f', 2) + .arg(fb.m_Blending.BlendFactor[3], 0, 'f', 2); + + m_Common.exportHTMLTable(xml, {tr("Framebuffer SRGB"), tr("Blend Factor")}, + { + fb.FramebufferSRGB ? tr("Yes") : tr("No"), blendFactor, + }); + + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Target Blends")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const GLPipe::Blend &b : fb.m_Blending.Blends) + { + if(i >= fb.m_DrawFBO.Color.count) + continue; + + rows.push_back({i, b.Enabled ? tr("Yes") : tr("No"), ToQStr(b.m_Blend.Source), + ToQStr(b.m_Blend.Destination), ToQStr(b.m_Blend.Operation), + ToQStr(b.m_AlphaBlend.Source), ToQStr(b.m_AlphaBlend.Destination), + ToQStr(b.m_AlphaBlend.Operation), ToQStr(b.Logic), + ((b.WriteMask & 0x1) == 0 ? tr("_") : tr("R")) + + ((b.WriteMask & 0x2) == 0 ? tr("_") : tr("G")) + + ((b.WriteMask & 0x4) == 0 ? tr("_") : tr("B")) + + ((b.WriteMask & 0x8) == 0 ? tr("_") : tr("A"))}); + + i++; + } + + m_Common.exportHTMLTable( + xml, + { + tr("Slot"), tr("Blend Enable"), tr("Blend Source"), tr("Blend Destination"), + tr("Blend Operation"), tr("Alpha Blend Source"), tr("Alpha Blend Destination"), + tr("Alpha Blend Operation"), tr("Logic Operation"), tr("Write Mask"), + }, + rows); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Depth State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Depth Test Enable"), tr("Depth Writes Enable"), + tr("Depth Function"), tr("Depth Bounds")}, + { + pipe.m_DepthState.DepthEnable ? tr("Yes") : tr("No"), + pipe.m_DepthState.DepthWrites ? tr("Yes") : tr("No"), + ToQStr(pipe.m_DepthState.DepthFunc), + pipe.m_DepthState.DepthEnable + ? QFormatStr("%1 - %2") + .arg(Formatter::Format(pipe.m_DepthState.NearBound)) + .arg(Formatter::Format(pipe.m_DepthState.FarBound)) + : tr("Disabled"), + }); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Stencil State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Stencil Test Enable")}, + {pipe.m_StencilState.StencilEnable ? tr("Yes") : tr("No")}); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Face"), tr("Reference"), tr("Value Mask"), tr("Write Mask"), tr("Function"), + tr("Pass Operation"), tr("Fail Operation"), tr("Depth Fail Operation")}, + { + {tr("Front"), Formatter::Format(pipe.m_StencilState.m_FrontFace.Ref, true), + Formatter::Format(pipe.m_StencilState.m_FrontFace.ValueMask, true), + Formatter::Format(pipe.m_StencilState.m_FrontFace.WriteMask, true), + ToQStr(pipe.m_StencilState.m_FrontFace.Func), + ToQStr(pipe.m_StencilState.m_FrontFace.PassOp), + ToQStr(pipe.m_StencilState.m_FrontFace.FailOp), + ToQStr(pipe.m_StencilState.m_FrontFace.DepthFailOp)}, + + {tr("Back"), Formatter::Format(pipe.m_StencilState.m_BackFace.Ref, true), + Formatter::Format(pipe.m_StencilState.m_BackFace.ValueMask, true), + Formatter::Format(pipe.m_StencilState.m_BackFace.WriteMask, true), + ToQStr(pipe.m_StencilState.m_BackFace.Func), + ToQStr(pipe.m_StencilState.m_BackFace.PassOp), + ToQStr(pipe.m_StencilState.m_BackFace.FailOp), + ToQStr(pipe.m_StencilState.m_BackFace.DepthFailOp)}, + }); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Draw FBO Attachments")); + xml.writeEndElement(); + + QList rows; + + QList atts; + for(const GLPipe::Attachment &att : fb.m_DrawFBO.Color) + atts.push_back(&att); + atts.push_back(&fb.m_DrawFBO.Depth); + atts.push_back(&fb.m_DrawFBO.Stencil); + + int i = 0; + for(const GLPipe::Attachment *att : atts) + { + const GLPipe::Attachment &a = *att; + + TextureDescription *tex = m_Ctx.GetTexture(a.Obj); + + QString name = tr("Image %1").arg(ToQStr(a.Obj)); + + if(tex) + name = ToQStr(tex->name); + if(a.Obj == ResourceId()) + name = tr("Empty"); + + QString slotname = QString::number(i); + + if(i == atts.count() - 2) + slotname = tr("Depth"); + else if(i == atts.count() - 1) + slotname = tr("Stencil"); + + rows.push_back({slotname, name, a.Mip, a.Layer}); + + i++; + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Image"), tr("First mip"), tr("First array slice"), + }, + rows); + + QList drawbuffers; + + for(i = 0; i < fb.m_DrawFBO.DrawBuffers.count; i++) + drawbuffers.push_back({fb.m_DrawFBO.DrawBuffers[i]}); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, + { + tr("Draw Buffers"), + }, + drawbuffers); + } + + { + xml.writeStartElement(tr("h3")); + xml.writeCharacters(tr("Read FBO Attachments")); + xml.writeEndElement(); + + QList rows; + + QList atts; + for(const GLPipe::Attachment &att : fb.m_ReadFBO.Color) + atts.push_back(&att); + atts.push_back(&fb.m_ReadFBO.Depth); + atts.push_back(&fb.m_ReadFBO.Stencil); + + int i = 0; + for(const GLPipe::Attachment *att : atts) + { + const GLPipe::Attachment &a = *att; + + TextureDescription *tex = m_Ctx.GetTexture(a.Obj); + + QString name = tr("Image %1").arg(ToQStr(a.Obj)); + + if(tex) + name = ToQStr(tex->name); + if(a.Obj == ResourceId()) + name = tr("Empty"); + + QString slotname = QString::number(i); + + if(i == atts.count() - 2) + slotname = tr("Depth"); + else if(i == atts.count() - 1) + slotname = tr("Stencil"); + + rows.push_back({slotname, name, a.Mip, a.Layer}); + + i++; + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Image"), tr("First mip"), tr("First array slice"), + }, + rows); + + xml.writeStartElement(tr("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, + { + tr("Read Buffer"), + }, + {fb.m_ReadFBO.ReadBuffer}); + } +} + void GLPipelineStateViewer::on_exportHTML_clicked() { + QXmlStreamWriter *xmlptr = m_Common.beginHTMLExport(); + + if(xmlptr) + { + QXmlStreamWriter &xml = *xmlptr; + + const QStringList &stageNames = ui->pipeFlow->stageNames(); + const QStringList &stageAbbrevs = ui->pipeFlow->stageAbbreviations(); + + int stage = 0; + for(const QString &sn : stageNames) + { + xml.writeStartElement(lit("div")); + xml.writeStartElement(lit("a")); + xml.writeAttribute(lit("name"), stageAbbrevs[stage]); + xml.writeEndElement(); + xml.writeEndElement(); + + xml.writeStartElement(lit("div")); + xml.writeAttribute(lit("class"), lit("stage")); + + xml.writeStartElement(lit("h1")); + xml.writeCharacters(sn); + xml.writeEndElement(); + + switch(stage) + { + case 0: exportHTML(xml, m_Ctx.CurGLPipelineState().m_VtxIn); break; + case 1: exportHTML(xml, m_Ctx.CurGLPipelineState().m_VS); break; + case 2: exportHTML(xml, m_Ctx.CurGLPipelineState().m_TCS); break; + case 3: exportHTML(xml, m_Ctx.CurGLPipelineState().m_TES); break; + case 4: + exportHTML(xml, m_Ctx.CurGLPipelineState().m_GS); + exportHTML(xml, m_Ctx.CurGLPipelineState().m_Feedback); + break; + case 5: exportHTML(xml, m_Ctx.CurGLPipelineState().m_Rasterizer); break; + case 6: exportHTML(xml, m_Ctx.CurGLPipelineState().m_FS); break; + case 7: exportHTML(xml, m_Ctx.CurGLPipelineState().m_FB); break; + case 8: exportHTML(xml, m_Ctx.CurGLPipelineState().m_CS); break; + } + + xml.writeEndElement(); + + stage++; + } + + m_Common.endHTMLExport(xmlptr); + } } void GLPipelineStateViewer::on_meshView_clicked() diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h index 841237571..e75fe8cc7 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h @@ -32,6 +32,8 @@ namespace Ui class GLPipelineStateViewer; } +class QXmlStreamWriter; + class RDTreeWidget; class RDTreeWidgetItem; class PipelineStateViewer; @@ -105,6 +107,12 @@ private: bool showNode(bool usedSlot, bool filledSlot); + void exportHTML(QXmlStreamWriter &xml, GLPipe::VertexInput &vtx); + void exportHTML(QXmlStreamWriter &xml, GLPipe::Shader &sh); + void exportHTML(QXmlStreamWriter &xml, GLPipe::Feedback &xfb); + void exportHTML(QXmlStreamWriter &xml, GLPipe::Rasterizer &rs); + void exportHTML(QXmlStreamWriter &xml, GLPipe::FrameBuffer &fb); + // keep track of the VB nodes (we want to be able to highlight them easily on hover) QList m_VBNodes; }; diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index 9cbfbe148..2c77f5584 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -23,6 +23,7 @@ ******************************************************************************/ #include "PipelineStateViewer.h" +#include #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "D3D11PipelineStateViewer.h" #include "D3D12PipelineStateViewer.h" @@ -87,20 +88,25 @@ void PipelineStateViewer::OnEventChanged(uint32_t eventID) m_Current->OnEventChanged(eventID); } +QString PipelineStateViewer::GetCurrentAPI() +{ + if(m_Current == m_D3D11) + return lit("D3D11"); + else if(m_Current == m_D3D12) + return lit("D3D12"); + else if(m_Current == m_GL) + return lit("OpenGL"); + else if(m_Current == m_Vulkan) + return lit("Vulkan"); + + return lit(""); +} + QVariant PipelineStateViewer::persistData() { QVariantMap state; - if(m_Current == m_D3D11) - state[lit("type")] = lit("D3D11"); - else if(m_Current == m_D3D12) - state[lit("type")] = lit("D3D12"); - else if(m_Current == m_GL) - state[lit("type")] = lit("GL"); - else if(m_Current == m_Vulkan) - state[lit("type")] = lit("Vulkan"); - else - state[lit("type")] = lit(""); + state[lit("type")] = GetCurrentAPI(); return state; } @@ -186,6 +192,234 @@ void PipelineStateViewer::setToVulkan() m_Ctx.CurPipelineState().DefaultType = GraphicsAPI::Vulkan; } +QXmlStreamWriter *PipelineStateViewer::beginHTMLExport() +{ + QString filename = RDDialog::getSaveFileName(this, tr("Export pipeline state as HTML"), QString(), + tr("HTML files (*.html)")); + + if(!filename.isEmpty()) + { + QDir dirinfo = QFileInfo(filename).dir(); + if(dirinfo.exists()) + { + QFile *f = new QFile(filename, this); + if(f->open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + QXmlStreamWriter *xmlptr = new QXmlStreamWriter(f); + + QXmlStreamWriter &xml = *xmlptr; + + xml.setAutoFormatting(true); + xml.setAutoFormattingIndent(4); + xml.writeStartDocument(); + xml.writeDTD(lit("")); + + xml.writeStartElement(lit("html")); + xml.writeAttribute(lit("lang"), lit("en")); + + QString title = tr("%1 EID %2 - %3 Pipeline export") + .arg(QFileInfo(m_Ctx.LogFilename()).fileName()) + .arg(m_Ctx.CurEvent()) + .arg(GetCurrentAPI()); + + { + xml.writeStartElement(lit("head")); + + xml.writeStartElement(lit("meta")); + xml.writeAttribute(lit("charset"), lit("utf-8")); + xml.writeEndElement(); + + xml.writeStartElement(lit("meta")); + xml.writeAttribute(lit("http-equiv"), lit("X-UA-Compatible")); + xml.writeAttribute(lit("content"), lit("IE=edge")); + xml.writeEndElement(); + + xml.writeStartElement(lit("meta")); + xml.writeAttribute(lit("name"), lit("viewport")); + xml.writeAttribute(lit("content"), lit("width=device-width, initial-scale=1")); + xml.writeEndElement(); + + xml.writeStartElement(lit("meta")); + xml.writeAttribute(lit("name"), lit("description")); + xml.writeAttribute(lit("content"), lit("")); + xml.writeEndElement(); + + xml.writeStartElement(lit("meta")); + xml.writeAttribute(lit("name"), lit("author")); + xml.writeAttribute(lit("content"), lit("")); + xml.writeEndElement(); + + xml.writeStartElement(lit("meta")); + xml.writeAttribute(lit("http-equiv"), lit("Content-Type")); + xml.writeAttribute(lit("content"), lit("text/html;charset=utf-8")); + xml.writeEndElement(); + + xml.writeStartElement(lit("title")); + xml.writeCharacters(title); + xml.writeEndElement(); + + xml.writeStartElement(lit("style")); + xml.writeComment(lit(R"( + +/* If you think this css is ugly/bad, open a pull request! */ +body { margin: 20px; } +div.stage { border: 1px solid #BBBBBB; border-radius: 5px; padding: 16px; margin-bottom: 32px; } +div.stage h1 { text-decoration: underline; margin-top: 0px; } +div.stage table { border: 1px solid #AAAAAA; border-collapse: collapse; } +div.stage table thead tr { border-bottom: 1px solid #AAAAAA; background-color: #EEEEFF; } +div.stage table tr th { border-right: 1px solid #AAAAAA; padding: 6px; } +div.stage table tr td { border-right: 1px solid #AAAAAA; background-color: #EEEEEE; padding: 3px; } + +)")); + xml.writeEndElement(); // + + xml.writeEndElement(); // + } + + { + xml.writeStartElement(lit("body")); + + xml.writeStartElement(lit("h1")); + xml.writeCharacters(title); + xml.writeEndElement(); + + xml.writeStartElement(lit("h3")); + { + QString context = tr("Frame %1").arg(m_Ctx.FrameInfo().frameNumber); + + const DrawcallDescription *draw = m_Ctx.CurDrawcall(); + + QList drawstack; + const DrawcallDescription *parent = m_Ctx.GetDrawcall(draw->parent); + while(parent) + { + drawstack.push_front(parent); + parent = m_Ctx.GetDrawcall(parent->parent); + } + + for(const DrawcallDescription *d : drawstack) + { + context += QFormatStr(" > %1").arg(ToQStr(d->name)); + } + + context += QFormatStr(" => %1").arg(ToQStr(draw->name)); + + xml.writeCharacters(context); + } + xml.writeEndElement(); // + } + + // body is open + + return xmlptr; + } + + RDDialog::critical( + this, tr("Error exporting pipeline state"), + tr("Couldn't open path %1 for write.\n%2").arg(filename).arg(f->errorString())); + + delete f; + + return NULL; + } + else + { + RDDialog::critical(this, tr("Invalid directory"), + tr("Cannot find target directory to save to")); + return NULL; + } + } + + return NULL; +} + +void PipelineStateViewer::exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols, + const QList &rows) +{ + xml.writeStartElement(lit("table")); + + { + xml.writeStartElement(lit("thead")); + xml.writeStartElement(lit("tr")); + + for(const QString &col : cols) + { + xml.writeStartElement(lit("th")); + xml.writeCharacters(col); + xml.writeEndElement(); + } + + xml.writeEndElement(); + xml.writeEndElement(); + } + + { + xml.writeStartElement(lit("tbody")); + + if(rows.isEmpty()) + { + xml.writeStartElement(lit("tr")); + + for(int i = 0; i < cols.count(); i++) + { + xml.writeStartElement(lit("td")); + xml.writeCharacters(lit("-")); + xml.writeEndElement(); + } + + xml.writeEndElement(); + } + else + { + for(const QVariantList &row : rows) + { + xml.writeStartElement(lit("tr")); + + for(const QVariant &el : row) + { + xml.writeStartElement(lit("td")); + + QMetaType::Type type = (QMetaType::Type)el.type(); + + if(el.type() == QMetaType::Bool) + xml.writeCharacters(el.toBool() ? tr("True") : tr("False")); + else + xml.writeCharacters(el.toString()); + + xml.writeEndElement(); + } + + xml.writeEndElement(); + } + } + + xml.writeEndElement(); + } + + xml.writeEndElement(); +} + +void PipelineStateViewer::exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols, + const QVariantList &row) +{ + exportHTMLTable(xml, cols, QList({row})); +} + +void PipelineStateViewer::endHTMLExport(QXmlStreamWriter *xml) +{ + xml->writeEndElement(); // + + xml->writeEndElement(); // + + xml->writeEndDocument(); + + // delete the file the writer was writing to + QFile *f = qobject_cast(xml->device()); + delete f; + + delete xml; +} + bool PipelineStateViewer::PrepareShaderEditing(const ShaderReflection *shaderDetails, QString &entryFunc, QStringMap &files, QString &mainfile) diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h index 0bf3ba076..bb705bdc7 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h @@ -32,6 +32,8 @@ namespace Ui class PipelineStateViewer; } +class QXmlStreamWriter; + class D3D11PipelineStateViewer; class D3D12PipelineStateViewer; class GLPipelineStateViewer; @@ -58,12 +60,18 @@ public: void OnEventChanged(uint32_t eventID) override; QVariant persistData(); + void setPersistData(const QVariant &persistData); bool PrepareShaderEditing(const ShaderReflection *shaderDetails, QString &entryFunc, QStringMap &files, QString &mainfile); void EditShader(ShaderStage shaderType, ResourceId id, const ShaderReflection *shaderDetails, const QString &entryFunc, const QStringMap &files, const QString &mainfile); + QXmlStreamWriter *beginHTMLExport(); + void exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols, + const QList &rows); + void exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols, const QVariantList &row); + void endHTMLExport(QXmlStreamWriter *xml); private: Ui::PipelineStateViewer *ui; @@ -75,6 +83,8 @@ private: void setToVulkan(); void reset(); + QString GetCurrentAPI(); + D3D11PipelineStateViewer *m_D3D11; D3D12PipelineStateViewer *m_D3D12; GLPipelineStateViewer *m_GL; diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index ee4491c29..7c976d79c 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "Code/Resources.h" #include "PipelineStateViewer.h" @@ -609,9 +610,8 @@ void VulkanPipelineStateViewer::clearState() ui->stencils->clear(); } -RDTreeWidgetItem *VulkanPipelineStateViewer::makeSampler(const QString &bindset, - const QString &slotname, - const VKPipe::BindingElement &descriptor) +QVariantList VulkanPipelineStateViewer::makeSampler(const QString &bindset, const QString &slotname, + const VKPipe::BindingElement &descriptor) { QString addressing; QString addPrefix; @@ -670,10 +670,13 @@ RDTreeWidgetItem *VulkanPipelineStateViewer::makeSampler(const QString &bindset, if(descriptor.mipBias != 0.0f) lod += lit(" Bias %1").arg(descriptor.mipBias); - return new RDTreeWidgetItem( - {QString(), bindset, slotname, - descriptor.immutableSampler ? tr("Immutable Sampler") : tr("Sampler"), - ToQStr(descriptor.name), addressing, filter + lit(", ") + lod}); + return {QString(), + bindset, + slotname, + descriptor.immutableSampler ? tr("Immutable Sampler") : tr("Sampler"), + ToQStr(descriptor.name), + addressing, + filter + lit(", ") + lod}; } void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, @@ -862,7 +865,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, name = ToQStr(buf->name); restype = TextureDim::Buffer; - if(descriptorLen == 0xFFFFFFFFFFFFFFFFULL) + if(descriptorLen == UINT64_MAX) descriptorLen = len - descriptorBind->offset; tag = QVariant::fromValue( @@ -923,7 +926,8 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, } else { - node = makeSampler(QString::number(bindset), slotname, *descriptorBind); + node = + new RDTreeWidgetItem(makeSampler(QString::number(bindset), slotname, *descriptorBind)); if(!filledSlot) setEmptyRow(node); @@ -1010,7 +1014,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, { if(!samplers.contains(descriptorBind->sampler)) { - samplerNode = makeSampler(QString(), QString(), *descriptorBind); + samplerNode = new RDTreeWidgetItem(makeSampler(QString(), QString(), *descriptorBind)); if(!filledSlot) setEmptyRow(samplerNode); @@ -1167,7 +1171,7 @@ void VulkanPipelineStateViewer::addConstantBlockRow(ShaderReflection *shaderDeta if(buf) { name = ToQStr(buf->name); - if(length == 0xFFFFFFFFFFFFFFFFULL) + if(length == UINT64_MAX) length = buf->length - descriptorBind->offset; } @@ -2414,8 +2418,833 @@ void VulkanPipelineStateViewer::shaderSave_clicked() m_Common.SaveShaderFile(shaderDetails); } +void VulkanPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, VKPipe::VertexInput &vi) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Attributes")); + xml.writeEndElement(); + + QList rows; + + for(const VKPipe::VertexAttribute &attr : vi.attrs) + rows.push_back({attr.location, attr.binding, ToQStr(attr.format.strname), attr.byteoffset}); + + m_Common.exportHTMLTable(xml, {tr("Location"), tr("Binding"), tr("Format"), tr("Offset")}, rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Bindings")); + xml.writeEndElement(); + + QList rows; + + for(const VKPipe::VertexBinding &attr : vi.binds) + rows.push_back({attr.vbufferBinding, attr.bytestride, + attr.perInstance ? tr("PER_INSTANCE") : tr("PER_VERTEX")}); + + m_Common.exportHTMLTable(xml, {tr("Binding"), tr("Byte Stride"), tr("Step Rate")}, rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Vertex Buffers")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const VKPipe::VB &vb : vi.vbuffers) + { + QString name = tr("Buffer %1").arg(ToQStr(vb.buffer)); + uint64_t length = 0; + + if(vb.buffer == ResourceId()) + { + continue; + } + else + { + BufferDescription *buf = m_Ctx.GetBuffer(vb.buffer); + if(buf) + { + name = ToQStr(buf->name); + length = buf->length; + } + } + + rows.push_back({i, name, vb.offset, length}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Binding"), tr("Buffer"), tr("Offset"), tr("Byte Length")}, + rows); + } +} + +void VulkanPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, VKPipe::InputAssembly &ia) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Index Buffer")); + xml.writeEndElement(); + + BufferDescription *ib = m_Ctx.GetBuffer(ia.ibuffer.buf); + + QString name = tr("Empty"); + uint64_t length = 0; + + if(ib) + { + name = ToQStr(ib->name); + length = ib->length; + } + + QString ifmt = lit("UNKNOWN"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 2) + ifmt = lit("UINT16"); + if(m_Ctx.CurDrawcall()->indexByteWidth == 4) + ifmt = lit("UINT32"); + + m_Common.exportHTMLTable( + xml, {tr("Buffer"), tr("Format"), tr("Offset"), tr("Byte Length"), tr("Primitive Restart")}, + {name, ifmt, ia.ibuffer.offs, length, ia.primitiveRestartEnable ? tr("Yes") : tr("No")}); + } + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Primitive Topology"), tr("Tessellation Control Points")}, + {ToQStr(m_Ctx.CurDrawcall()->topology), m_Ctx.CurVulkanPipelineState().Tess.numControlPoints}); +} + +void VulkanPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, VKPipe::Shader &sh) +{ + ShaderReflection *shaderDetails = sh.ShaderDetails; + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Shader")); + xml.writeEndElement(); + + QString shadername = tr("Unknown"); + + if(sh.Object == ResourceId()) + shadername = tr("Unbound"); + else + shadername = ToQStr(sh.name); + + if(shaderDetails && shaderDetails->DebugInfo.entryFunc.count > 0) + { + QString entryFunc = ToQStr(shaderDetails->DebugInfo.entryFunc); + if(shaderDetails->DebugInfo.files.count > 0 || entryFunc != lit("main")) + shadername = QFormatStr("%1()").arg(entryFunc); + + if(shaderDetails->DebugInfo.files.count > 0) + { + QString shaderfn = QString(); + + int entryFile = shaderDetails->DebugInfo.entryFile; + if(entryFile < 0 || entryFile >= shaderDetails->DebugInfo.files.count) + entryFile = 0; + + shaderfn = QFileInfo(ToQStr(shaderDetails->DebugInfo.files[entryFile].first)).fileName(); + + shadername = QFormatStr("%1() - %2").arg(entryFunc).arg(shaderfn); + } + } + + xml.writeStartElement(lit("p")); + xml.writeCharacters(shadername); + xml.writeEndElement(); + + if(sh.Object == ResourceId()) + return; + } + + const VKPipe::Pipeline &pipeline = + (sh.stage == ShaderStage::Compute ? m_Ctx.CurVulkanPipelineState().compute + : m_Ctx.CurVulkanPipelineState().graphics); + + if(shaderDetails && shaderDetails->ConstantBlocks.count > 0) + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("UBOs")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < shaderDetails->ConstantBlocks.count; i++) + { + const ConstantBlock &b = shaderDetails->ConstantBlocks[i]; + const BindpointMap &bindMap = sh.BindpointMapping.ConstantBlocks[i]; + + if(!bindMap.used) + continue; + + const VKPipe::DescriptorSet &set = + pipeline.DescSets[sh.BindpointMapping.ConstantBlocks[i].bindset]; + const VKPipe::DescriptorBinding &bind = + set.bindings[sh.BindpointMapping.ConstantBlocks[i].bind]; + + QString setname = QString::number(bindMap.bindset); + + QString slotname = QFormatStr("%1: %2").arg(bindMap.bind).arg(ToQStr(b.name)); + + for(uint32_t a = 0; a < bind.descriptorCount; a++) + { + const VKPipe::BindingElement &descriptorBind = bind.binds[a]; + + ResourceId id = bind.binds[a].res; + + if(bindMap.arraySize > 1) + slotname = QFormatStr("%1: %2[%3]").arg(bindMap.bind).arg(ToQStr(b.name)).arg(a); + + QString name; + uint64_t byteOffset = descriptorBind.offset; + uint64_t length = descriptorBind.size; + int numvars = b.variables.count; + + if(descriptorBind.res == ResourceId()) + { + name = tr("Empty"); + length = 0; + } + + BufferDescription *buf = m_Ctx.GetBuffer(id); + if(buf) + { + name = ToQStr(buf->name); + + if(length == UINT64_MAX) + length = buf->length - byteOffset; + } + + if(name.isEmpty()) + name = tr("UBO %1").arg(ToQStr(descriptorBind.res)); + + // push constants + if(!b.bufferBacked) + { + setname = QString(); + slotname = ToQStr(b.name); + name = tr("Push constants"); + byteOffset = 0; + length = 0; + + // could maybe get range/size from ShaderVariable.reg if it's filled out + // from SPIR-V side. + } + + rows.push_back({setname, slotname, name, byteOffset, length, numvars, b.byteSize}); + } + } + + m_Common.exportHTMLTable(xml, {tr("Set"), tr("Bind"), tr("Buffer"), tr("Byte Offset"), + tr("Byte Size"), tr("Number of Variables"), tr("Bytes Needed")}, + rows); + } + + if(shaderDetails->ReadOnlyResources.count > 0) + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Read-only Resources")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < shaderDetails->ReadOnlyResources.count; i++) + { + const ShaderResource &b = shaderDetails->ReadOnlyResources[i]; + const BindpointMap &bindMap = sh.BindpointMapping.ReadOnlyResources[i]; + + if(!bindMap.used) + continue; + + const VKPipe::DescriptorSet &set = + pipeline.DescSets[sh.BindpointMapping.ReadOnlyResources[i].bindset]; + const VKPipe::DescriptorBinding &bind = + set.bindings[sh.BindpointMapping.ReadOnlyResources[i].bind]; + + QString setname = QString::number(bindMap.bindset); + + QString slotname = QFormatStr("%1: %2").arg(bindMap.bind).arg(ToQStr(b.name)); + + for(uint32_t a = 0; a < bind.descriptorCount; a++) + { + const VKPipe::BindingElement &descriptorBind = bind.binds[a]; + + ResourceId id = bind.binds[a].res; + + if(bindMap.arraySize > 1) + slotname = QFormatStr("%1: %2[%3]").arg(bindMap.bind).arg(ToQStr(b.name)).arg(a); + + QString name; + + if(descriptorBind.res == ResourceId()) + name = tr("Empty"); + + BufferDescription *buf = m_Ctx.GetBuffer(id); + if(buf) + name = ToQStr(buf->name); + + TextureDescription *tex = m_Ctx.GetTexture(id); + if(tex) + name = ToQStr(tex->name); + + if(name.isEmpty()) + name = tr("Resource %1").arg(ToQStr(descriptorBind.res)); + + uint64_t w = 1; + uint32_t h = 1, d = 1; + uint32_t arr = 0; + QString format = tr("Unknown"); + QString viewParams; + + if(tex) + { + w = tex->width; + h = tex->height; + d = tex->depth; + arr = tex->arraysize; + format = ToQStr(tex->format.strname); + name = ToQStr(tex->name); + + if(tex->mips > 1) + { + viewParams = tr("Mips: %1-%2") + .arg(descriptorBind.baseMip) + .arg(descriptorBind.baseMip + descriptorBind.numMip - 1); + } + + if(tex->arraysize > 1) + { + if(!viewParams.isEmpty()) + viewParams += lit(", "); + viewParams += tr("Layers: %1-%2") + .arg(descriptorBind.baseLayer) + .arg(descriptorBind.baseLayer + descriptorBind.numLayer - 1); + } + } + + if(buf) + { + w = buf->length; + h = 0; + d = 0; + a = 0; + format = lit("-"); + name = ToQStr(buf->name); + + uint64_t length = descriptorBind.size; + + if(length == UINT64_MAX) + length = buf->length - descriptorBind.offset; + + viewParams = + tr("Byte Range: %1 - %2").arg(descriptorBind.offset).arg(descriptorBind.offset + length); + } + + if(bind.type != BindType::Sampler) + rows.push_back( + {setname, slotname, name, ToQStr(bind.type), w, h, d, arr, format, viewParams}); + + if(bind.type == BindType::ImageSampler || bind.type == BindType::Sampler) + { + name = tr("Sampler %1").arg(ToQStr(descriptorBind.sampler)); + + if(bind.type == BindType::ImageSampler) + setname = slotname = QString(); + + QVariantList sampDetails = makeSampler(QString(), QString(), descriptorBind); + rows.push_back({setname, slotname, name, ToQStr(bind.type), QString(), QString(), + QString(), QString(), sampDetails[5], sampDetails[6]}); + } + } + } + + m_Common.exportHTMLTable( + xml, {tr("Set"), tr("Bind"), tr("Buffer"), tr("Resource Type"), tr("Width"), tr("Height"), + tr("Depth"), tr("Array Size"), tr("Resource Format"), tr("View Parameters")}, + rows); + } + + if(shaderDetails->ReadWriteResources.count > 0) + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Read-write Resources")); + xml.writeEndElement(); + + QList rows; + + for(int i = 0; i < shaderDetails->ReadWriteResources.count; i++) + { + const ShaderResource &b = shaderDetails->ReadWriteResources[i]; + const BindpointMap &bindMap = sh.BindpointMapping.ReadWriteResources[i]; + + if(!bindMap.used) + continue; + + const VKPipe::DescriptorSet &set = + pipeline.DescSets[sh.BindpointMapping.ReadWriteResources[i].bindset]; + const VKPipe::DescriptorBinding &bind = + set.bindings[sh.BindpointMapping.ReadWriteResources[i].bind]; + + QString setname = bindMap.bindset; + + QString slotname = QFormatStr("%1: %2").arg(bindMap.bind).arg(ToQStr(b.name)); + + for(uint32_t a = 0; a < bind.descriptorCount; a++) + { + const VKPipe::BindingElement &descriptorBind = bind.binds[a]; + + ResourceId id = bind.binds[a].res; + + if(bindMap.arraySize > 1) + slotname = QFormatStr("%1: %2[%3]").arg(bindMap.bind).arg(ToQStr(b.name)).arg(a); + + QString name; + + if(descriptorBind.res == ResourceId()) + name = tr("Empty"); + + BufferDescription *buf = m_Ctx.GetBuffer(id); + if(buf) + name = ToQStr(buf->name); + + TextureDescription *tex = m_Ctx.GetTexture(id); + if(tex) + name = ToQStr(tex->name); + + if(name.isEmpty()) + name = tr("Resource %1").arg(ToQStr(descriptorBind.res)); + + uint64_t w = 1; + uint32_t h = 1, d = 1; + uint32_t arr = 0; + QString format = tr("Unknown"); + QString viewParams; + + if(tex) + { + w = tex->width; + h = tex->height; + d = tex->depth; + arr = tex->arraysize; + format = ToQStr(tex->format.strname); + name = ToQStr(tex->name); + + if(tex->mips > 1) + { + viewParams = tr("Mips: %1-%2") + .arg(descriptorBind.baseMip) + .arg(descriptorBind.baseMip + descriptorBind.numMip - 1); + } + + if(tex->arraysize > 1) + { + if(!viewParams.isEmpty()) + viewParams += lit(", "); + viewParams += tr("Layers: %1-%2") + .arg(descriptorBind.baseLayer) + .arg(descriptorBind.baseLayer + descriptorBind.numLayer - 1); + } + } + + if(buf) + { + w = buf->length; + h = 0; + d = 0; + a = 0; + format = lit("-"); + name = ToQStr(buf->name); + + uint64_t length = descriptorBind.size; + + if(length == UINT64_MAX) + length = buf->length - descriptorBind.offset; + + viewParams = + tr("Byte Range: %1 - %2").arg(descriptorBind.offset).arg(descriptorBind.offset + length); + } + + rows.push_back({setname, slotname, name, ToQStr(bind.type), w, h, d, arr, format, viewParams}); + } + } + + m_Common.exportHTMLTable( + xml, {tr("Set"), tr("Bind"), tr("Buffer"), tr("Resource Type"), tr("Width"), tr("Height"), + tr("Depth"), tr("Array Size"), tr("Resource Format"), tr("View Parameters")}, + rows); + } +} + +void VulkanPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, VKPipe::Raster &rs) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Raster State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Fill Mode"), tr("Cull Mode"), tr("Front CCW")}, + {ToQStr(rs.fillMode), ToQStr(rs.cullMode), rs.FrontCCW ? tr("Yes") : tr("No")}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable(xml, {tr("Depth Clip Enable"), tr("Rasterizer Discard Enable")}, + {rs.depthClampEnable ? tr("Yes") : tr("No"), + rs.rasterizerDiscardEnable ? tr("Yes") : tr("No")}); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Depth Bias"), tr("Depth Bias Clamp"), tr("Slope Scaled Bias"), tr("Line Width")}, + {Formatter::Format(rs.depthBias), Formatter::Format(rs.depthBiasClamp), + Formatter::Format(rs.slopeScaledDepthBias), Formatter::Format(rs.lineWidth)}); + } + + VKPipe::MultiSample &msaa = m_Ctx.CurVulkanPipelineState().MSAA; + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Multisampling State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Raster Samples"), tr("Sample-rate shading"), tr("Min Sample Shading Rate"), + tr("Sample Mask")}, + {msaa.rasterSamples, msaa.sampleShadingEnable ? tr("Yes") : tr("No"), + Formatter::Format(msaa.minSampleShading), Formatter::Format(msaa.sampleMask, true)}); + } + + VKPipe::ViewState &vp = m_Ctx.CurVulkanPipelineState().VP; + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Viewports")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const VKPipe::ViewportScissor &vs : vp.viewportScissors) + { + const VKPipe::Viewport &v = vs.vp; + + rows.push_back({i, v.x, v.y, v.width, v.height, v.minDepth, v.maxDepth}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("X"), tr("Y"), tr("Width"), tr("Height"), + tr("Min Depth"), tr("Max Depth")}, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Scissors")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const VKPipe::ViewportScissor &vs : vp.viewportScissors) + { + const VKPipe::Scissor &s = vs.scissor; + + rows.push_back({i, s.x, s.y, s.width, s.height}); + + i++; + } + + m_Common.exportHTMLTable(xml, {tr("Slot"), tr("X"), tr("Y"), tr("Width"), tr("Height")}, rows); + } +} + +void VulkanPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, VKPipe::ColorBlend &cb) +{ + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Color Blend State")); + xml.writeEndElement(); + + QString blendConst = QFormatStr("%1, %2, %3, %4") + .arg(cb.blendConst[0], 0, 'f', 2) + .arg(cb.blendConst[1], 0, 'f', 2) + .arg(cb.blendConst[2], 0, 'f', 2) + .arg(cb.blendConst[3], 0, 'f', 2); + + m_Common.exportHTMLTable( + xml, {tr("Alpha to Coverage"), tr("Alpha to One"), tr("Logic Op"), tr("Blend Constant")}, + { + cb.alphaToCoverageEnable ? tr("Yes") : tr("No"), + cb.alphaToOneEnable ? tr("Yes") : tr("No"), + cb.logicOpEnable ? ToQStr(cb.logic) : tr("Disabled"), blendConst, + }); + + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Attachment Blends")); + xml.writeEndElement(); + + QList rows; + + int i = 0; + for(const VKPipe::Blend &b : cb.attachments) + { + rows.push_back( + {i, b.blendEnable ? tr("Yes") : tr("No"), ToQStr(b.blend.Source), ToQStr(b.blend.Destination), + ToQStr(b.blend.Operation), ToQStr(b.alphaBlend.Source), ToQStr(b.alphaBlend.Destination), + ToQStr(b.alphaBlend.Operation), ((b.writeMask & 0x1) == 0 ? lit("_") : lit("R")) + + ((b.writeMask & 0x2) == 0 ? lit("_") : lit("G")) + + ((b.writeMask & 0x4) == 0 ? lit("_") : lit("B")) + + ((b.writeMask & 0x8) == 0 ? lit("_") : lit("A"))}); + + i++; + } + + m_Common.exportHTMLTable( + xml, + { + tr("Slot"), tr("Blend Enable"), tr("Blend Source"), tr("Blend Destination"), + tr("Blend Operation"), tr("Alpha Blend Source"), tr("Alpha Blend Destination"), + tr("Alpha Blend Operation"), tr("Write Mask"), + }, + rows); +} + +void VulkanPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, VKPipe::DepthStencil &ds) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Depth State")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Depth Test Enable"), tr("Depth Writes Enable"), tr("Depth Function"), + tr("Depth Bounds")}, + { + ds.depthTestEnable ? tr("Yes") : tr("No"), ds.depthWriteEnable ? tr("Yes") : tr("No"), + ToQStr(ds.depthCompareOp), ds.depthBoundsEnable + ? QFormatStr("%1 - %2") + .arg(Formatter::Format(ds.minDepthBounds)) + .arg(Formatter::Format(ds.maxDepthBounds)) + : tr("Disabled"), + }); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Stencil State")); + xml.writeEndElement(); + + if(ds.stencilTestEnable) + { + QList rows; + + rows.push_back({ + tr("Front"), Formatter::Format(ds.front.ref, true), + Formatter::Format(ds.front.compareMask, true), + Formatter::Format(ds.front.writeMask, true), ToQStr(ds.front.Func), + ToQStr(ds.front.PassOp), ToQStr(ds.front.FailOp), ToQStr(ds.front.DepthFailOp), + }); + + rows.push_back({ + tr("back"), Formatter::Format(ds.back.ref, true), + Formatter::Format(ds.back.compareMask, true), Formatter::Format(ds.back.writeMask, true), + ToQStr(ds.back.Func), ToQStr(ds.back.PassOp), ToQStr(ds.back.FailOp), + ToQStr(ds.back.DepthFailOp), + }); + + m_Common.exportHTMLTable(xml, + {tr("Face"), tr("Ref"), tr("Compare Mask"), tr("Write Mask"), + tr("Function"), tr("Pass Op"), tr("Fail Op"), tr("Depth Fail Op")}, + rows); + } + else + { + xml.writeStartElement(lit("p")); + xml.writeCharacters(tr("Disabled")); + xml.writeEndElement(); + } + } +} + +void VulkanPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, VKPipe::CurrentPass &pass) +{ + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Framebuffer")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("Width"), tr("Height"), tr("Layers")}, + {pass.framebuffer.width, pass.framebuffer.height, pass.framebuffer.layers}); + + QList rows; + + int i = 0; + for(const VKPipe::Attachment &a : pass.framebuffer.attachments) + { + TextureDescription *tex = m_Ctx.GetTexture(a.img); + + QString name = tr("Image %1").arg(ToQStr(a.img)); + + if(tex) + name = ToQStr(tex->name); + + rows.push_back({i, name, a.baseMip, a.numMip, a.baseLayer, a.numLayer}); + + i++; + } + + m_Common.exportHTMLTable(xml, + { + tr("Slot"), tr("Image"), tr("First mip"), tr("Number of mips"), + tr("First array layer"), tr("Number of layers"), + }, + rows); + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Render Pass")); + xml.writeEndElement(); + + if(pass.renderpass.inputAttachments.count > 0) + { + QList inputs; + + for(int i = 0; i < pass.renderpass.inputAttachments.count; i++) + inputs.push_back({pass.renderpass.inputAttachments[i]}); + + m_Common.exportHTMLTable(xml, + { + tr("Input Attachment"), + }, + inputs); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + } + + if(pass.renderpass.colorAttachments.count > 0) + { + QList colors; + + for(int i = 0; i < pass.renderpass.colorAttachments.count; i++) + colors.push_back({pass.renderpass.colorAttachments[i]}); + + m_Common.exportHTMLTable(xml, + { + tr("Color Attachment"), + }, + colors); + + xml.writeStartElement(lit("p")); + xml.writeEndElement(); + } + + if(pass.renderpass.depthstencilAttachment >= 0) + { + xml.writeStartElement(lit("p")); + xml.writeCharacters( + tr("Depth-stencil Attachment: %1").arg(pass.renderpass.depthstencilAttachment)); + xml.writeEndElement(); + } + } + + { + xml.writeStartElement(lit("h3")); + xml.writeCharacters(tr("Render Area")); + xml.writeEndElement(); + + m_Common.exportHTMLTable( + xml, {tr("X"), tr("Y"), tr("Width"), tr("Height")}, + {pass.renderArea.x, pass.renderArea.y, pass.renderArea.width, pass.renderArea.height}); + } +} + void VulkanPipelineStateViewer::on_exportHTML_clicked() { + QXmlStreamWriter *xmlptr = m_Common.beginHTMLExport(); + + if(xmlptr) + { + QXmlStreamWriter &xml = *xmlptr; + + const QStringList &stageNames = ui->pipeFlow->stageNames(); + const QStringList &stageAbbrevs = ui->pipeFlow->stageAbbreviations(); + + int stage = 0; + for(const QString &sn : stageNames) + { + xml.writeStartElement(lit("div")); + xml.writeStartElement(lit("a")); + xml.writeAttribute(lit("name"), stageAbbrevs[stage]); + xml.writeEndElement(); + xml.writeEndElement(); + + xml.writeStartElement(lit("div")); + xml.writeAttribute(lit("class"), lit("stage")); + + xml.writeStartElement(lit("h1")); + xml.writeCharacters(sn); + xml.writeEndElement(); + + switch(stage) + { + case 0: + // VTX + xml.writeStartElement(lit("h2")); + xml.writeCharacters(tr("Input Assembly")); + xml.writeEndElement(); + exportHTML(xml, m_Ctx.CurVulkanPipelineState().IA); + + xml.writeStartElement(lit("h2")); + xml.writeCharacters(tr("Vertex Input")); + xml.writeEndElement(); + exportHTML(xml, m_Ctx.CurVulkanPipelineState().VI); + break; + case 1: exportHTML(xml, m_Ctx.CurVulkanPipelineState().m_VS); break; + case 2: exportHTML(xml, m_Ctx.CurVulkanPipelineState().m_TCS); break; + case 3: exportHTML(xml, m_Ctx.CurVulkanPipelineState().m_TES); break; + case 4: exportHTML(xml, m_Ctx.CurVulkanPipelineState().m_GS); break; + case 5: exportHTML(xml, m_Ctx.CurVulkanPipelineState().RS); break; + case 6: exportHTML(xml, m_Ctx.CurVulkanPipelineState().m_FS); break; + case 7: + // FB + xml.writeStartElement(lit("h2")); + xml.writeCharacters(tr("Color Blend")); + xml.writeEndElement(); + exportHTML(xml, m_Ctx.CurVulkanPipelineState().CB); + + xml.writeStartElement(lit("h2")); + xml.writeCharacters(tr("Depth Stencil")); + xml.writeEndElement(); + exportHTML(xml, m_Ctx.CurVulkanPipelineState().DS); + + xml.writeStartElement(lit("h2")); + xml.writeCharacters(tr("Current Pass")); + xml.writeEndElement(); + exportHTML(xml, m_Ctx.CurVulkanPipelineState().Pass); + break; + case 8: exportHTML(xml, m_Ctx.CurVulkanPipelineState().m_CS); break; + } + + xml.writeEndElement(); + + stage++; + } + + m_Common.endHTMLExport(xmlptr); + } } void VulkanPipelineStateViewer::on_meshView_clicked() diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h index 707b7532c..18b6e3615 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h @@ -32,6 +32,8 @@ namespace Ui class VulkanPipelineStateViewer; } +class QXmlStreamWriter; + class RDTreeWidget; class RDTreeWidgetItem; class PipelineStateViewer; @@ -84,8 +86,8 @@ private: ICaptureContext &m_Ctx; PipelineStateViewer &m_Common; - RDTreeWidgetItem *makeSampler(const QString &bindset, const QString &slotname, - const VKPipe::BindingElement &descriptor); + QVariantList makeSampler(const QString &bindset, const QString &slotname, + const VKPipe::BindingElement &descriptor); void addResourceRow(ShaderReflection *shaderDetails, const VKPipe::Shader &stage, int bindset, int bind, const VKPipe::Pipeline &pipe, RDTreeWidget *resources, QMap &samplers); @@ -116,6 +118,14 @@ private: bool showNode(bool usedSlot, bool filledSlot); + void exportHTML(QXmlStreamWriter &xml, VKPipe::VertexInput &vi); + void exportHTML(QXmlStreamWriter &xml, VKPipe::InputAssembly &ia); + void exportHTML(QXmlStreamWriter &xml, VKPipe::Shader &sh); + void exportHTML(QXmlStreamWriter &xml, VKPipe::Raster &rs); + void exportHTML(QXmlStreamWriter &xml, VKPipe::ColorBlend &cb); + void exportHTML(QXmlStreamWriter &xml, VKPipe::DepthStencil &ds); + void exportHTML(QXmlStreamWriter &xml, VKPipe::CurrentPass &pass); + // keep track of the VB nodes (we want to be able to highlight them easily on hover) QList m_VBNodes; QList m_BindNodes;