c++vulkanhlslspir-vdxc

Why is my HLSL shader unable to compile to SPIRV?


In an update to my last post about compiling HLSL files directly through Visual Studio, I finally gave up and decided to use the offline compilation method instead. I was able to compile both of my HLSL shaders into .spv files, but now I am getting the following validation error:

Validation Error: [ VUID-VkShaderModuleCreateInfo-pCode-01379] 
Object 0: handle = 0x1c63c99aa70, type = VK_OBJECT_TYPE_DEVICE; 
| MessageID = 0x2a1bf17f |
SPIR-V module not valid: Invalid SPIR-V magic number. The Vulkan spec states: 
If pCode is a pointer to GLSL code, it must be valid GLSL code written to the GL_KHR_vulkan_glsl GLSL extension specification
(https://vulkan.lunarg.com/doc/view/1.3.250.1/windows/1.3-extensions/vkspec.html#VUID-VkShaderModuleCreateInfo-pCode-01379)

Unfortunately, the .spv file was compiled from HLSL code, so I'm not sure what the problem could be here.

These are the commands that I used when compiling the .hlsl files:

dxc.exe -spirv -T vs_6_4 -E main .\vertex_vert.hlsl -Fo .\vertex_vert.spv
dxc.exe -spirv -T ps_6_4 -E main .\pixel_frag.hlsl -Fo .\pixel_frag.spv

This is the contents of the vertex shader:

struct VSInput
{
    [[vk::location(0)]] float4 Position : POSITION0;
    [[vk::location(1)]] float4 Color : COLOR0;
    [[vk::location(2)]] float2 UVcoord : TEXCOORD0;
};
struct VSOutput
{
    float4 Position : SV_POSITION;
    [[vk::location(0)]] float4 Color : COLOR0;
    [[vk::location(1)]] float2 UVcoord : TEXCOORD0;
};
struct Camera
{
    float4x4 view;
    float4x4 proj;
    float3 position;
};
struct UBO
{
    float dt;
    float4x4 model;
    Camera cam;
};
/* neither of the below options changed the outcome */
//cbuffer ubo : register(b0, space0);
//{ UBO ubo; }
ConstantBuffer <UBO> ubo : register(b0, space0);

VSOutput main(VSInput input, uint VertexIndex : SV_VertexID)
{
    VSOutput output = (VSOutput) 0;
    
    float4x4 camMatrix = mul(ubo.cam.proj, ubo.cam.view);
    output.Position = mul(camMatrix, mul(ubo.model, input.Position));
    
    output.Color = input.Color;
    output.UVcoord = input.UVcoord;
    
    return output;
}

and this is the pixel/fragment shader HLSL code:

struct PSInput
{
    float4 Position : SV_Position;
    [[vk::location(0)]] float4 Color : COLOR0;
    [[vk::location(1)]] float2 UVcoord : TEXCOORD0;
};

// Descriptor loading in HLSL
//Texture2D tex2D : register(t0); // TODO: Figure out texture loading and sampling in HLSL
//sampler sample2D : register(s0, space1); // texture sampler at (binding = 0, set = 1)

struct PSOutput
{
    [[vk::location(0)]] float4 Color : COLOR0;
};

PSOutput main(PSInput input) : SV_TARGET
{
    PSOutput output = (PSOutput) 0;
    //output.Color = tex2D.Sample(sample2D, input.UVcoord); //TODO: figure out the right syntax for this
    output.Color = input.Color;
    return output;
}

and finally my shader struct:

struct Shader {
    Shader(const std::string& filename, VkShaderStageFlagBits shaderStage) {
        try {
            auto shaderCode = readFile(".\\shaders\\" + filename);
            createShaderModule(shaderCode, filename);
        }
        catch (const std::exception& e) {
                std::cerr << e.what() << std::endl;
            }
        stageInfo.stage = shaderStage;
        stageInfo.module = shaderModule;
        stageInfo.pName = "main";
    }
    ~Shader() {
        vkDestroyShaderModule(GPU::device, shaderModule, nullptr);
    }
public:
    VkShaderModule shaderModule;
    VkPipelineShaderStageCreateInfo stageInfo
    { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO };
private:
    void createShaderModule(const std::vector<char>& code, std::string filename) {
        VkShaderModuleCreateInfo createInfo
        { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
        createInfo.codeSize = code.size();// also tried code.size()*sizeof(uint32_t) and code.size()*4
        createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());

        if (vkCreateShaderModule(GPU::device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
            throw std::runtime_error("failed to create shader module from " + filename + "!");
        }
    }
    // File Reader
    static std::vector<char> readFile(const std::string& filename) {
        std::ifstream file(filename, std::ios::ate | std::ios::binary);

        if (!file.is_open()) {
            throw std::runtime_error(std::format("failed to open {}!", filename));
        }

        size_t fileSize = (size_t)file.tellg();
        std::vector<char> buffer(fileSize);

        file.seekg(0);
        file.read(buffer.data(), fileSize);

        file.close();

        return buffer;
    }
};

The shaders are created with:

Shader vertShader("vertex_vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
Shader fragShader("pixel_frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);

I know my pixel/fragment shader will have some problems because I have yet to do the texture loading on the C++ side of things, but the vertex shader still produces the validation error even without the pixel shader. How can I fix this?

Edit: Changed the section containing the shader module function to show all of the code behind shader loading, most of which has been sourced from the Vulkan-Tutorial guide.

Edit 2: I'm not sure which variable to look at after running the debugger, but here are a few that I believe are of interest:

When run with code.size() * uint32_t, the value of createInfo.pCode is 0x000001c2da20ff60 or 119734787 and the value of code is

\x3\x2#\a\0\0\x1\0\0\0\xe\0*\0\0\0\0\0\0\0\x11\0\x2\0\x1\0\0\0\xe\0\x3\0\0\0\0\0\x1\0\0\0\xf\0\v\0\0\0\0\0\x1\0\0\0main\0\0\0\0\x2\0\0\0\x3\0\0\0\x4\0\0\0\x5\0\0\0\x6\0\0\0\a\0\0\0\x3\0\x3\0\x5\0\0\0€\x2\0\0\x5\0\x5\0\b\0\0\0type.ubo\0\0\0\0\x6\0\x4\0\b\0\0\0\0\0\0\0ubo\0\x5\0\x3\0\t\0\0\0UBO\0\x6\0\x4\0\t\0\0\0\0\0\0\0dt\0\0\x6\0\x5\0\t\0\0\0\x1\0\0\0model\0\0\0\x6\0\x4\0\t\0\0\0\x2\0\0\0cam\0\x5\0\x4\0...

When run with code.size(), the value of createInfo.pCode is 0x00000196624fff60 or 119734787 and the value of code is

\x3\x2#\a\0\0\x1\0\0\0\xe\0*\0\0\0\0\0\0\0\x11\0\x2\0\x1\0\0\0\xe\0\x3\0\0\0\0\0\x1\0\0\0\xf\0\v\0\0\0\0\0\x1\0\0\0main\0\0\0\0\x2\0\0\0\x3\0\0\0\x4\0\0\0\x5\0\0\0\x6\0\0\0\a\0\0\0\x3\0\x3\0\x5\0\0\0€\x2\0\0\x5\0\x5\0\b\0\0\0type.ubo\0\0\0\0\x6\0\x4\0\b\0\0\0\0\0\0\0ubo\0\x5\0\x3\0\t\0\0\0UBO\0\x6\0\x4\0\t\0\0\0\0\0\0\0dt\0\0\x6\0\x5\0\t\0\0\0\x1\0\0\0model\0\0\0\x6\0\x4\0\t\0\0\0\x2\0\0\0cam\0\x5\0\x4\0...

Solution

  • The actual problem here is that your Visual Studio project is using the "HLSL Compiler" settings to compile the shaders. The *.spv files in the GitHub are OK (I was wrong about that in the long comment thread). When you build the solution, Visual Studio is invoking the MSFT compiler which probably doesn't know about spirv. Building the solution causes the *.spv files to get overwritten with incorrect contents.

    I think the best way to fix this is to not use the "HLSL Compiler" build step in your project properties since this isn't a DX app. Instead use a Custom Build Step to invoke the dxc compiler from the Vulkan SDK.