bkaradzic / bgfx

Cross-platform, graphics API agnostic, "Bring Your Own Engine/Framework" style rendering library.
https://bkaradzic.github.io/bgfx/overview.html
BSD 2-Clause "Simplified" License
14.94k stars 1.94k forks source link

Feature Request: Source-level debugging of SPIR-V #2971

Open pourtheworld opened 1 year ago

pourtheworld commented 1 year ago

Description

The latest version of renderdoc supports source-level debugging of SPIR-V, which relies on glslangValidator to generate debug information from GLSL or HLSL.

In fact,glslangValidator generates information through the following command line statements:

  1. \path\to\glslangValidator.exe -e main -gVS -D -o
  2. \path\to\glslangValidator.exe -e main -gVS -V -o

The -gVS argument corresponds to the three sub-options in glslang::SpvOptions:

  1. generateDebugInfo
  2. emitNonSemanticShaderDebugInfo
  3. emitNonSemanticShaderDebugSource

So I tried to set these boolean variables to true in the compile function of the shaderc_spirv.cpp file and pass them to the glslang::GlslangToSpv.But when the function is run to Builder::addDebugScopeAndLine of SpvBuilder.cpp file it will get an error,and the error is that the stack corresponding to currentDebugScopeId is empty, but attempts to read its back().

image

To Reproduce

  1. Add EShMsgDebugInfo to the messages of Line 456 of Shaderc_spirv.cpp.
  2. Set DebugInfo to the shader of of Line 458 of Shaderc_spirv.cpp.
  3. Set generateDebugInfoemitNonSemanticShaderDebugInfoemitNonSemanticShaderDebugSource of the options to true in Line 700 of Shaderc_spirv.cpp.

Expected behavior

image

pourtheworld commented 1 year ago

Renderdoc1.24 explains that after adding the -gVS option to glslangVailaditor, all debug information will be embedded in the SPIR-V shader: https://renderdoc.org/docs/how/how_debug_shader.html#including-debug-info-in-shaders

In fact, The changes in the -gVS option in StandAlone.cpp correspond to the following changes in shaderc_spirv.cpp:

static bool compile(const Options& _options, uint32_t _version, const std::string& _code, bx::WriterI* _writer, bool _firstPass)
    {
 ...
        EShMessages messages = EShMessages(0
            | EShMsgDefault
            | EShMsgReadHlsl
            | EShMsgVulkanRules
            | EShMsgSpvRules
            | EShMsgDebugInfo
            );
        shader->setDebugInfo(true);
        shader->setEntryPoint("main");
        shader->setSourceEntryPoint("main");
...
        glslang::SpvOptions options;
        options.disableOptimizer = false;
        options.generateDebugInfo = true;
        options.emitNonSemanticShaderDebugInfo = true;
        options.emitNonSemanticShaderDebugSource = true;
        glslang::GlslangToSpv(*intermediate, spirv, &options);
...

However, in the addDebugScopeAndLine function of glslang's SpvBuilder.cpp, the currentDebugScopeId is often null, resulting in an error:

void Builder::addDebugScopeAndLine(Id fileName, int lineNum, int column)
{
    if (currentDebugScopeId.top() != lastDebugScopeId) {
         ...
    }
...
}

After debugging, we found that this was due to problems traversing the Initializer(when its type is EOpSequence) in the makeGlobalInitializers function after passing Intermediate from shaderc_spirv.cpp to GlslangToSpv.cpp:

void TGlslangToSpvTraverser::makeGlobalInitializers(const glslang::TIntermSequence& initializers)
{
    builder.setBuildPoint(shaderEntry->getLastBlock());
    for (int i = 0; i < (int)initializers.size(); ++i) {
        glslang::TIntermAggregate* initializer = initializers[i]->getAsAggregate();
        if (initializer && initializer->getOp() != glslang::EOpFunction && initializer->getOp() !=
            glslang::EOpLinkerObjects) {

            // We're on a top-level node that's not a function.  Treat as an initializer, whose
            // code goes into the beginning of the entry point.
            initializer->traverse(this);
        }
    }
}

But if you remove the debug information from the shaderc_spirv.cpp, .sc shader can be properly compiled into binary with the source shader's code embedded in the Renderdoc for debugging. However, the debugging information required during the debugging process was not properly embedded in the binary file, and the Renderdoc still did not perform source-level debugging correctly:

static bool compile(const Options& _options, uint32_t _version, const std::string& _code, bx::WriterI* _writer, bool _firstPass)
    {
 ...
        EShMessages messages = EShMessages(0
            | EShMsgDefault
            | EShMsgReadHlsl
            | EShMsgVulkanRules
            | EShMsgSpvRules
            | EShMsgDebugInfo
            );
        //shader->setDebugInfo(true);
        shader->setEntryPoint("main");
        shader->setSourceEntryPoint("main");
...
        glslang::SpvOptions options;
        options.disableOptimizer = false;
        options.generateDebugInfo = true;
        //options.emitNonSemanticShaderDebugInfo = true;
        options.emitNonSemanticShaderDebugSource = true;
        glslang::GlslangToSpv(*intermediate, spirv, &options);
...

image

So my question is whether I need to modify the intermediate parse process in shaderc_spirv so that the correct debug informaion can be embedded in SPIR-V in GlslangToSpirv

bkaradzic commented 1 year ago

cc @pezcode