From 5ef3a25f7f444ff2ae65d3ee5a14f97284baac44 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 12 Feb 2020 14:43:22 +0000 Subject: [PATCH] Add skeleton of SPIR-V shader debugging class --- renderdoc/driver/shaders/spirv/CMakeLists.txt | 2 + .../shaders/spirv/renderdoc_spirv.vcxproj | 2 + .../spirv/renderdoc_spirv.vcxproj.filters | 2 + renderdoc/driver/shaders/spirv/spirv_debug.h | 114 ++++++++++ .../shaders/spirv/spirv_debug_setup.cpp | 196 ++++++++++++++++++ .../shaders/spirv/spirv_disassemble.cpp | 2 +- renderdoc/driver/vulkan/CMakeLists.txt | 1 + .../driver/vulkan/renderdoc_vulkan.vcxproj | 1 + .../vulkan/renderdoc_vulkan.vcxproj.filters | 1 + renderdoc/driver/vulkan/vk_replay.cpp | 27 --- renderdoc/driver/vulkan/vk_shaderdebug.cpp | 85 ++++++++ 11 files changed, 405 insertions(+), 28 deletions(-) create mode 100644 renderdoc/driver/shaders/spirv/spirv_debug.h create mode 100644 renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp create mode 100644 renderdoc/driver/vulkan/vk_shaderdebug.cpp diff --git a/renderdoc/driver/shaders/spirv/CMakeLists.txt b/renderdoc/driver/shaders/spirv/CMakeLists.txt index 79ce54429..58a2e237f 100644 --- a/renderdoc/driver/shaders/spirv/CMakeLists.txt +++ b/renderdoc/driver/shaders/spirv/CMakeLists.txt @@ -98,6 +98,8 @@ set(sources spirv_op_helpers.h spirv_compile.cpp spirv_compile.h + spirv_debug_setup.cpp + spirv_debug.h spirv_reflect.cpp spirv_reflect.h spirv_processor.cpp diff --git a/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj b/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj index bdcce8b6e..3c53f1c0a 100644 --- a/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj +++ b/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj @@ -156,6 +156,7 @@ precompiled.h precompiled.h + Level4 Use @@ -243,6 +244,7 @@ + diff --git a/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj.filters b/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj.filters index c88771a72..d4fa2546a 100644 --- a/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj.filters +++ b/renderdoc/driver/shaders/spirv/renderdoc_spirv.vcxproj.filters @@ -143,6 +143,7 @@ + @@ -295,5 +296,6 @@ JSON-Generated helpers + \ No newline at end of file diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h new file mode 100644 index 000000000..8214ce1af --- /dev/null +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -0,0 +1,114 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include "api/replay/rdcarray.h" +#include "spirv_common.h" +#include "spirv_processor.h" + +namespace rdcspv +{ +struct GlobalState +{ +public: + GlobalState() {} + // allocated storage for opaque uniform blocks, does not change over the course of debugging + rdcarray constantBlocks; +}; + +class ThreadState +{ +public: + ThreadState(int workgroupIdx, GlobalState &globalState); + + bool Finished() const { return done; } + uint32_t nextInstruction; + + GlobalState &global; + + // thread-local inputs/outputs. This array does not change over the course of debugging + rdcarray inputs, outputs; + + // every ID's variable, if a pointer it may be pointing at a ShaderVariable stored elsewhere + DenseIdMap ids; + + // the list of IDs that are currently valid and live + rdcarray live; + +private: + // index in the pixel quad + int workgroupIndex; + bool done; +}; + +class Debugger : public Processor, public ShaderDebugger +{ +public: + Debugger(); + ~Debugger(); + virtual void Parse(const rdcarray &spirvWords); + ShaderDebugTrace *BeginDebug(const ShaderStage stage, const rdcstr &entryPoint, + const rdcarray &specInfo, + const std::map &instructionLines, + uint32_t activeIndex); + + rdcarray ContinueDebug(); + + GlobalState GetGlobal() { return global; } + ThreadState &GetActiveLane() { return workgroup[activeLaneIndex]; } +private: + virtual void PreParse(uint32_t maxId); + virtual void PostParse(); + virtual void RegisterOp(Iter it); + + GlobalState global; + rdcarray workgroup; + + rdcarray sourceVars; + rdcarray instructionOffsets; + + uint32_t activeLaneIndex = 0; + + int steps = 0; + + DenseIdMap strings; + + struct MemberName + { + Id id; + uint32_t member; + rdcstr name; + }; + + rdcarray memberNames; + + rdcstr GetRawName(Id id); + rdcstr GetHumanName(Id id); + + std::set usedNames; + std::map dynamicNames; +}; + +}; // namespace rdcspv diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp new file mode 100644 index 000000000..b4618760a --- /dev/null +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -0,0 +1,196 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "spirv_debug.h" +#include "common/formatting.h" +#include "spirv_op_helpers.h" + +namespace rdcspv +{ +ThreadState::ThreadState(int workgroupIdx, GlobalState &globalState) : global(globalState) +{ + workgroupIndex = workgroupIdx; + nextInstruction = 0; + done = false; +} + +Debugger::Debugger() +{ +} + +Debugger::~Debugger() +{ +} + +void Debugger::Parse(const rdcarray &spirvWords) +{ + Processor::Parse(spirvWords); +} + +ShaderDebugTrace *Debugger::BeginDebug(const ShaderStage stage, const rdcstr &entryPoint, + const rdcarray &specInfo, + const std::map &instructionLines, + uint32_t activeIndex) +{ + ShaderDebugTrace *ret = new ShaderDebugTrace; + ret->debugger = this; + this->activeLaneIndex = activeIndex; + + int workgroupSize = stage == ShaderStage::Pixel ? 4 : 1; + for(int i = 0; i < workgroupSize; i++) + workgroup.push_back(ThreadState(i, global)); + + ThreadState &active = GetActiveLane(); + + active.ids.resize(idOffsets.size()); + + // evaluate all constants + for(auto it = constants.begin(); it != constants.end(); it++) + active.ids[it->first] = EvaluateConstant(it->first, specInfo); + + ret->lineInfo.resize(instructionOffsets.size()); + for(size_t i = 0; i < instructionOffsets.size(); i++) + { + auto it = instructionLines.find(instructionOffsets[i]); + if(it != instructionLines.end()) + ret->lineInfo[i].disassemblyLine = it->second; + else + ret->lineInfo[i].disassemblyLine = 0; + } + + ret->constantBlocks = global.constantBlocks; + ret->inputs = active.inputs; + + return ret; +} + +rdcarray Debugger::ContinueDebug() +{ + ThreadState &active = GetActiveLane(); + + rdcarray ret; + + // if we've finished, return an empty set to signify that + if(active.Finished()) + return ret; + + // initialise a blank set of shader variable changes in the first ShaderDebugState + if(steps == 0) + { + ShaderDebugState initial; + + for(const Id &v : active.live) + initial.changes.push_back({ShaderVariable(), active.ids[v]}); + + initial.sourceVars = sourceVars; + + ret.push_back(initial); + + steps++; + } + + return ret; +} + +rdcstr Debugger::GetRawName(Id id) +{ + return StringFormat::Fmt("_%u", id.value()); +} + +rdcstr Debugger::GetHumanName(Id id) +{ + // see if we have a dynamic name assigned (to disambiguate), if so use that + auto it = dynamicNames.find(id); + if(it != dynamicNames.end()) + return it->second; + + // otherwise try the string first + rdcstr name = strings[id]; + + // if we don't have a string name, we can be sure the id is unambiguous + if(name.empty()) + return GetRawName(id); + + rdcstr basename = name; + + // otherwise check to see if it's been used before. If so give it a new name + int alias = 2; + while(usedNames.find(name) != usedNames.end()) + { + name = basename + "@" + ToStr(alias); + alias++; + } + + usedNames.insert(name); + dynamicNames[id] = name; + + return name; +} + +void Debugger::PreParse(uint32_t maxId) +{ + Processor::PreParse(maxId); + + strings.resize(idTypes.size()); +} + +void Debugger::PostParse() +{ + Processor::PostParse(); + + for(const MemberName &mem : memberNames) + dataTypes[mem.id].children[mem.member].name = mem.name; + + memberNames.clear(); +} + +void Debugger::RegisterOp(Iter it) +{ + Processor::RegisterOp(it); + + OpDecoder opdata(it); + + if(opdata.op == Op::String) + { + OpString string(it); + + strings[string.result] = string.string; + } + else if(opdata.op == Op::Name) + { + OpName name(it); + + // technically you could name a string - in that case we ignore the name + if(strings[name.target].empty()) + strings[name.target] = name.name; + } + else if(opdata.op == Op::MemberName) + { + OpMemberName memberName(it); + + memberNames.push_back({memberName.type, memberName.member, memberName.name}); + } +} + +}; // namespace rdcspv diff --git a/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp b/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp index f84b4e444..c45cf5eaf 100644 --- a/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp @@ -166,7 +166,7 @@ rdcstr Reflector::Disassemble(const rdcstr &entryPoint, int alias = 2; while(usedNames.find(name) != usedNames.end()) { - name = basename + "_" + ToStr(alias); + name = basename + "@" + ToStr(alias); alias++; } diff --git a/renderdoc/driver/vulkan/CMakeLists.txt b/renderdoc/driver/vulkan/CMakeLists.txt index ad11b4292..22a23dbf2 100644 --- a/renderdoc/driver/vulkan/CMakeLists.txt +++ b/renderdoc/driver/vulkan/CMakeLists.txt @@ -35,6 +35,7 @@ set(sources vk_replay.h vk_resources.cpp vk_resources.h + vk_shaderdebug.cpp vk_state.cpp vk_state.h vk_serialise.cpp diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj index f1c0e5ce6..951d22327 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj @@ -119,6 +119,7 @@ + diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters index 176b58798..90c84e2fb 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters @@ -151,6 +151,7 @@ Util + diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index e4dd818f4..ef1154764 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -3870,33 +3870,6 @@ void VulkanReplay::RefreshDerivedReplacements() m_pDriver->vkDestroyPipeline(dev, pipe, NULL); } -ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid, - uint32_t idx) -{ - VULKANNOTIMP("DebugVertex"); - return new ShaderDebugTrace(); -} - -ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, - uint32_t sample, uint32_t primitive) -{ - VULKANNOTIMP("DebugPixel"); - return new ShaderDebugTrace(); -} - -ShaderDebugTrace *VulkanReplay::DebugThread(uint32_t eventId, const uint32_t groupid[3], - const uint32_t threadid[3]) -{ - VULKANNOTIMP("DebugThread"); - return new ShaderDebugTrace(); -} - -rdcarray VulkanReplay::ContinueDebug(ShaderDebugger *debugger) -{ - VULKANNOTIMP("ContinueDebug"); - return {}; -} - ResourceId VulkanReplay::CreateProxyTexture(const TextureDescription &templateTex) { VULKANNOTIMP("CreateProxyTexture"); diff --git a/renderdoc/driver/vulkan/vk_shaderdebug.cpp b/renderdoc/driver/vulkan/vk_shaderdebug.cpp new file mode 100644 index 000000000..632b1318d --- /dev/null +++ b/renderdoc/driver/vulkan/vk_shaderdebug.cpp @@ -0,0 +1,85 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "driver/shaders/spirv/spirv_debug.h" +#include "vk_core.h" +#include "vk_replay.h" + +ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid, + uint32_t idx) +{ + const VulkanRenderState &state = m_pDriver->m_RenderState; + VulkanCreationInfo &c = m_pDriver->m_CreationInfo; + + VkMarkerRegion region( + StringFormat::Fmt("DebugVertex @ %u of (%u,%u,%u)", eventId, vertid, instid, idx)); + + const DrawcallDescription *draw = m_pDriver->GetDrawcall(eventId); + + if(!(draw->flags & DrawFlags::Drawcall)) + return new ShaderDebugTrace(); + + const VulkanCreationInfo::Pipeline &pipe = c.m_Pipeline[state.graphics.pipeline]; + VulkanCreationInfo::ShaderModule &shader = c.m_ShaderModule[pipe.shaders[0].module]; + rdcstr entryPoint = pipe.shaders[0].entryPoint; + const rdcarray &spec = pipe.shaders[0].specialization; + + VulkanCreationInfo::ShaderModuleReflection &shadRefl = + shader.GetReflection(entryPoint, state.graphics.pipeline); + + shadRefl.PopulateDisassembly(shader.spirv); + + rdcspv::Debugger *debugger = new rdcspv::Debugger; + debugger->Parse(shader.spirv.GetSPIRV()); + ShaderDebugTrace *ret = + debugger->BeginDebug(ShaderStage::Vertex, entryPoint, spec, shadRefl.instructionLines, 0); + + return ret; +} + +ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, + uint32_t sample, uint32_t primitive) +{ + VULKANNOTIMP("DebugPixel"); + return new ShaderDebugTrace(); +} + +ShaderDebugTrace *VulkanReplay::DebugThread(uint32_t eventId, const uint32_t groupid[3], + const uint32_t threadid[3]) +{ + VULKANNOTIMP("DebugThread"); + return new ShaderDebugTrace(); +} + +rdcarray VulkanReplay::ContinueDebug(ShaderDebugger *debugger) +{ + rdcspv::Debugger *spvDebugger = (rdcspv::Debugger *)debugger; + + if(!spvDebugger) + return {}; + + VkMarkerRegion region("ContinueDebug Simulation Loop"); + + return spvDebugger->ContinueDebug(); +}