From 4151720bdc18172c807597af72cc1883577ab95c Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 2 Mar 2026 15:05:57 +0000 Subject: [PATCH] Flatten user spec constants when debugging shaders. Closes #3803 --- .../driver/shaders/spirv/spirv_editor.cpp | 57 +++++++++++++++++++ renderdoc/driver/shaders/spirv/spirv_editor.h | 2 + renderdoc/driver/vulkan/vk_shaderdebug.cpp | 29 +++++----- 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/renderdoc/driver/shaders/spirv/spirv_editor.cpp b/renderdoc/driver/shaders/spirv/spirv_editor.cpp index 0b217a15d..6157b364e 100644 --- a/renderdoc/driver/shaders/spirv/spirv_editor.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_editor.cpp @@ -1094,6 +1094,63 @@ rdcpair Editor::AddBufferVariable(rdcarray &addedGlobals, Id varType return ret; } +void Editor::FlattenSpecConstants(const rdcarray &userSpec) +{ + // patch all bindings up trivially to account for the extra reservation + for(Iter it = Begin(Section::Annotations), end = End(Section::Annotations); it < end; ++it) + { + if(it.opcode() == Op::Decorate) + { + OpDecorate dec(it); + if(dec.decoration == Decoration::SpecId) + { + for(const SpecConstant &s : userSpec) + { + if(s.specID == dec.decoration.specId) + { + Iter target = GetID(dec.target); + + if(target.opcode() == Op::SpecConstantTrue || target.opcode() == Op::SpecConstantFalse) + { + RDCCOMPILE_ASSERT( + OpConstantTrue::FixedWordSize == OpSpecConstantTrue::FixedWordSize && + OpConstantTrue::FixedWordSize == OpSpecConstantFalse::FixedWordSize && + OpConstantTrue::FixedWordSize == OpConstantFalse::FixedWordSize, + "OpConstantTrue and False should be interchangeable and equal to Spec versions"); + + OpSpecConstantTrue orig(target); + + if(s.value != 0) + target = OpConstantTrue(orig.resultType, orig.result); + else + target = OpConstantFalse(orig.resultType, orig.result); + } + else if(target.opcode() == Op::SpecConstant) + { + rdcarray data; + data.assign(target.words() + 1, target.size() - 1); + + // first two IDs are type and result + size_t constSize = data.byteSize() - 2 * sizeof(Id); + + RDCASSERTEQUAL(s.dataSize, constSize); + + memcpy(&data[2], &s.value, RDCMIN(s.dataSize, constSize)); + + target = Operation(Op::Constant, data); + } + + break; + } + } + + // remove the spec id whether we found one or not. If we didn't find one, the default should be used + it.nopRemove(); + } + } + } +} + #define TYPETABLE(StructType, variable) \ template <> \ std::map &Editor::GetTable() \ diff --git a/renderdoc/driver/shaders/spirv/spirv_editor.h b/renderdoc/driver/shaders/spirv/spirv_editor.h index 96af50e1b..5edf02a2d 100644 --- a/renderdoc/driver/shaders/spirv/spirv_editor.h +++ b/renderdoc/driver/shaders/spirv/spirv_editor.h @@ -140,6 +140,8 @@ public: Id AddConstant(const Operation &op); void AddFunction(const OperationList &ops); + void FlattenSpecConstants(const rdcarray &userSpec); + Iter GetID(Id id); // the entry point has 'two' opcodes, the entrypoint declaration and the function. // This returns the first, GetID returns the second. diff --git a/renderdoc/driver/vulkan/vk_shaderdebug.cpp b/renderdoc/driver/vulkan/vk_shaderdebug.cpp index 203a7eebf..1273be3bc 100644 --- a/renderdoc/driver/vulkan/vk_shaderdebug.cpp +++ b/renderdoc/driver/vulkan/vk_shaderdebug.cpp @@ -3767,7 +3767,7 @@ static rdcspv::Id AddQuadSwizzleHelper(rdcspv::Editor &editor, uint32_t count) return func; } -static void CreateInputFetcher(rdcarray &spv, +static void CreateInputFetcher(rdcarray &spv, const rdcarray &userSpec, VulkanCreationInfo::ShaderModuleReflection &shadRefl, BufferStorageMode storageMode, bool usePrimitiveID, bool useSampleID, bool useViewIndex, SubgroupCapability subgroupCapability, @@ -3815,6 +3815,8 @@ static void CreateInputFetcher(rdcarray &spv, editor.Prepare(); editor.SetBufferStorageMode(storageMode); + editor.FlattenSpecConstants(userSpec); + // remove any OpSource { // remove any OpName that refers to deleted IDs - functions or results @@ -5471,7 +5473,7 @@ ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, u VkSpecializationInfo patchedSpecInfo = MakeSpecInfo(specData, specMaps); - auto patchCallback = [this, &shadRefl, &patchedSpecInfo, useViewIndex, subgroupCapability, + auto patchCallback = [this, &spec, &shadRefl, &patchedSpecInfo, useViewIndex, subgroupCapability, maxSubgroupSize](const AddedDescriptorData &patchedBufferdata, VkShaderStageFlagBits stage, const char *entryName, const rdcarray &origSpirv, @@ -5485,14 +5487,13 @@ ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, u if(!Vulkan_Debug_PSDebugDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_vsinput_before.spv", modSpirv); - CreateInputFetcher(modSpirv, shadRefl, m_StorageMode, false, false, useViewIndex, + CreateInputFetcher(modSpirv, spec, shadRefl, m_StorageMode, false, false, useViewIndex, subgroupCapability, maxSubgroupSize); if(!Vulkan_Debug_PSDebugDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_vsinput_after.spv", modSpirv); - // overwrite user's specialisation info, assuming that the old specialisation info is not - // relevant for codegen (the only thing it would be used for) + // overwrite user's specialisation info. We flattened the user's spec constants when patching the SPIR-V above. specInfo = &patchedSpecInfo; return true; @@ -6039,7 +6040,7 @@ ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_ VkSpecializationInfo patchedSpecInfo = MakeSpecInfo(specData, specMaps); - auto patchCallback = [this, &shadRefl, &patchedSpecInfo, usePrimitiveID, useSampleID, + auto patchCallback = [this, &spec, &shadRefl, &patchedSpecInfo, usePrimitiveID, useSampleID, useViewIndex, subgroupCapability, maxSubgroupSize]( const AddedDescriptorData &patchedBufferdata, VkShaderStageFlagBits stage, const char *entryName, const rdcarray &origSpirv, @@ -6052,14 +6053,13 @@ ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_ if(!Vulkan_Debug_PSDebugDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_psinput_before.spv", modSpirv); - CreateInputFetcher(modSpirv, shadRefl, m_StorageMode, usePrimitiveID, useSampleID, useViewIndex, - subgroupCapability, maxSubgroupSize); + CreateInputFetcher(modSpirv, spec, shadRefl, m_StorageMode, usePrimitiveID, useSampleID, + useViewIndex, subgroupCapability, maxSubgroupSize); if(!Vulkan_Debug_PSDebugDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_psinput_after.spv", modSpirv); - // overwrite user's specialisation info, assuming that the old specialisation info is not - // relevant for codegen (the only thing it would be used for) + // overwrite user's specialisation info. We flattened the user's spec constants when patching the SPIR-V above. specInfo = &patchedSpecInfo; return true; @@ -6575,7 +6575,7 @@ ShaderDebugTrace *VulkanReplay::DebugComputeCommon(ShaderStage stage, uint32_t e VkSpecializationInfo patchedSpecInfo = MakeSpecInfo(specData, specMaps); - auto patchCallback = [this, stageBit, &shadRefl, &patchedSpecInfo, subgroupCapability, + auto patchCallback = [this, stageBit, &spec, &shadRefl, &patchedSpecInfo, subgroupCapability, maxSubgroupSize](const AddedDescriptorData &patchedBufferdata, VkShaderStageFlagBits stage, const char *entryName, const rdcarray &origSpirv, @@ -6597,14 +6597,13 @@ ShaderDebugTrace *VulkanReplay::DebugComputeCommon(ShaderStage stage, uint32_t e if(!Vulkan_Debug_PSDebugDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/before_" + filename[idx], modSpirv); - CreateInputFetcher(modSpirv, shadRefl, m_StorageMode, false, false, false, subgroupCapability, - maxSubgroupSize); + CreateInputFetcher(modSpirv, spec, shadRefl, m_StorageMode, false, false, false, + subgroupCapability, maxSubgroupSize); if(!Vulkan_Debug_PSDebugDumpDirPath().empty()) FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/after_" + filename[idx], modSpirv); - // overwrite user's specialisation info, assuming that the old specialisation info is not - // relevant for codegen (the only thing it would be used for) + // overwrite user's specialisation info. We flattened the user's spec constants when patching the SPIR-V above. specInfo = &patchedSpecInfo; return true;