1<!-- 2 Copyright 2020-2021 The Khronos Group, Inc. 3 SPDX-License-Identifier: CC-BY-4.0 4--> 5 6# SPIRV-Cross 7 8SPIRV-Cross is a tool designed for parsing and converting SPIR-V to other shader languages. 9 10[](https://github.com/KhronosGroup/SPIRV-Cross/actions/workflows/main.yml) 11[](https://ci.appveyor.com/project/HansKristian-Work/SPIRV-Cross) 12 13## Features 14 15 - Convert SPIR-V to readable, usable and efficient GLSL 16 - Convert SPIR-V to readable, usable and efficient Metal Shading Language (MSL) 17 - Convert SPIR-V to readable, usable and efficient HLSL 18 - Convert SPIR-V to a JSON reflection format 19 - Convert SPIR-V to debuggable C++ [DEPRECATED] 20 - Reflection API to simplify the creation of Vulkan pipeline layouts 21 - Reflection API to modify and tweak OpDecorations 22 - Supports "all" of vertex, fragment, tessellation, geometry and compute shaders. 23 24SPIRV-Cross tries hard to emit readable and clean output from the SPIR-V. 25The goal is to emit GLSL or MSL that looks like it was written by a human and not awkward IR/assembly-like code. 26 27NOTE: Individual features are expected to be mostly complete, but it is possible that certain obscure GLSL features are not yet supported. 28However, most missing features are expected to be "trivial" improvements at this stage. 29 30## Building 31 32SPIRV-Cross has been tested on Linux, iOS/OSX, Windows and Android. CMake is the main build system. 33 34### Linux and macOS 35 36Building with CMake is recommended, as it is the only build system which is tested in continuous integration. 37It is also the only build system which has install commands and other useful build system features. 38 39However, you can just run `make` on the command line as a fallback if you only care about the CLI tool. 40 41A non-ancient GCC (4.8+) or Clang (3.x+) compiler is required as SPIRV-Cross uses C++11 extensively. 42 43### Windows 44 45Building with CMake is recommended, which is the only way to target MSVC. 46MinGW-w64 based compilation works with `make` as a fallback. 47 48### Android 49 50SPIRV-Cross is only useful as a library here. Use the CMake build to link SPIRV-Cross to your project. 51 52### C++ exceptions 53 54The make and CMake build flavors offer the option to treat exceptions as assertions. To disable exceptions for make just append `SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS=1` to the command line. For CMake append `-DSPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS=ON`. By default exceptions are enabled. 55 56### Static, shared and CLI 57 58You can use `-DSPIRV_CROSS_STATIC=ON/OFF` `-DSPIRV_CROSS_SHARED=ON/OFF` `-DSPIRV_CROSS_CLI=ON/OFF` to control which modules are built (and installed). 59 60## Usage 61 62### Using the C++ API 63 64The C++ API is the main API for SPIRV-Cross. For more in-depth documentation than what's provided in this README, 65please have a look at the [Wiki](https://github.com/KhronosGroup/SPIRV-Cross/wiki). 66**NOTE**: This API is not guaranteed to be ABI-stable, and it is highly recommended to link against this API statically. 67The API is generally quite stable, but it can change over time, see the C API for more stability. 68 69To perform reflection and convert to other shader languages you can use the SPIRV-Cross API. 70For example: 71 72```c++ 73#include "spirv_glsl.hpp" 74#include <vector> 75#include <utility> 76 77extern std::vector<uint32_t> load_spirv_file(); 78 79int main() 80{ 81 // Read SPIR-V from disk or similar. 82 std::vector<uint32_t> spirv_binary = load_spirv_file(); 83 84 spirv_cross::CompilerGLSL glsl(std::move(spirv_binary)); 85 86 // The SPIR-V is now parsed, and we can perform reflection on it. 87 spirv_cross::ShaderResources resources = glsl.get_shader_resources(); 88 89 // Get all sampled images in the shader. 90 for (auto &resource : resources.sampled_images) 91 { 92 unsigned set = glsl.get_decoration(resource.id, spv::DecorationDescriptorSet); 93 unsigned binding = glsl.get_decoration(resource.id, spv::DecorationBinding); 94 printf("Image %s at set = %u, binding = %u\n", resource.name.c_str(), set, binding); 95 96 // Modify the decoration to prepare it for GLSL. 97 glsl.unset_decoration(resource.id, spv::DecorationDescriptorSet); 98 99 // Some arbitrary remapping if we want. 100 glsl.set_decoration(resource.id, spv::DecorationBinding, set * 16 + binding); 101 } 102 103 // Set some options. 104 spirv_cross::CompilerGLSL::Options options; 105 options.version = 310; 106 options.es = true; 107 glsl.set_options(options); 108 109 // Compile to GLSL, ready to give to GL driver. 110 std::string source = glsl.compile(); 111} 112``` 113 114### Using the C API wrapper 115 116To facilitate C compatibility and compatibility with foreign programming languages, a C89-compatible API wrapper is provided. Unlike the C++ API, 117the goal of this wrapper is to be fully stable, both API and ABI-wise. 118This is the only interface which is supported when building SPIRV-Cross as a shared library. 119 120An important point of the wrapper is that all memory allocations are contained in the `spvc_context`. 121This simplifies the use of the API greatly. However, you should destroy the context as soon as reasonable, 122or use `spvc_context_release_allocations()` if you intend to reuse the `spvc_context` object again soon. 123 124Most functions return a `spvc_result`, where `SPVC_SUCCESS` is the only success code. 125For brevity, the code below does not do any error checking. 126 127```c 128#include <spirv_cross_c.h> 129 130const SpvId *spirv = get_spirv_data(); 131size_t word_count = get_spirv_word_count(); 132 133spvc_context context = NULL; 134spvc_parsed_ir ir = NULL; 135spvc_compiler compiler_glsl = NULL; 136spvc_compiler_options options = NULL; 137spvc_resources resources = NULL; 138const spvc_reflected_resource *list = NULL; 139const char *result = NULL; 140size_t count; 141size_t i; 142 143// Create context. 144spvc_context_create(&context); 145 146// Set debug callback. 147spvc_context_set_error_callback(context, error_callback, userdata); 148 149// Parse the SPIR-V. 150spvc_context_parse_spirv(context, spirv, word_count, &ir); 151 152// Hand it off to a compiler instance and give it ownership of the IR. 153spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler_glsl); 154 155// Do some basic reflection. 156spvc_compiler_create_shader_resources(compiler_glsl, &resources); 157spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count); 158 159for (i = 0; i < count; i++) 160{ 161 printf("ID: %u, BaseTypeID: %u, TypeID: %u, Name: %s\n", list[i].id, list[i].base_type_id, list[i].type_id, 162 list[i].name); 163 printf(" Set: %u, Binding: %u\n", 164 spvc_compiler_get_decoration(compiler_glsl, list[i].id, SpvDecorationDescriptorSet), 165 spvc_compiler_get_decoration(compiler_glsl, list[i].id, SpvDecorationBinding)); 166} 167 168// Modify options. 169spvc_compiler_create_compiler_options(compiler_glsl, &options); 170spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 330); 171spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_FALSE); 172spvc_compiler_install_compiler_options(compiler_glsl, options); 173 174spvc_compiler_compile(compiler_glsl, &result); 175printf("Cross-compiled source: %s\n", result); 176 177// Frees all memory we allocated so far. 178spvc_context_destroy(context); 179``` 180 181### Linking 182 183#### CMake add_subdirectory() 184 185This is the recommended way if you are using CMake and want to link against SPIRV-Cross statically. 186 187#### Integrating SPIRV-Cross in a custom build system 188 189To add SPIRV-Cross to your own codebase, just copy the source and header files from root directory 190and build the relevant .cpp files you need. Make sure to build with C++11 support, e.g. `-std=c++11` in GCC and Clang. 191Alternatively, the Makefile generates a libspirv-cross.a static library during build that can be linked in. 192 193#### Linking against SPIRV-Cross as a system library 194 195It is possible to link against SPIRV-Cross when it is installed as a system library, 196which would be mostly relevant for Unix-like platforms. 197 198##### pkg-config 199 200For Unix-based systems, a pkg-config is installed for the C API, e.g.: 201 202``` 203$ pkg-config spirv-cross-c-shared --libs --cflags 204-I/usr/local/include/spirv_cross -L/usr/local/lib -lspirv-cross-c-shared 205``` 206 207##### CMake 208 209If the project is installed, it can be found with `find_package()`, e.g.: 210 211``` 212cmake_minimum_required(VERSION 3.5) 213set(CMAKE_C_STANDARD 99) 214project(Test LANGUAGES C) 215 216find_package(spirv_cross_c_shared) 217if (spirv_cross_c_shared_FOUND) 218 message(STATUS "Found SPIRV-Cross C API! :)") 219else() 220 message(STATUS "Could not find SPIRV-Cross C API! :(") 221endif() 222 223add_executable(test test.c) 224target_link_libraries(test spirv-cross-c-shared) 225``` 226 227test.c: 228```c 229#include <spirv_cross_c.h> 230 231int main(void) 232{ 233 spvc_context context; 234 spvc_context_create(&context); 235 spvc_context_destroy(context); 236} 237``` 238 239### CLI 240 241The CLI is suitable for basic cross-compilation tasks, but it cannot support the full flexibility that the API can. 242Some examples below. 243 244#### Creating a SPIR-V file from GLSL with glslang 245 246``` 247glslangValidator -H -V -o test.spv test.frag 248``` 249 250#### Converting a SPIR-V file to GLSL ES 251 252``` 253glslangValidator -H -V -o test.spv shaders/comp/basic.comp 254./spirv-cross --version 310 --es test.spv 255``` 256 257#### Converting to desktop GLSL 258 259``` 260glslangValidator -H -V -o test.spv shaders/comp/basic.comp 261./spirv-cross --version 330 --no-es test.spv --output test.comp 262``` 263 264#### Disable prettifying optimizations 265 266``` 267glslangValidator -H -V -o test.spv shaders/comp/basic.comp 268./spirv-cross --version 310 --es test.spv --output test.comp --force-temporary 269``` 270 271### Using shaders generated from C++ backend 272 273Please see `samples/cpp` where some GLSL shaders are compiled to SPIR-V, decompiled to C++ and run with test data. 274Reading through the samples should explain how to use the C++ interface. 275A simple Makefile is included to build all shaders in the directory. 276 277### Implementation notes 278 279When using SPIR-V and SPIRV-Cross as an intermediate step for cross-compiling between high level languages there are some considerations to take into account, 280as not all features used by one high-level language are necessarily supported natively by the target shader language. 281SPIRV-Cross aims to provide the tools needed to handle these scenarios in a clean and robust way, but some manual action is required to maintain compatibility. 282 283#### HLSL source to GLSL 284 285##### HLSL entry points 286 287When using SPIR-V shaders compiled from HLSL, there are some extra things you need to take care of. 288First make sure that the entry point is used correctly. 289If you forget to set the entry point correctly in glslangValidator (-e MyFancyEntryPoint), 290you will likely encounter this error message: 291 292``` 293Cannot end a function before ending the current block. 294Likely cause: If this SPIR-V was created from glslang HLSL, make sure the entry point is valid. 295``` 296 297##### Vertex/Fragment interface linking 298 299HLSL relies on semantics in order to effectively link together shader stages. In the SPIR-V generated by glslang, the transformation from HLSL to GLSL ends up looking like 300 301```c++ 302struct VSOutput { 303 // SV_Position is rerouted to gl_Position 304 float4 position : SV_Position; 305 float4 coord : TEXCOORD0; 306}; 307 308VSOutput main(...) {} 309``` 310 311```c++ 312struct VSOutput { 313 float4 coord; 314} 315layout(location = 0) out VSOutput _magicNameGeneratedByGlslang; 316``` 317 318While this works, be aware of the type of the struct which is used in the vertex stage and the fragment stage. 319There may be issues if the structure type name differs in vertex stage and fragment stage. 320 321You can make use of the reflection interface to force the name of the struct type. 322 323``` 324// Something like this for both vertex outputs and fragment inputs. 325compiler.set_name(varying_resource.base_type_id, "VertexFragmentLinkage"); 326``` 327 328Some platform may require identical variable name for both vertex outputs and fragment inputs. (for example MacOSX) 329to rename variable base on location, please add 330``` 331--rename-interface-variable <in|out> <location> <new_variable_name> 332``` 333 334#### HLSL source to legacy GLSL/ESSL 335 336HLSL tends to emit varying struct types to pass data between vertex and fragment. 337This is not supported in legacy GL/GLES targets, so to support this, varying structs are flattened. 338This is done automatically, but the API user might need to be aware that this is happening in order to support all cases. 339 340Modern GLES code like this: 341```c++ 342struct Output { 343 vec4 a; 344 vec2 b; 345}; 346out Output vout; 347``` 348 349Is transformed into: 350```c++ 351struct Output { 352 vec4 a; 353 vec2 b; 354}; 355varying vec4 Output_a; 356varying vec2 Output_b; 357``` 358 359Note that now, both the struct name and the member names will participate in the linking interface between vertex and fragment, so 360API users might want to ensure that both the struct names and member names match so that vertex outputs and fragment inputs can link properly. 361 362 363#### Separate image samplers (HLSL/Vulkan) for backends which do not support it (GLSL) 364 365Another thing you need to remember is when using samplers and textures in HLSL these are separable, and not directly compatible with GLSL. If you need to use this with desktop GL/GLES, you need to call `Compiler::build_combined_image_samplers` first before calling `Compiler::compile`, or you will get an exception. 366 367```c++ 368// From main.cpp 369// Builds a mapping for all combinations of images and samplers. 370compiler->build_combined_image_samplers(); 371 372// Give the remapped combined samplers new names. 373// Here you can also set up decorations if you want (binding = #N). 374for (auto &remap : compiler->get_combined_image_samplers()) 375{ 376 compiler->set_name(remap.combined_id, join("SPIRV_Cross_Combined", compiler->get_name(remap.image_id), 377 compiler->get_name(remap.sampler_id))); 378} 379``` 380 381If your target is Vulkan GLSL, `--vulkan-semantics` will emit separate image samplers as you'd expect. 382The command line client calls `Compiler::build_combined_image_samplers` automatically, but if you're calling the library, you'll need to do this yourself. 383 384#### Descriptor sets (Vulkan GLSL) for backends which do not support them (HLSL/GLSL/Metal) 385 386Descriptor sets are unique to Vulkan, so make sure that descriptor set + binding is remapped to a flat binding scheme (set always 0), so that other APIs can make sense of the bindings. 387This can be done with `Compiler::set_decoration(id, spv::DecorationDescriptorSet)`. 388 389#### Linking by name for targets which do not support explicit locations (legacy GLSL/ESSL) 390 391Modern GLSL and HLSL sources (and SPIR-V) relies on explicit layout(location) qualifiers to guide the linking process between shader stages, 392but older GLSL relies on symbol names to perform the linking. When emitting shaders with older versions, these layout statements will be removed, 393so it is important that the API user ensures that the names of I/O variables are sanitized so that linking will work properly. 394The reflection API can rename variables, struct types and struct members to deal with these scenarios using `Compiler::set_name` and friends. 395 396#### Clip-space conventions 397 398SPIRV-Cross can perform some common clip space conversions on gl_Position/SV_Position by enabling `CompilerGLSL::Options.vertex.fixup_clipspace`. 399While this can be convenient, it is recommended to modify the projection matrices instead as that can achieve the same result. 400 401For GLSL targets, enabling this will convert a shader which assumes `[0, w]` depth range (Vulkan / D3D / Metal) into `[-w, w]` range. 402For MSL and HLSL targets, enabling this will convert a shader in `[-w, w]` depth range (OpenGL) to `[0, w]` depth range. 403 404By default, the CLI will not enable `fixup_clipspace`, but in the API you might want to set an explicit value using `CompilerGLSL::set_options()`. 405 406Y-flipping of gl_Position and similar is also supported. 407The use of this is discouraged, because relying on vertex shader Y-flipping tends to get quite messy. 408To enable this, set `CompilerGLSL::Options.vertex.flip_vert_y` or `--flip-vert-y` in CLI. 409 410#### Reserved identifiers 411 412When cross-compiling, certain identifiers are considered to be reserved by the implementation. 413Code generated by SPIRV-Cross cannot emit these identifiers as they are reserved and used for various internal purposes, 414and such variables will typically show up as `_RESERVED_IDENTIFIER_FIXUP_` 415or some similar name to make it more obvious that an identifier has been renamed. 416 417Reflection output will follow the exact name specified in the SPIR-V module. It might not be a valid identifier in the C sense, 418as it may contain non-alphanumeric/non-underscore characters. 419 420Reserved identifiers currently assumed by the implementation are (in pseudo-regex): 421 422- _$digit+, e.g. `_100`, `_2` 423- _$digit+_.+, e.g. `_100_tmp`, `_2_foobar`. `_2Bar` is **not** reserved. 424- gl_- prefix 425- spv- prefix 426- SPIRV_Cross prefix. This prefix is generally used for interface variables where app needs to provide data for workaround purposes. 427 This identifier will not be rewritten, but be aware of potential collisions. 428- Double underscores (reserved by all target languages). 429 430Members of structs also have a reserved identifier: 431- _m$digit+$END, e.g. `_m20` and `_m40` are reserved, but not `_m40Foobar`. 432 433## Contributing 434 435Contributions to SPIRV-Cross are welcome. See Testing and Licensing sections for details. 436 437### Testing 438 439SPIRV-Cross maintains a test suite of shaders with reference output of how the output looks after going through a roundtrip through 440glslangValidator/spirv-as then back through SPIRV-Cross again. 441The reference files are stored inside the repository in order to be able to track regressions. 442 443All pull requests should ensure that test output does not change unexpectedly. This can be tested with: 444 445``` 446./checkout_glslang_spirv_tools.sh # Checks out glslang and SPIRV-Tools at a fixed revision which matches the reference output. 447 # NOTE: Some users have reported problems cloning from git:// paths. To use https:// instead pass in 448 # $ PROTOCOL=https ./checkout_glslang_spirv_tools.sh 449 # instead. 450./build_glslang_spirv_tools.sh # Builds glslang and SPIRV-Tools. 451./test_shaders.sh # Runs over all changes and makes sure that there are no deltas compared to reference files. 452``` 453 454`./test_shaders.sh` currently requires a Makefile setup with GCC/Clang to be set up. 455However, on Windows, this can be rather inconvenient if a MinGW environment is not set up. 456To use a spirv-cross binary you built with CMake (or otherwise), you can pass in an environment variable as such: 457 458``` 459SPIRV_CROSS_PATH=path/to/custom/spirv-cross ./test_shaders.sh 460``` 461 462However, when improving SPIRV-Cross there are of course legitimate cases where reference output should change. 463In these cases, run: 464 465``` 466./update_test_shaders.sh # SPIRV_CROSS_PATH also works here. 467``` 468 469to update the reference files and include these changes as part of the pull request. 470Always make sure you are running the correct version of glslangValidator as well as SPIRV-Tools when updating reference files. 471See `checkout_glslang_spirv_tools.sh` which revisions are currently expected. The revisions change regularly. 472 473In short, the master branch should always be able to run `./test_shaders.py shaders` and friends without failure. 474SPIRV-Cross uses Travis CI to test all pull requests, so it is not strictly needed to perform testing yourself if you have problems running it locally. 475A pull request which does not pass testing on Travis will not be accepted however. 476 477When adding support for new features to SPIRV-Cross, a new shader and reference file should be added which covers usage of the new shader features in question. 478Travis CI runs the test suite with the CMake, by running `ctest`. This is a more straight-forward alternative to `./test_shaders.sh`. 479 480### Licensing 481 482Contributors of new files should add a copyright header at the top of every new source code file with their copyright 483along with the Apache 2.0 licensing stub. 484 485### Formatting 486 487SPIRV-Cross uses `clang-format` to automatically format code. 488Please use `clang-format` with the style sheet found in `.clang-format` to automatically format code before submitting a pull request. 489 490To make things easy, the `format_all.sh` script can be used to format all 491source files in the library. In this directory, run the following from the 492command line: 493 494 ./format_all.sh 495 496## Regression testing 497 498In shaders/ a collection of shaders are maintained for purposes of regression testing. 499The current reference output is contained in reference/. 500`./test_shaders.py shaders` can be run to perform regression testing. 501 502See `./test_shaders.py --help` for more. 503 504### Metal backend 505 506To test the roundtrip path GLSL -> SPIR-V -> MSL, `--msl` can be added, e.g. `./test_shaders.py --msl shaders-msl`. 507 508### HLSL backend 509 510To test the roundtrip path GLSL -> SPIR-V -> HLSL, `--hlsl` can be added, e.g. `./test_shaders.py --hlsl shaders-hlsl`. 511 512### Updating regression tests 513 514When legitimate changes are found, use `--update` flag to update regression files. 515Otherwise, `./test_shaders.py` will fail with error code. 516 517### Mali Offline Compiler cycle counts 518 519To obtain a CSV of static shader cycle counts before and after going through spirv-cross, add 520`--malisc` flag to `./test_shaders`. This requires the Mali Offline Compiler to be installed in PATH. 521 522