Crate wgpu_hal

source ·
Expand description

A cross-platform unsafe graphics abstraction.

This crate defines a set of traits abstracting over modern graphics APIs, with implementations (“backends”) for Vulkan, Metal, Direct3D, and GL.

wgpu-hal is a spiritual successor to gfx-hal, but with reduced scope, and oriented towards WebGPU implementation goals. It has no overhead for validation or tracking, and the API translation overhead is kept to the bare minimum by the design of WebGPU. This API can be used for resource-demanding applications and engines.

The wgpu-hal crate’s main design choices:

  • Our traits are meant to be portable: proper use should get equivalent results regardless of the backend.

  • Our traits’ contracts are unsafe: implementations perform minimal validation, if any, and incorrect use will often cause undefined behavior. This allows us to minimize the overhead we impose over the underlying graphics system. If you need safety, the wgpu-core crate provides a safe API for driving wgpu-hal, implementing all necessary validation, resource state tracking, and so on. (Note that wgpu-core is designed for use via FFI; the wgpu crate provides more idiomatic Rust bindings for wgpu-core.) Or, you can do your own validation.

  • In the same vein, returned errors only cover cases the user can’t anticipate, like running out of memory or losing the device. Any errors that the user could reasonably anticipate are their responsibility to avoid. For example, wgpu-hal returns no error for mapping a buffer that’s not mappable: as the buffer creator, the user should already know if they can map it.

  • We use static dispatch. The traits are not generally object-safe. You must select a specific backend type like vulkan::Api or metal::Api, and then use that according to the main traits, or call backend-specific methods.

  • We use idiomatic Rust parameter passing, taking objects by reference, returning them by value, and so on, unlike wgpu-core, which refers to objects by ID.

  • We map buffer contents persistently. This means that the buffer can remain mapped on the CPU while the GPU reads or writes to it. You must explicitly indicate when data might need to be transferred between CPU and GPU, if Device::map_buffer indicates that this is necessary.

  • You must record explicit barriers between different usages of a resource. For example, if a buffer is written to by a compute shader, and then used as and index buffer to a draw call, you must use CommandEncoder::transition_buffers between those two operations.

  • Pipeline layouts are explicitly specified when setting bind groups. Incompatible layouts disturb groups bound at higher indices.

  • The API accepts collections as iterators, to avoid forcing the user to store data in particular containers. The implementation doesn’t guarantee that any of the iterators are drained, unless stated otherwise by the function documentation. For this reason, we recommend that iterators don’t do any mutating work.

Unfortunately, wgpu-hal‘s safety requirements are not fully documented. Ideally, all trait methods would have doc comments setting out the requirements users must meet to ensure correct and portable behavior. If you are aware of a specific requirement that a backend imposes that is not ensured by the traits’ documented rules, please file an issue. Or, if you are a capable technical writer, please file a pull request!

Primary backends

The wgpu-hal crate has full-featured backends implemented on the following platform graphics APIs:

  • Vulkan, available on Linux, Android, and Windows, using the ash crate’s Vulkan bindings. It’s also available on macOS, if you install MoltenVK.

  • Metal on macOS, using the metal crate’s bindings.

  • Direct3D 12 on Windows, using the d3d12 crate’s bindings.

Secondary backends

The wgpu-hal crate has a partial implementation based on the following platform graphics API:

  • The GL backend is available anywhere OpenGL, OpenGL ES, or WebGL are available. See the gles module documentation for details.

You can see what capabilities an adapter is missing by checking the DownlevelCapabilities in ExposedAdapter::capabilities, available from Instance::enumerate_adapters.

The API is generally designed to fit the primary backends better than the secondary backends, so the latter may impose more overhead.

Traits

The wgpu-hal crate defines a handful of traits that together represent a cross-platform abstraction for modern GPU APIs.

  • The Api trait represents a wgpu-hal backend. It has no methods of its own, only a collection of associated types.

  • Api::Instance implements the Instance trait. Instance::init creates an instance value, which you can use to enumerate the adapters available on the system. For example, vulkan::Api::Instance::init returns an instance that can enumerate the Vulkan physical devices on your system.

  • Api::Adapter implements the Adapter trait, representing a particular device from a particular backend. For example, a Vulkan instance might have a Lavapipe software adapter and a GPU-based adapter.

  • Api::Device implements the Device trait, representing an active link to a device. You get a device value by calling Adapter::open, and then use it to create buffers, textures, shader modules, and so on.

  • Api::Queue implements the Queue trait, which you use to submit command buffers to a given device.

  • Api::CommandEncoder implements the CommandEncoder trait, which you use to build buffers of commands to submit to a queue. This has all the methods for drawing and running compute shaders, which is presumably what you’re here for.

  • Api::Surface implements the Surface trait, which represents a swapchain for presenting images on the screen, via interaction with the system’s window manager.

The Api trait has various other associated types like Api::Buffer and Api::Texture that represent resources the rest of the interface can operate on, but these generally do not have their own traits.

Validation is the calling code’s responsibility, not wgpu-hal’s

As much as possible, wgpu-hal traits place the burden of validation, resource tracking, and state tracking on the caller, not on the trait implementations themselves. Anything which can reasonably be handled in backend-independent code should be. A wgpu_hal backend’s sole obligation is to provide portable behavior, and report conditions that the calling code can’t reasonably anticipate, like device loss or running out of memory.

The wgpu crate collection is intended for use in security-sensitive applications, like web browsers, where the API is available to untrusted code. This means that wgpu-core’s validation is not simply a service to developers, to be provided opportunistically when the performance costs are acceptable and the necessary data is ready at hand. Rather, wgpu-core’s validation must be exhaustive, to ensure that even malicious content cannot provoke and exploit undefined behavior in the platform’s graphics API.

Because graphics APIs’ requirements are complex, the only practical way for wgpu to provide exhaustive validation is to comprehensively track the lifetime and state of all the resources in the system. Implementing this separately for each backend is infeasible; effort would be better spent making the cross-platform validation in wgpu-core legible and trustworthy. Fortunately, the requirements are largely similar across the various platforms, so cross-platform validation is practical.

Some backends have specific requirements that aren’t practical to foist off on the wgpu-hal user. For example, properly managing macOS Objective-C or Microsoft COM reference counts is best handled by using appropriate pointer types within the backend.

A desire for “defense in depth” may suggest performing additional validation in wgpu-hal when the opportunity arises, but this must be done with caution. Even experienced contributors infer the expectations their changes must meet by considering not just requirements made explicit in types, tests, assertions, and comments, but also those implicit in the surrounding code. When one sees validation or state-tracking code in wgpu-hal, it is tempting to conclude, “Oh, wgpu-hal checks for this, so wgpu-core needn’t worry about it - that would be redundant!” The responsibility for exhaustive validation always rests with wgpu-core, regardless of what may or may not be checked in wgpu-hal.

To this end, any “defense in depth” validation that does appear in wgpu-hal for requirements that wgpu-core should have enforced should report failure via the unreachable! macro, because problems detected at this stage always indicate a bug in wgpu-core.

Debugging

Most of the information on the wiki Debugging wgpu Applications page still applies to this API, with the exception of API tracing/replay functionality, which is only available in wgpu-core.

Modules

Structs

Enums

Constants

Statics

  • Stores the text of any validation errors that have occurred since the last call to get_and_reset.

Traits

Type Aliases