diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 0bdabcc8f..f417138ce 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -394,6 +394,7 @@ struct PipelineCache::ProgramCache { options.back_code = params.back_code; options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent; options.early_dump = options.dump_ir; + options.emit_debug_info = Config::GraphicsDebugDumpEnabled(); options.dump_label = label; options.input_info = stage_input; diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index f7029eebb..1fc2a6d37 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -887,7 +887,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", GetDumpLabel(options), StageName(ir.stage), ir.shader_hash); auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword, - options.maintenance8_enabled); + options.maintenance8_enabled, options.emit_debug_info); LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(ir.stage), ir.shader_hash, diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.h b/src/graphics/shader/recompiler/ShaderRecompiler.h index a8e3b77b5..2c006153c 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.h +++ b/src/graphics/shader/recompiler/ShaderRecompiler.h @@ -21,6 +21,7 @@ struct CompileOptions { uint64_t shader_hash = 0; bool dump_ir = true; bool early_dump = false; + bool emit_debug_info = false; bool maintenance8_enabled = false; const char* dump_label = nullptr; std::span user_data; diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp index 33051f9ab..29b7db89b 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp @@ -160,6 +160,14 @@ void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_ AppendInstruction(m_entry_points, spv::OpEntryPoint, operands); } +uint32_t Builder::AddString(const char* text) { + const auto id = AllocateId(); + std::vector operands = {id}; + AppendString(operands, text); + AppendInstruction(m_debug, spv::OpString, operands); + return id; +} + void Builder::AddName(uint32_t target, const char* name) { std::vector operands = {target}; AppendString(operands, name); diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h index 307716dea..55a2d01c9 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h @@ -66,6 +66,7 @@ public: AppendInstruction(m_execution_modes, spv::OpExecutionMode, entry_point, mode, operands...); } + uint32_t AddString(const char* text); void AddName(uint32_t target, const char* name); template void AddAnnotation(spv::Op opcode, const Args&... operands) { diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp index b25b91625..791333ef9 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace Libs::Graphics::ShaderRecompiler::Spirv { @@ -203,7 +204,8 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) { } // namespace std::vector EmitProgram(IR::Program& program, ShaderStageInputInfo input_info, - uint32_t push_data_start_dword, bool maintenance8_enabled) { + uint32_t push_data_start_dword, bool maintenance8_enabled, + bool emit_debug_info) { using namespace Emitter; if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex && @@ -219,6 +221,12 @@ std::vector EmitProgram(IR::Program& program, ShaderStageInputInfo inp IR::ValidateProgram(program, true); program.bindings = {.push_data_start_dword = push_data_start_dword}; EmitterState state(program, input_info, maintenance8_enabled); + if (emit_debug_info) { + char name[64]; + std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one", + program.shader_hash); + state.debug_source = state.builder.AddString(name); + } const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info); state.lane_count = workgroup != nullptr && program.wave_size == 64u && workgroup->host_subgroup_size == 32u diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h index 94f3c7453..7c3393840 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h @@ -12,7 +12,8 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv { // Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently. std::vector EmitProgram(IR::Program& program, ShaderStageInputInfo input_info, uint32_t push_data_start_dword = 0, - bool maintenance8_enabled = false); + bool maintenance8_enabled = false, + bool emit_debug_info = false); } // namespace Libs::Graphics::ShaderRecompiler::Spirv diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index a5dfbc67f..96daaae15 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -89,6 +89,7 @@ struct EmitterState { IR::Program& program; ShaderStageInputInfo input_info; bool maintenance8_enabled; + uint32_t debug_source = 0; uint32_t void_type = 0; uint32_t bool_type = 0; uint32_t u32_type = 0; @@ -182,6 +183,11 @@ uint32_t TypeU32ElementPointer(EmitterState& state, spv::StorageClass storage_cl inline void EmitLabel(EmitterState& state, uint32_t label) { state.current_label = label; state.builder.AddFunction(spv::OpLabel, label); + if (state.debug_source != 0 && state.current_block != nullptr) { + // Source lines encode the containing native block's byte offset plus one. + state.builder.AddFunction(spv::OpLine, state.debug_source, + state.current_block->start_pc + 1u, 0u); + } } uint32_t TypeId(EmitterState& state, IR::Type type); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp index c44764566..c49d4f286 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp @@ -348,6 +348,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState const auto initial_parent = state.current_label; state.builder.AddFunction(spv::OpBranch, dispatcher.header_label); + state.current_block = nullptr; EmitLabel(state, dispatcher.header_label); const auto pc = state.builder.AllocateId(); const auto next_pc = state.builder.AllocateId(); @@ -383,6 +384,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState next_pc_words.push_back(state.current_label); state.builder.AddFunction(spv::OpBranch, dispatcher.after_switch_label); } + state.current_block = nullptr; EmitLabel(state, dispatcher.after_switch_label); state.builder.AddFunction(next_pc_words); state.builder.AddFunction(spv::OpBranch, dispatcher.continue_label); @@ -747,6 +749,7 @@ void EmitProgram(EmitterState& state) { EmitStructuredFunction(ctx); } state.builder.AddFunction(spv::OpFunctionEnd); + state.current_block = nullptr; if (state.program.stage == ShaderType::Mesh) { EmitMeshEntryPoint(state); } diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 321e4da39..fdcfebbb0 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -15711,6 +15711,46 @@ void TestRepeatedExportsHaveOneInterface() { "repeated exports duplicated Position or its clipping-error plane"); } +void CheckSpirvBlockSourceMapping(TestCompileResult &result) { + using namespace ShaderRecompiler; + const auto options = MakeCompileOptions(ShaderType::Compute); + Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpString) == 0 && + SpirvInstructionOpcodeCount(result.spirv, spv::OpLine) == 0, + "default emission unexpectedly contains source mapping"); + const auto mapped = Spirv::EmitProgram(result.program, options.input_info, 0, + false, true); + CheckSpirvBinaryValidates(mapped); + uint32_t source = 0; + uint32_t label = 0; + size_t lines = 0; + uint32_t previous = spv::OpNop; + for (size_t at = 5; at < mapped.size();) { + const auto op = mapped[at] & spv::OpCodeMask; + if (op == spv::OpLabel) label = mapped[at + 1]; + if (op == spv::OpString) { + source = mapped[at + 1]; + Check(std::string(reinterpret_cast(&mapped[at + 2])) + .find("-block-byte-pc-plus-one") != std::string::npos, + "debug source filename does not identify block offset encoding"); + } else if (op == spv::OpLine) { + Check(previous == spv::OpLabel && mapped[at + 1] == source && + mapped[at + 3] == 0 && + std::ranges::any_of(result.program.blocks, [&](const auto *block) { + return block->Definition() == label && + mapped[at + 2] == block->start_pc + 1u; + }), + "debug source line does not identify a native block after its label"); + ++lines; + } + previous = op; + at += mapped[at] >> spv::WordCountShift; + } + Check(source != 0 && lines >= result.program.blocks.size(), + "debug emission omitted native block source mapping"); + Check(Spirv::EmitProgram(result.program, options.input_info) == result.spirv, + "debug re-emission changed the default binary"); +} + void TestNewShaderRecompilerSpirvSizeBaselines() { const auto compile = [](const char *name, std::span shader, const SpirvMetrics &budget, @@ -15755,7 +15795,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() { EncodeSopp(0x02, 0xfffcu), // continue/backedge EncodeSopp(0x01), }; - const auto structured_result = compile("structured-phi", structured_phi, + auto structured_result = compile("structured-phi", structured_phi, {.words = 174, .instructions = 49, .phis = 1, @@ -15772,6 +15812,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() { structured_metrics.loads == 0u && structured_metrics.stores == 0u, "structured Phi retained a spill variable or edge memory operation"); CheckSpirvPhiParents(structured_result.spirv); + CheckSpirvBlockSourceMapping(structured_result); const auto structured_repeat = compile("structured-phi-repeat", structured_phi, {.words = 174, @@ -15942,7 +15983,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() { EncodeSopp(0x02, 0xfffeu), // B -> C EncodeSopp(0x01), }; - const auto dispatcher_result = compile("dispatcher", dispatcher, + auto dispatcher_result = compile("dispatcher", dispatcher, {.words = 255, .instructions = 70, .variables = 3, @@ -15963,6 +16004,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() { SpirvInstructionOpcodeCount(dispatcher_result.spirv, 251u) == 1u, "dispatcher size fixture lost its two control Phis or switch"); CheckSpirvPhiParents(dispatcher_result.spirv); + CheckSpirvBlockSourceMapping(dispatcher_result); } #include "ShaderRayTracingTests.inc"