in renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp [1566:2224]
void Debugger::FillDebugSourceVars(rdcarray<InstructionSourceInfo> &instInfo)
{
for(InstructionSourceInfo &i : instInfo)
{
size_t offs = instructionOffsets[i.instruction];
const ScopeData *scope = GetScope(offs);
if(!scope)
continue;
// track which mappings we've processed, so if the same variable has mappings in multiple scopes
// we only pick the innermost.
rdcarray<LocalMapping> processed;
rdcarray<Id> sourceVars;
// capture the scopes upwards (from child to parent)
rdcarray<const ScopeData *> scopes;
while(scope)
{
scopes.push_back(scope);
// if we reach a function scope, don't go up any further.
if(scope->type == DebugScope::Function)
break;
scope = scope->parent;
}
// Iterate over the scopes downwards (parent->child)
for(size_t s = 0; s < scopes.size(); ++s)
{
scope = scopes[scopes.size() - 1 - s];
for(size_t m = 0; m < scope->localMappings.size(); m++)
{
const LocalMapping &mapping = scope->localMappings[m];
// if this mapping is past the current instruction, stop here.
if(mapping.instIndex > i.instruction)
break;
// see if this mapping is superceded by a later mapping in this scope for this instruction.
// This is a bit inefficient but simple. The alternative would be to do record
// start and end points for each mapping and update the end points, but this is simple and
// should be limited since it's only per-scope
bool supercede = false;
for(size_t n = m + 1; n < scope->localMappings.size(); n++)
{
const LocalMapping &laterMapping = scope->localMappings[n];
// if this mapping is past the current instruction, stop here.
if(laterMapping.instIndex > i.instruction)
break;
// if this mapping will supercede and starts later
if(laterMapping.isSourceSupersetOf(mapping) && laterMapping.instIndex > mapping.instIndex)
{
supercede = true;
break;
}
}
// don't add the current mapping if it's going to be superceded by something later
if(supercede)
continue;
processed.push_back(mapping);
Id sourceVar = mapping.sourceVar;
if(!sourceVars.contains(mapping.sourceVar))
sourceVars.push_back(mapping.sourceVar);
}
}
// Converting debug variable mappings to SourceVariableMapping is a two phase algorithm.
// Phase One
// For each source variable, repeatedly apply the debug variable mappings.
// This debug variable usage is tracked in a tree-like structure built using DebugVarNode
// elements.
// As each mapping is applied, the new mapping can fully or partially override the
// existing mapping. When an existing mapping is:
// - fully overrideen: any sub-elements of that mapping are cleared
// i.e. assigning a vector, array, structure
// - partially overriden: the existing mapping is expanded into its sub-elements which are
// mapped to the current mapping and then the new mapping is set to its corresponding
// elements i.e. y-component in a vector, member in a structure, a single array element
// The DebugVarNode member "emitSourceVar" determines if the DebugVar mapping should be
// converted to a source variable mapping.
// Phase Two
// The DebugVarNode tree is walked to find the nodes which have "emitSourceVar" set to true and
// then those nodes are converted to SourceVariableMapping
struct DebugVarNode
{
rdcarray<DebugVarNode> children;
Id debugVar;
rdcstr name;
rdcstr debugVarSuffix;
VarType type = VarType::Unknown;
uint32_t rows = 0;
uint32_t columns = 0;
uint32_t debugVarComponent = 0;
uint32_t offset = 0;
bool emitSourceVar = false;
};
::std::map<Id, DebugVarNode> roots;
// Phase One: generate the DebugVarNode tree by repeatedly apply debug variables updating
// existing mappings with later mappings
for(size_t sv = 0; sv < sourceVars.size(); ++sv)
{
Id sourceVarId = sourceVars[sv];
const LocalData &l = m_DebugInfo.locals[sourceVarId];
// Convert processed mappings into a usage map
for(size_t m = 0; m < processed.size(); ++m)
{
const LocalMapping &mapping = processed[m];
if(mapping.sourceVar != sourceVarId)
continue;
const TypeData *typeWalk = l.type;
DebugVarNode *usage = &roots[sourceVarId];
if(usage->name.isEmpty())
{
usage->name = l.name;
usage->rows = 1U;
usage->columns = 1U;
}
// if it doesn't have indexes this is simple, set up a 1:1 map
if(mapping.indexes.isEmpty())
{
uint32_t rows = 1;
uint32_t columns = 1;
// skip past any pointer types to get the 'real' type that we'll see
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::GPUPointer)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
const uint32_t arrayDimension = typeWalk->arrayDimensions.size();
if(arrayDimension > 0)
{
// walk down until we get to a scalar type, if we get there. This means arrays of
// basic types will get the right type
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
usage->type = typeWalk->type;
}
else if(!typeWalk->structMembers.empty())
{
usage->type = typeWalk->type;
}
if(typeWalk->matSize != 0)
{
const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
usage->type = scalar.type;
if(typeWalk->colMajorMat)
{
rows = RDCMAX(1U, vec.vecSize);
columns = RDCMAX(1U, typeWalk->matSize);
}
else
{
columns = RDCMAX(1U, vec.vecSize);
rows = RDCMAX(1U, typeWalk->matSize);
}
}
else if(typeWalk->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];
usage->type = scalar.type;
columns = RDCMAX(1U, typeWalk->vecSize);
}
usage->debugVar = mapping.debugVar;
// Remove any child mappings : this mapping covers everything
usage->children.clear();
usage->emitSourceVar = true;
usage->rows = rows;
usage->columns = columns;
}
else
{
rdcarray<uint32_t> indexes = mapping.indexes;
// walk any aggregate types
while(!indexes.empty())
{
uint32_t idx = ~0U;
const TypeData *childType = NULL;
const uint32_t arrayDimension = typeWalk->arrayDimensions.size();
if(arrayDimension > 0)
{
const rdcarray<uint32_t> &dims = typeWalk->arrayDimensions;
uint32_t numIdxs = (uint32_t)indexes.size();
childType = &m_DebugInfo.types[typeWalk->baseType];
uint32_t childRows = 1U;
uint32_t childColumns = 1U;
VarType elementType = childType->type;
uint32_t elementSize = 1;
if(childType->matSize != 0)
{
const TypeData &vec = m_DebugInfo.types[childType->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
elementType = scalar.type;
if(childType->colMajorMat)
{
childRows = RDCMAX(1U, vec.vecSize);
childColumns = RDCMAX(1U, childType->matSize);
}
else
{
childColumns = RDCMAX(1U, vec.vecSize);
childRows = RDCMAX(1U, childType->matSize);
}
}
else if(childType->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[childType->baseType];
uint32_t vecColumns = RDCMAX(1U, childType->vecSize);
elementType = scalar.type;
childRows = 1U;
childColumns = vecColumns;
}
else if(!childType->structMembers.empty())
{
elementSize += childType->memberOffsets[childType->memberOffsets.count() - 1];
}
elementSize *= childRows * childColumns;
const uint32_t countDims = RDCMIN(arrayDimension, numIdxs);
// handle N dimensional arrays
for(uint32_t d = 0; d < countDims; ++d)
{
idx = indexes[0];
indexes.erase(0);
uint32_t rows = dims[d];
usage->rows = rows;
usage->columns = 1U;
// Expand the node if required
if(usage->children.isEmpty())
{
usage->children.resize(rows);
for(uint32_t x = 0; x < rows; x++)
{
usage->children[x].debugVar = usage->debugVar;
rdcstr suffix = StringFormat::Fmt("[%u]", x);
usage->children[x].debugVarSuffix = usage->debugVarSuffix + suffix;
usage->children[x].name = usage->name + suffix;
usage->children[x].type = elementType;
usage->children[x].rows = childRows;
usage->children[x].columns = childColumns;
usage->children[x].offset = usage->offset + x * elementSize;
}
}
RDCASSERTEQUAL(usage->children.size(), rows);
// if the whole node was displayed : display the sub-elements
if(usage->emitSourceVar)
{
for(uint32_t x = 0; x < rows; x++)
usage->children[x].emitSourceVar = true;
usage->emitSourceVar = false;
}
usage = &usage->children[idx];
usage->type = childType->type;
typeWalk = childType;
}
}
else if(!typeWalk->structMembers.empty())
{
idx = indexes[0];
indexes.erase(0);
childType = &m_DebugInfo.types[typeWalk->structMembers[idx].second];
uint32_t rows = typeWalk->structMembers.size();
usage->rows = rows;
usage->columns = 1U;
// Expand the node if required
if(usage->children.isEmpty())
{
usage->children.resize(rows);
for(uint32_t x = 0; x < rows; x++)
{
rdcstr suffix = StringFormat::Fmt(".%s", typeWalk->structMembers[x].first.c_str());
usage->children[x].debugVar = usage->debugVar;
usage->children[x].debugVarSuffix = usage->debugVarSuffix + suffix;
usage->children[x].name = usage->name + suffix;
usage->children[x].offset = usage->offset + typeWalk->memberOffsets[x];
uint32_t memberRows = 1U;
uint32_t memberColumns = 1U;
const TypeData *memberType = &m_DebugInfo.types[typeWalk->structMembers[x].second];
VarType elementType = memberType->type;
if(memberType->matSize != 0)
{
const TypeData &vec = m_DebugInfo.types[memberType->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
elementType = scalar.type;
if(memberType->colMajorMat)
{
memberRows = RDCMAX(1U, vec.vecSize);
memberColumns = RDCMAX(1U, memberType->matSize);
}
else
{
memberColumns = RDCMAX(1U, vec.vecSize);
memberRows = RDCMAX(1U, memberType->matSize);
}
}
else if(memberType->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[memberType->baseType];
uint32_t vecColumns = RDCMAX(1U, memberType->vecSize);
elementType = scalar.type;
memberRows = 1U;
memberColumns = vecColumns;
}
usage->children[x].type = elementType;
usage->children[x].rows = memberRows;
usage->children[x].columns = memberColumns;
}
}
RDCASSERTEQUAL(usage->children.size(), rows);
// if the whole node was displayed : display the sub-elements
if(usage->emitSourceVar)
{
for(uint32_t x = 0; x < rows; x++)
usage->children[x].emitSourceVar = true;
usage->emitSourceVar = false;
}
usage = &usage->children[idx];
usage->type = childType->type;
typeWalk = childType;
}
else
{
break;
}
}
const char swizzle[] = "xyzw";
uint32_t rows = 1U;
uint32_t columns = 1U;
size_t countRemainingIndexes = indexes.size();
if(typeWalk->matSize != 0)
{
const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
usage->type = scalar.type;
if(typeWalk->colMajorMat)
{
rows = RDCMAX(1U, vec.vecSize);
columns = RDCMAX(1U, typeWalk->matSize);
}
else
{
columns = RDCMAX(1U, vec.vecSize);
rows = RDCMAX(1U, typeWalk->matSize);
}
usage->rows = rows;
usage->columns = columns;
if((countRemainingIndexes == 2) || (countRemainingIndexes == 1))
{
if(usage->children.isEmpty())
{
// Matrices are stored as [row][col]
usage->children.resize(rows);
for(uint32_t r = 0; r < rows; ++r)
{
usage->children[r].emitSourceVar = false;
usage->children[r].name = usage->name + StringFormat::Fmt(".row%u", r);
usage->children[r].type = scalar.type;
usage->children[r].debugVar = usage->debugVar;
usage->children[r].debugVarComponent = 0;
usage->children[r].rows = 1U;
usage->children[r].columns = columns;
usage->children[r].offset = usage->offset + r * rows;
usage->children[r].children.resize(columns);
for(uint32_t c = 0; c < columns; ++c)
{
usage->children[r].children[c].emitSourceVar = false;
usage->children[r].children[c].name =
usage->name + StringFormat::Fmt(".row%u.%c", r, swizzle[RDCMIN(c, 3U)]);
usage->children[r].children[c].type = scalar.type;
usage->children[r].children[c].debugVar = usage->debugVar;
usage->children[r].children[c].debugVarComponent = r;
usage->children[r].children[c].rows = 1U;
usage->children[r].children[c].columns = 1U;
usage->children[r].children[c].offset = usage->children[r].offset + c;
}
}
}
RDCASSERTEQUAL(usage->children.size(), rows);
// two remaining indices selects a scalar within the matrix
if(countRemainingIndexes == 2)
{
uint32_t row, col;
if(typeWalk->colMajorMat)
{
col = indexes[0];
row = indexes[1];
}
else
{
row = indexes[0];
col = indexes[1];
}
RDCASSERT(row < rows, row, rows);
RDCASSERT(col < columns, col, columns);
RDCASSERTEQUAL(usage->children[row].children.size(), columns);
usage->children[row].children[col].emitSourceVar =
!usage->children[row].emitSourceVar;
usage->children[row].children[col].debugVar = mapping.debugVar;
usage->children[row].children[col].debugVarComponent = 0;
// try to recombine matrix rows to a single source var display
if(!usage->children[row].emitSourceVar)
{
bool collapseVector = true;
for(uint32_t c = 0; c < columns; ++c)
{
collapseVector = usage->children[row].children[c].emitSourceVar;
if(!collapseVector)
break;
}
if(collapseVector)
{
usage->children[row].emitSourceVar = true;
for(uint32_t c = 0; c < columns; ++c)
usage->children[row].children[c].emitSourceVar = false;
}
}
}
else
{
if(typeWalk->colMajorMat)
{
uint32_t col = indexes[0];
RDCASSERT(col < columns, col, columns);
// one remaining index selects a column within the matrix.
// source vars are displayed as row-major, need <rows> mappings
for(uint32_t r = 0; r < rows; ++r)
{
RDCASSERTEQUAL(usage->children[r].children.size(), columns);
usage->children[r].children[col].emitSourceVar =
!usage->children[r].emitSourceVar;
usage->children[r].children[col].debugVar = mapping.debugVar;
usage->children[r].children[col].debugVarComponent = r;
}
}
else
{
uint32_t row = indexes[0];
RDCASSERT(row < rows, row, rows);
RDCASSERTEQUAL(usage->children.size(), rows);
RDCASSERTEQUAL(usage->children[row].children.size(), columns);
// one remaining index selects a row within the matrix.
// source vars are displayed as row-major, need <rows> mappings
for(uint32_t c = 0; c < columns; ++c)
{
usage->children[row].children[c].emitSourceVar =
!usage->children[row].emitSourceVar;
usage->children[row].children[c].debugVar = mapping.debugVar;
usage->children[row].children[c].debugVarComponent = c;
}
}
}
// try to recombine matrix rows to a single source var display
for(uint32_t r = 0; r < rows; ++r)
{
if(!usage->children[r].emitSourceVar)
{
bool collapseVector = true;
RDCASSERTEQUAL(usage->children[r].children.size(), columns);
for(uint32_t c = 0; c < columns; ++c)
{
collapseVector = usage->children[r].children[c].emitSourceVar;
if(!collapseVector)
break;
}
if(collapseVector)
{
usage->children[r].emitSourceVar = true;
for(uint32_t c = 0; c < columns; ++c)
usage->children[r].children[c].emitSourceVar = false;
}
}
}
usage->emitSourceVar = false;
}
else
{
RDCASSERTEQUAL(countRemainingIndexes, 0);
// Remove mappings : this mapping covers everything
usage->debugVar = mapping.debugVar;
usage->children.clear();
usage->emitSourceVar = true;
usage->debugVarSuffix.clear();
}
}
else if(typeWalk->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];
columns = RDCMAX(1U, typeWalk->vecSize);
usage->type = scalar.type;
usage->rows = 1U;
usage->columns = columns;
// remaining index selects a scalar within the vector
if(countRemainingIndexes == 1)
{
if(usage->children.isEmpty())
{
usage->children.resize(columns);
for(uint32_t x = 0; x < columns; ++x)
{
usage->children[x].emitSourceVar = usage->emitSourceVar;
usage->children[x].name =
usage->name + StringFormat::Fmt(".%c", swizzle[RDCMIN(x, 3U)]);
usage->children[x].type = scalar.type;
usage->children[x].debugVar = usage->debugVar;
usage->children[x].debugVarComponent = x;
usage->children[x].rows = 1U;
usage->children[x].columns = 1U;
usage->children[x].offset = usage->offset + x;
}
usage->emitSourceVar = false;
}
uint32_t col = indexes[0];
RDCASSERT(col < columns, col, columns);
RDCASSERTEQUAL(usage->children.size(), columns);
usage->children[col].debugVar = mapping.debugVar;
usage->children[col].debugVarComponent = 0;
usage->children[col].emitSourceVar = true;
// try to recombine vector to a single source var display
bool collapseVector = true;
for(uint32_t x = 0; x < columns; ++x)
{
collapseVector = usage->children[x].emitSourceVar;
if(!collapseVector)
break;
}
if(collapseVector)
{
usage->emitSourceVar = true;
for(uint32_t x = 0; x < columns; ++x)
usage->children[x].emitSourceVar = false;
}
}
else
{
RDCASSERTEQUAL(countRemainingIndexes, 0);
// Remove mappings : this mapping covers everything
usage->debugVar = mapping.debugVar;
usage->children.clear();
usage->emitSourceVar = true;
usage->debugVarSuffix.clear();
}
}
else
{
// walk down until we get to a scalar type, if we get there. This means arrays of
// basic types will get the right type
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
usage->type = typeWalk->type;
usage->debugVar = mapping.debugVar;
usage->debugVarComponent = 0;
usage->rows = 1U;
usage->columns = 1U;
usage->emitSourceVar = true;
usage->children.clear();
usage->debugVarSuffix.clear();
}
}
}
}
// Phase Two: walk the DebugVarNode tree and convert "emitSourceVar = true" nodes to a SourceVariableMapping
for(size_t sv = 0; sv < sourceVars.size(); ++sv)
{
Id sourceVarId = sourceVars[sv];
DebugVarNode *usage = &roots[sourceVarId];
rdcarray<const DebugVarNode *> nodesToProcess;
rdcarray<const DebugVarNode *> sourceVarNodes;
nodesToProcess.push_back(usage);
while(!nodesToProcess.isEmpty())
{
const DebugVarNode *n = nodesToProcess.back();
nodesToProcess.pop_back();
if(n->emitSourceVar)
{
sourceVarNodes.push_back(n);
}
else
{
for(size_t x = 0; x < n->children.size(); ++x)
{
const DebugVarNode *child = &n->children[x];
nodesToProcess.push_back(child);
}
}
}
for(size_t x = 0; x < sourceVarNodes.size(); ++x)
{
const DebugVarNode *n = sourceVarNodes[x];
SourceVariableMapping sourceVar;
sourceVar.name = n->name;
sourceVar.type = n->type;
sourceVar.signatureIndex = -1;
sourceVar.offset = n->offset;
sourceVar.variables.clear();
// unknown is treated as a struct
if(sourceVar.type == VarType::Unknown)
sourceVar.type = VarType::Struct;
if(n->children.empty())
{
RDCASSERTNOTEQUAL(n->rows * n->columns, 0);
for(uint32_t c = 0; c < n->rows * n->columns; ++c)
{
sourceVar.variables.push_back(DebugVariableReference(
DebugVariableType::Variable, GetRawName(n->debugVar) + n->debugVarSuffix, c));
}
}
else
{
RDCASSERTEQUAL(n->rows * n->columns, (uint32_t)n->children.count());
for(int32_t c = 0; c < n->children.count(); ++c)
sourceVar.variables.push_back(DebugVariableReference(
DebugVariableType::Variable,
GetRawName(n->children[c].debugVar) + n->children[c].debugVarSuffix,
n->children[c].debugVarComponent));
}
i.sourceVars.push_back(sourceVar);
}
}
}
}