How to create single-file SPIR-V shaders for Vulkan

I figured out all the steps to create a single SPIR-V file and load it up in Vulkan. This includes some simple parsing code so you can tell what components are present in the file, and retrieve the entrypoint names.

Why though? You are just forcing driver to compile bunch of unrelated\dead code for each stage.

If I wanted single file, I would just zip it or something.

1 Like

Because there is now a single standard file people can pass around. It makes our workflows neater and our tools less prone to errors. I’ve always hated the concept of loading a shader from several different files, when every other asset has a paradigm of one object = one file. And we have GLTF now, so I am trying to get away from custom file formats and find suitable standards that already exist.

Another possible reason for a single SPIR-V shader per program: when the shader source contains a fair amount of shared shader code (e.g. functions) which can be called at different rates (vertex, fragment, etc.), switchable by specialization constants. That is, the app uses specialization constants to set which “shader permutation” is desired, letting SPIR-V specialization do dead code elimination from that. For this use case, it’d be useful to have this all in one SPIR-V file (all stage mains + shared code), with the shared shader code actually shared between the stages pre-specialization.

I’m not sure whether the above method can make use of that sharing though.

The alternative is you handle your “shader permutation breakout” up at the GLSL level, generating, specializing, and linking a bunch of simple SPIR-V programs and stages from that. However, in this case you have the GLSL->SPIR-V compile overhead in the mix N times, once per shader permutation+stage.

1 Like

I’m also using fewer shaders nowadays with more branching logic, so the total number of shaders in our program is going down, a lot. So concerns about efficiency of resources in shaders is pretty trivial in our usage.

Plus, you don’t have to worry about linking errors at runtime because these get caught in the file creation process.

Hi, I also want to use a single spirv file in my app. But somehow it only works with vertex+fragment shader. If I add tesc and tese shader, I get a validation error:

Invalid Pipeline CreateInfo State: Fragment shader exceeds VkPhysicalDeviceLimits::maxFragmentInputComponents.

The same shader works as seperate modules. Any ideas?

I have not tried adding other shader stages but it looks like maybe you are adding one of the stages as an extra fragment stage.

I tried it with passing different entry points for each stage “-e” and the stage “-S” to glslangValidator. I link with “–target-env vulkan1.1spv1.4”. But no success.
My pipelineCreateInfo.pStages looks fine, no stage is missing and there is no additional fragment stage. Does the order of the stages matter here?

For shaders with more uniforms I get DevideLimit exceed for other stages too.

ERROR: [Validation] Code 0 :  [ UNASSIGNED-CoreValidation-Shader-ExceedDeviceLimit ] Object: VK_NULL_HANDLE (Type = 19) | Invalid Pipeline CreateInfo State: Fragment shader exceeds VkPhysicalDeviceLimits::maxFragmentInputComponents of 128 components by 1037 components
ERROR: [Validation] Code 0 :  [ UNASSIGNED-CoreValidation-Shader-ExceedDeviceLimit ] Object: VK_NULL_HANDLE (Type = 19) | Invalid Pipeline CreateInfo State: Tessellation evaluation shader exceeds VkPhysicalDeviceLimits::maxTessellationEvaluationOutputComponents of 128 components by 35 components
ERROR: [Validation] Code 0 :  [ UNASSIGNED-CoreValidation-Shader-ExceedDeviceLimit ] Object: VK_NULL_HANDLE (Type = 19) | Invalid Pipeline CreateInfo State: Vertex shader exceeds VkPhysicalDeviceLimits::maxVertexOutputComponents of 128 components by 35 components

I used the file extensions .tesc and .tese for the control and evaluation shader files and it worked for me.

Well, I got a bit further and I get a crash on a GEForce 1070M on the pipeline creation, with no errors or explanation why. The crash occurs in the driver, on the call to CreatePipeline().

I have a working tess shader now and I tried to revert back to the single-file design, but I get a max physical device limits like the one above, even though it works fine as separate shader modules. So I am concluding that there are additional issues or limitations that make single-file SPV shaders impractical.