diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index bbc68a76e..c1b184c29 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -264,6 +264,7 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderResource) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderSampler) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderSourceFile) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderVariable) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, LocalVariableMapping) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, SigParameter) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, TextureDescription) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderEntryPoint) diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index d9bdbfdce..3ca60ecb2 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -66,6 +66,7 @@ ShaderViewer::ShaderViewer(ICaptureContext &ctx, QWidget *parent) ui->constants->setFont(Formatter::PreferredFont()); ui->variables->setFont(Formatter::PreferredFont()); + ui->locals->setFont(Formatter::PreferredFont()); ui->watch->setFont(Formatter::PreferredFont()); ui->inputSig->setFont(Formatter::PreferredFont()); ui->outputSig->setFont(Formatter::PreferredFont()); @@ -210,6 +211,7 @@ void ShaderViewer::editShader(bool customShader, const QString &entryPoint, cons ui->variables->hide(); ui->constants->hide(); ui->callstack->hide(); + ui->locals->hide(); ui->snippets->setVisible(customShader); @@ -433,6 +435,12 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR ui->variables->header()->setSectionResizeMode(1, QHeaderView::Interactive); ui->variables->header()->setSectionResizeMode(2, QHeaderView::Stretch); + ui->locals->setColumns({tr("Name"), tr("Register"), tr("Type"), tr("Value")}); + ui->locals->header()->setSectionResizeMode(0, QHeaderView::Interactive); + ui->locals->header()->setSectionResizeMode(1, QHeaderView::Interactive); + ui->locals->header()->setSectionResizeMode(2, QHeaderView::Interactive); + ui->locals->header()->setSectionResizeMode(3, QHeaderView::Stretch); + ui->constants->setColumns({tr("Name"), tr("Type"), tr("Value")}); ui->constants->header()->setSectionResizeMode(0, QHeaderView::Interactive); ui->constants->header()->setSectionResizeMode(1, QHeaderView::Interactive); @@ -469,6 +477,20 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR ui->docking->setToolWindowProperties( ui->callstack, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); + if(m_Trace->hasLocals) + { + ui->locals->setWindowTitle(tr("Local Variables")); + ui->docking->addToolWindow( + ui->locals, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, + ui->docking->areaOf(ui->variables))); + ui->docking->setToolWindowProperties( + ui->locals, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); + } + else + { + ui->locals->hide(); + } + m_DisassemblyView->setMarginWidthN(1, 20.0 * devicePixelRatioF()); // display current line in margin 2, distinct from breakpoint in margin 1 @@ -526,6 +548,8 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR ui->watch->hide(); ui->variables->hide(); ui->constants->hide(); + ui->locals->hide(); + ui->callstack->hide(); // hide debugging toolbar buttons ui->debugSep->hide(); @@ -1360,6 +1384,117 @@ void ShaderViewer::updateDebugging() } } + if(m_Trace->hasLocals) + { + ui->locals->clear(); + + for(size_t lidx = 0; lidx < state.locals.size(); lidx++) + { + // iterate in reverse order, so newest locals tend to end up on top + const LocalVariableMapping &l = state.locals[state.locals.size() - 1 - lidx]; + const ShaderVariable *var = NULL; + + switch(l.registerType) + { + case RegisterType::Input: + continue; // skip inputs, they are immutable + case RegisterType::Temporary: + if(l.registerIndex < state.registers.size()) + var = &state.registers[l.registerIndex]; + break; + case RegisterType::IndexedTemporary: + if(l.registerIndex < state.indexableTemps.size()) + var = &state.indexableTemps[l.registerIndex]; + break; + case RegisterType::Output: + if(l.registerIndex < state.outputs.size()) + var = &state.outputs[l.registerIndex]; + break; + } + + QString localName = l.localName; + QString regName = lit("-"), typeName = lit("-"); + QString value = tr(""); + + if(var) + { + value.clear(); + + regName = var->name; + + if(l.variableType == VarType::UInt) + typeName = lit("uint"); + else if(l.variableType == VarType::Int) + typeName = lit("int"); + else if(l.variableType == VarType::Float) + typeName = lit("float"); + else if(l.variableType == VarType::Double) + typeName = lit("double"); + + if(l.registerType == RegisterType::IndexedTemporary) + { + typeName += lit("[]"); + + regName = QFormatStr("x%1").arg(l.registerIndex); + } + else + { + for(int i = 1; i < 4; i++) + { + if(i == 3 || l.variableSwizzle[i] == -1) + { + typeName += QString::number(i); + break; + } + } + + regName += lit("."); + localName += lit("."); + + QString swizzle = lit("xyzw"); + + for(uint32_t i = 0; i < 4; i++) + { + if(l.variableSwizzle[i] != -1) + { + int8_t vs = l.variableSwizzle[i]; + int8_t rs = l.registerSwizzle[i]; + + localName += swizzle[vs]; + regName += swizzle[rs]; + if(!value.isEmpty()) + value += lit(", "); + + if(l.variableType == VarType::UInt) + value += Formatter::Format(var->value.uv[rs]); + else if(l.variableType == VarType::Int) + value += Formatter::Format(var->value.iv[rs]); + else if(l.variableType == VarType::Float) + value += Formatter::Format(var->value.fv[rs]); + else if(l.variableType == VarType::Double) + value += Formatter::Format(var->value.dv[rs]); + } + } + } + } + + RDTreeWidgetItem *node = new RDTreeWidgetItem({localName, regName, typeName, value}); + + if(l.registerType == RegisterType::IndexedTemporary) + { + for(int t = 0; t < var->members.count(); t++) + { + node->addChild(new RDTreeWidgetItem({ + QFormatStr("%1[%2]").arg(localName).arg(t), QFormatStr("%1[%2]").arg(regName).arg(t), + typeName, RowString(var->members[t], 0, l.variableType), + })); + } + } + + ui->locals->addTopLevelItem(node); + } + } + if(ui->variables->topLevelItemCount() == 0) { for(int i = 0; i < state.registers.count(); i++) diff --git a/qrenderdoc/Windows/ShaderViewer.ui b/qrenderdoc/Windows/ShaderViewer.ui index d33a54788..c746e0d36 100644 --- a/qrenderdoc/Windows/ShaderViewer.ui +++ b/qrenderdoc/Windows/ShaderViewer.ui @@ -137,15 +137,6 @@ QFrame::NoFrame - - 0 - - - false - - - false - true @@ -492,8 +483,8 @@ - 390 - 90 + 790 + 60 151 131 @@ -554,6 +545,22 @@ QAbstractItemView::NoSelection + + + + 290 + 90 + 256 + 192 + + + + QFrame::NoFrame + + + true + + diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index 409cef8e4..f2084bf33 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -683,6 +683,18 @@ std::string DoStringise(const VarType &el) END_ENUM_STRINGISE(); } +template <> +std::string DoStringise(const RegisterType &el) +{ + BEGIN_ENUM_STRINGISE(RegisterType) + { + STRINGISE_ENUM_CLASS(Temporary); + STRINGISE_ENUM_CLASS(IndexedTemporary); + STRINGISE_ENUM_CLASS(Output); + } + END_ENUM_STRINGISE(); +} + template <> std::string DoStringise(const GPUCounter &el) { diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 778126d88..08645d826 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -95,6 +95,34 @@ DECLARE_REFLECTION_ENUM(SectionType); // replay_shader.h +DOCUMENT(R"(Represents the type of register a local variable maps to. + +.. data:: Input + + An input register. + +.. data:: Temporary + + A normal temporary register. + +.. data:: IndexedTemporary + + An indexed temporary register. + +.. data:: Output + + An output register. +)"); +enum class RegisterType : uint32_t +{ + Input, + Temporary, + IndexedTemporary, + Output, +}; + +DECLARE_REFLECTION_ENUM(RegisterType); + DOCUMENT(R"(Represents the base type of a shader variable in debugging or constant blocks. .. data:: Float diff --git a/renderdoc/api/replay/shader_types.h b/renderdoc/api/replay/shader_types.h index afd4e8c6a..324cae2c8 100644 --- a/renderdoc/api/replay/shader_types.h +++ b/renderdoc/api/replay/shader_types.h @@ -226,6 +226,69 @@ struct ShaderVariable DECLARE_REFLECTION_STRUCT(ShaderVariable); +DOCUMENT(R"(Refers to a shader variable in a :class:`ShaderDebugState` as a high-level local +variable, with type information. Since locals don't always map directly this can change over time. + +Locals can also be split and mapped to multiple registers, so a given high level variable may appear +several times with different subsections. +)"); +struct LocalVariableMapping +{ + DOCUMENT(""); + bool operator==(const LocalVariableMapping &o) const + { + return localName == o.localName && variableType == o.variableType && + registerType == o.registerType && registerIndex == o.registerIndex && + registerSwizzle == o.registerSwizzle; + } + bool operator<(const LocalVariableMapping &o) const + { + if(!(localName == o.localName)) + return localName < o.localName; + if(!(variableType == o.variableType)) + return variableType < o.variableType; + if(!(registerType == o.registerType)) + return registerType < o.registerType; + if(!(registerIndex == o.registerIndex)) + return registerIndex < o.registerIndex; + for(int i = 0; i < 4; i++) + { + if(!(registerSwizzle[i] == o.registerSwizzle[i])) + return registerSwizzle[i] < o.registerSwizzle[i]; + } + for(int i = 0; i < 4; i++) + { + if(!(variableSwizzle[i] == o.variableSwizzle[i])) + return variableSwizzle[i] < o.variableSwizzle[i]; + } + return false; + } + DOCUMENT("The name and member of this local variable that's being mapped from."); + rdcstr localName; + + DOCUMENT("The variable type of the local being mapped from, if the register is untyped."); + VarType variableType = VarType::Unknown; + + DOCUMENT("The :class:`RegisterType` of the register being mapped to."); + RegisterType registerType = RegisterType::Temporary; + + DOCUMENT("The index of the register within its type."); + uint32_t registerIndex = 0; + + DOCUMENT(R"(A swizzle mask - each element in the list is set to the component of the register to +map the variable component to. If an element is -1, there is no source component (i.e. not all 4 +components are used). This list will have the same number of elements as :data:`variableSwizzle`. +)"); + int8_t registerSwizzle[4] = {-1, -1, -1, -1}; + + DOCUMENT(R"(A swizzle mask - each element in the list is set to the component of the variable +being mapped from. If an element is -1, there is no source component (i.e. not all 4 +components are used). This list will have the same number of elements as :data:`registerSwizzle`. +)"); + int8_t variableSwizzle[4] = {-1, -1, -1, -1}; +}; +DECLARE_REFLECTION_STRUCT(LocalVariableMapping); + DOCUMENT(R"(This stores the current state of shader debugging at one particular step in the shader, with all mutable variable contents. )"); @@ -235,7 +298,8 @@ struct ShaderDebugState bool operator==(const ShaderDebugState &o) const { return registers == o.registers && outputs == o.outputs && indexableTemps == o.indexableTemps && - nextInstruction == o.nextInstruction && flags == o.flags && callstack == o.callstack; + locals == o.locals && nextInstruction == o.nextInstruction && flags == o.flags && + callstack == o.callstack; } bool operator<(const ShaderDebugState &o) const { @@ -245,6 +309,8 @@ struct ShaderDebugState return outputs < o.outputs; if(!(indexableTemps == o.indexableTemps)) return indexableTemps < o.indexableTemps; + if(!(locals == o.locals)) + return locals < o.locals; if(!(nextInstruction == o.nextInstruction)) return nextInstruction < o.nextInstruction; if(!(flags == o.flags)) @@ -253,15 +319,19 @@ struct ShaderDebugState return callstack < o.callstack; return false; } - DOCUMENT("The temporary variables for this shader as a list of :class:`ShaderValue`."); + DOCUMENT("The temporary variables for this shader as a list of :class:`ShaderVariable`."); rdcarray registers; - DOCUMENT("The output variables for this shader as a list of :class:`ShaderValue`."); + DOCUMENT("The output variables for this shader as a list of :class:`ShaderVariable`."); rdcarray outputs; - DOCUMENT( - "Indexable temporary variables for this shader as a list of :class:`ShaderValue` lists."); + DOCUMENT("Indexable temporary variables for this shader as a list of :class:`ShaderVariable`."); rdcarray indexableTemps; + DOCUMENT(R"(An optional list of :class:`ShaderVariableRef` indicating which high-level locals map +to which registers, and their type +)"); + rdcarray locals; + DOCUMENT("An optional callstack listing function calls at the present instruction"); rdcarray callstack; @@ -295,6 +365,9 @@ Each entry in this list corresponds to a constant block with the same index in t instruction was executed )"); rdcarray states; + + DOCUMENT("A flag indicating whether this trace has locals information"); + bool hasLocals = false; }; DECLARE_REFLECTION_STRUCT(ShaderDebugTrace); diff --git a/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp b/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp index 06b6bb4de..252d4756e 100644 --- a/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp +++ b/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp @@ -941,6 +941,7 @@ ShaderDebugTrace D3D11Replay::DebugVertex(uint32_t eventId, uint32_t vertid, uin vector states; dxbc->m_DebugInfo->GetStack(0, dxbc->GetInstruction(0).offset, initialState.callstack); + dxbc->m_DebugInfo->GetLocals(0, dxbc->GetInstruction(0).offset, initialState.locals); states.push_back((State)initialState); @@ -956,6 +957,7 @@ ShaderDebugTrace D3D11Replay::DebugVertex(uint32_t eventId, uint32_t vertid, uin { const ASMOperation &op = dxbc->GetInstruction((size_t)initialState.nextInstruction); dxbc->m_DebugInfo->GetStack(initialState.nextInstruction, op.offset, initialState.callstack); + dxbc->m_DebugInfo->GetLocals(initialState.nextInstruction, op.offset, initialState.locals); } states.push_back((State)initialState); @@ -969,6 +971,8 @@ ShaderDebugTrace D3D11Replay::DebugVertex(uint32_t eventId, uint32_t vertid, uin ret.states = states; + ret.hasLocals = dxbc->m_DebugInfo->HasLocals(); + return ret; } @@ -1818,6 +1822,7 @@ ShaderDebugTrace D3D11Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t vector states; dxbc->m_DebugInfo->GetStack(0, dxbc->GetInstruction(0).offset, quad[destIdx].callstack); + dxbc->m_DebugInfo->GetLocals(0, dxbc->GetInstruction(0).offset, quad[destIdx].locals); states.push_back((State)quad[destIdx]); @@ -1858,6 +1863,7 @@ ShaderDebugTrace D3D11Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t { const ASMOperation &op = dxbc->GetInstruction((size_t)s.nextInstruction); dxbc->m_DebugInfo->GetStack(s.nextInstruction, op.offset, s.callstack); + dxbc->m_DebugInfo->GetLocals(s.nextInstruction, op.offset, s.locals); } states.push_back(s); @@ -1953,6 +1959,8 @@ ShaderDebugTrace D3D11Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t traces[destIdx].states = states; + traces[destIdx].hasLocals = dxbc->m_DebugInfo->HasLocals(); + return traces[destIdx]; } @@ -2011,6 +2019,7 @@ ShaderDebugTrace D3D11Replay::DebugThread(uint32_t eventId, const uint32_t group vector states; dxbc->m_DebugInfo->GetStack(0, dxbc->GetInstruction(0).offset, initialState.callstack); + dxbc->m_DebugInfo->GetLocals(0, dxbc->GetInstruction(0).offset, initialState.locals); states.push_back((State)initialState); @@ -2024,6 +2033,7 @@ ShaderDebugTrace D3D11Replay::DebugThread(uint32_t eventId, const uint32_t group { const ASMOperation &op = dxbc->GetInstruction((size_t)initialState.nextInstruction); dxbc->m_DebugInfo->GetStack(initialState.nextInstruction, op.offset, initialState.callstack); + dxbc->m_DebugInfo->GetLocals(initialState.nextInstruction, op.offset, initialState.locals); } states.push_back((State)initialState); @@ -2037,5 +2047,7 @@ ShaderDebugTrace D3D11Replay::DebugThread(uint32_t eventId, const uint32_t group ret.states = states; + ret.hasLocals = dxbc->m_DebugInfo->HasLocals(); + return ret; } diff --git a/renderdoc/driver/shaders/dxbc/dxbc_inspect.h b/renderdoc/driver/shaders/dxbc/dxbc_inspect.h index 271501192..1f8d65879 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_inspect.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_inspect.h @@ -333,6 +333,10 @@ public: virtual void GetLineInfo(size_t instruction, uintptr_t offset, int32_t &fileIdx, int32_t &lineNum, std::string &funcName) const = 0; virtual void GetStack(size_t instruction, uintptr_t offset, rdcarray &stack) const = 0; + + virtual bool HasLocals() const = 0; + virtual void GetLocals(size_t instruction, uintptr_t offset, + rdcarray &locals) const = 0; }; uint32_t DecodeFlags(const ShaderCompileFlags &compileFlags); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_sdbg.cpp b/renderdoc/driver/shaders/dxbc/dxbc_sdbg.cpp index f8c4916c9..6e26384f8 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_sdbg.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_sdbg.cpp @@ -120,6 +120,16 @@ void SDBGChunk::GetStack(size_t instruction, uintptr_t offset, rdcarray stack = {"Stack not available"}; } +bool SDBGChunk::HasLocals() const +{ + return false; +} + +void SDBGChunk::GetLocals(size_t instruction, uintptr_t offset, + rdcarray &locals) const +{ +} + string SDBGChunk::GetSymbolName(int symbolID) { RDCASSERT(symbolID >= 0 && symbolID < (int)m_SymbolTable.size()); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_sdbg.h b/renderdoc/driver/shaders/dxbc/dxbc_sdbg.h index e4e4f6181..78040ded9 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_sdbg.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_sdbg.h @@ -264,6 +264,9 @@ public: std::string &func) const; void GetStack(size_t instruction, uintptr_t offset, rdcarray &stack) const; + bool HasLocals() const; + void GetLocals(size_t instruction, uintptr_t offset, rdcarray &locals) const; + private: SDBGChunk(); SDBGChunk(const SDBGChunk &); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp b/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp index d1fc7efd9..65cc2d620 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp @@ -175,6 +175,357 @@ SPDBChunk::SPDBChunk(void *chunk) } } + std::map typeInfo; + + // prepopulate with basic types + typeInfo[T_INT4] = VarType::Int; + typeInfo[T_INT2] = VarType::Int; + typeInfo[T_INT1] = VarType::Int; + typeInfo[T_LONG] = VarType::Int; + typeInfo[T_SHORT] = VarType::Int; + typeInfo[T_CHAR] = VarType::Int; + typeInfo[T_UINT4] = VarType::UInt; + typeInfo[T_UINT2] = VarType::UInt; + typeInfo[T_UINT1] = VarType::UInt; + typeInfo[T_ULONG] = VarType::UInt; + typeInfo[T_USHORT] = VarType::UInt; + typeInfo[T_UCHAR] = VarType::UInt; + typeInfo[T_REAL16] = VarType::Float; + typeInfo[T_REAL32] = VarType::Float; + typeInfo[T_REAL64] = VarType::Double; + + if(streams.size() >= 3) + { + SPDBLOG("Got types stream"); + + PDBStream &s = streams[2]; + PageMapping fileContents(pages, header->PageSize, &s.pageIndices[0], + (uint32_t)s.pageIndices.size()); + + byte *bytes = (byte *)fileContents.Data(); + byte *end = bytes + s.byteLength; + + TPIHeader *tpi = (TPIHeader *)bytes; + + // skip header + bytes += tpi->headerSize; + + RDCASSERT(bytes + tpi->dataSize == end); + +// this isn't needed, but this is the hash stream +#if 0 + PageMapping hashContents; + + if(tpi->hash.streamNumber < streams.size()) + { + PDBStream &hashstrm = streams[tpi->hash.streamNumber]; + hashContents = PageMapping(pages, header->PageSize, &hashstrm.pageIndices[0], + (uint32_t)hashstrm.pageIndices.size()); + } +#endif + + uint32_t id = tpi->typeMin; + + while(bytes < end) + { + uint16_t *leafheader = (uint16_t *)bytes; + + uint16_t length = leafheader[0]; + LEAF_ENUM_e type = (LEAF_ENUM_e)leafheader[1]; + + byte *leaf = (byte *)&leafheader[1]; + + bytes += 2 + length; + + switch(type) + { + case LF_VECTOR: + { + lfVector *vector = (lfVector *)leaf; + // documentation isn't clear, but seems like byte size is always a uint16_t + uint16_t *bytelength = (uint16_t *)vector->data; + char *name = (char *)(bytelength + 1); + SPDBLOG("Type %x is '%s': a vector of %x with %u elements over %u bytes", id, name, + vector->elemtype, vector->count, *bytelength); + + typeInfo[id] = typeInfo[vector->elemtype]; + + break; + } + case LF_MATRIX: + { + lfMatrix *matrix = (lfMatrix *)leaf; + // documentation isn't clear, but seems like byte size is always a uint16_t + uint16_t *bytelength = (uint16_t *)matrix->data; + char *name = (char *)(bytelength + 1); + SPDBLOG( + "Type %x is '%s': a matrix of %x with %u rows, %u columns over %u bytes with %u " + "byte %s major stride", + id, name, matrix->elemtype, matrix->rows, matrix->cols, *bytelength, + matrix->majorStride, matrix->matattr.row_major ? "row" : "column"); + + typeInfo[id] = typeInfo[matrix->elemtype]; + + break; + } + case LF_HLSL: + { + lfHLSL *hlsl = (lfHLSL *)leaf; + // documentation mentions "numeric properties followed by byte size" but we don't need + // that + + const char *hlslTypeName = ""; + switch((CV_builtin_e)hlsl->kind) + { + case CV_BI_HLSL_INTERFACE_POINTER: hlslTypeName = "INTERFACE_POINTER"; break; + case CV_BI_HLSL_TEXTURE1D: hlslTypeName = "TEXTURE1D"; break; + case CV_BI_HLSL_TEXTURE1D_ARRAY: hlslTypeName = "TEXTURE1D_ARRAY"; break; + case CV_BI_HLSL_TEXTURE2D: hlslTypeName = "TEXTURE2D"; break; + case CV_BI_HLSL_TEXTURE2D_ARRAY: hlslTypeName = "TEXTURE2D_ARRAY"; break; + case CV_BI_HLSL_TEXTURE3D: hlslTypeName = "TEXTURE3D"; break; + case CV_BI_HLSL_TEXTURECUBE: hlslTypeName = "TEXTURECUBE"; break; + case CV_BI_HLSL_TEXTURECUBE_ARRAY: hlslTypeName = "TEXTURECUBE_ARRAY"; break; + case CV_BI_HLSL_TEXTURE2DMS: hlslTypeName = "TEXTURE2DMS"; break; + case CV_BI_HLSL_TEXTURE2DMS_ARRAY: hlslTypeName = "TEXTURE2DMS_ARRAY"; break; + case CV_BI_HLSL_SAMPLER: hlslTypeName = "SAMPLER"; break; + case CV_BI_HLSL_SAMPLERCOMPARISON: hlslTypeName = "SAMPLERCOMPARISON"; break; + case CV_BI_HLSL_BUFFER: hlslTypeName = "BUFFER"; break; + case CV_BI_HLSL_POINTSTREAM: hlslTypeName = "POINTSTREAM"; break; + case CV_BI_HLSL_LINESTREAM: hlslTypeName = "LINESTREAM"; break; + case CV_BI_HLSL_TRIANGLESTREAM: hlslTypeName = "TRIANGLESTREAM"; break; + case CV_BI_HLSL_INPUTPATCH: hlslTypeName = "INPUTPATCH"; break; + case CV_BI_HLSL_OUTPUTPATCH: hlslTypeName = "OUTPUTPATCH"; break; + case CV_BI_HLSL_RWTEXTURE1D: hlslTypeName = "RWTEXTURE1D"; break; + case CV_BI_HLSL_RWTEXTURE1D_ARRAY: hlslTypeName = "RWTEXTURE1D_ARRAY"; break; + case CV_BI_HLSL_RWTEXTURE2D: hlslTypeName = "RWTEXTURE2D"; break; + case CV_BI_HLSL_RWTEXTURE2D_ARRAY: hlslTypeName = "RWTEXTURE2D_ARRAY"; break; + case CV_BI_HLSL_RWTEXTURE3D: hlslTypeName = "RWTEXTURE3D"; break; + case CV_BI_HLSL_RWBUFFER: hlslTypeName = "RWBUFFER"; break; + case CV_BI_HLSL_BYTEADDRESS_BUFFER: hlslTypeName = "BYTEADDRESS_BUFFER"; break; + case CV_BI_HLSL_RWBYTEADDRESS_BUFFER: hlslTypeName = "RWBYTEADDRESS_BUFFER"; break; + case CV_BI_HLSL_STRUCTURED_BUFFER: hlslTypeName = "STRUCTURED_BUFFER"; break; + case CV_BI_HLSL_RWSTRUCTURED_BUFFER: hlslTypeName = "RWSTRUCTURED_BUFFER"; break; + case CV_BI_HLSL_APPEND_STRUCTURED_BUFFER: + hlslTypeName = "APPEND_STRUCTURED_BUFFER"; + break; + case CV_BI_HLSL_CONSUME_STRUCTURED_BUFFER: + hlslTypeName = "CONSUME_STRUCTURED_BUFFER"; + break; + case CV_BI_HLSL_MIN8FLOAT: hlslTypeName = "MIN8FLOAT"; break; + case CV_BI_HLSL_MIN10FLOAT: hlslTypeName = "MIN10FLOAT"; break; + case CV_BI_HLSL_MIN16FLOAT: hlslTypeName = "MIN16FLOAT"; break; + case CV_BI_HLSL_MIN12INT: hlslTypeName = "MIN12INT"; break; + case CV_BI_HLSL_MIN16INT: hlslTypeName = "MIN16INT"; break; + case CV_BI_HLSL_MIN16UINT: hlslTypeName = "MIN16UINT"; break; + default: hlslTypeName = "Unknown type"; + } + + SPDBLOG("Type %x is an hlsl %s[%u] (subtype %x)", id, hlslTypeName, hlsl->numprops, + hlsl->subtype); + break; + } + case LF_MODIFIER_EX: + { + lfModifierEx *modifier = (lfModifierEx *)leaf; + SPDBLOG("Type %x is %x modified with:", id, modifier->type); + + typeInfo[id] = typeInfo[modifier->type]; + + uint16_t *mods = (uint16_t *)modifier->mods; + for(unsigned short i = 0; i < modifier->count; i++) + { + CV_modifier_e mod = (CV_modifier_e)mods[i]; + + const char *modName = ""; + switch(mod) + { + case CV_MOD_CONST: modName = "CONST"; break; + case CV_MOD_VOLATILE: modName = "VOLATILE"; break; + case CV_MOD_UNALIGNED: modName = "UNALIGNED"; break; + case CV_MOD_HLSL_UNIFORM: modName = "HLSL_UNIFORM"; break; + case CV_MOD_HLSL_LINE: modName = "HLSL_LINE"; break; + case CV_MOD_HLSL_TRIANGLE: modName = "HLSL_TRIANGLE"; break; + case CV_MOD_HLSL_LINEADJ: modName = "HLSL_LINEADJ"; break; + case CV_MOD_HLSL_TRIANGLEADJ: modName = "HLSL_TRIANGLEADJ"; break; + case CV_MOD_HLSL_LINEAR: modName = "HLSL_LINEAR"; break; + case CV_MOD_HLSL_CENTROID: modName = "HLSL_CENTROID"; break; + case CV_MOD_HLSL_CONSTINTERP: modName = "HLSL_CONSTINTERP"; break; + case CV_MOD_HLSL_NOPERSPECTIVE: modName = "HLSL_NOPERSPECTIVE"; break; + case CV_MOD_HLSL_SAMPLE: modName = "HLSL_SAMPLE"; break; + case CV_MOD_HLSL_CENTER: modName = "HLSL_CENTER"; break; + case CV_MOD_HLSL_SNORM: modName = "HLSL_SNORM"; break; + case CV_MOD_HLSL_UNORM: modName = "HLSL_UNORM"; break; + case CV_MOD_HLSL_PRECISE: modName = "HLSL_PRECISE"; break; + case CV_MOD_HLSL_UAV_GLOBALLY_COHERENT: modName = "HLSL_UAV_GLOBALLY_COHERENT"; break; + default: modName = "Unknown modification"; + } + + SPDBLOG(" + %s", modName); + } + break; + } + case LF_FIELDLIST: + { + lfFieldList *fieldList = (lfFieldList *)leaf; + SPDBLOG("Type %x is a field list containing:", id); + + uint32_t idx = 0; + + byte *iter = (byte *)fieldList->data; + while(iter < bytes) + { + if(*iter >= LF_PAD0) + { + iter += (*iter) - LF_PAD0; + continue; + } + + LEAF_ENUM_e memberType = LEAF_ENUM_e(*(uint16_t *)iter); + + switch(memberType) + { + case LF_MEMBER: + { + lfMember *member = (lfMember *)iter; + + uint16_t *byteoffset = (uint16_t *)member->offset; + char *name = (char *)(byteoffset + 1); + + char *access = "???"; + + if(member->attr.access == 1) + access = "private"; + else if(member->attr.access == 2) + access = "protected"; + else if(member->attr.access == 3) + access = "public"; + + SPDBLOG(" [%u]: %x %s (%s) (at offset %u bytes)", idx, member->index, name, access, + *byteoffset); + + idx++; + + iter = (byte *)(name + strlen(name) + 1); + break; + } + case LF_ONEMETHOD: + { + lfOneMethod *method = (lfOneMethod *)iter; + + iter = (byte *)method->vbaseoff; + + // MTintro = 0x04, MTpureintro = 0x06 + if(method->attr.mprop == 0x04 || method->attr.mprop == 0x06) + { + iter += 4; + } + + char *name = (char *)iter; + iter += strlen(name) + 1; + + char *access = "???"; + + if(method->attr.access == 1) + access = "private"; + else if(method->attr.access == 2) + access = "protected"; + else if(method->attr.access == 3) + access = "public"; + + SPDBLOG(" [%u]: Method %s (%s) (at offset %u bytes)", idx, name, access); + + idx++; + break; + } + case LF_BINTERFACE: + { + lfBClass *binterface = (lfBClass *)iter; + + char *access = "???"; + + if(binterface->attr.access == 1) + access = "private"; + else if(binterface->attr.access == 2) + access = "protected"; + else if(binterface->attr.access == 3) + access = "public"; + + SPDBLOG(" [%u]: %x Interface (%s)", idx, binterface->index, access); + + iter = (byte *)binterface->offset + 2; + + idx++; + break; + } + default: + { + RDCERR("Unexpected member type %x", memberType); + // skip the remaining data as we don't know how to safely advance - no length fields + // to use + iter = bytes; + break; + } + } + } + break; + } + case LF_ARGLIST: + { + lfArgList *argList = (lfArgList *)leaf; + SPDBLOG("Type %x is a field list containing:", id); + + for(unsigned long i = 0; i < argList->count; i++) + SPDBLOG(" %x", argList->arg[i]); + break; + } + case LF_INTERFACE: + case LF_CLASS: + case LF_STRUCTURE: + { + lfStructure *structure = (lfStructure *)leaf; + // documentation isn't clear, but seems like byte size is always a uint16_t + uint16_t *bytelength = (uint16_t *)structure->data; + char *name = (char *)(bytelength + 1); + + const char *structType = "struct"; + if(type == LF_INTERFACE) + structType = "interface"; + else if(type == LF_CLASS) + structType = "class"; + + SPDBLOG( + "Type %x is '%s': a %s with %u fields %x derived from %x and vshape %x over %u " + "bytes", + id, name, structType, structure->count, structure->field, structure->derived, + structure->vshape, *bytelength); + break; + } + case LF_PROCEDURE: + { + lfProc *procedure = (lfProc *)leaf; + SPDBLOG("Type %x is a procedure returning %x with %u args: %x", id, procedure->rvtype, + procedure->parmcount, procedure->arglist); + break; + } + case LF_MFUNCTION: + { + lfMFunc *mfunction = (lfMFunc *)leaf; + SPDBLOG("Type %x is a member function of class %x returning %x with %u args: %x", id, + mfunction->classtype, mfunction->rvtype, mfunction->parmcount, mfunction->arglist); + break; + } + default: + { + SPDBLOG("Encountered unknown type leaf %x", type); + break; + } + } + id++; + } + + RDCASSERT(id == tpi->typeMax); + } + if(streams.size() >= 5) { SPDBLOG("Got function calls stream"); @@ -668,25 +1019,33 @@ SPDBChunk::SPDBChunk(void *chunk) { DEFRANGESYMHLSL *defrange = (DEFRANGESYMHLSL *)sym; + LocalMapping mapping; + + bool indexable = false; const char *regtype = ""; const char *regprefix = "?"; switch((CV_HLSLREG_e)defrange->regType) { case CV_HLSLREG_TEMP: + mapping.var.registerType = RegisterType::Temporary; regtype = "temp"; regprefix = "r"; break; case CV_HLSLREG_INPUT: + mapping.var.registerType = RegisterType::Input; regtype = "input"; regprefix = "v"; break; case CV_HLSLREG_OUTPUT: + mapping.var.registerType = RegisterType::Output; regtype = "output"; regprefix = "o"; break; case CV_HLSLREG_INDEXABLE_TEMP: + mapping.var.registerType = RegisterType::IndexedTemporary; regtype = "indexable"; regprefix = "x"; + indexable = true; break; default: break; } @@ -709,9 +1068,9 @@ SPDBChunk::SPDBChunk(void *chunk) char regcomps[] = "xyzw"; - uint32_t regindex = regoffset / 16; + uint32_t regindex = indexable ? regoffset : regoffset / 16; uint32_t regfirstcomp = (regoffset % 16) / 4; - uint32_t regnumcomps = defrange->sizeInParent / 4; + uint32_t regnumcomps = indexable ? 4 : defrange->sizeInParent / 4; char *regswizzle = regcomps; regswizzle += regfirstcomp; @@ -719,9 +1078,23 @@ SPDBChunk::SPDBChunk(void *chunk) SPDBLOG("Stored in %s%u.%s", regprefix, regindex, regswizzle); + mapping.var.localName = localName; + + mapping.var.variableType = typeInfo[localType]; + + mapping.var.registerIndex = regindex; + for(uint32_t i = 0; i < regnumcomps; i++) + { + mapping.var.registerSwizzle[i] = uint8_t(regfirstcomp + i); + mapping.var.variableSwizzle[i] = uint8_t((defrange->offsetParent % 16) / 4 + i); + } + SPDBLOG("Valid from %x to %x", defrange->range.offStart, defrange->range.offStart + defrange->range.cbRange); + mapping.range.startRange = defrange->range.offStart; + mapping.range.endRange = defrange->range.offStart + defrange->range.cbRange; + const CV_LVAR_ADDR_GAP *gaps = CV_DEFRANGESYMHLSL_GAPS_CONST_PTR(defrange); size_t gapcount = CV_DEFRANGESYMHLSL_GAPS_COUNT(defrange); if(gapcount > 0) @@ -730,7 +1103,16 @@ SPDBChunk::SPDBChunk(void *chunk) { SPDBLOG(" Gap %zu: %x -> %x", i, defrange->range.offStart + gaps[i].gapStartOffset, defrange->range.offStart + gaps[i].gapStartOffset + gaps[i].cbRange); + + LocalRange r = {defrange->range.offStart + gaps[i].gapStartOffset, + defrange->range.offStart + gaps[i].gapStartOffset + gaps[i].cbRange}; + + mapping.gaps.push_back(r); } + + // don't add input variables as they don't change + if(mapping.var.registerType != RegisterType::Input) + m_Locals.push_back(mapping); } else if(type == S_INLINESITE_END) { @@ -1041,6 +1423,8 @@ SPDBChunk::SPDBChunk(void *chunk) it->second.fileIndex = remapping[filenames[it->second.fileIndex]]; } + std::sort(m_Locals.begin(), m_Locals.end()); + m_HasDebugInfo = true; } @@ -1069,4 +1453,108 @@ void SPDBChunk::GetStack(size_t instruction, uintptr_t offset, rdcarray } } +bool SPDBChunk::HasLocals() const +{ + return true; +} + +void SPDBChunk::GetLocals(size_t instruction, uintptr_t offset, + rdcarray &locals) const +{ + locals.clear(); + + for(auto it = m_Locals.begin(); it != m_Locals.end(); ++it) + { + if(it->range.startRange > offset) + break; + + if(it->range.endRange <= offset) + continue; + + bool ingap = false; + + for(auto gapit = it->gaps.begin(); gapit != it->gaps.end(); gapit++) + { + if(gapit->startRange >= offset && gapit->endRange < offset) + { + ingap = true; + break; + } + } + + if(ingap) + continue; + + bool added = false; + + // check for duplicate registers + for(LocalVariableMapping &a : locals) + { + const LocalVariableMapping &b = it->var; + + // if the mapping was the same register, same variable, etc + if(a.registerIndex == b.registerIndex && a.registerType == b.registerType && + a.variableType == b.variableType && a.localName == b.localName) + { + // insert b into a, in variableSwizzle sorted order. Note the number of nested loops might + // seem scary but they only iterate up to 4 and in many cases will early out. + for(int i = 0; i < 4; i++) + { + if(b.variableSwizzle[i] == -1) + break; + + for(int j = 0; j < 4; j++) + { + if(a.variableSwizzle[j] == b.variableSwizzle[i]) + { + // allow overlaps as long as they come from the same register component + RDCASSERT(a.registerSwizzle[j] == b.registerSwizzle[i]); + break; + } + else if(a.variableSwizzle[j] == -1) + { + // if we reached the end of the swizzles, just append our swizzle here as we know it's + // in sorted order + a.variableSwizzle[j] = b.variableSwizzle[i]; + RDCASSERT(a.registerSwizzle[j] == -1); + a.registerSwizzle[j] = b.registerSwizzle[i]; + break; + } + else if(a.variableSwizzle[j] < b.variableSwizzle[i]) + { + // keep going if we haven't found where we want to insert this component yet + continue; + } + else // a.variableSwizzle[j] > b.variableSwizzle[i] + { + // we shouldn't reach here on the last element, since then we should have found an + // exact match above - there are only 4 possible components + RDCASSERT(j < 3); + + // the hard case - we need to insert our new component in the middle. + // First, shift everything up by one starting from the end and moving j to j+1 + for(int k = 3; k > j; k--) + { + a.variableSwizzle[k] = a.variableSwizzle[k - 1]; + a.registerSwizzle[k] = a.registerSwizzle[k - 1]; + } + + // now insert our variable + a.variableSwizzle[j] = b.variableSwizzle[i]; + a.registerSwizzle[j] = b.registerSwizzle[i]; + break; + } + } + } + + added = true; + break; + } + } + + if(!added) + locals.push_back(it->var); + } +} + }; // namespace DXBC \ No newline at end of file diff --git a/renderdoc/driver/shaders/dxbc/dxbc_spdb.h b/renderdoc/driver/shaders/dxbc/dxbc_spdb.h index 9f4fd6e68..3efd1235b 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_spdb.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_spdb.h @@ -249,6 +249,26 @@ struct PDBStream vector pageIndices; }; +struct LocalRange +{ + uint32_t startRange; + uint32_t endRange; + + bool operator==(const LocalRange &o) const + { + return startRange == o.startRange && endRange == o.endRange; + } +}; + +struct LocalMapping +{ + bool operator<(const LocalMapping &o) const { return range.startRange < o.range.startRange; } + LocalRange range; + std::vector gaps; + + LocalVariableMapping var; +}; + class SPDBChunk : public DXBCDebugChunk { public: @@ -262,6 +282,9 @@ public: std::string &func) const; void GetStack(size_t instruction, uintptr_t offset, rdcarray &stack) const; + bool HasLocals() const; + void GetLocals(size_t instruction, uintptr_t offset, rdcarray &locals) const; + private: SPDBChunk(const SPDBChunk &); SPDBChunk &operator=(const SPDBChunk &o); @@ -275,6 +298,8 @@ private: uint32_t m_ShaderFlags; + std::vector m_Locals; + std::map m_Functions; std::map m_Lines; }; diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 6b47cbba8..a52639150 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -341,17 +341,31 @@ void DoSerialise(SerialiserType &ser, ShaderVariable &el) SIZE_CHECK(184); } +template +void DoSerialise(SerialiserType &ser, LocalVariableMapping &el) +{ + SERIALISE_MEMBER(localName); + SERIALISE_MEMBER(variableType); + SERIALISE_MEMBER(registerType); + SERIALISE_MEMBER(registerIndex); + SERIALISE_MEMBER(registerSwizzle); + SERIALISE_MEMBER(variableSwizzle); + + SIZE_CHECK(40); +} + template void DoSerialise(SerialiserType &ser, ShaderDebugState &el) { SERIALISE_MEMBER(registers); SERIALISE_MEMBER(outputs); SERIALISE_MEMBER(indexableTemps); + SERIALISE_MEMBER(locals); SERIALISE_MEMBER(nextInstruction); SERIALISE_MEMBER(flags); SERIALISE_MEMBER(callstack); - SIZE_CHECK(56); + SIZE_CHECK(88); } template @@ -360,8 +374,9 @@ void DoSerialise(SerialiserType &ser, ShaderDebugTrace &el) SERIALISE_MEMBER(inputs); SERIALISE_MEMBER(constantBlocks); SERIALISE_MEMBER(states); + SERIALISE_MEMBER(hasLocals); - SIZE_CHECK(48); + SIZE_CHECK(56); } template @@ -2099,6 +2114,7 @@ INSTANTIATE_SERIALISE_TYPE(ShaderCompileFlags) INSTANTIATE_SERIALISE_TYPE(ShaderDebugInfo) INSTANTIATE_SERIALISE_TYPE(ShaderReflection) INSTANTIATE_SERIALISE_TYPE(ShaderVariable) +INSTANTIATE_SERIALISE_TYPE(LocalVariableMapping); INSTANTIATE_SERIALISE_TYPE(ShaderDebugState) INSTANTIATE_SERIALISE_TYPE(ShaderDebugTrace) INSTANTIATE_SERIALISE_TYPE(ResourceDescription)