Expand description
Unofficial bindings for the Vulkan Graphics API
The goal is to provide a nice and safer way to use Vulkan with Rust while having in most cases no overhead.
These bindings try to replicate an experience similar to using the official Vulkan C++ bindings on Rust which makes it differ from the popular Vulkan crate ash.
§Features
§Safety
Vulkan handles can be mostly be seen as references to Vulkan object. As such, using this crate, Vulkan handles cannot be NULL
(there is no vk::NULL_HANDLE value), being given a handle means you can assume it is not null and valid.
All vulkan functions taking as parameters handles which can be null now instead take as parameter an Option<Handle>
.
Concerning command safety, I decided to take an approach similar to the cxx
crate: guarantee safety at the boundary between rust and Vulkan API/driver code (likely written in C). These bindings also try to ensure that anything that can be checked to be according to the Vulkan Specification while being mostly cost-free / ran at compile time is done this way.
When using the Vulkan API, driver code will be called which is possibly proprietary and on which you have no control. Even if you completely follow the Vulkan Specification and have no validation error, you might still get some surprise segfault when running your program on some GPUs/drivers (I speak from experience). As such the first solution would be to make every vulkan command or function calling a vulkan command unsafe, but this is from my point of view counter-productive. I chose to keep most Vulkan commands safe. The exceptions are destroy commands for which you must ensure everything created by what you are about to destroyed have already been destroyed.
Note that these bindings assume the driver implementation complies, at least minimally, with the Vulkan Specification. In particular if the driver returns a completely unkown VkStatus
code (which is not allowed by the specification), this
will lead to undefined behavior in the rust code.
§Smart handles
Similar to the C++ bindings, this binding groups vulkan commands by the handle which ‘executes’ it. Therefore the following code using ash:
unsafe {
device.begin_command_buffer(
cmd_buffer,
&vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT)
)
...
swapchain_khr.queue_present_khr(queue, &present_info)?;
}
becomes with vulkanite:
cmd_buffer.begin(
&vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::OneTimeSubmit)
)?;
queue.present_khr(&present_info)?;
§Result
The Vulkan VkResult
enum has been renamed to vk::Status (this is the only enum/structure whose name is different compared to the C bindings).
Instead vk::Result<A>
is defined as Result<A, vk::Status> and all Vulkan commands which return a Result in the final specification instead
return a vk::Result now:
let device = instance.create_device(&device_info)?;
Moreover, if a Vulkan command can return multiple success codes, the status will be part of the result:
let (status, image_idx) = self.device.acquire_next_image_khr(
&self.swapchain_objects.swapchain,
u64::MAX,
Some(semaphore),
None, // not signaling any fence
)?;
if status == vk::Status::vk::Status::SuboptimalKHR {
...
}
§Slices
Every vulkan command or structure that takes as input a length along a raw pointer now takes as input a slice. If a length is used for multiple raw pointers, the matching slices must be entered together and it is checked that they have the same length.
impl CommandBuffer {
pub fn set_viewport(&self, first_viewport: u32, viewports: impl AsSlice<vk::Viewport>);
}
cmd_buffer.set_viewport(0, &[vk::Viewport{..}, vk::Viewport{..}])
let color_attachment = vec![...];
let resolve_attachment = vec![...];
let subpass_description = vk::SubpassDescription::default()
// the resolve attachment field is optional for SubpassDescription
// but if it is supplied it must have the same length as color_attachment
.color_attachment(&color_attachment, Some(&resolve_attachment))
....;
§Arrays as command outputs
For Vulkan commands that return an array (a length pointer and data pointer), it is instead possible to use as output any structure implementing the DynamicArray (and AdvancedDynamicArray for the case of smart handles). This is the case of Vec:
let surface_formats : Vec<_> = physical_device.get_surface_formats_khr(Some(surface))?;
The reason : Vec<_>
has to be added is when using the smallvec
feature, it is possible to do the following:
// won't make any heap allocation if you have less than 3 GPUs
let physical_devices: SmallVec<[_; 3]> = instance.enumerate_physical_devices()?;
Note that the case of vk::Status::Incomplete is handled by the implementation: you can assume vk::Status::Incomplete is never returned
§Structure chain as command outputs
In case a vulkan command returns a structure that can be extended, a tuple can be used to specify which structure to ieve:
let (vk_props, vk11_props) : (_, vk::PhysicalDeviceVulkan11Properties) = physical_device.get_properties2();
println!("Max supported API is {}, Subgroup size is {}", vk_props.properties.api_version, vk11_props.subgroup_size);
In case you don’t want to use a structure chain, you have the 2 following choices (the type cannot be deduced explicitely in the default case):
let vk_props: vk::PhysicalDeviceProperties2 = physical_device.get_properties2();
let (vk_props,) = physical_device.get_properties2();
Note that the structure chain integrity is checked as compile time: the following code will lead to a compile error vk::PhysicalDeviceVulkan11Features cannot be used in a structure chain whose head is vk::PhysicalDeviceProperties2):
let (_, _) : (_, vk::PhysicalDeviceVulkan11Features) = physical_device.get_properties2();
This compile-time check applies also to the next part:
§Structure chain as command inputs
The first possible way to build a structure chain is to use structure.push_next(&mut next_structure)
. There are also multiple
macros provided to do the same in a cleaner way similar to the C++ bindings:
let mut device_info = vk_headers::structure_chain!(
vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_info)
.enabled_features(Some(&features))
.enabled_extension(&required_extensions),
vk::PhysicalDeviceShaderObjectFeaturesEXT::default().shader_object(true)
);
if does_not_support_extension {
device_info.unlink::<vk::PhysicalDeviceShaderObjectFeaturesEXT>()
}
if does_not_support_feature {
device_info.get_mut::<vk::PhysicalDeviceShaderObjectFeaturesEXT>().shader_object(false);
}
let device = physical_device.create_device(device_info.as_ref())?;
§Features
The following features are available:
loaded
: Allow the crate to dynamically load the vulkan library using thelibloading
crate, see Dispatcher::new_loadedsmallvec
: Add support for the smallvec crate to minimize heap allocations, enabling this feature allows the following:let physical_devices: SmallVec<[_; 3]> = instance.enumerate_physical_devices()?;
.arrayvec
: Add support for the arrayvec crate to minimize heap allocations, enabling this feature allows the following:let pipeline: ArrayVec<_; 1> = device.create_compute_pipelines(None, &create_info)?;
.raw-window-handle
: Add interoperability with the raw-window-handle crate, to create surfaces from raw handles, see the window module
§MSRV
The current MSRV for this crate is Rust 1.77 (C-String literals are heavily used). It is not planned to increase this version anytime soon and if this happens a reasonable (at least 6 months old) MSRV will be chosen.
Note that these bindings are still a Work-In-Process, the public API may see breaking changes if this improves safety or how nice to use the code is.
Please be aware that this crate should not be considered production ready yet, breaking changes are to be expected in the future versions.
Modules§
Macros§
- create_
structure_ chain - flagbits
- Quality-Of-Life macro to create bitflags with multiple flags.
- include_
spirv - Includes a file as a reference to a u32 array. This macro is really similar to rust macro include_bytes, the main difference is that data is provided as a u32 array instead of a u8 array As a consequence the data is 4-byte aligned. Moreover, if the file included has not a size which is a multiple of 4 bytes, it will cause a compile-time error The main purpose of this macro in this library is to embed spirv code in a program, as include_bytes! requires at least an additional copy and can easily be misused for this case
- structure_
chain
Structs§
- Borrowed
Handle - This represents a reference to an handle Its internal representation is the same as the handle
- Borrowed
MutHandle - This represents a reference to a mutable handle Its internal representation is the same as the handle
- Default
Allocator - The default vulkan allocator, Using this allocator will let Vulkan use the default allocator It is the same as specifying NULL (on C) or None (on Ash) every time the parameter pAllocator is required
- Dynamic
Dispatcher - Dynamic dispatcher Dispatcher implementation loading commands in static memory. This is a cost-free abstraction assuming you follow the safety rule below. Cloning this object is free (the object has size 0 and the clone function is empty) and it never makes any heap allocation. Use this dispatcher if you can.
- Header
- Multi
Dispatcher - MultiDispatcher Dispatcher implementation which stores vulkan commands on the heap using smart pointers This adds a small overhead:
- Structure
Chain Vec - Structure Chain that can take an arbitrary number of structures extending it This is done by putting the structures on the heap
Traits§
- Advanced
Dynamic Array - When using advanced commands, we must be able to provide a dynamic array for both the type and the underlying type This trait allows given a type T with a dynamic array to get a dynamic array for another type S
- Alias
- If A implements
Alias<B>
, this means A and B have exactly the same memory representation Thus transmuting from A to B is safe - Allocator
- See https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#memory-allocation Cost-free allocator implementation for Vulkan Vulkan allows a custom memory allocator to be specified for host allocations Note that the vulkan implementation is not required to use this allocator (for example it might have to allocate memory with execute permissions), but you will at least receive the Allocator::on_internal_alloc and Allocator::on_internal_free notifications
- AsSlice
- Custom type which represents types that can be seen as slices This is especially useful for this crate as there are multiple commands/structs which accept slices but for which one would usually only supply one element using std::slice::from_ref.
- Dispatcher
- Dispatcher type used to hold the vk::CommandsDispatcher object
The dispatcher type loads once all the vulkan commands that can be used then is used
every time a vulkan command is called.
It is initialized by calling Dispatcher::new with the GetInstanceProcAddrSignature entry point
Or when the
loaded
feature is enabled by calling Dispatcher::new_loaded which will load the library There are two Dispatcher implementation provided: - Dynamic
Array - A trait implemented by types which can allocate memory for an array of given size in a contiguous memory
This is used for vulkan commands returning arrays
Vec<T>
implements this trait as well as SmallVec if the smallvec feature is enabled and ArrayVec if the arrayvec feature is enabled This trait is unsafe because no allocating a memory area of the proper size when calling allocate_with_capacity can cause undefined behavior when using this library - Extendable
Structure - Extendable
Structure Base - A trait implemented by Vulkan C structs whose first 2 fields are: VkStructureType sType; const void* pNext; sType must always be set to STRUCTURE_TYPE This trait contains the minimum to be object safe, ExtendableStructure extends on it
- Extending
Structure - If an extendable structure A implements ExtendingStructure< B > This means A can be used to extend B For example, VkPhysicalDeviceFeatures2 can be used to extend VkDeviceCreateInfo So vk::PhysicalDeviceFeatures2 has the trait ExtendingStructurevk::DeviceCreateInfo This is used for additional security, making it impossible to extend a structure with an extension that wasn’t planed for this structure
- Handle
- A dispatchable or non-dispatchable Vulkan Handle
- Structure
Chain - Structure chain trait
- Structure
Chain Out - Represent an object that can be used as the return value of a vulkan function that outputs a structure chain It must therefore internally represent what vulkan recognizes as a structure chain
Type Aliases§
- GetInstance
Proc Addr Signature - https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/PFN_vkGetInstanceProcAddr.html
Entry point of the vulkan library, this is used to retrieve Vulkan functions
This function can be retrieved by loading the library using Dispatcher::new_loaded, using your own library
loading code or some external libraries provide it like SDL with
SDL_Vulkan_GetVkGetInstanceProcAddr