From 7f38c690d8206c0456d047eca70f6a957d0bb787 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 20 Feb 2020 13:41:57 +0000 Subject: [PATCH] Populate input and output variables in SPIR-V debugging --- renderdoc/driver/shaders/spirv/spirv_debug.h | 7 + .../shaders/spirv/spirv_debug_setup.cpp | 187 ++++++++++++++++++ renderdoc/driver/vulkan/vk_shaderdebug.cpp | 137 +++++++++++++ 3 files changed, 331 insertions(+) diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h index 027175b84..c3da05f4d 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.h +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -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 &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; diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index 97d0457aa..510918bee 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -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 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); diff --git a/renderdoc/driver/vulkan/vk_shaderdebug.cpp b/renderdoc/driver/vulkan/vk_shaderdebug.cpp index 5ace678fc..1c84e44ef 100644 --- a/renderdoc/driver/vulkan/vk_shaderdebug.cpp +++ b/renderdoc/driver/vulkan/vk_shaderdebug.cpp @@ -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 builtin_inputs; + rdcarray 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 &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 &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,