Crate vulkanite

Source
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 the libloading crate, see Dispatcher::new_loaded
  • smallvec: 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§

vk
window
Functions that provide interop with the raw-window-handle crate.

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§

BorrowedHandle
This represents a reference to an handle Its internal representation is the same as the handle
BorrowedMutHandle
This represents a reference to a mutable handle Its internal representation is the same as the handle
DefaultAllocator
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
DynamicDispatcher
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
MultiDispatcher
MultiDispatcher Dispatcher implementation which stores vulkan commands on the heap using smart pointers This adds a small overhead:
StructureChainVec
Structure Chain that can take an arbitrary number of structures extending it This is done by putting the structures on the heap

Traits§

AdvancedDynamicArray
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:
DynamicArray
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
ExtendableStructure
ExtendableStructureBase
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
ExtendingStructure
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
StructureChain
Structure chain trait
StructureChainOut
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§

GetInstanceProcAddrSignature
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