diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h index f87cbec23..59fbcd838 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.h +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -250,6 +250,17 @@ enum class DebugScope Block, }; +struct TypeData +{ + VarType type = VarType::Unknown; + uint32_t vecSize = 0, matSize = 0; + bool colMajorMat = false; + + Id baseType; + uint32_t arrayDimension = 0; + rdcarray> structMembers; +}; + struct ScopeData { DebugScope type; @@ -274,11 +285,48 @@ struct LocalData { rdcstr name; ScopeData *scope; - - Id curId; + TypeData *type; }; -typedef rdcpair LocalMapping; +struct LocalMapping +{ + bool operator<(const LocalMapping &o) const + { + if(sourceVar != o.sourceVar) + return sourceVar < o.sourceVar; + if(indexes != o.indexes) + return indexes < o.indexes; + return debugVar < o.debugVar; + } + + bool isSourceSupersetOf(const LocalMapping &o) const + { + // this mapping is a superset of the other if: + + // it's the same source var + if(sourceVar != o.sourceVar) + return false; + + // it contains the same or fewer indices + if(o.indexes.size() < indexes.size()) + return false; + + // the common prefix of indexes is identical + for(size_t i = 0; i < indexes.size(); i++) + if(indexes[i] != o.indexes[i]) + return false; + + // if all those conditions are true, we either map to the same index (indexes size is the same - + // likely case) or else we cover a whole sub-tree where the other only covers a leaf (other has + // more indexes - unlikely but possible) + return true; + } + + size_t offset; + Id sourceVar; + Id debugVar; + rdcarray indexes; +}; class Debugger : public Processor, public ShaderDebugger { @@ -406,6 +454,7 @@ private: { bool valid = false; + SparseIdMap types; SparseIdMap scopes; SparseIdMap inlined; ScopeData *curScope = NULL; @@ -422,7 +471,11 @@ private: std::map lineScope; std::map lineInline; std::map localMappings; + + rdcarray activeLocalMappings; } m_DebugInfo; + + const ScopeData *GetScope(size_t offset) const; }; // this does a 'safe' value assignment, by doing parallel depth-first iteration of both variables diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index 04042270a..2c4310f23 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -1347,66 +1347,238 @@ rdcarray Debugger::ContinueDebug() const LocalMapping &mapping = it->second; - if(mapping.second == Id()) + if(mapping.debugVar == Id()) { // if the Ids are empty, this is a scope exiting. Potentially multiple scopes at // once. The only scopes that could have exited are the ones that we were previously // in, so start from the scope of the previous instruction and walk up its parents. // Set curId to empty on all the locals of any scope that's exited - ScopeData *scope = m_DebugInfo.lineScope[startOffs]; + const ScopeData *scope = GetScope(startOffs); while(scope) { if(scope->end <= endOffs) { - for(Id id : scope->locals) - m_DebugInfo.locals[id].curId = Id(); + m_DebugInfo.activeLocalMappings.removeIf([scope](const LocalMapping &l) { + return scope->locals.contains(l.sourceVar); + }); } scope = scope->parent; } } + else if(thread.ids[mapping.debugVar].type == VarType::Unknown) + { + continue; + } else { - // otherwise this is indicating the new current Id of a local - m_DebugInfo.locals[mapping.first].curId = mapping.second; + LocalMapping search; + search.sourceVar = mapping.sourceVar; + + // find start of this sourceVar + LocalMapping *pos = std::lower_bound(m_DebugInfo.activeLocalMappings.begin(), + m_DebugInfo.activeLocalMappings.end(), search); + + // look over all existing local mappings, and if any have been made redundant remove + // them + for(size_t i = (pos - m_DebugInfo.activeLocalMappings.begin()); + i < m_DebugInfo.activeLocalMappings.size();) + { + if(m_DebugInfo.activeLocalMappings[i].sourceVar != mapping.sourceVar) + break; + + if(mapping.isSourceSupersetOf(m_DebugInfo.activeLocalMappings[i])) + { + m_DebugInfo.activeLocalMappings.erase(i); + continue; + } + + i++; + } + + // now add the new mapping in sorted order + pos = std::lower_bound(m_DebugInfo.activeLocalMappings.begin(), + m_DebugInfo.activeLocalMappings.end(), mapping); + m_DebugInfo.activeLocalMappings.insert( + pos - m_DebugInfo.activeLocalMappings.begin(), mapping); } } // start with the global source vars state.sourceVars = globalSourceVars; - // now go from the innermost scope to the outermost. At each scope add its locals - ScopeData *scope = m_DebugInfo.lineScope[endOffs]; - while(scope) + const ScopeData *scope = GetScope(endOffs); + // only add locals when in a scope + if(scope) { - for(Id id : scope->locals) + // get the function, only add locals that are in this function or a block child + const ScopeData *func = scope; + while(func && func->parent && func->type != DebugScope::Function) + func = func->parent; + + rdcarray sorted = m_DebugInfo.activeLocalMappings; + std::sort( + sorted.begin(), sorted.end(), + [](const LocalMapping &a, const LocalMapping &b) { return a.offset < b.offset; }); + + for(const LocalMapping &mapping : sorted) { - const LocalData &l = m_DebugInfo.locals[id]; + const LocalData &l = m_DebugInfo.locals[mapping.sourceVar]; - if(l.curId != Id()) + const ScopeData *lfunc = l.scope; + while(lfunc && lfunc->parent && lfunc->type != DebugScope::Function) + lfunc = lfunc->parent; + + if(lfunc != func) + continue; + + // if it doesn't have indexes this is simple, set up a 1:1 map + if(mapping.indexes.isEmpty()) { - ShaderVariable var = ReadFromPointer(thread.ids[l.curId]); + SourceVariableMapping sourceVar; - // don't map locals to IDs that don't exist yet - if(!var.name.empty()) + const TypeData *typeWalk = l.type; + + sourceVar.name = l.name; + sourceVar.offset = 0; + sourceVar.rows = 1U; + sourceVar.columns = 1U; + + if(typeWalk->matSize != 0) { - SourceVariableMapping sourceVar; + const TypeData &vec = m_DebugInfo.types[typeWalk->baseType]; + const TypeData &scalar = m_DebugInfo.types[vec.baseType]; - sourceVar.name = l.name; - sourceVar.offset = 0; - sourceVar.type = var.type; - sourceVar.rows = RDCMAX(1U, (uint32_t)var.rows); - sourceVar.columns = RDCMAX(1U, (uint32_t)var.columns); - for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++) - sourceVar.variables.push_back( - DebugVariableReference(DebugVariableType::Variable, var.name, x)); + sourceVar.type = scalar.type; - state.sourceVars.push_back(sourceVar); + if(typeWalk->colMajorMat) + { + sourceVar.rows = RDCMAX(1U, vec.vecSize); + sourceVar.columns = RDCMAX(1U, typeWalk->matSize); + } + else + { + sourceVar.rows = RDCMAX(1U, typeWalk->matSize); + sourceVar.columns = RDCMAX(1U, vec.vecSize); + } } + else if(typeWalk->vecSize != 0) + { + const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType]; + + sourceVar.type = scalar.type; + sourceVar.columns = RDCMAX(1U, typeWalk->vecSize); + } + else + { + while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown) + typeWalk = &m_DebugInfo.types[typeWalk->baseType]; + + sourceVar.type = typeWalk->type; + if(sourceVar.type == VarType::Unknown) + sourceVar.type = VarType::Struct; + + ShaderVariable var = ReadFromPointer(thread.ids[mapping.debugVar]); + + if(var.type != VarType::Struct) + { + sourceVar.rows = var.rows; + sourceVar.columns = var.columns; + } + } + + for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++) + sourceVar.variables.push_back(DebugVariableReference( + DebugVariableType::Variable, GetRawName(mapping.debugVar), x)); + + state.sourceVars.push_back(sourceVar); + } + else + { + SourceVariableMapping sourceVar; + + rdcarray indexes = mapping.indexes; + + const TypeData *typeWalk = l.type; + + sourceVar.name = l.name; + sourceVar.offset = 0; + sourceVar.rows = 1U; + sourceVar.columns = 1U; + + while(!indexes.empty()) + { + if(typeWalk->arrayDimension > 0) + { + uint32_t numIdxs = (uint32_t)indexes.size(); + for(size_t i = 0; i < RDCMIN(typeWalk->arrayDimension, numIdxs); i++) + { + sourceVar.name += StringFormat::Fmt("[%u]", indexes.back()); + indexes.pop_back(); + } + + typeWalk = &m_DebugInfo.types[typeWalk->baseType]; + } + else if(!typeWalk->structMembers.empty()) + { + uint32_t idx = indexes.back(); + indexes.pop_back(); + + sourceVar.name += + StringFormat::Fmt(".%s", typeWalk->structMembers[idx].first.c_str()); + + typeWalk = &m_DebugInfo.types[typeWalk->structMembers[idx].second]; + } + else + { + break; + } + } + + RDCASSERT(indexes.empty()); + + if(typeWalk->matSize != 0) + { + const TypeData &vec = m_DebugInfo.types[typeWalk->baseType]; + const TypeData &scalar = m_DebugInfo.types[vec.baseType]; + + sourceVar.type = scalar.type; + + if(typeWalk->colMajorMat) + { + sourceVar.rows = RDCMAX(1U, vec.vecSize); + sourceVar.columns = RDCMAX(1U, typeWalk->matSize); + } + else + { + sourceVar.rows = RDCMAX(1U, typeWalk->matSize); + sourceVar.columns = RDCMAX(1U, vec.vecSize); + } + } + else if(typeWalk->vecSize != 0) + { + const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType]; + + sourceVar.type = scalar.type; + sourceVar.columns = RDCMAX(1U, typeWalk->vecSize); + } + else + { + while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown) + typeWalk = &m_DebugInfo.types[typeWalk->baseType]; + + sourceVar.type = typeWalk->type; + if(sourceVar.type == VarType::Unknown) + sourceVar.type = VarType::Struct; + } + + for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++) + sourceVar.variables.push_back(DebugVariableReference( + DebugVariableType::Variable, GetRawName(mapping.debugVar), x)); + + state.sourceVars.push_back(sourceVar); } } - - scope = scope->parent; } // append any inlined functions to the top of the stack @@ -1417,7 +1589,7 @@ rdcarray Debugger::ContinueDebug() // start with the current scope, it refers to the *inlined* function if(inlined) { - scope = m_DebugInfo.lineScope[endOffs]; + scope = GetScope(endOffs); // find the function parent of the current scope while(scope && scope->parent && scope->type == DebugScope::Block) scope = scope->parent; @@ -1443,17 +1615,17 @@ rdcarray Debugger::ContinueDebug() else { state.sourceVars = thread.sourceVars; + + // sort sourceVars by last write to the underlying variable + std::sort(state.sourceVars.begin(), state.sourceVars.end(), + [&thread](const SourceVariableMapping &a, const SourceVariableMapping &b) { + Id aId = ParseRawName(a.variables[0].name); + Id bId = ParseRawName(b.variables[0].name); + + return thread.lastWrite[aId] < thread.lastWrite[bId]; + }); } - // sort sourceVars by last write to the underlying variable - std::sort(state.sourceVars.begin(), state.sourceVars.end(), - [&thread](const SourceVariableMapping &a, const SourceVariableMapping &b) { - Id aId = ParseRawName(a.variables[0].name); - Id bId = ParseRawName(b.variables[0].name); - - return thread.lastWrite[aId] < thread.lastWrite[bId]; - }); - ret.push_back(state); steps++; @@ -2553,6 +2725,14 @@ bool Debugger::IsDebugExtInstSet(Id id) const return knownExtSet[ExtSet_ShaderDbg] == id; } +const ScopeData *Debugger::GetScope(size_t offset) const +{ + auto it = m_DebugInfo.lineScope.find(offset); + if(it == m_DebugInfo.lineScope.end()) + return NULL; + return it->second; +} + void Debugger::PreParse(uint32_t maxId) { Processor::PreParse(maxId); @@ -2587,26 +2767,22 @@ void Debugger::PostParse() // add a dummy localMappings entry for each scope end for(auto it = m_DebugInfo.scopes.begin(); it != m_DebugInfo.scopes.end(); ++it) - m_DebugInfo.localMappings[it->second.end] = {Id(), Id()}; + m_DebugInfo.localMappings[it->second.end] = {0, Id(), Id()}; for(auto it = m_DebugInfo.localMappings.begin(); it != m_DebugInfo.localMappings.end(); ++it) { - if(it->second.second == Id()) + if(it->second.debugVar == Id()) continue; - const LocalData &l = m_DebugInfo.locals[it->second.first]; + const LocalData &l = m_DebugInfo.locals[it->second.sourceVar]; if(l.scope == NULL) continue; // keep last raw ID alive until the scope ends - Id id = it->second.second; + Id id = it->second.debugVar; idDeathOffset[id] = RDCMAX(l.scope->end + 1, RDCMAX(it->first + 1, idDeathOffset[id])); } - - // reset current Ids - for(auto it = m_DebugInfo.locals.begin(); it != m_DebugInfo.locals.end(); ++it) - it->second.curId = Id(); } memberNames.clear(); @@ -2654,184 +2830,286 @@ void Debugger::RegisterOp(Iter it) // the types are identical just with different accessors OpShaderDbg &dbg = (OpShaderDbg &)extinst; - if(dbg.inst == ShaderDbg::Source) + switch(dbg.inst) { - int32_t fileIndex = (int32_t)m_DebugInfo.sources.size(); - - m_DebugInfo.sources[dbg.result] = fileIndex; - m_DebugInfo.filenames[dbg.result] = strings[dbg.arg(0)]; - } - else if(dbg.inst == ShaderDbg::CompilationUnit) - { - m_DebugInfo.scopes[dbg.result] = { - DebugScope::CompilationUnit, - NULL, - 1, - 1, - m_DebugInfo.sources[dbg.arg(2)], - 0, - m_DebugInfo.filenames[dbg.arg(2)], - }; - } - else if(dbg.inst == ShaderDbg::Function) - { - rdcstr name = strings[dbg.arg(0)]; - // ignore arg 1 type - // don't use arg 2 source - assume the parent is in the same file so it's redundant - uint32_t line = EvaluateConstant(dbg.arg(3), {}).value.u32v[0]; - uint32_t column = EvaluateConstant(dbg.arg(4), {}).value.u32v[0]; - ScopeData *parent = &m_DebugInfo.scopes[dbg.arg(5)]; - // ignore arg 6 linkage name - // ignore arg 7 flags - // ignore arg 8 scope line - // ignore arg 9 (optional) declaration - - m_DebugInfo.scopes[dbg.result] = { - DebugScope::Function, parent, line, column, parent->fileIndex, 0, name, - }; - } - else if(dbg.inst == ShaderDbg::TypeComposite) - { - rdcstr name = strings[dbg.arg(0)]; - uint32_t tag = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; - const rdcstr tagString[3] = { - "class ", "struct ", "union ", - }; - - // don't use arg 2 source - assume the parent is in the same file so it's redundant - uint32_t line = EvaluateConstant(dbg.arg(3), {}).value.u32v[0]; - uint32_t column = EvaluateConstant(dbg.arg(4), {}).value.u32v[0]; - ScopeData *parent = &m_DebugInfo.scopes[dbg.arg(5)]; - // ignore arg 6 linkage name - // ignore arg 7 size - // ignore arg 8 flags - // ignore arg 9... members - - name = tagString[tag % 3] + name; - - m_DebugInfo.scopes[dbg.result] = { - DebugScope::Composite, parent, line, column, parent->fileIndex, 0, name, - }; - } - else if(dbg.inst == ShaderDbg::LexicalBlock) - { - // don't use arg 0 source - assume the parent is in the same file so it's redundant - uint32_t line = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; - uint32_t column = EvaluateConstant(dbg.arg(2), {}).value.u32v[0]; - ScopeData *parent = &m_DebugInfo.scopes[dbg.arg(3)]; - - rdcstr name; - if(dbg.params.count() >= 5) + case ShaderDbg::Source: { - name = strings[dbg.arg(4)]; - if(name.isEmpty()) - name = "anonymous_scope"; + int32_t fileIndex = (int32_t)m_DebugInfo.sources.size(); + + m_DebugInfo.sources[dbg.result] = fileIndex; + m_DebugInfo.filenames[dbg.result] = strings[dbg.arg(0)]; + break; } - else + case ShaderDbg::CompilationUnit: { - name = parent->name + ":" + ToStr(line); + m_DebugInfo.scopes[dbg.result] = { + DebugScope::CompilationUnit, + NULL, + 1, + 1, + m_DebugInfo.sources[dbg.arg(2)], + 0, + m_DebugInfo.filenames[dbg.arg(2)], + }; + break; } - - m_DebugInfo.scopes[dbg.result] = { - DebugScope::Block, parent, line, column, parent->fileIndex, 0, name, - }; - } - else if(dbg.inst == ShaderDbg::Scope) - { - if(m_DebugInfo.curScope) - m_DebugInfo.curScope->end = it.offs(); - - m_DebugInfo.curScope = &m_DebugInfo.scopes[dbg.arg(0)]; - - if(dbg.params.size() >= 2) - m_DebugInfo.curInline = &m_DebugInfo.inlined[dbg.arg(1)]; - else - m_DebugInfo.curInline = NULL; - } - else if(dbg.inst == ShaderDbg::NoScope) - { - // don't want to set curScope to NULL until after this instruction. That way flood-fill of - // scopes in PostParse() can find this instruction in a scope. - leaveScope = true; - } - else if(dbg.inst == ShaderDbg::GlobalVariable) - { - // copy the name string to the variable string only if it's empty. If it has a name already, - // we prefer that. If the variable is DebugInfoNone then we don't care about it's name. - if(strings[dbg.arg(7)].empty()) - strings[dbg.arg(7)] = strings[dbg.arg(0)]; - - OpVariable var(GetID(dbg.arg(7))); - - if(var.storageClass == StorageClass::Private || - var.storageClass == StorageClass::Workgroup || var.storageClass == StorageClass::Output) + case ShaderDbg::Function: { - m_DebugInfo.globals.push_back(var.result); + rdcstr name = strings[dbg.arg(0)]; + // ignore arg 1 type + // don't use arg 2 source - assume the parent is in the same file so it's redundant + uint32_t line = EvaluateConstant(dbg.arg(3), {}).value.u32v[0]; + uint32_t column = EvaluateConstant(dbg.arg(4), {}).value.u32v[0]; + ScopeData *parent = &m_DebugInfo.scopes[dbg.arg(5)]; + // ignore arg 6 linkage name + // ignore arg 7 flags + // ignore arg 8 scope line + // ignore arg 9 (optional) declaration + + m_DebugInfo.scopes[dbg.result] = { + DebugScope::Function, parent, line, column, parent->fileIndex, 0, name, + }; + break; } - } - else if(dbg.inst == ShaderDbg::LocalVariable) - { - m_DebugInfo.locals[dbg.result] = { - strings[dbg.arg(0)], &m_DebugInfo.scopes[dbg.arg(5)], - }; - - m_DebugInfo.scopes[dbg.arg(5)].locals.push_back(dbg.result); - } - else if(dbg.inst == ShaderDbg::Declare || dbg.inst == ShaderDbg::Value) - { - Id id = dbg.arg(1); - - m_DebugInfo.localMappings[it.offs()] = {dbg.arg(0), dbg.arg(1)}; - - LocalData &local = m_DebugInfo.locals[dbg.arg(0)]; - - // keep the previous raw ID alive at least until this location starts - if(local.curId != Id()) - idDeathOffset[local.curId] = RDCMAX(it.offs() + 1, idDeathOffset[local.curId]); - - local.curId = id; - - if(constants.find(id) != constants.end() && !m_DebugInfo.constants.contains(id)) - m_DebugInfo.constants.push_back(id); - - OpShaderDbg expr(GetID(dbg.arg(2))); - - // don't support expressions yet - RDCASSERT(expr.params.empty()); - - // don't support indexes yet for values - RDCASSERT(dbg.params.size() == 3); - } - else if(dbg.inst == ShaderDbg::InlinedAt) - { - // ignore arg 0 the line number - ScopeData *scope = &m_DebugInfo.scopes[dbg.arg(1)]; - - if(dbg.params.count() >= 3) - m_DebugInfo.inlined[dbg.result] = {scope, &m_DebugInfo.inlined[dbg.arg(2)]}; - else - m_DebugInfo.inlined[dbg.result] = {scope, NULL}; - } - else if(dbg.inst == ShaderDbg::InlinedVariable) - { - // TODO handle inlined variables - } - else if(dbg.inst == ShaderDbg::Line) - { - m_CurLineCol.lineStart = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; - m_CurLineCol.lineEnd = EvaluateConstant(dbg.arg(2), {}).value.u32v[0]; - if(Vulkan_Debug_UseDebugColumnInformation()) + case ShaderDbg::TypeBasic: { - m_CurLineCol.colStart = EvaluateConstant(dbg.arg(3), {}).value.u32v[0]; - m_CurLineCol.colEnd = EvaluateConstant(dbg.arg(4), {}).value.u32v[0]; + uint32_t byteSize = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; + uint32_t encoding = EvaluateConstant(dbg.arg(2), {}).value.u32v[0]; + switch(encoding) + { + case 2: m_DebugInfo.types[dbg.result].type = VarType::Bool; break; + case 3: + if(byteSize == 64) + m_DebugInfo.types[dbg.result].type = VarType::Double; + else if(byteSize == 32) + m_DebugInfo.types[dbg.result].type = VarType::Float; + else if(byteSize == 16) + m_DebugInfo.types[dbg.result].type = VarType::Half; + break; + case 4: + if(byteSize == 64) + m_DebugInfo.types[dbg.result].type = VarType::SLong; + else if(byteSize == 32) + m_DebugInfo.types[dbg.result].type = VarType::SInt; + else if(byteSize == 16) + m_DebugInfo.types[dbg.result].type = VarType::SShort; + else if(byteSize == 8) + m_DebugInfo.types[dbg.result].type = VarType::SByte; + break; + case 5: m_DebugInfo.types[dbg.result].type = VarType::SByte; break; + case 6: + if(byteSize == 64) + m_DebugInfo.types[dbg.result].type = VarType::ULong; + else if(byteSize == 32) + m_DebugInfo.types[dbg.result].type = VarType::UInt; + else if(byteSize == 16) + m_DebugInfo.types[dbg.result].type = VarType::UShort; + else if(byteSize == 8) + m_DebugInfo.types[dbg.result].type = VarType::UByte; + break; + case 7: m_DebugInfo.types[dbg.result].type = VarType::UByte; break; + } + break; } + case ShaderDbg::TypePointer: + { + m_DebugInfo.types[dbg.result].baseType = dbg.arg(0); + break; + } + case ShaderDbg::TypeVector: + { + m_DebugInfo.types[dbg.result].baseType = dbg.arg(0); + m_DebugInfo.types[dbg.result].vecSize = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; + break; + } + case ShaderDbg::TypeMatrix: + { + m_DebugInfo.types[dbg.result].baseType = dbg.arg(0); + m_DebugInfo.types[dbg.result].matSize = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; + m_DebugInfo.types[dbg.result].colMajorMat = + EvaluateConstant(dbg.arg(2), {}).value.u32v[0] != 0; + break; + } + case ShaderDbg::TypeArray: + { + m_DebugInfo.types[dbg.result].baseType = dbg.arg(0); + m_DebugInfo.types[dbg.result].arrayDimension = (uint32_t)dbg.params.size() - 1; + break; + } + case ShaderDbg::TypeComposite: + { + rdcstr name = strings[dbg.arg(0)]; + uint32_t tag = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; + const rdcstr tagString[3] = { + "class ", "struct ", "union ", + }; - // find file index by filename matching, this would be nice to improve as it's brittle - m_CurLineCol.fileIndex = m_DebugInfo.sources[dbg.arg(0)]; - } - else if(dbg.inst == ShaderDbg::NoLine) - { - m_CurLineCol = LineColumnInfo(); + // don't use arg 2 source - assume the parent is in the same file so it's redundant + uint32_t line = EvaluateConstant(dbg.arg(3), {}).value.u32v[0]; + uint32_t column = EvaluateConstant(dbg.arg(4), {}).value.u32v[0]; + ScopeData *parent = &m_DebugInfo.scopes[dbg.arg(5)]; + // ignore arg 6 linkage name + // ignore arg 7 size + // ignore arg 8 flags + + for(uint32_t i = 9; i < dbg.params.size(); i++) + { + OpShaderDbg member(GetID(dbg.arg(i))); + + m_DebugInfo.types[dbg.result].structMembers.push_back( + {strings[member.arg(0)], member.arg(1)}); + } + + name = tagString[tag % 3] + name; + + m_DebugInfo.scopes[dbg.result] = { + DebugScope::Composite, parent, line, column, parent->fileIndex, 0, name, + }; + break; + } + case ShaderDbg::LexicalBlock: + { + // don't use arg 0 source - assume the parent is in the same file so it's redundant + uint32_t line = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; + uint32_t column = EvaluateConstant(dbg.arg(2), {}).value.u32v[0]; + ScopeData *parent = &m_DebugInfo.scopes[dbg.arg(3)]; + + rdcstr name; + if(dbg.params.count() >= 5) + { + name = strings[dbg.arg(4)]; + if(name.isEmpty()) + name = "anonymous_scope"; + } + else + { + name = parent->name + ":" + ToStr(line); + } + + m_DebugInfo.scopes[dbg.result] = { + DebugScope::Block, parent, line, column, parent->fileIndex, 0, name, + }; + break; + } + case ShaderDbg::Scope: + { + if(m_DebugInfo.curScope) + m_DebugInfo.curScope->end = it.offs(); + + m_DebugInfo.curScope = &m_DebugInfo.scopes[dbg.arg(0)]; + + if(dbg.params.size() >= 2) + m_DebugInfo.curInline = &m_DebugInfo.inlined[dbg.arg(1)]; + else + m_DebugInfo.curInline = NULL; + break; + } + case ShaderDbg::NoScope: + { + // don't want to set curScope to NULL until after this instruction. That way flood-fill of + // scopes in PostParse() can find this instruction in a scope. + leaveScope = true; + break; + } + case ShaderDbg::GlobalVariable: + { + // copy the name string to the variable string only if it's empty. If it has a name + // already, + // we prefer that. If the variable is DebugInfoNone then we don't care about it's name. + if(strings[dbg.arg(7)].empty()) + strings[dbg.arg(7)] = strings[dbg.arg(0)]; + + OpVariable var(GetID(dbg.arg(7))); + + if(var.storageClass == StorageClass::Private || + var.storageClass == StorageClass::Workgroup || var.storageClass == StorageClass::Output) + { + m_DebugInfo.globals.push_back(var.result); + } + break; + } + case ShaderDbg::LocalVariable: + { + m_DebugInfo.locals[dbg.result] = { + strings[dbg.arg(0)], &m_DebugInfo.scopes[dbg.arg(5)], + &m_DebugInfo.types[dbg.arg(1)], + }; + + m_DebugInfo.scopes[dbg.arg(5)].locals.push_back(dbg.result); + break; + } + case ShaderDbg::Declare: + case ShaderDbg::Value: + { + Id id = dbg.arg(1); + + LocalMapping &mapping = m_DebugInfo.localMappings[it.offs()]; + + mapping = {it.offs(), dbg.arg(0), id}; + + if(constants.find(id) != constants.end() && !m_DebugInfo.constants.contains(id)) + m_DebugInfo.constants.push_back(id); + + mapping.indexes.resize(dbg.params.size() - 3); + for(uint32_t i = 0; i < mapping.indexes.size(); i++) + { + size_t idx = mapping.indexes.size() - 1 - i; + mapping.indexes[idx] = EvaluateConstant(dbg.arg(i + 3), {}).value.u32v[0]; + } + + { + // don't support expressions, only allow for a single 'deref' which is used for + // variables to 'deref' into the pointed value + OpShaderDbg expr(GetID(dbg.arg(2))); + + for(uint32_t i = 0; i < expr.params.size(); i++) + { + OpShaderDbg op(GetID(expr.arg(i))); + + if(op.params.size() > 1 || EvaluateConstant(op.arg(0), {}).value.u32v[0] != 0) + { + RDCERR("Only deref expressions supported"); + } + } + } + break; + } + case ShaderDbg::InlinedAt: + { + // ignore arg 0 the line number + ScopeData *scope = &m_DebugInfo.scopes[dbg.arg(1)]; + + if(dbg.params.count() >= 3) + m_DebugInfo.inlined[dbg.result] = {scope, &m_DebugInfo.inlined[dbg.arg(2)]}; + else + m_DebugInfo.inlined[dbg.result] = {scope, NULL}; + break; + } + case ShaderDbg::InlinedVariable: + { + // TODO handle inlined variables + break; + } + case ShaderDbg::Line: + { + m_CurLineCol.lineStart = EvaluateConstant(dbg.arg(1), {}).value.u32v[0]; + m_CurLineCol.lineEnd = EvaluateConstant(dbg.arg(2), {}).value.u32v[0]; + if(Vulkan_Debug_UseDebugColumnInformation()) + { + m_CurLineCol.colStart = EvaluateConstant(dbg.arg(3), {}).value.u32v[0]; + m_CurLineCol.colEnd = EvaluateConstant(dbg.arg(4), {}).value.u32v[0]; + } + + // find file index by filename matching, this would be nice to improve as it's brittle + m_CurLineCol.fileIndex = m_DebugInfo.sources[dbg.arg(0)]; + break; + } + case ShaderDbg::NoLine: + { + m_CurLineCol = LineColumnInfo(); + break; + } + default: break; } } }