From 4eb2621bcade0e10809ef7bf64a7b4c6f6df7d35 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 7 May 2021 17:01:21 +0100 Subject: [PATCH] Fetch shader messages from DebugPrintf --- qrenderdoc/Code/pyrenderdoc/renderdoc.i | 1 + renderdoc/api/replay/common_pipestate.h | 175 +++ renderdoc/api/replay/pipestate.h | 7 + renderdoc/api/replay/pipestate.inl | 15 + renderdoc/api/replay/vk_pipestate.h | 6 + .../shaders/spirv/spirv_debug_setup.cpp | 9 + renderdoc/driver/vulkan/CMakeLists.txt | 2 +- .../driver/vulkan/renderdoc_vulkan.vcxproj | 2 +- .../vulkan/renderdoc_vulkan.vcxproj.filters | 6 +- renderdoc/driver/vulkan/vk_replay.cpp | 5 +- renderdoc/driver/vulkan/vk_replay.h | 5 +- ...ss_feedback.cpp => vk_shader_feedback.cpp} | 1024 ++++++++++++++++- renderdoc/replay/renderdoc_serialise.inl | 50 +- 13 files changed, 1251 insertions(+), 56 deletions(-) rename renderdoc/driver/vulkan/{vk_bindless_feedback.cpp => vk_shader_feedback.cpp} (50%) diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index 4014971f6..0bafd47d2 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -360,6 +360,7 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, LineColumnInfo) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderCompileFlag) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderConstant) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderDebugState) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderMessage) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderResource) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderSampler) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderSourceFile) diff --git a/renderdoc/api/replay/common_pipestate.h b/renderdoc/api/replay/common_pipestate.h index f731051cf..1cb58d347 100644 --- a/renderdoc/api/replay/common_pipestate.h +++ b/renderdoc/api/replay/common_pipestate.h @@ -487,3 +487,178 @@ from the vertex buffer before advancing to the next value. }; DECLARE_REFLECTION_STRUCT(VertexInputAttribute); + +DOCUMENT("A compute shader message's location."); +struct ShaderComputeMessageLocation +{ + DOCUMENT(""); + ShaderComputeMessageLocation() = default; + ShaderComputeMessageLocation(const ShaderComputeMessageLocation &) = default; + ShaderComputeMessageLocation &operator=(const ShaderComputeMessageLocation &) = default; + + bool operator==(const ShaderComputeMessageLocation &o) const + { + return workgroup == o.workgroup && thread == o.thread; + } + bool operator<(const ShaderComputeMessageLocation &o) const + { + if(!(workgroup == o.workgroup)) + return workgroup < o.workgroup; + if(!(thread == o.thread)) + return thread < o.thread; + return false; + } + + DOCUMENT(R"(The workgroup index within the dispatch. + +:type: Tuple[int,int,int] +)"); + rdcfixedarray workgroup; + + DOCUMENT(R"(The thread index within the workgroup. + +:type: Tuple[int,int,int] +)"); + rdcfixedarray thread; +}; + +DECLARE_REFLECTION_STRUCT(ShaderComputeMessageLocation); + +DOCUMENT("A vertex shader message's location."); +struct ShaderVertexMessageLocation +{ + DOCUMENT(R"(The vertex or index for this vertex. + +:type: int +)"); + uint32_t vertexIndex; + + DOCUMENT(R"(The instance for this vertex. + +:type: int +)"); + uint32_t instance; + + DOCUMENT(R"(The multiview view for this vertex, or ``0`` if multiview is disabled. + +:type: int +)"); + uint32_t view; +}; + +DECLARE_REFLECTION_STRUCT(ShaderVertexMessageLocation); + +DOCUMENT(R"(A pixel shader message's location. + +.. data:: NoLocation + + No frame number is available. +)"); +struct ShaderPixelMessageLocation +{ + DOCUMENT(R"(The x co-ordinate of the pixel. + +:type: int +)"); + uint32_t x; + + DOCUMENT(R"(The y co-ordinate of the pixel. + +:type: int +)"); + uint32_t y; + + DOCUMENT(R"(The sample, or :data:`NoLocation` if sample shading is disabled. + +:type: int +)"); + uint32_t sample; + + DOCUMENT(R"(The generating primitive, or :data:`NoLocation` if the primitive ID is unavailable. + +:type: int +)"); + uint32_t primitive; + + static const uint32_t NoLocation = ~0U; +}; + +DECLARE_REFLECTION_STRUCT(ShaderPixelMessageLocation); + +DOCUMENT("A shader message's location."); +union ShaderMessageLocation +{ + DOCUMENT(R"(The location if the shader is a compute shader. + +:type: ShaderComputeMessageLocation +)"); + ShaderComputeMessageLocation compute; + + DOCUMENT(R"(The location if the shader is a vertex shader. + +:type: ShaderVertexMessageLocation +)"); + ShaderVertexMessageLocation vertex; + + DOCUMENT(R"(The location if the shader is a pixel shader. + +:type: ShaderPixelMessageLocation +)"); + ShaderPixelMessageLocation pixel; +}; + +DECLARE_REFLECTION_STRUCT(ShaderMessageLocation); + +DOCUMENT("A shader printed message."); +struct ShaderMessage +{ + DOCUMENT(""); + ShaderMessage() = default; + ShaderMessage(const ShaderMessage &) = default; + ShaderMessage &operator=(const ShaderMessage &) = default; + + bool operator==(const ShaderMessage &o) const + { + return stage == o.stage && disassemblyLine == o.disassemblyLine && + location.compute == o.location.compute && message == o.message; + } + bool operator<(const ShaderMessage &o) const + { + if(!(stage == o.stage)) + return stage < o.stage; + if(!(disassemblyLine == o.disassemblyLine)) + return disassemblyLine < o.disassemblyLine; + if(!(location.compute == o.location.compute)) + return location.compute < o.location.compute; + if(!(message == o.message)) + return message < o.message; + return false; + } + + DOCUMENT(R"(The shader stage this message comes from. + +:type: ShaderStage +)"); + ShaderStage stage; + + DOCUMENT(R"(The line (starting from 1) of the disassembly where this message came from, or -1 if +it is not associated with any line. + +:type: int +)"); + int32_t disassemblyLine; + + DOCUMENT(R"(The location (thread/invocation) of the shader that this message comes from. + +:type: ShaderMessageLocation +)"); + ShaderMessageLocation location; + + DOCUMENT(R"(The formatted message. + +:type: str +)"); + rdcstr message; +}; + +DECLARE_REFLECTION_STRUCT(ShaderMessage); diff --git a/renderdoc/api/replay/pipestate.h b/renderdoc/api/replay/pipestate.h index 0f341d699..a0a23bac0 100644 --- a/renderdoc/api/replay/pipestate.h +++ b/renderdoc/api/replay/pipestate.h @@ -385,6 +385,13 @@ For some APIs that don't distinguish by entry point, this may be empty. )"); bool IsIndependentBlendingEnabled() const; + DOCUMENT(R"(Retrieves the shader messages obtained for the current draw. + +:return: The shader messages obtained for the current draw. +:rtype: List[ShaderMessage] +)"); + const rdcarray &GetShaderMessages() const; + private: const D3D11Pipe::State *m_D3D11 = NULL; const D3D12Pipe::State *m_D3D12 = NULL; diff --git a/renderdoc/api/replay/pipestate.inl b/renderdoc/api/replay/pipestate.inl index cbd7c070d..968f77941 100644 --- a/renderdoc/api/replay/pipestate.inl +++ b/renderdoc/api/replay/pipestate.inl @@ -1758,6 +1758,21 @@ rdcpair PipeState::GetStencilFaces() const return {StencilFace(), StencilFace()}; } +const rdcarray &PipeState::GetShaderMessages() const +{ + if(IsCaptureLoaded()) + { + if(IsCaptureVK()) + { + return m_Vulkan->shaderMessages; + } + } + + static rdcarray empty; + + return empty; +} + bool PipeState::IsIndependentBlendingEnabled() const { if(IsCaptureLoaded()) diff --git a/renderdoc/api/replay/vk_pipestate.h b/renderdoc/api/replay/vk_pipestate.h index 0f553450e..4eb667abd 100644 --- a/renderdoc/api/replay/vk_pipestate.h +++ b/renderdoc/api/replay/vk_pipestate.h @@ -1297,6 +1297,12 @@ struct State )"); rdcarray images; + DOCUMENT(R"(The shader messages retrieved for this draw. + +:type: List[ShaderMessage] +)"); + rdcarray shaderMessages; + DOCUMENT(R"(The current conditional rendering state. :type: VKConditionalRendering diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index d94b59ef8..e59247cde 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -2363,6 +2363,15 @@ void Debugger::RegisterOp(Iter it) idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]); } } + else if(extSets[extinst.set] == "NonSemantic.DebugPrintf") + { + // all parameters to NonSemantic.DebugPrintf are Ids, extend idDeathOffset appropriately + for(const uint32_t param : extinst.params) + { + Id id = Id::fromWord(param); + idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]); + } + } } if(opdata.op == Op::Source) diff --git a/renderdoc/driver/vulkan/CMakeLists.txt b/renderdoc/driver/vulkan/CMakeLists.txt index ca36d437a..cf74b3fad 100644 --- a/renderdoc/driver/vulkan/CMakeLists.txt +++ b/renderdoc/driver/vulkan/CMakeLists.txt @@ -8,7 +8,7 @@ set(sources vk_debug.h vk_debug.cpp vk_postvs.cpp - vk_bindless_feedback.cpp + vk_shader_feedback.cpp vk_overlay.cpp vk_msaa_array_conv.cpp vk_outputwindow.cpp diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj index 94de6126d..8114a8e3e 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj @@ -109,7 +109,7 @@ true - + diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters index 3a4ab9418..8830e7c53 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters @@ -133,9 +133,6 @@ Util - - Replay - Util @@ -151,6 +148,9 @@ Replay + + Replay + diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index 1ee117c4a..701b50473 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -1623,6 +1623,10 @@ void VulkanReplay::SavePipelineState(uint32_t eventId) &state.graphics.descSets, &state.compute.descSets, }; + const DynamicShaderFeedback &usage = m_BindlessFeedback.Usage[eventId]; + + m_VulkanPipelineState.shaderMessages = usage.messages; + for(size_t p = 0; p < ARRAY_COUNT(srcs); p++) { bool hasUsedBinds = false; @@ -1630,7 +1634,6 @@ void VulkanReplay::SavePipelineState(uint32_t eventId) size_t usedBindsSize = 0; { - const DynamicUsedBinds &usage = m_BindlessFeedback.Usage[eventId]; bool curCompute = (p == 1); if(usage.valid && usage.compute == curCompute) { diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index ca3c24e0a..cf1978e98 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -209,10 +209,11 @@ struct VulkanPostVSData } }; -struct DynamicUsedBinds +struct DynamicShaderFeedback { bool compute = false, valid = false; rdcarray used; + rdcarray messages; }; enum TexDisplayFlags @@ -742,7 +743,7 @@ private: GPUBuffer FeedbackBuffer; - std::map Usage; + std::map Usage; } m_BindlessFeedback; ShaderDebugData m_ShaderDebugData; diff --git a/renderdoc/driver/vulkan/vk_bindless_feedback.cpp b/renderdoc/driver/vulkan/vk_shader_feedback.cpp similarity index 50% rename from renderdoc/driver/vulkan/vk_bindless_feedback.cpp rename to renderdoc/driver/vulkan/vk_shader_feedback.cpp index 166c74662..fc45dfd36 100644 --- a/renderdoc/driver/vulkan/vk_bindless_feedback.cpp +++ b/renderdoc/driver/vulkan/vk_shader_feedback.cpp @@ -22,7 +22,9 @@ * THE SOFTWARE. ******************************************************************************/ +#include #include +#include "common/formatting.h" #include "core/settings.h" #include "driver/shaders/spirv/spirv_editor.h" #include "driver/shaders/spirv/spirv_op_helpers.h" @@ -36,22 +38,218 @@ RDOC_CONFIG(rdcstr, Vulkan_Debug_FeedbackDumpDirPath, "", RDOC_CONFIG( bool, Vulkan_BindlessFeedback, true, "Enable fetching from GPU which descriptors were dynamically used in descriptor arrays."); +RDOC_CONFIG(bool, Vulkan_PrintfFetch, true, "Enable fetching printf messages from GPU."); +RDOC_CONFIG(uint32_t, Vulkan_Debug_PrintfBufferSize, 64 * 1024, + "How many bytes to reserve for a printf output buffer."); RDOC_EXTERN_CONFIG(bool, Vulkan_Debug_DisableBufferDeviceAddress); +static const uint32_t ShaderStageHeaderBitShift = 28U; + struct feedbackData { uint64_t offset; uint32_t numEntries; }; -void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const char *entryName, - const std::map &offsetMap, uint32_t maxSlot, - VkDeviceAddress addr, bool bufferAddressKHR, rdcarray &modSpirv) +struct PrintfData { + rdcstr format; + // vectors are expanded so there's one for each component (as printf will expect) + rdcarray argTypes; + size_t payloadWords; +}; + +struct ShaderPrintfArgs : public StringFormat::Args +{ +public: + ShaderPrintfArgs(const uint32_t *payload, const PrintfData &formats) + : m_Start(payload), m_Cur(payload), m_Idx(0), m_Formats(formats) + { + } + + void reset() override + { + m_Cur = m_Start; + m_Idx = 0; + } + int get_int() override + { + int32_t ret = *(int32_t *)m_Cur; + m_Idx++; + m_Cur++; + return ret; + } + unsigned int get_uint() override + { + uint32_t ret = *(uint32_t *)m_Cur; + m_Idx++; + m_Cur++; + return ret; + } + double get_double() override + { + // here we need to know if a real double was stored or not. It probably isn't but we handle it + if(m_Idx < m_Formats.argTypes.size()) + { + if(m_Formats.argTypes[m_Idx].width == 64) + { + double ret = *(double *)m_Cur; + m_Idx++; + m_Cur += 2; + return ret; + } + else + { + float ret = *(float *)m_Cur; + m_Idx++; + m_Cur++; + return ret; + } + } + else + { + return 0.0; + } + } + void *get_ptr() override + { + m_Idx++; + return NULL; + } + uint64_t get_uint64() override + { + uint64_t ret = *(uint64_t *)m_Cur; + m_Idx++; + m_Cur += 2; + return ret; + } + + size_t get_size() override { return sizeof(size_t) == 8 ? get_uint64() : get_uint(); } +private: + const uint32_t *m_Cur; + const uint32_t *m_Start; + size_t m_Idx; + const PrintfData &m_Formats; +}; + +rdcstr PatchFormatString(rdcstr format) +{ + // we don't support things like %XX.YYv2f so look for vector formatters and expand them to + // %XX.YYf, %XX.YYf + // Also annoyingly the printf specification for 64-bit integers is printed as %ul instead of %llu, + // so we need to patch that up too + + for(size_t i = 0; i < format.size(); i++) + { + if(format[i] == '%') + { + size_t start = i; + + i++; + if(format[i] == '%') + continue; + + // skip to first letter + while(i < format.size() && !isalpha(format[i])) + i++; + + // malformed string, abort + if(!isalpha(format[i])) + { + RDCERR("Malformed format string '%s'", format.c_str()); + break; + } + + // if the first letter is v, this is a vector format + if(format[i] == 'v' || format[i] == 'V') + { + size_t vecStart = i; + + int vecsize = int(format[i + 1]) - int('0'); + + if(vecsize < 2 || vecsize > 4) + { + RDCERR("Malformed format string '%s'", format.c_str()); + break; + } + + // skip the v and the [234] + i += 2; + + if(i >= format.size()) + { + RDCERR("Malformed format string '%s'", format.c_str()); + break; + } + + bool int64 = false; + // if the final letter is u, we need to peek ahead to see if there's a l following + if(format[i] == 'u' && i + 1 < format.size() && format[i + 1] == 'l') + { + i++; + int64 = true; + } + + rdcstr componentFormat = format.substr(start, i - start + 1); + + // remove the vX from the component format + componentFormat.erase(vecStart - start, 2); + + // if it's a 64-bit ul, transform to llu + if(int64) + { + componentFormat.pop_back(); + componentFormat.pop_back(); + componentFormat += "llu"; + } + + rdcstr vectorExpandedFormat; + for(int v = 0; v < vecsize; v++) + { + vectorExpandedFormat += componentFormat; + if(v + 1 < vecsize) + vectorExpandedFormat += ", "; + } + + // remove the vector formatter + format.erase(start, i - start + 1); + format.insert(start, vectorExpandedFormat); + + continue; + } + + // if the letter is u, see if the next is l. If so we translate ul to llu + if(format[i] == 'u' && i + 1 < format.size() && format[i + 1] == 'l') + { + format[i] = 'l'; + format[i + 1] = 'u'; + format.insert(i, 'l'); + } + } + } + + return format; +} + +void AnnotateShader(const ShaderReflection &refl, const SPIRVPatchData &patchData, + ShaderStage stage, const char *entryName, + const std::map &offsetMap, uint32_t maxSlot, + bool usePrimitiveID, VkDeviceAddress addr, bool bufferAddressKHR, + rdcarray &modSpirv, std::map &printfData) +{ + // calculate offsets for IDs on the original unmodified SPIR-V. The editor may insert some nops, + // so we do it manually here + std::map idToOffset; + + for(rdcspv::Iter it(modSpirv, rdcspv::FirstRealWord); it; it++) + idToOffset[rdcspv::OpDecoder(it).result] = (uint32_t)it.offs(); + rdcspv::Editor editor(modSpirv); editor.Prepare(); + RDCASSERTMSG("SPIR-V module is too large to encode instruction ID!", modSpirv.size() < 0xfffffffU); + const bool useBufferAddress = (addr != 0); const uint32_t targetIndexWidth = useBufferAddress ? 64 : 32; @@ -60,26 +258,31 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch rdcspv::Id maxSlotID = useBufferAddress ? editor.AddConstantImmediate(maxSlot) : editor.AddConstantImmediate(maxSlot); - rdcspv::Id uint32ID = editor.DeclareType(rdcspv::scalar()); - rdcspv::Id int32ID = editor.DeclareType(rdcspv::scalar()); - rdcspv::Id uint64ID, int64ID; + rdcspv::Id maxPrintfWordOffset = + editor.AddConstantImmediate(Vulkan_Debug_PrintfBufferSize() / sizeof(uint32_t)); + + rdcspv::Id uint32Type = editor.DeclareType(rdcspv::scalar()); + rdcspv::Id int32Type = editor.DeclareType(rdcspv::scalar()); + rdcspv::Id f32Type = editor.DeclareType(rdcspv::scalar()); + rdcspv::Id uint64Type, int64Type; rdcspv::Id uint32StructID; rdcspv::Id funcParamType; if(useBufferAddress) { // declare the int64 types we'll need - uint64ID = editor.DeclareType(rdcspv::scalar()); - int64ID = editor.DeclareType(rdcspv::scalar()); + uint64Type = editor.DeclareType(rdcspv::scalar()); + int64Type = editor.DeclareType(rdcspv::scalar()); - uint32StructID = editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), {uint32ID})); + uint32StructID = editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), {uint32Type})); // any function parameters we add are uint64 byte offsets - funcParamType = uint64ID; + funcParamType = uint64Type; } else { - rdcspv::Id runtimeArrayID = editor.AddType(rdcspv::OpTypeRuntimeArray(editor.MakeId(), uint32ID)); + rdcspv::Id runtimeArrayID = + editor.AddType(rdcspv::OpTypeRuntimeArray(editor.MakeId(), uint32Type)); editor.AddDecoration(rdcspv::OpDecorate( runtimeArrayID, rdcspv::DecorationParam(sizeof(uint32_t)))); @@ -87,7 +290,15 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch uint32StructID = editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), {runtimeArrayID})); // any function parameters we add are uint32 indices - funcParamType = uint32ID; + funcParamType = uint32Type; + + // if the module declares int64 capability, ensure uint64/int64 are declared in case we need to + // transform them for printf arguments + if(editor.HasCapability(rdcspv::Capability::Int64)) + { + uint64Type = editor.DeclareType(rdcspv::scalar()); + int64Type = editor.DeclareType(rdcspv::scalar()); + } } editor.SetName(uint32StructID, "__rd_feedbackStruct"); @@ -139,6 +350,11 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch rdcspv::Id bufferAddressConst, ssboVar, uint32ptrtype; + if(usePrimitiveID && stage == ShaderStage::Fragment && Vulkan_PrintfFetch()) + { + editor.AddCapability(rdcspv::Capability::Geometry); + } + if(useBufferAddress) { // add the extension @@ -158,7 +374,7 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch // declare the address constants and make our pointers physical storage buffer pointers bufferAddressConst = editor.AddConstantImmediate(addr); uint32ptrtype = - editor.DeclareType(rdcspv::Pointer(uint32ID, rdcspv::StorageClass::PhysicalStorageBuffer)); + editor.DeclareType(rdcspv::Pointer(uint32Type, rdcspv::StorageClass::PhysicalStorageBuffer)); editor.SetName(bufferAddressConst, "__rd_feedbackAddress"); @@ -171,7 +387,7 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch // the pointers are SSBO pointers rdcspv::Id bufptrtype = editor.DeclareType(rdcspv::Pointer(uint32StructID, ssboClass)); - uint32ptrtype = editor.DeclareType(rdcspv::Pointer(uint32ID, ssboClass)); + uint32ptrtype = editor.DeclareType(rdcspv::Pointer(uint32Type, ssboClass)); // patch all bindings up by 1 for(rdcspv::Iter it = editor.Begin(rdcspv::Section::Annotations), @@ -211,11 +427,27 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch } rdcspv::Id rtarrayOffset = editor.AddConstantImmediate(0U); + rdcspv::Id printfArrayOffset = rtarrayOffset; + rdcspv::Id zero = rtarrayOffset; rdcspv::Id usedValue = editor.AddConstantImmediate(0xFFFFFFFFU); rdcspv::Id scope = editor.AddConstantImmediate((uint32_t)rdcspv::Scope::Invocation); rdcspv::Id semantics = editor.AddConstantImmediate(0U); rdcspv::Id uint32shift = editor.AddConstantImmediate(2U); + rdcspv::MemoryAccessAndParamDatas memoryAccess; + memoryAccess.setAligned(sizeof(uint32_t)); + + rdcspv::Id printfIncrement; + + if(useBufferAddress) + { + printfIncrement = editor.AddConstantImmediate(sizeof(uint32_t)); + } + else + { + printfIncrement = editor.AddConstantImmediate(1U); + } + rdcspv::Id glsl450 = editor.ImportExtInst("GLSL.std.450"); std::map intTypeLookup; @@ -234,6 +466,211 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch } } + rdcarray newGlobals; + + rdcspv::Id uvec2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 2)); + rdcspv::Id uvec3Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 3)); + rdcspv::Id uvec4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 4)); + + // we'll initialise this at the start of the entry point, and use it globally to get the location + // for printf statements + rdcspv::Id printfLocationVar = editor.MakeId(); + + if(Vulkan_PrintfFetch()) + { + editor.AddVariable(rdcspv::OpVariable( + editor.DeclareType(rdcspv::Pointer(uvec3Type, rdcspv::StorageClass::Private)), + printfLocationVar, rdcspv::StorageClass::Private)); + + if(editor.EntryPointAllGlobals()) + newGlobals.push_back(printfLocationVar); + } + + rdcspv::Id shaderStageConstant = + editor.AddConstantImmediate(uint32_t(stage) << ShaderStageHeaderBitShift); + rdcspv::Id int64wordshift = editor.AddConstantImmediate(32U); + + // build up operations to pull in the location from globals - either existing or ones we add + rdcspv::OperationList locationGather; + + if(Vulkan_PrintfFetch()) + { + rdcarray idxs; + + auto fetchOrAddGlobalInput = [&editor, &idxs, &refl, &patchData, &locationGather, &newGlobals]( + const char *name, ShaderBuiltin builtin, rdcspv::BuiltIn spvBuiltin, rdcspv::Id varType) { + rdcspv::Id ret; + + rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(varType, rdcspv::StorageClass::Input)); + + for(size_t i = 0; i < refl.inputSignature.size(); i++) + { + if(refl.inputSignature[i].systemValue == builtin) + { + if(patchData.inputs[i].accessChain.empty()) + { + ret = + locationGather.add(rdcspv::OpLoad(varType, editor.MakeId(), patchData.inputs[i].ID)); + } + else + { + rdcarray chain; + + for(uint32_t accessIdx : patchData.inputs[i].accessChain) + { + idxs.resize_for_index(accessIdx); + if(idxs[accessIdx] == 0) + idxs[accessIdx] = editor.AddConstantImmediate(accessIdx); + + chain.push_back(idxs[accessIdx]); + } + + rdcspv::Id subElement = locationGather.add( + rdcspv::OpAccessChain(ptrType, editor.MakeId(), patchData.inputs[i].ID, chain)); + + ret = + locationGather.add(rdcspv::OpLoad(varType, editor.MakeId(), patchData.inputs[i].ID)); + } + } + } + + if(ret == rdcspv::Id()) + { + rdcspv::Id rdocGlobalVar = editor.AddVariable( + rdcspv::OpVariable(ptrType, editor.MakeId(), rdcspv::StorageClass::Input)); + editor.AddDecoration(rdcspv::OpDecorate( + rdocGlobalVar, rdcspv::DecorationParam(spvBuiltin))); + + newGlobals.push_back(rdocGlobalVar); + + editor.SetName(rdocGlobalVar, name); + + ret = locationGather.add(rdcspv::OpLoad(varType, editor.MakeId(), rdocGlobalVar)); + } + + return ret; + }; + + rdcspv::Id location; + + // the location encoding varies by stage + if(stage == ShaderStage::Compute) + { + // the location for compute is easy, it's just the global invocation + location = fetchOrAddGlobalInput("rdoc_invocation", ShaderBuiltin::DispatchThreadIndex, + rdcspv::BuiltIn::GlobalInvocationId, uvec3Type); + } + else if(stage == ShaderStage::Vertex) + { + rdcspv::Id vtx = fetchOrAddGlobalInput("rdoc_vertexIndex", ShaderBuiltin::VertexIndex, + rdcspv::BuiltIn::VertexIndex, uint32Type); + rdcspv::Id inst = fetchOrAddGlobalInput("rdoc_instanceIndex", ShaderBuiltin::InstanceIndex, + rdcspv::BuiltIn::InstanceIndex, uint32Type); + + rdcspv::Id view; + + // only search for the view index is the multiview capability is declared, otherwise it's + // invalid and we just set 0 + if(editor.HasCapability(rdcspv::Capability::MultiView)) + { + view = fetchOrAddGlobalInput("rdoc_viewIndex", ShaderBuiltin::ViewportIndex, + rdcspv::BuiltIn::ViewIndex, uint32Type); + } + else + { + view = editor.AddConstantImmediate(0U); + } + + location = locationGather.add( + rdcspv::OpCompositeConstruct(uvec3Type, editor.MakeId(), {vtx, inst, view})); + } + else if(stage == ShaderStage::Pixel) + { + rdcspv::Id float2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 2)); + rdcspv::Id float4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 4)); + + rdcspv::Id coord = fetchOrAddGlobalInput("rdoc_fragCoord", ShaderBuiltin::Position, + rdcspv::BuiltIn::FragCoord, float4Type); + + // grab just the xy + coord = locationGather.add( + rdcspv::OpVectorShuffle(float2Type, editor.MakeId(), coord, coord, {0, 1})); + + // convert to int + coord = locationGather.add(rdcspv::OpConvertFToU(uvec2Type, editor.MakeId(), coord)); + + rdcspv::Id x = + locationGather.add(rdcspv::OpCompositeExtract(uint32Type, editor.MakeId(), coord, {0})); + rdcspv::Id y = + locationGather.add(rdcspv::OpCompositeExtract(uint32Type, editor.MakeId(), coord, {1})); + + // shift x up into top 16-bits + x = locationGather.add(rdcspv::OpShiftLeftLogical( + uint32Type, editor.MakeId(), x, editor.AddConstantImmediate(16U))); + + // OR together + coord = locationGather.add(rdcspv::OpBitwiseOr(uint32Type, editor.MakeId(), x, y)); + + rdcspv::Id samp; + + // only grab the sample ID if sample shading is already enabled + for(size_t i = 0; i < refl.inputSignature.size(); i++) + { + if(refl.inputSignature[i].systemValue == ShaderBuiltin::MSAASampleIndex || + refl.inputSignature[i].systemValue == ShaderBuiltin::MSAASamplePosition) + { + samp = fetchOrAddGlobalInput("rdoc_sampleIndex", ShaderBuiltin::MSAASampleIndex, + rdcspv::BuiltIn::SampleId, uint32Type); + } + } + + if(samp == rdcspv::Id()) + { + samp = editor.AddConstantImmediate(~0U); + } + + rdcspv::Id prim; + + if(usePrimitiveID) + { + prim = fetchOrAddGlobalInput("rdoc_primitiveIndex", ShaderBuiltin::PrimitiveIndex, + rdcspv::BuiltIn::PrimitiveId, uint32Type); + } + else + { + prim = editor.AddConstantImmediate(~0U); + } + + location = locationGather.add( + rdcspv::OpCompositeConstruct(uvec3Type, editor.MakeId(), {coord, samp, prim})); + } + else + { + RDCWARN("No identifier stored for %s stage", ToStr(stage).c_str()); + location = locationGather.add( + rdcspv::OpCompositeConstruct(uvec3Type, editor.MakeId(), {zero, zero, zero})); + } + + locationGather.add(rdcspv::OpStore(printfLocationVar, location)); + } + + if(!newGlobals.empty()) + { + rdcspv::Iter it = editor.GetEntry(entryID); + + RDCASSERT(it.opcode() == rdcspv::Op::EntryPoint); + + rdcspv::OpEntryPoint entry(it); + + editor.Remove(it); + + entry.iface.append(newGlobals); + + editor.AddOperation(it, entry); + } + + rdcspv::Id debugPrintfSet = editor.HasExtInst("NonSemantic.DebugPrintf"); + rdcspv::TypeToIds funcTypes = editor.GetTypes(); // functions that have been patched with annotation & extra function parameters if needed @@ -330,6 +767,22 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch ++it; } + // continue to the first label so we can insert things at the start of the entry point + for(; it; ++it) + { + if(it.opcode() == rdcspv::Op::Label) + { + ++it; + break; + } + } + + for(const rdcspv::Operation &op : locationGather) + { + editor.AddOperation(it, op); + ++it; + } + // now patch accesses in the function body for(; it; ++it) { @@ -396,6 +849,289 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch functionPatchQueue[call.function] = patchArgs; } + if(it.opcode() == rdcspv::Op::ExtInst && Vulkan_PrintfFetch()) + { + rdcspv::OpExtInst extinst(it); + // is this a printf extinst? + if(extinst.set == debugPrintfSet) + { + uint32_t printfID = idToOffset[extinst.result]; + + rdcspv::Id resultConstant = editor.AddConstantDeferred(printfID); + + PrintfData &format = printfData[printfID]; + + { + rdcspv::OpString str(editor.GetID(rdcspv::Id::fromWord(extinst.params[0]))); + format.format = PatchFormatString(str.string); + } + + rdcarray packetWords; + + // pack all the parameters into uint32s + for(size_t i = 1; i < extinst.params.size(); i++) + { + rdcspv::Id printfparam = rdcspv::Id::fromWord(extinst.params[i]); + rdcspv::Id type = editor.GetIDType(printfparam); + + rdcspv::Iter typeIt = editor.GetID(type); + + // handle vectors, but no other composites + uint32_t vecDim = 0; + if(typeIt.opcode() == rdcspv::Op::TypeVector) + { + rdcspv::OpTypeVector vec(typeIt); + vecDim = vec.componentCount; + type = vec.componentType; + typeIt = editor.GetID(type); + } + + rdcspv::Scalar scalarType(typeIt); + + for(uint32_t comp = 0; comp < RDCMAX(1U, vecDim); comp++) + { + rdcspv::Id input = printfparam; + + format.argTypes.push_back(scalarType); + + // if the input is a vector, extract the component we're working on + if(vecDim > 0) + { + input = editor.AddOperation( + it, rdcspv::OpCompositeExtract(type, editor.MakeId(), input, {comp})); + it++; + } + + // handle ints and floats + if(typeIt.opcode() == rdcspv::Op::TypeInt) + { + rdcspv::OpTypeInt intType(typeIt); + + rdcspv::Id param = input; + + if(intType.signedness) + { + // extend to 32-bit if needed then bitcast to unsigned + if(intType.width < 32) + { + param = editor.AddOperation( + it, rdcspv::OpSConvert(int32Type, editor.MakeId(), param)); + it++; + } + + param = editor.AddOperation( + it, rdcspv::OpBitcast(intType.width == 64 ? uint64Type : uint32Type, + editor.MakeId(), param)); + it++; + } + else + { + // just extend to 32-bit if needed + if(intType.width < 32) + { + param = editor.AddOperation( + it, rdcspv::OpSConvert(uint32Type, editor.MakeId(), param)); + it++; + } + } + + // 64-bit integers we now need to split up the words and add them. Otherwise we have + // a 32-bit uint to add + if(intType.width == 64) + { + rdcspv::Id lo = editor.AddOperation( + it, rdcspv::OpUConvert(uint32Type, editor.MakeId(), param)); + it++; + + rdcspv::Id shifted = editor.AddOperation( + it, rdcspv::OpShiftRightLogical(uint64Type, editor.MakeId(), param, + int64wordshift)); + it++; + + rdcspv::Id hi = editor.AddOperation( + it, rdcspv::OpUConvert(uint32Type, editor.MakeId(), shifted)); + it++; + + packetWords.push_back(lo); + packetWords.push_back(hi); + } + else + { + packetWords.push_back(param); + } + } + else if(typeIt.opcode() == rdcspv::Op::TypeFloat) + { + rdcspv::OpTypeFloat floatType(typeIt); + + rdcspv::Id param = input; + + // if it's not at least a float, upconvert. We don't convert to doubles since that + // would require double capability + if(floatType.width < 32) + { + param = + editor.AddOperation(it, rdcspv::OpFConvert(f32Type, editor.MakeId(), param)); + it++; + } + + if(floatType.width == 64) + { + // for doubles we use the GLSL unpack operation + rdcspv::Id unpacked = editor.AddOperation( + it, rdcspv::OpGLSL450(uvec2Type, editor.MakeId(), glsl450, + rdcspv::GLSLstd450::UnpackDouble2x32, {param})); + + // then extract the components + rdcspv::Id lo = editor.AddOperation( + it, rdcspv::OpCompositeExtract(uint32Type, editor.MakeId(), unpacked, {0})); + it++; + + rdcspv::Id hi = editor.AddOperation( + it, rdcspv::OpCompositeExtract(uint32Type, editor.MakeId(), unpacked, {1})); + it++; + + packetWords.push_back(lo); + packetWords.push_back(hi); + } + else + { + // otherwise we bitcast to uint32 + param = + editor.AddOperation(it, rdcspv::OpBitcast(uint32Type, editor.MakeId(), param)); + it++; + + packetWords.push_back(param); + } + } + else + { + RDCERR("Unexpected type of operand to printf %s, ignoring", + ToStr(typeIt.opcode()).c_str()); + } + } + } + + format.payloadWords = packetWords.size(); + + // pack header uint32 + rdcspv::Id header = + editor.AddOperation(it, rdcspv::OpBitwiseOr(uint32Type, editor.MakeId(), + shaderStageConstant, resultConstant)); + it++; + + packetWords.insert(0, header); + + // load the location out of the global where we put it + rdcspv::Id location = + editor.AddOperation(it, rdcspv::OpLoad(uvec3Type, editor.MakeId(), printfLocationVar)); + it++; + + // extract each component and add it as a new word after the header + packetWords.insert( + 1, editor.AddOperation( + it, rdcspv::OpCompositeExtract(uint32Type, editor.MakeId(), location, {0}))); + it++; + packetWords.insert( + 2, editor.AddOperation( + it, rdcspv::OpCompositeExtract(uint32Type, editor.MakeId(), location, {1}))); + it++; + packetWords.insert( + 3, editor.AddOperation( + it, rdcspv::OpCompositeExtract(uint32Type, editor.MakeId(), location, {2}))); + it++; + + rdcspv::Id counterptr; + + if(useBufferAddress) + { + // make a pointer out of the buffer address + // uint32_t *bufptr = (uint32_t *)offsetaddr + counterptr = editor.AddOperation( + it, rdcspv::OpConvertUToPtr(uint32ptrtype, editor.MakeId(), bufferAddressConst)); + it++; + } + else + { + // accesschain to get the pointer we'll atomic into. + // accesschain is 0 to access rtarray (first member) then zero for the first array index + // uint32_t *bufptr = (uint32_t *)&buf.printfWords[ssboindex]; + counterptr = + editor.AddOperation(it, rdcspv::OpAccessChain(uint32ptrtype, editor.MakeId(), + ssboVar, {printfArrayOffset, zero})); + it++; + } + + rdcspv::Id packetSize = editor.AddConstantDeferred((uint32_t)packetWords.size()); + + // atomically reserve enough space + rdcspv::Id idx = + editor.AddOperation(it, rdcspv::OpAtomicIAdd(uint32Type, editor.MakeId(), counterptr, + scope, semantics, packetSize)); + it++; + + // clamp to the buffer size so we don't overflow + idx = editor.AddOperation( + it, rdcspv::OpGLSL450(uint32Type, editor.MakeId(), glsl450, rdcspv::GLSLstd450::UMin, + {idx, maxPrintfWordOffset})); + it++; + + if(useBufferAddress) + { + // convert to a 64-bit value + idx = editor.AddOperation(it, rdcspv::OpUConvert(uint64Type, editor.MakeId(), idx)); + it++; + + // the index is in words, so multiply by the increment to get a byte offset + rdcspv::Id byteOffset = editor.AddOperation( + it, rdcspv::OpIMul(uint64Type, editor.MakeId(), idx, printfIncrement)); + it++; + + // add the offset to the base address + rdcspv::Id bufAddr = editor.AddOperation( + it, rdcspv::OpIAdd(uint64Type, editor.MakeId(), bufferAddressConst, byteOffset)); + it++; + + for(rdcspv::Id word : packetWords) + { + // we pre-increment idx because it starts from 0 but we want to write into words + // starting from [1] to leave the counter itself alone. + bufAddr = editor.AddOperation( + it, rdcspv::OpIAdd(uint64Type, editor.MakeId(), bufAddr, printfIncrement)); + it++; + + rdcspv::Id ptr = editor.AddOperation( + it, rdcspv::OpConvertUToPtr(uint32ptrtype, editor.MakeId(), bufAddr)); + it++; + + editor.AddOperation(it, rdcspv::OpStore(ptr, word, memoryAccess)); + it++; + } + } + else + { + for(rdcspv::Id word : packetWords) + { + // we pre-increment idx because it starts from 0 but we want to write into words + // starting from [1] to leave the counter itself alone. + idx = editor.AddOperation( + it, rdcspv::OpIAdd(uint32Type, editor.MakeId(), idx, printfIncrement)); + it++; + + rdcspv::Id ptr = + editor.AddOperation(it, rdcspv::OpAccessChain(uint32ptrtype, editor.MakeId(), + ssboVar, {printfArrayOffset, idx})); + it++; + + editor.AddOperation(it, rdcspv::OpStore(ptr, word)); + it++; + } + } + + // no it++ here, it will happen implicitly on loop continue + } + } + // if we see an access chain of a variable we're snooping, save out the result if(it.opcode() == rdcspv::Op::AccessChain || it.opcode() == rdcspv::Op::InBoundsAccessChain) { @@ -424,7 +1160,7 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch if(indexType == rdcspv::Id()) { RDCERR("Unknown type for ID %u, defaulting to uint32_t", index.value()); - indexType = uint32ID; + indexType = uint32Type; } rdcspv::Scalar indexTypeData = rdcspv::scalar(); @@ -480,19 +1216,19 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch // get our output slot address by adding an offset to the base pointer // baseaddr = bufferAddressConst + bindingOffset rdcspv::Id baseaddr = editor.AddOperation( - it, rdcspv::OpIAdd(uint64ID, editor.MakeId(), bufferAddressConst, varIt->second)); + it, rdcspv::OpIAdd(uint64Type, editor.MakeId(), bufferAddressConst, varIt->second)); it++; // shift the index since this is a byte offset // shiftedindex = index << uint32shift rdcspv::Id shiftedindex = editor.AddOperation( - it, rdcspv::OpShiftLeftLogical(uint64ID, editor.MakeId(), index, uint32shift)); + it, rdcspv::OpShiftLeftLogical(uint64Type, editor.MakeId(), index, uint32shift)); it++; // add the index on top of that // offsetaddr = baseaddr + shiftedindex rdcspv::Id offsetaddr = editor.AddOperation( - it, rdcspv::OpIAdd(uint64ID, editor.MakeId(), baseaddr, shiftedindex)); + it, rdcspv::OpIAdd(uint64Type, editor.MakeId(), baseaddr, shiftedindex)); it++; // make a pointer out of it @@ -508,7 +1244,7 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch // add the index to this binding's base index // ssboindex = bindingOffset + index rdcspv::Id ssboindex = editor.AddOperation( - it, rdcspv::OpIAdd(uint32ID, editor.MakeId(), index, varIt->second)); + it, rdcspv::OpIAdd(uint32Type, editor.MakeId(), index, varIt->second)); it++; // accesschain to get the pointer we'll atomic into. @@ -521,7 +1257,7 @@ void AnnotateShader(const SPIRVPatchData &patchData, ShaderStage stage, const ch } // atomically set the uint32 that's pointed to - editor.AddOperation(it, rdcspv::OpAtomicUMax(uint32ID, editor.MakeId(), bufptr, scope, + editor.AddOperation(it, rdcspv::OpAtomicUMax(uint32Type, editor.MakeId(), bufptr, scope, semantics, usedValue)); // no it++ here, it will happen implicitly on loop continue @@ -546,7 +1282,7 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) // create it here so we won't re-run any code if the event is re-selected. We'll mark it as valid // if it actually has any data in it later. - DynamicUsedBinds &result = m_BindlessFeedback.Usage[eventId]; + DynamicShaderFeedback &result = m_BindlessFeedback.Usage[eventId]; bool useBufferAddress = (m_pDriver->GetExtensions(NULL).ext_KHR_buffer_device_address || m_pDriver->GetExtensions(NULL).ext_EXT_buffer_device_address) && @@ -579,9 +1315,48 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) std::map offsetMap; - // reserve some space at the start for a general counter indicating that successful data was - // written. - feedbackStorageSize += 16; + bool usesPrintf = false; + + VkGraphicsPipelineCreateInfo graphicsInfo = {}; + VkComputePipelineCreateInfo computeInfo = {}; + + // get pipeline create info + if(result.compute) + { + m_pDriver->GetShaderCache()->MakeComputePipelineInfo(computeInfo, state.compute.pipeline); + } + else + { + m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(graphicsInfo, state.graphics.pipeline); + + graphicsInfo.renderPass = + creationInfo.m_RenderPass[GetResID(graphicsInfo.renderPass)].loadRPs[graphicsInfo.subpass]; + graphicsInfo.subpass = 0; + } + + if(result.compute) + { + usesPrintf = pipeInfo.shaders[5].patchData->usesPrintf; + } + else + { + for(uint32_t i = 0; i < graphicsInfo.stageCount; i++) + { + VkPipelineShaderStageCreateInfo &stage = + (VkPipelineShaderStageCreateInfo &)graphicsInfo.pStages[i]; + + int idx = StageIndex(stage.stage); + + usesPrintf |= pipeInfo.shaders[idx].patchData->usesPrintf; + } + } + + if(usesPrintf) + { + // reserve some space at the start for an atomic offset counter then the buffer size, and an + // overflow section for any clamped messages + feedbackStorageSize += 16 + Vulkan_Debug_PrintfBufferSize() + 1024; + } { const rdcarray &descSetLayoutIds = @@ -622,8 +1397,8 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) // add some extra padding just in case of out-of-bounds writes feedbackStorageSize += 128; - // if we don't have any array descriptors to feedback then just return now - if(offsetMap.empty()) + // if we don't have any array descriptors or printf's to feedback then just return now + if(offsetMap.empty() && !usesPrintf) return; if(!result.compute) @@ -641,23 +1416,6 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) VkResult vkr = VK_SUCCESS; VkDevice dev = m_Device; - VkGraphicsPipelineCreateInfo graphicsInfo = {}; - VkComputePipelineCreateInfo computeInfo = {}; - - // get pipeline create info - if(result.compute) - { - m_pDriver->GetShaderCache()->MakeComputePipelineInfo(computeInfo, state.compute.pipeline); - } - else - { - m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(graphicsInfo, state.graphics.pipeline); - - graphicsInfo.renderPass = - creationInfo.m_RenderPass[GetResID(graphicsInfo.renderPass)].loadRPs[graphicsInfo.subpass]; - graphicsInfo.subpass = 0; - } - if(feedbackStorageSize > m_BindlessFeedback.FeedbackBuffer.sz) { uint32_t flags = GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO; @@ -767,6 +1525,8 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) "bindless_geometry.spv", "bindless_pixel.spv", "bindless_compute.spv", }; + std::map printfData[6]; + if(result.compute) { VkPipelineShaderStageCreateInfo &stage = computeInfo.stage; @@ -779,8 +1539,9 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) if(!Vulkan_Debug_FeedbackDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_FeedbackDumpDirPath() + "/before_" + filename[5], modSpirv); - AnnotateShader(*pipeInfo.shaders[5].patchData, ShaderStage(StageIndex(stage.stage)), - stage.pName, offsetMap, maxSlot, bufferAddress, useBufferAddressKHR, modSpirv); + AnnotateShader(*pipeInfo.shaders[5].refl, *pipeInfo.shaders[5].patchData, + ShaderStage(StageIndex(stage.stage)), stage.pName, offsetMap, maxSlot, false, + bufferAddress, useBufferAddressKHR, modSpirv, printfData[5]); if(!Vulkan_Debug_FeedbackDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_FeedbackDumpDirPath() + "/after_" + filename[5], modSpirv); @@ -795,6 +1556,23 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) } else { + bool hasGeom = false; + + for(uint32_t i = 0; i < graphicsInfo.stageCount; i++) + { + VkPipelineShaderStageCreateInfo &stage = + (VkPipelineShaderStageCreateInfo &)graphicsInfo.pStages[i]; + + if((stage.stage & VK_SHADER_STAGE_GEOMETRY_BIT) != 0) + { + hasGeom = true; + break; + } + } + + bool usePrimitiveID = + !hasGeom && m_pDriver->GetDeviceEnabledFeatures().geometryShader != VK_FALSE; + for(uint32_t i = 0; i < graphicsInfo.stageCount; i++) { VkPipelineShaderStageCreateInfo &stage = @@ -821,8 +1599,9 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) if(!Vulkan_Debug_FeedbackDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_FeedbackDumpDirPath() + "/before_" + filename[idx], modSpirv); - AnnotateShader(*pipeInfo.shaders[idx].patchData, ShaderStage(StageIndex(stage.stage)), - stage.pName, offsetMap, maxSlot, bufferAddress, useBufferAddressKHR, modSpirv); + AnnotateShader(*pipeInfo.shaders[idx].refl, *pipeInfo.shaders[idx].patchData, + ShaderStage(StageIndex(stage.stage)), stage.pName, offsetMap, maxSlot, + usePrimitiveID, bufferAddress, useBufferAddressKHR, modSpirv, printfData[idx]); if(!Vulkan_Debug_FeedbackDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_FeedbackDumpDirPath() + "/after_" + filename[idx], modSpirv); @@ -949,6 +1728,115 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) result.valid = true; + uint32_t *printfBuf = (uint32_t *)data.data(); + uint32_t *printfBufEnd = (uint32_t *)(data.data() + Vulkan_Debug_PrintfBufferSize()); + if(*printfBuf > 0) + { + uint32_t wordsNeeded = *printfBuf; + + if(wordsNeeded > Vulkan_Debug_PrintfBufferSize()) + { + RDCLOG("printf buffer overflowed, needed %u bytes but printf buffer is only %u bytes", + wordsNeeded * 4, Vulkan_Debug_PrintfBufferSize()); + } + + printfBuf++; + + while(*printfBuf && printfBuf < printfBufEnd) + { + ShaderStage stage = ShaderStage((*printfBuf) >> ShaderStageHeaderBitShift); + uint32_t printfID = *printfBuf & 0xfffffffU; + + printfBuf++; + + if(stage < ShaderStage::Count) + { + auto it = printfData[(uint32_t)stage].find(printfID); + if(it == printfData[(uint32_t)stage].end()) + { + RDCERR("Error parsing DebugPrintf buffer, unexpected printf ID %x from header %x", + printfID, *printfBuf); + break; + } + + uint32_t *location = printfBuf; + + printfBuf += 3; + + const PrintfData &fmt = it->second; + + ShaderPrintfArgs args(printfBuf, fmt); + + printfBuf += fmt.payloadWords; + + // this message overflowed, don't process it + if(printfBuf >= printfBufEnd) + break; + + ShaderMessage msg; + + msg.stage = stage; + + const VulkanCreationInfo::Pipeline::Shader &sh = pipeInfo.shaders[(uint32_t)stage]; + + { + VulkanCreationInfo::ShaderModule &mod = creationInfo.m_ShaderModule[sh.module]; + VulkanCreationInfo::ShaderModuleReflection &modrefl = + mod.GetReflection(sh.entryPoint, pipe.pipeline); + modrefl.PopulateDisassembly(mod.spirv); + + const std::map instructionLines = modrefl.instructionLines; + + auto instit = instructionLines.find(printfID); + if(instit != instructionLines.end()) + msg.disassemblyLine = (int32_t)instit->second; + else + msg.disassemblyLine = -1; + } + + if(stage == ShaderStage::Compute) + { + for(int x = 0; x < 3; x++) + { + uint32_t threadDimX = sh.refl->dispatchThreadsDimension[x]; + msg.location.compute.workgroup[x] = location[x] / threadDimX; + msg.location.compute.thread[x] = location[x] % threadDimX; + } + } + else if(stage == ShaderStage::Vertex) + { + msg.location.vertex.vertexIndex = location[0]; + if(!(drawcall->flags & DrawFlags::Indexed)) + { + // for non-indexed draws get back to 0-based index + msg.location.vertex.vertexIndex -= drawcall->vertexOffset; + } + msg.location.vertex.instance = location[1]; + msg.location.vertex.view = location[2]; + } + else + { + msg.location.pixel.x = location[0] >> 16U; + msg.location.pixel.y = location[0] & 0xffff; + msg.location.pixel.sample = location[1]; + msg.location.pixel.primitive = location[2]; + + RDCLOG("pixel %u, %u", msg.location.pixel.x, msg.location.pixel.y); + } + + msg.message = StringFormat::FmtArgs(fmt.format.c_str(), args); + + result.messages.push_back(msg); + } + else + { + RDCERR("Error parsing DebugPrintf buffer, unexpected stage %x from header %x", stage, + *printfBuf); + break; + } + } + } + if(descpool != VK_NULL_HANDLE) { // delete descriptors. Technically we don't have to free the descriptor sets, but our tracking @@ -976,3 +1864,45 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) // replay from the start as we may have corrupted state while fetching the above feedback. m_pDriver->ReplayLog(0, eventId, eReplay_Full); } + +#if ENABLED(ENABLE_UNIT_TESTS) + +#undef Always +#undef None + +#include "catch/catch.hpp" + +TEST_CASE("Test printf format string mangling", "[vulkan]") +{ + SECTION("Vector format expansion") + { + CHECK(PatchFormatString("hello %f normal %i string") == "hello %f normal %i string"); + CHECK(PatchFormatString("hello %% normal %2i string") == "hello %% normal %2i string"); + CHECK(PatchFormatString("hello %fv normal %iv string") == "hello %fv normal %iv string"); + CHECK(PatchFormatString("hello %02.3fv normal % 2.fiv string") == + "hello %02.3fv normal % 2.fiv string"); + CHECK(PatchFormatString("vector string: %v2f | %v3i") == "vector string: %f, %f | %i, %i, %i"); + CHECK(PatchFormatString("vector with precision: %04.3v4f !") == + "vector with precision: %04.3f, %04.3f, %04.3f, %04.3f !"); + CHECK(PatchFormatString("vector at end %v2f") == "vector at end %f, %f"); + CHECK(PatchFormatString("%v3f vector at start") == "%f, %f, %f vector at start"); + CHECK(PatchFormatString("%v2f") == "%f, %f"); + CHECK(PatchFormatString("%v2u") == "%u, %u"); + }; + + SECTION("64-bit format twiddling") + { + CHECK(PatchFormatString("hello %ul") == "hello %llu"); + CHECK(PatchFormatString("%ul hello") == "%llu hello"); + CHECK(PatchFormatString("%ul") == "%llu"); + CHECK(PatchFormatString("hello %04ul there") == "hello %04llu there"); + CHECK(PatchFormatString("hello %v2ul there") == "hello %llu, %llu there"); + + CHECK(PatchFormatString("hello %u l there") == "hello %u l there"); + + CHECK(PatchFormatString("%v2u") == "%u, %u"); + CHECK(PatchFormatString("%v2ul") == "%llu, %llu"); + }; +}; + +#endif // ENABLED(ENABLE_UNIT_TESTS) diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 0c5cb8733..575a4b082 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -1023,6 +1023,54 @@ void DoSerialise(SerialiserType &ser, StencilFace &el) SIZE_CHECK(28); } +template +void DoSerialise(SerialiserType &ser, ShaderComputeMessageLocation &el) +{ + SERIALISE_MEMBER(workgroup); + SERIALISE_MEMBER(thread); + + SIZE_CHECK(24); +} + +template +void DoSerialise(SerialiserType &ser, ShaderVertexMessageLocation &el) +{ + SERIALISE_MEMBER(vertexIndex); + SERIALISE_MEMBER(instance); + SERIALISE_MEMBER(view); + + SIZE_CHECK(12); +} + +template +void DoSerialise(SerialiserType &ser, ShaderPixelMessageLocation &el) +{ + SERIALISE_MEMBER(x); + SERIALISE_MEMBER(y); + SERIALISE_MEMBER(sample); + SERIALISE_MEMBER(primitive); + + SIZE_CHECK(16); +} + +template +void DoSerialise(SerialiserType &ser, ShaderMessageLocation &el) +{ + SERIALISE_MEMBER(compute); + + SIZE_CHECK(24); +} + +template +void DoSerialise(SerialiserType &ser, ShaderMessage &el) +{ + SERIALISE_MEMBER(stage); + SERIALISE_MEMBER(location); + SERIALISE_MEMBER(message); + + SIZE_CHECK(52); +} + #pragma endregion #pragma region D3D11 pipeline state @@ -2274,7 +2322,7 @@ void DoSerialise(SerialiserType &ser, VKPipe::State &el) SERIALISE_MEMBER(conditionalRendering); - SIZE_CHECK(1976); + SIZE_CHECK(2000); } #pragma endregion Vulkan pipeline state