We are having fun moving the gfx-rs project forward thanks to the power provided by the Rust language. We are building rich abstractions which expand beyond the gfx-hal API itself and into the internal layers of the backends, structured to be modular and maintainable. We are building high-performance graphics and compute infrastructure that deeply interacts with OS and drivers, thanks to the Rust’s FFI capabilities and the lack of runtime. We do all of this while requiring only a portion of our developers’ time and attention, who work on gfx-rs mostly as a side project. Rust allows us to move forward confidently and quickly, experiment with features as well as land production-quality code.
But gfx-rs is not all written in Rust. There is one complex and important component we rely on that is written in a mixture of C and C++: SPIRV-Cross. It’s a shader translation library developed by @TheMaister and a few Khronos members, although not an official Khronos product. We use it to generate platform-specific shaders from SPIR-V sources. It has a test suite, and its Metal backend is mostly developed and used by MoltenVK.
SPIRV-Cross shows up quite a bit in our performance profiles (e.g. on Dota2). It is also written in a way that is far from idiomatic Rust: the code prefers large mutable data structures, which makes it hard to modularize, test, optimize, and especially interact with behind a C/C++ FII. And while it moves fast (in terms of contributions), it defines for us today what we can and what we can not do in terms of using advanced backend features, e.g. tessellation, argument buffers, etc. It complicates our build process, especially on the Web where it requires a separate Emscripten build (which Rust code doesn’t need) to generate the WASM module, becoming a pain point for developers and users alike.
We think the time has come for us to attack that last bastion of C++ code in gfx-rs. That is what the Javelin project is about: “A SPIR that flies above the garden walls”. It’s a very complex piece of software and we have not made much progress on it yet. However, we feel that Rust, once again, is the best tool for the job of shader translation: it’s about parsing, working with bytes and data structures, with an ability to do unit and fuzzy testing, and no external dependencies.
This post is meant to communicate our vision and plans for this future development. Note that here we are only talking about the path of shader translation from SPIR-V to anything (for example, translating SPIR-V to HLSL, MSL, or GLSL), we don’t plan to have GLSL to SPIR-V (or anything to SPIR-V) in scope. The key advantages we hope to gain by implementing Javelin are:
- Achieve a pure-Rust code base, which is easier to build and port to platforms, such as the Web, or Android
- Make the shader translation faster, and its artifacts easier to cache and reuse
- Make it easier to maintain and identify issues in the shader translation
- Start working on SPIR-V validation and sanitization for safety and WebGPU
- Unlimit our progress with advanced backend features: - explore direct translation to LLVM, AIR, and DXIL (all of those are LLVM-IR variations) - get Metal argument buffers and tessellation in a way that works best for us
In terms of implementation, the first step is getting the proper tools to work with SPIR-V. Fortunately, there is a library rspirv for doing this, and we are pleased to announce that it has just moved to our organization (thanks @antiagainst!). We are currently looking into building a proper structured representation for SPIR-V in rspirv, which would allow us to build and reflect the SPIR-V modules without too much boilerplate. Once this is done, we’ll start experimenting with simple shader outputs in the backends behind an optional feature. We are also planning to have a web playground (similar to one by Tim Jones) to be able to convert the shaders online and investigate issues. The path forward is hard, and we appreciate any help!