Yesterday I published for the first time the vulkano library on crates.io. I have been actively working on this library since the release of the Vulkan API in february, and I think it is now in a semi-usable state. This means that breaking changes are still happening, but the general design is more or less stable.
What is Vulkan?
Vulkan is a next generation graphics API by Khronos, which successes to OpenGL. It is meant to be the lowest-possible level for graphics programming that is still cross-platform, right above hardware.
The OpenGL API was initially designed to be easy to use, which means that it abstracts a lot over the hardware and that implementations have to do a lot of work to validate and translate the user's requests into actual hardware instructions.
This approach has several problems:
- The validations performed by the OpenGL driver are expensive and most of the time unnecessary.
- The OpenGL API tries to be easy to use by hiding a lot of implementation details. For example you don't have any guarantee about the amount of time it takes to execute a command.
- Since the validation and conveniences are per-driver, each driver has its own internal magic and its own bugs. Your code can run correctly on one driver, but can end up running slowly on another driver, or reveal a bug in a third one.
Vulkan, instead, looks like this:
The "validation and conveniences" stage is gone. It's the job of the user code to handle the validation (if necessary) and to provide all the details to the driver. Any invalid usage of Vulkan immediately results in undefined behavior.
What is vulkano?
Taking the schema above, vulkano is here:
If we compare the vulkano API with the OpenGL API:
- Vulkano is still lower-level than OpenGL. Vulkano doesn't hide anything from Vulkan, so you have to perform all the additional initialization that you wouldn't have to do in OpenGL.
- Vulkano is safer than OpenGL, while probably still being faster because lots of checks are performed at compile-time (more details below).
- Vulkano is more predictable. For example if you ask the GPU to write to a memory location, then try to write from the CPU in that same memory location, vulkano will block until the GPU is finished writing. OpenGL on the other side would perform some dark magic and avoid blocking by writing to a different location. As you can see, this also means that vulkano lets you shoot yourself more easily in the foot when it comes to performances.
One important design goal is that vulkano should never let you trigger any undefined behavior, unless you use one of its unsafe APIs. The goal of vulkano is not to let you easily draw a triangle, but to let you use the entirety of the Vulkan API in a safe way.
Note that as of this post, this is not yet the case. Some considerations are notably blocked by ambiguities in the specs.
Example of how vulkano performs validation
To give you an overview about how vulkano enforces safety, let's comment its triangle example.
At lines 193 to 210, we create a buffer that holds the shape of the triangle. To do so, we create a
CpuAccessibleBuffer. Vulkano provides several high-level wrappers, like the CpuAccessibleBuffer, around raw buffers. These wrappers handle details like synchronization, cache flushing, or queue ownership transfers for you. If you're not satisfied with the available wrappers, you can also write your own by implementing an unsafe trait.
At lines 229 to 232, we import the pre-compiled shaders. To use vulkano you are encouraged to use the
vulkano-shaders crate which compiles your GLSL code in SPIR-V and performs introspection of the shader's content. This is one of the key parts of the safety of vulkano.
At lines 245 to 282, we invoke a macro to create several structs. These structs are safe to use but contain a lot of unsafe code. The fact that you can invoke the macro in a specific way means that vulkano knows that the unsafe code is correct.
Lines 295 to 351 is where we create the pipeline. A lot of checks are performed here, and one of them is that the output of the vertex shader has to match the input of the fragment shader. To do so, the
vulkano-shaders crate generates structs that implement the
ShaderInterfaceDef trait: one struct for the output of the vertex shader and one for the input of the fragment shader. Then the data provided by the traits is compared. This comparison is expensive, but in the future this method will be specialized to return
true for structs that are known to be always compatible. This design with a generic method that is then specialized is used several times in vulkano.
Line 302 is an illustration of the shader introspection. The method named
main_entry_point is generated by
vulkano-shaders and is named like this because the entry point of our shader is the function named
Lines 360 to 364 are an illustration of the render pass macro we called earlier. The
AList struct is generated by the macro and contains one field for each attachment we are going to draw upon. For the moment you can pass any type of image, but in the future vulkano will add some trait bounds to restrict the possible values to images that can serve as attachments.
The type of data passed at line 401 depends on the format set at line 272. In this example we are using a generic format (because we don't know in advance what the hardware supports), which means that the check is performed at runtime. But for example if we had set the format to
R32Uint, then we would only be able to pass a single unsigned integer for the clear values. Thanks to strong typing and trait implementations, several parts of vulkano can use either runtime checks or compile-time checks depending on which you need.
The best to get started for now is to read the triangle example. The documentation should already be good enough, I think.
However for now I assume that you are already familiar with graphics programming. Vulkan is harder than OpenGL, so it's recommended to learn OpenGL first. This is also why I'm not writing any blog post or long article describing what the advantages of vulkano are. I expect that people who know OpenGL and who are interested in Vulkan already know what the advantages of a wrapper are.
That being said, contrary to what many people say I'm convinced that Vulkan is meant to replace OpenGL. Tutorials and examples should eventually be ported to Vulkan so that people learn Vulkan first and no longer have to fight with OpenGL.
Keep in mind that many parts of vulkano are still changing. If you want to use this library, I recommend using one specific commit, and upgrading from time to time. If you have a question about how to upgrade or how to use a specific feature, just ask me on #rust or #rust-gamedev.