mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-08 08:40:55 +00:00
Populate input and output variables in SPIR-V debugging
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user