Gfx-rs nuts and bolts

Gfx-rs is a low-level graphics abstraction layer in Rust. This blog supposedly hosts the major milestones, concepts, and recaps of the project.

Project update

01 Oct 2019

gfx-rs is a Rust project aiming to make low-level GPU programming portable with low overhead. It’s a single Vulkan-like Rust API with multiple backends that implement it: Direct3D 12/11, Metal, Vulkan, and even OpenGL.

wgpu-rs is a Rust project on top of gfx-rs that provides safety, accessibility, and even stronger portability.

This is an update that is not aligned to any dates or releases. We just want to share about some of the exciting work that landed recently, which will make it to the next release cycle.

Slimmed down gfx-rs

We’ve done a few big steps towards making gfx-hal easier to build and maintain. First of all, we removed the “magical” dependencies, such as failure and derivative in favor of straight and simple code. That sped up the fresh gfx-hal build by a factor of 8.5X.

Secondly, the “typed” layer of gfx-hal got removed. Previously, we recognized that the view of the API for the backends (when implementing it) needs to be different from the users (to use it). Backends just need to implement “raw” interfaces, while users need more type safety, such as limiting the set of operations available on a command buffer when recording a render pass. For example, we had different entry points create_command_pool versus create_command_pool_typed.

This duality of APIs in gfx-hal worked to an extent. Providing safety was impossible without imposing some restrictions or overhead. In the command pool example, it’s only safe to re-use a command buffer if it’s done execution on the GPU, and the user doesn’t intent to use it. We can’t possibly know this in gfx-hal without introducing overhead… So, at the end of the day, we decided that the “typed” (user-facing) layer is still useful, it doesn’t have to be a part of gfx-hal. Thus, we removed it, recommending rendy-command as a replacement. This slimmed up gfx-hal API surface and allowed us to straighten up the terminology (no more “RawXXX” or “XxxTyped”).

Feature-less wgpu-rs

From the very beginning of gfx-rs project, it was structured in a way that backend implementations of the API lived in separate crates. The user (or library client) was responsible to select the proper backend for their needs. It was very difficult for a client to support multiple backends within the same binary, and to our knowledge - nobody did. A typical application would expose separate features for backends up to the user:

vulkan = ["rgx/vulkan"]
metal = ["rgx/metal"]
dx11 = ["rgx/dx11"]
dx12 = ["rgx/dx12"]

With the set of supported backends growing, this became more and more of an issue. Asking the user to enable the proper feature doesn’t solve the problem - it just puts it on the users shoulders. Moreover, using features in such a way is typically non-idiomatic, because in Rust features are supposed to be additive, but all of the user code (including our examples) was written to work with a single backend, even if it’s generic.

Today, we are happy to announce that the problem is solved in wgpu-rs! Features are gone. The library knows which backends are supported on the target platform, it checks for their availability and enumerates their physical adapters. When the user requests an adapter, they can provide a mask of backends, and the implementation will pick the most suitable adapter on one of the enabled backends.

The immediate effect on the users is that the library “just works”. All the complicated machinery of the backends is now hidden within the implementation. This is the case currently for master and will make it to the next release.

New swapchain model

Vulkan swapchain model is extremely powerful from the user perspective. Users control when and how each image is used, and explictly synchronize the rendering work with the presentation engine. Mapping this model to other APIs proved to be extremely challenging. Fortunately, Vulkan designers made it an extension from the start (VK_swapchain_KHR) as opposed to a part of the core, which leaves the room for experimentation with different models.

To solve this, an entirely new API was prototyped and implemented - all the way from gfx-rs backends to wgpu-rs. This API is roughly as limited as CAMetalLayer presentation:

  1. get the next image view
  2. render to it
  3. present it

It maps very nicely to Metal, OpenGL, and DX12/DX11, however requiring a bit more complexity on the Vulkan backend side, which is a cost we can live with. This new swapchain model is currently provided alongside the old Vulkan-style one. If we are able to make gfx-portability to use it in the future, we’ll consider removing the old model entirely.

What we have in the end is:

  • fast start-up
  • proper full-screen and resizing behavior
  • less bugs across the backends, consistency across platforms