SPIRV-Reflect: reading descriptor bindings back out of SPIR-V at runtime
SPIRV-Reflect is a lightweight library that provides a C/C++ reflection API for SPIR-V shader bytecode in Vulkan applications.
At a glance
- What is it?
- SPIRV-Reflect is a small C and C++ library that parses SPIR-V shader bytecode and hands back the descriptor bindings, push constant sizes and interface variables, so a Vulkan application can build pipeline layouts from its own shaders. It suits engine and tooling developers who need the reflection data that a shader compiler would otherwise discard.
- Who is it for?
- SPIRV-Reflect is a good fit for a Vulkan application that generates its descriptor set and pipeline layouts at run time from compiled shaders, because it gives you the binding numbers and push constant sizes without a build-time generation step, and for a tool author who needs to inspect a binary SPIR-V module directly.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 18 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem it solves is that a compiler throws layout information away
The library exists because a shader compiler is a lossy step for the application that will use the shader. When a GLSL or HLSL shader is compiled to SPIR-V, the descriptor bindings, the push constant block sizes and the input and output variable layout all end up in the bytecode, but they are not in any form the application can use to build a Vulkan pipeline without help. SPIRV-Reflect reads that information back out. The description in the readme calls it a lightweight library providing a C and C++ reflection API for SPIR-V shader bytecode in Vulkan applications, and it says it has been tested on Linux and Windows. The point of the library is that the alternative approaches all require a separate step. A build-time generator can emit a header of constants, which is fast and reviewable but ties the shader pipeline to the C++ build. A reflection call at load time is flexible and needs no extra tooling, at the cost of doing the work when the application starts. The feature list also includes remapping descriptor bindings at run time and updating the source SPIR-V bytecode accordingly, which is the capability that pushes this beyond a build-time convenience.
What it can extract, and what it is explicitly not
The feature list is short and every item maps to a Vulkan structure you would otherwise have to hardcode. It can extract descriptor bindings from SPIR-V bytecode, to assist in generating Vulkan descriptor set and pipeline layouts. It can extract the push constant block size, again for pipeline layout generation. It can extract the full layout data for uniform buffer and push constant blocks, to assist in application updates of those structures. It can extract input and output variables, including the semantic decorations for HLSL shaders, to assist in validation of pipeline input and output settings. And it can log all reflection data as human-readable text, which is the feature that turns it into a debugging tool as well as a runtime component. The non-features section is one sentence and it matters. SPIRV-Reflect is not a validator and assumes valid SPIR-V is being passed in, and you are asked to ensure that with the validator before using the tool. That is a reasonable division of labour, since validating SPIR-V properly is a large piece of work, but it means a validation step has to exist somewhere in your pipeline or a malformed module will produce garbage output rather than an error.
The C API, and why the enumerate pattern is the shape to expect
The core API is designed to be familiar to Vulkan developers, and the example walks through the lifecycle. You create a shader module from the code and its byte count, and you check the result code. Then you enumerate a category of variable, and the interesting detail is the two-call pattern:
// Enumerate and extract shader's input variables
uint32_t var_count = 0;
result = spvReflectEnumerateInputVariables(&module, &var_count, NULL);
assert(result == SPV_REFLECT_RESULT_SUCCESS);
SpvReflectInterfaceVariable** input_vars =
(SpvReflectInterfaceVariable**)malloc(var_count * sizeof(SpvReflectInterfaceVariable*));
result = spvReflectEnumerateInputVariables(&module, &var_count, input_vars);
assert(result == SPV_REFLECT_RESULT_SUCCESS);The first call passes a null pointer for the array and gets the count back, and the second call fills the array you allocated with that count. Every category follows the same pattern, and the readme says as much, noting that output variables, descriptor bindings, descriptor sets and push constants can be enumerated and extracted using a similar mechanism. The module is then destroyed with the destroy call when it is no longer required. Every call returns a result code that the example asserts on, which is the pattern to follow. A C++ wrapper is also provided, and the repository carries both a C and a C++ implementation file next to the header.
Dropping it into a project is two files and one define
The integration story is deliberately unremarkable, which is the point of a library that wants to be used. The only external dependency is the Vulkan SDK, and to integrate SPIRV-Reflect into a project you add the header and the source file to the build and include the header from the necessary source files. There is one option worth knowing about. If the project wants to use its own SPIRV-Header path, it can set the macro that points at the system copy, and the readme gives the CMake form:
target_compile_definitions(project_name PUBLIC SPIRV_REFLECT_USE_SYSTEM_SPIRV_H)That matters for reproducible builds and for distributions that already ship the header, because otherwise the build vendors its own copy. The repository layout is correspondingly small, with the header and the two implementation files at the root, an include directory, a common directory, an examples directory, a tests directory, a third_party directory for the test framework, a main file, a CMakeLists.txt, a clang format configuration, a security policy and a code of conduct document. The change log in the readme is worth skimming for a sense of the project's direction, since it records the removal of Bazel support in July 2023 and the rename of the master branch to main in May 2023, and the branch rename is the kind of thing that breaks a fork silently.
The examples, the explorer and the self-test suite
There are three sample programs, and building them is opt-in with a CMake flag. The descriptors sample demonstrates the retrieval of descriptor bindings, including populating the descriptor set layout create info structures from the reflection data. The HLSL resource types sample shows how various HLSL resource types are represented in SPIR-V, which is the one to read if you are porting shaders written in HLSL and the binding numbers do not match what you expect. The input and output variables sample demonstrates retrieving interface variables, including populating the pipeline vertex input state create info structures. The examples directory also contains an argument parser, a common header and source, a sample HLSL file, a sample SPIR-V binary and a script that builds the header for the sample. Building them requires CMake to generate the project files, and the readme notes that you can point the build at a particular SDK directory, giving a Linux example that sets the Vulkan SDK environment variable to a versioned path before invoking CMake with the Ninja generator. There is a separate explorer program whose purpose is to let a developer set a breakpoint in their debugger of choice and walk through the contents of the shader module object, built as a debug configuration. The self-test suite is separate again, it uses googletest, it is optional and generally of interest only to developers modifying the library, and running it means initialising and updating the git submodules, enabling the tests option in CMake and building and running the test project.
Editorial conclusion
SPIRV-Reflect is a good fit for a Vulkan application that generates its descriptor set and pipeline layouts at run time from compiled shaders, because it gives you the binding numbers and push constant sizes without a build-time generation step, and for a tool author who needs to inspect a binary SPIR-V module directly. It is the wrong tool if you have a build-time shader compilation pipeline, because a generated header is easier to trust and easier to review than a reflection at load time, and it is the wrong tool if you have malformed shaders, because the library is explicitly not a validator and assumes valid SPIR-V is passed in, so you should run the validator first. It is also the wrong tool if you need a self-contained build, since the one external dependency is the Vulkan SDK. Before adopting it, read the non-features note and decide where the validation step goes, and read the C++ header yourself rather than relying on the wrapper. The project is Apache-2.0 licensed, the last push was on 2026-10-05, and the repository publishes no GitHub releases.
Frequently asked questions
What does SPIRV-Reflect do?
It is a lightweight C and C++ library that provides a reflection API for SPIR-V shader bytecode in Vulkan applications. It extracts descriptor bindings, push constant block sizes, uniform and push constant layout data and input and output variables, and it can remap descriptor bindings at run time while updating the source bytecode.
Is SPIRV-Reflect a validator?
No, and the readme says so directly in its non-features section. It assumes valid SPIR-V is being passed in, and you should ensure that with spirv-val before using the tool, since a malformed module would otherwise produce wrong output rather than an error.
How do I add SPIRV-Reflect to my project?
Add spirv_reflect.h and spirv_reflect.c to the build and include the header from the necessary source files. The only external dependency is the Vulkan SDK, and if the project wants to use its own SPIRV-Header path it can define the macro that selects the system header, with a CMake example given in the readme.
What does the SPIRV-Reflect enumeration API look like?
You create a shader module from the code and its byte count, then enumerate a category of variables with two calls, first passing a null array pointer to get the count and then filling an array you allocated with that count. Every call returns a result code, and the module is destroyed when no longer required. A C++ wrapper is provided as well.
How do you run the SPIRV-Reflect self-tests?
The self-test suite is optional and is mainly of interest to developers modifying the library. It uses googletest, so you initialise and update the git submodules, enable the tests option in CMake, then build and run the test project. The examples are built separately by turning on the examples option in CMake.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/khronosgroup-spirv-reflect)Was this useful?