SPIRV-Cross

repository·main·Indexed 25 days ago

https://github.com/khronosgroup/spirv-cross

A tool for parsing and converting SPIR-V shader code into high-level shader languages including GLSL, MSL, and HLSL, or into a JSON reflection format. It provides a Reflection API to simplify Vulkan pipeline layout creation and modify OpDecorations, supporting vertex, fragment, tessellation, geometry, and compute shaders. The project offers both a C++ API and a stable C89-compatible C API wrapper, as well as a CLI tool for basic cross-compilation tasks.

Tokens
7K
Snippets
6
Records
25
Agent score
32%

What's inside SPIRV-Cross

  1. Overview of SPIRV-Cross features

    main

    SPIRV-Cross is a tool for parsing and converting SPIR-V to various shader languages and formats. It aims to produce clean, human-readable output that looks like hand-written code rather than assembly-like IR.

    Key capabilities include:

    • Converting SPIR-V to GLSL, Metal Shading Language (MSL), or HLSL.
    • Converting SPIR-V to a JSON reflection format.
    • Providing a Reflection API to simplify Vulkan pipeline layout creation and to modify/tweak OpDecorations.
    • Supporting vertex, fragment, tessellation, geometry, and compute shaders.
  2. Handle HLSL to GLSL cross-compilation considerations

    main

    When cross-compiling from HLSL to GLSL, several manual steps may be required to ensure compatibility:

    1. Entry Points

    Ensure the entry point is correctly specified when creating SPIR-V (e.g., using glslangValidator -e MyFancyEntryPoint). Incorrect entry points can lead to errors like Cannot end a function before ending the current block.

    2. Vertex/Fragment Interface Linking

    HLSL uses semantics for linking. In SPIR-V, this often results in generated struct names. If the struct type name differs between vertex and fragment stages, linking may fail. Use the reflection interface to force a consistent name:

    compiler.set_name(varying_resource.base_type_id, "VertexFragmentLinkage");

    To rename variables based on location via CLI, use: --rename-interface-variable <in|out> <location> <new_variable_name>.

    3. Combined Image Samplers

    HLSL/Vulkan treat samplers and textures as separate, but many GLSL backends do not. If targeting desktop GL/GLES, you must call Compiler::build_combined_image_samplers() before Compiler::compile() to avoid exceptions.

    4. Descriptor Sets

    Descriptor sets are unique to Vulkan. For backends that do not support them (pre-HLSL 5.1 / GLSL), you must remap them to a flat binding scheme (set 0) using Compiler::set_decoration(id, spv::DecorationDescriptorSet).

    5. Clip-space Conventions

    To convert depth ranges (e.g., Vulkan [0, w] to OpenGL [-w, w]), enable CompilerGLSL::Options.vertex.fixup_clipspace. It is generally recommended to modify projection matrices instead.

  3. Use the SPIRV-Cross C++ API

    main

    The C++ API is the primary interface for performing shader reflection and cross-compilation.

    Important Considerations:

    • ABI Stability: This API is not guaranteed to be ABI-stable. It is highly recommended to link against this API statically.
    • Stability: While the API is generally stable, it can change over time. For higher stability (especially when building as a shared library), use the C API.
    #include "spirv_glsl.hpp"
    #include <vector>
    #include <utility>
    
    extern std::vector<uint32_t> load_spirv_file();
    
    int main()
    {
    	// Read SPIR-V from disk or similar.
    	std::vector<uint32_t> spirv_binary = load_spirv_file();
    
    	spirv_cross::CompilerGLSL glsl(std::move(spirv_binary));
    
    	// The SPIR-V is now parsed, and we can perform reflection on it.
    	spirv_cross::ShaderResources resources = glsl.get_shader_resources();
    
    	// Get all sampled images in the shader.
    	for (auto &resource : resources.sampled_images)
    	{
    		unsigned set = glsl.get_decoration(resource.id, spv::DecorationDescriptorSet);
    		unsigned binding = glsl.get_decoration(resource.id, spv::DecorationBinding);
    		printf("Image %s at set = %u, binding = %u\n", resource.name.c_str(), set, binding);
    
    		// Modify the decoration to prepare it for GLSL.
    		glsl.unset_decoration(resource.id, spv::DecorationDescriptorSet);
    
    		// Some arbitrary remapping if we want to.
    		glsl.set_decoration(resource.id, spv::DecorationBinding, set * 16 + binding);
    	}
    
    	// Set some options.
    	spirv_cross::CompilerGLSL::Options options;
    	options.version = 310;
    	options.es = true;
    	glsl.set_common_options(options);
    
    	// Compile to GLSL, ready to give to GL driver.
    	std::string source = glsl.compile();
    }
  4. Install SPIRV-Cross via vcpkg

    main

    You can use the vcpkg dependency manager to build and install SPIRV-Cross. Follow these steps:

    1. Clone the vcpkg repository.
    2. Bootstrap vcpkg.
    3. Integrate vcpkg with your environment.
    4. Install the spirv-cross port.
    git clone https://github.com/Microsoft/vcpkg.git
    cd vcpkg
    ./bootstrap-vcpkg.sh
    ./vcpkg integrate install
    ./vcpkg install spirv-cross
  5. Run regression tests for SPIRV-Cross shaders

    main

    SPIRV-Cross uses a collection of shaders in the shaders/ directory and reference outputs in reference/ to perform regression testing. You can run these tests using the ./test_shaders.py script. This ensures that changes to the library do not unexpectedly alter the output of shader translations.

    To run the standard regression test suite, use:

    ./test_shaders.py shaders

    If you have built a custom version of the spirv-cross binary using CMake or another method, you can point the test script to it using the SPIRV_CROSS_PATH environment variable:

    SPIRV_CROSS_PATH=path/to/custom/spirv-cross ./test_shaders.py shaders
  6. Update regression test reference files

    main

    If you have made legitimate changes to SPIRV-Cross that intentionally change the shader output, the regression tests will fail. To update the reference files in the reference/ directory to match the new output, use the --update flag with the test script.

    Warning: Ensure you are using the correct versions of glslangValidator and SPIRV-Tools when updating references, as revisions change regularly.

  7. Link against SPIRV-Cross

    main

    Depending on your build system and requirements, you can link against SPIRV-Cross in several ways:

    Use add_subdirectory() in your CMake configuration to link against SPIRV-Cross statically.

    Custom Build Systems

    Copy the source and header files from the root directory and build the relevant .cpp files. Ensure you build with C++11 support (e.g., -std=c++11 in GCC/Clang). Alternatively, link against the libspirv-cross.a static library generated by the Makefile.

    System Library (Unix-like platforms)

    If installed as a system library, you can use pkg-config or find_package() in CMake.

    Using pkg-config:

    $ pkg-config spirv-cross-c-shared --libs --cflags
    -I/usr/local/include/spirv_cross -L/usr/local/lib -lspirv-cross-c-shared

    Using CMake find_package():

    cmake_minimum_required(VERSION 3.5)
    set(CMAKE_C_STANDARD 99)
    project(Test LANGUAGES C)
    
    find_package(spirv_cross_c_shared)
    if (spirv_cross_c_shared_FOUND)
            message(STATUS "Found SPIRV-Cross C API! :)")
    else()
            message(STATUS "Could not find SPIRV-Cross C API! :(")
    endif()
    
    add_executable(test test.c)
    target_link_libraries(test spirv-cross-c-shared)
  8. Build SPIRV-Cross using CMake

    main

    CMake is the recommended build system for all platforms (Linux, macOS, Windows, Android) and is the only system tested in continuous integration. It provides full support for install commands and module control.

    Requirements:

    • A C++11 compatible compiler (GCC 4.8+ or Clang 3.x+).
    • For Windows, CMake is required to target MSVC.
  9. Use the SPIRV-Cross C API wrapper

    main

    The C API wrapper is C89-compatible and designed for stability in both API and ABI. This is the only interface supported when building SPIRV-Cross as a shared library. It is ideal for use with foreign programming languages.

    Memory Management:

    • All memory allocations are managed within the spvc_context.
    • You must destroy the context using spvc_context_destroy(context) to free memory.
    • If you intend to reuse a context object soon, call spvc_context_release_allocations() to free current allocations without destroying the context.

    Error Handling:

    • Most functions return an spvc_result.
    • SPVC_SUCCESS is the only success code.
    #include <spirv_cross_c.h>
    
    const SpvId *spirv = get_spirv_data();
    size_t word_count = get_spirv_word_count();
    
    spvc_context context = NULL;
    spvc_parsed_ir ir = NULL;
    spvc_compiler compiler_glsl = NULL;
    spvc_compiler_options options = NULL;
    spvc_resources resources = NULL;
    const spvc_reflected_resource *list = NULL;
    const char *result = NULL;
    size_t count;
    size_t i;
    
    // Create context.
    spvc_context_create(&context);
    
    // Set debug callback.
    spvc_context_set_error_callback(context, error_callback, userdata);
    
    // Parse the SPIR-V.
    spvc_context_parse_spirv(context, spirv, word_count, &ir);
    
    // Hand it off to a compiler instance and give it ownership of the IR.
    spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler_glsl);
    
    // Do some basic reflection.
    spvc_compiler_create_shader_resources(compiler_glsl, &resources);
    spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count);
    
    for (i = 0; i < count; i++)
    {
        printf("ID: %u, BaseTypeID: %u, TypeID: %u, Name: %s\n", list[i].id, list[i].base_type_id, list[i].type_id,
               list[i].name);
        printf("  Set: %u, Binding: %u\n",
               spvc_compiler_get_decoration(compiler_glsl, list[i].id, SpvDecorationDescriptorSet),
               spvc_compiler_get_decoration(compiler_glsl, list[i].id, SpvDecorationBinding));
    }
    
    // Modify options.
    spvc_compiler_create_compiler_options(compiler_glsl, &options);
    spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 330);
    spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_FALSE);
    spvc_compiler_install_compiler_options(compiler_glsl, options);
    
    spvc_compiler_compile(compiler_glsl, &result);
    printf("Cross-compiled source: %s\n", result);
    
    // Frees all memory we allocated so far.
    spvc_context_destroy(context);
  10. Disable C++ exceptions in SPIRV-Cross

    main

    By default, C++ exceptions are enabled. You can configure the build to treat exceptions as assertions instead using the following methods:

    • Using CMake: Append -DSPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS=ON to your CMake command.
    • Using Make: Append SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS=1 to your make command.
  11. Configure SPIRV-Cross build modules

    main

    When building with CMake, you can control which modules are built and installed using the following flags:

    • -DSPIRV_CROSS_STATIC=ON/OFF: Controls the static library build.
    • -DSPIRV_CROSS_SHARED=ON/OFF: Controls the shared library build.
    • -DSPIRV_CROSS_CLI=ON/OFF: Controls the build of the CLI tool.
  12. Use the SPIRV-Cross CLI for shader conversion and reflection

    main

    The SPIRV-Cross command-line interface allows you to convert SPIR-V shaders into various shading languages (GLSL, MSL, HLSL) or extract shader reflection data in JSON format.

    Basic Usage Pattern:

    1. Provide the input SPIR-V file as a positional argument or via - for stdin.
    2. Specify the target language using flags like --glsl, --msl, or --hlsl.
    3. (Optional) Use --output <file> to save the result to a file instead of stdout.
    4. (Optional) Use --reflect <format> (e.g., json) to output reflection data instead of shader code.

    Common Flags:

    • --output <file>: Write output to a specific file.
    • --reflect <format>: Output shader reflection data in the specified format (e.g., json).
    • --entry <name>: Specify the entry point name.
    • --stage <stage>: Specify the shader stage (e.g., vertex, fragment).
    • --version <version>: Set the target language version.
    • --dump-resources: Print resource information, push constants, spec constants, and capabilities.