Populate input and output variables in SPIR-V debugging

This commit is contained in:
baldurk
2020-02-20 13:41:57 +00:00
parent b4b100004f
commit 7f38c690d8
3 changed files with 331 additions and 0 deletions
@@ -35,6 +35,9 @@ class DebugAPIWrapper
public:
virtual ~DebugAPIWrapper() {}
virtual void AddDebugMessage(MessageCategory c, MessageSeverity sv, MessageSource src, rdcstr d) = 0;
virtual void FillInputValue(ShaderVariable &var, ShaderBuiltin builtin, uint32_t location,
uint32_t offset) = 0;
};
struct GlobalState
@@ -105,6 +108,10 @@ private:
void WriteThroughPointer(const ShaderVariable &ptr, const ShaderVariable &val);
ShaderVariable MakeCompositePointer(const ShaderVariable &base, Id id, rdcarray<uint32_t> &indices);
void AllocateVariable(const Decorations &varDecorations, const Decorations &curDecorations,
DebugVariableType sourceVarType, const rdcstr &sourceName, uint32_t offset,
const DataType &inType, ShaderVariable &outVar);
DebugAPIWrapper *apiWrapper = NULL;
GlobalState global;
@@ -83,6 +83,76 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *apiWrapper, const Shader
for(auto it = constants.begin(); it != constants.end(); it++)
active.ids[it->first] = EvaluateConstant(it->first, specInfo);
rdcarray<Id> inputIDs, outputIDs;
// allocate storage for globals with opaque storage classes, and prepare to set up pointers to
// them for the global variables themselves
for(const Variable &v : globals)
{
if(v.storage == StorageClass::Input || v.storage == StorageClass::Output)
{
const bool isInput = (v.storage == StorageClass::Input);
ShaderVariable var;
var.name = GetRawName(v.id);
rdcstr sourceName = GetHumanName(v.id);
size_t oldSize = sourceVars.size();
const DataType &type = dataTypes[v.type];
// global variables should all be pointers into opaque storage
RDCASSERT(type.type == DataType::PointerType);
// fill the interface variable
AllocateVariable(decorations[v.id], decorations[v.id],
isInput ? DebugVariableType::Input : DebugVariableType::Variable, sourceName,
0, dataTypes[type.InnerType()], var);
// I/O variable structs don't have offsets, so give them fake offsets to ensure they sort as
// we want. Since FillVariable is depth-first the source vars are already in order.
for(size_t i = oldSize; i < sourceVars.size(); i++)
sourceVars[i].offset = uint32_t(i - oldSize);
if(isInput)
{
// create the opaque storage
active.inputs.push_back(var);
// then make sure we know which ID to set up for the pointer
inputIDs.push_back(v.id);
}
else
{
active.outputs.push_back(var);
outputIDs.push_back(v.id);
}
}
}
// now that the globals are allocated and their storage won't move, we can take pointers to them
for(size_t i = 0; i < active.inputs.size(); i++)
active.ids[inputIDs[i]] = MakePointerVariable(inputIDs[i], &active.inputs[i]);
for(size_t i = 0; i < active.outputs.size(); i++)
active.ids[outputIDs[i]] = MakePointerVariable(outputIDs[i], &active.outputs[i]);
// only outputs are considered mutable
active.live.append(outputIDs);
for(size_t i = 0; i < sourceVars.size();)
{
if(!sourceVars[i].variables.empty() &&
(sourceVars[i].variables[0].type == DebugVariableType::Input ||
sourceVars[i].variables[0].type == DebugVariableType::Constant))
{
ret->sourceVars.push_back(sourceVars[i]);
sourceVars.erase(i);
continue;
}
i++;
}
ret->lineInfo.resize(instructionOffsets.size());
for(size_t i = 0; i < instructionOffsets.size(); i++)
{
@@ -346,6 +416,123 @@ rdcstr Debugger::GetHumanName(Id id)
return name;
}
void Debugger::AllocateVariable(const Decorations &varDecorations, const Decorations &curDecorations,
DebugVariableType sourceVarType, const rdcstr &sourceName,
uint32_t offset, const DataType &inType, ShaderVariable &outVar)
{
switch(inType.type)
{
case DataType::PointerType:
{
RDCERR("Pointers not supported in interface variables");
return;
}
case DataType::ScalarType:
{
outVar.type = inType.scalar().Type();
outVar.rows = 1;
outVar.columns = 1;
break;
}
case DataType::VectorType:
{
outVar.type = inType.scalar().Type();
outVar.rows = 1;
outVar.columns = RDCMAX(1U, inType.vector().count);
break;
}
case DataType::MatrixType:
{
outVar.type = inType.scalar().Type();
outVar.columns = RDCMAX(1U, inType.matrix().count);
outVar.rows = RDCMAX(1U, inType.vector().count);
break;
}
case DataType::StructType:
{
for(int32_t i = 0; i < inType.children.count(); i++)
{
ShaderVariable var;
var.name = StringFormat::Fmt("%s._child%d", outVar.name.c_str(), i);
rdcstr childName;
if(inType.children[i].name.empty())
childName = StringFormat::Fmt("%s._child%d", sourceName.c_str(), i);
else
childName = sourceName + "." + inType.children[i].name;
uint32_t childOffset = offset;
const Decorations &childDecorations = inType.children[i].decorations;
if(childDecorations.flags & Decorations::HasOffset)
childOffset += childDecorations.offset;
AllocateVariable(varDecorations, childDecorations, sourceVarType, childName, childOffset,
dataTypes[inType.children[i].type], var);
var.name = StringFormat::Fmt("_child%d", i);
outVar.members.push_back(var);
}
return;
}
case DataType::ArrayType:
{
// array stride is decorated on the type, not the member itself
const Decorations &typeDecorations = decorations[inType.id];
ShaderVariable len = GetActiveLane().ids[inType.length];
for(uint32_t i = 0; i < len.value.u.x; i++)
{
rdcstr idx = StringFormat::Fmt("[%u]", i);
ShaderVariable var;
var.name = outVar.name + idx;
AllocateVariable(varDecorations, curDecorations, sourceVarType, sourceName + idx, offset,
dataTypes[inType.InnerType()], var);
var.name = idx;
if(typeDecorations.flags & Decorations::HasArrayStride)
offset += typeDecorations.arrayStride;
outVar.members.push_back(var);
}
return;
}
case DataType::ImageType:
case DataType::SamplerType:
case DataType::SampledImageType:
case DataType::UnknownType:
{
RDCERR("Unexpected variable type %d", inType.type);
break;
}
}
SourceVariableMapping sourceVar;
sourceVar.name = sourceName;
sourceVar.offset = offset;
sourceVar.type = outVar.type;
sourceVar.rows = outVar.rows;
sourceVar.columns = outVar.columns;
for(uint32_t x = 0; x < uint32_t(outVar.rows) * outVar.columns; x++)
sourceVar.variables.push_back(DebugVariableReference(sourceVarType, outVar.name, x));
if(curDecorations.flags & Decorations::HasBuiltIn)
sourceVar.builtin = MakeShaderBuiltin(stage, curDecorations.builtIn);
sourceVars.push_back(sourceVar);
if(sourceVarType == DebugVariableType::Input)
{
apiWrapper->FillInputValue(
outVar, sourceVar.builtin,
(curDecorations.flags & Decorations::HasLocation) ? curDecorations.location : 0,
(curDecorations.flags & Decorations::HasOffset) ? curDecorations.offset : 0);
}
}
void Debugger::PreParse(uint32_t maxId)
{
Processor::PreParse(maxId);
+137
View File
@@ -23,7 +23,9 @@
******************************************************************************/
#include "driver/shaders/spirv/spirv_debug.h"
#include "maths/formatpacking.h"
#include "vk_core.h"
#include "vk_debug.h"
#include "vk_replay.h"
class VulkanAPIWrapper : public rdcspv::DebugAPIWrapper
@@ -36,6 +38,36 @@ public:
m_pDriver->AddDebugMessage(c, sv, src, d);
}
virtual void FillInputValue(ShaderVariable &var, ShaderBuiltin builtin, uint32_t location,
uint32_t offset) override
{
if(builtin != ShaderBuiltin::Undefined)
{
auto it = builtin_inputs.find(builtin);
if(it != builtin_inputs.end())
{
var.value = it->second.value;
return;
}
RDCERR("Couldn't get input for %s", ToStr(builtin).c_str());
return;
}
RDCASSERT(offset == 0);
if(location < location_inputs.size())
{
var.value = location_inputs[location].value;
return;
}
RDCERR("Couldn't get input for location=%u, offset=%u", location, offset);
}
std::map<ShaderBuiltin, ShaderVariable> builtin_inputs;
rdcarray<ShaderVariable> location_inputs;
private:
WrappedVulkan *m_pDriver = NULL;
};
@@ -65,6 +97,111 @@ ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, u
shadRefl.PopulateDisassembly(shader.spirv);
VulkanAPIWrapper *apiWrapper = new VulkanAPIWrapper(m_pDriver);
std::map<ShaderBuiltin, ShaderVariable> &builtins = apiWrapper->builtin_inputs;
builtins[ShaderBuiltin::BaseInstance] = ShaderVariable(rdcstr(), draw->instanceOffset, 0U, 0U, 0U);
builtins[ShaderBuiltin::BaseVertex] = ShaderVariable(
rdcstr(), (draw->flags & DrawFlags::Indexed) ? draw->baseVertex : draw->vertexOffset, 0U, 0U,
0U);
builtins[ShaderBuiltin::DeviceIndex] = ShaderVariable(rdcstr(), 0U, 0U, 0U, 0U);
builtins[ShaderBuiltin::DrawIndex] = ShaderVariable(rdcstr(), draw->drawIndex, 0U, 0U, 0U);
builtins[ShaderBuiltin::VertexIndex] = ShaderVariable(rdcstr(), vertid, 0U, 0U, 0U);
builtins[ShaderBuiltin::InstanceIndex] = ShaderVariable(rdcstr(), instid, 0U, 0U, 0U);
rdcarray<ShaderVariable> &locations = apiWrapper->location_inputs;
for(const VulkanCreationInfo::Pipeline::Attribute &attr : pipe.vertexAttrs)
{
if(attr.location >= locations.size())
locations.resize(attr.location + 1);
ShaderValue &val = locations[attr.location].value;
bytebuf data;
size_t size = GetByteSize(1, 1, 1, attr.format, 0);
if(attr.binding < pipe.vertexBindings.size())
{
const VulkanCreationInfo::Pipeline::Binding &bind = pipe.vertexBindings[attr.binding];
if(bind.vbufferBinding < state.vbuffers.size())
{
const VulkanRenderState::VertBuffer &vb = state.vbuffers[bind.vbufferBinding];
uint32_t vertexOffset = 0;
if(bind.perInstance)
{
if(bind.instanceDivisor == 0)
vertexOffset = draw->instanceOffset * bind.bytestride;
else
vertexOffset = draw->instanceOffset + (instid / bind.instanceDivisor) * bind.bytestride;
}
else
{
vertexOffset = idx * bind.bytestride;
}
GetDebugManager()->GetBufferData(vb.buf, vb.offs + attr.byteoffset + vertexOffset, size,
data);
}
}
if(size > data.size())
{
// out of bounds read
m_pDriver->AddDebugMessage(
MessageCategory::Execution, MessageSeverity::Medium, MessageSource::RuntimeWarning,
StringFormat::Fmt(
"Attribute location %u from binding %u reads out of bounds at vertex %u "
"(index %u) in instance %u.",
attr.location, attr.binding, vertid, idx, instid));
if(IsUIntFormat(attr.format) || IsSIntFormat(attr.format))
val.u = {0, 0, 0, 1};
else
val.f = {0.0f, 0.0f, 0.0f, 1.0f};
}
else
{
ResourceFormat fmt = MakeResourceFormat(attr.format);
if(fmt.Special())
{
switch(fmt.type)
{
case ResourceFormatType::R10G10B10A2:
{
Vec4f decoded;
if(fmt.compType == CompType::SNorm)
decoded = ConvertFromR10G10B10A2SNorm(*(uint32_t *)data.data());
else
decoded = ConvertFromR10G10B10A2(*(uint32_t *)data.data());
val.f.x = decoded.x;
val.f.y = decoded.y;
val.f.z = decoded.z;
val.f.w = decoded.w;
break;
}
case ResourceFormatType::R11G11B10:
{
Vec3f decoded = ConvertFromR11G11B10(*(uint32_t *)data.data());
val.f.x = decoded.x;
val.f.y = decoded.y;
val.f.z = decoded.z;
break;
}
default:
RDCERR("Unexpected resource format %s as vertex buffer input",
ToStr(attr.format).c_str());
}
}
else
{
memcpy(&val.u.x, data.data(), size);
}
}
}
rdcspv::Debugger *debugger = new rdcspv::Debugger;
debugger->Parse(shader.spirv.GetSPIRV());
ShaderDebugTrace *ret = debugger->BeginDebug(apiWrapper, ShaderStage::Vertex, entryPoint, spec,