vk.xml
1.1
, 1.2
, 1.3
The Vulkan Video bindings are experimental and still seeing breaking changes in their upstream specification, and are only provided by Ash for early adopters. All related functions and types are semver-exempt [^1] (we allow breaking API changes while releasing Ash with non-breaking semver bumps).
[^1]: generator
complexity makes it so that we cannot easily hide these bindings behind a non-default
feature flag, and they are widespread across the generated codebase.
Result
// function signature pub fn create_instance(&self, create_info: &vk::InstanceCreateInfo, allocation_callbacks: Option<&vk::AllocationCallbacks>) -> Result<Instance, InstanceError> { .. } let instance = entry.create_instance(&create_info, None) .expect("Instance creation error");
Vec<T>
instead of mutable slicespub fn get_swapchain_images(&self, swapchain: vk::SwapchainKHR) -> VkResult<Vec<vk::Image>>; let present_images = swapchain_loader.get_swapchain_images_khr(swapchain).unwrap();
Note: Functions don't return Vec<T>
if this would limit the functionality. See p_next
.
pub fn cmd_pipeline_barrier(&self, command_buffer: vk::CommandBuffer, src_stage_mask: vk::PipelineStageFlags, dst_stage_mask: vk::PipelineStageFlags, dependency_flags: vk::DependencyFlags, memory_barriers: &[vk::MemoryBarrier], buffer_memory_barriers: &[vk::BufferMemoryBarrier], image_memory_barriers: &[vk::ImageMemoryBarrier]);
Each Vulkan handle type is exposed as a newtyped struct for improved type safety. Null handles can be constructed with T::null()
, and handles may be freely converted to and from u64
with Handle::from_raw
and Handle::as_raw
for interop with non-Ash Vulkan code.
// No need to manually set the structure type let desc_alloc_info = vk::DescriptorSetAllocateInfo { descriptor_pool: self.pool, descriptor_set_count: self.layouts.len() as u32, p_set_layouts: self.layouts.as_ptr(), ..Default::default() };
// We lose all lifetime information when we call `.build()`. Be careful! let queue_info = [vk::DeviceQueueCreateInfo::builder() .queue_family_index(queue_family_index) .queue_priorities(&priorities) .build()]; // We don't need to call `.build()` here because builders implement `Deref`. let device_create_info = vk::DeviceCreateInfo::builder() .queue_create_infos(&queue_info) .enabled_extension_names(&device_extension_names_raw) .enabled_features(&features); let device: Device = instance .create_device(pdevice, &device_create_info, None) .unwrap();
To not lose this lifetime single items can be “cast” to a slice of length one with std::slice::from_ref
while still taking advantage of Deref
:
let queue_info = vk::DeviceQueueCreateInfo::builder() .queue_family_index(queue_family_index) .queue_priorities(&priorities); let device_create_info = vk::DeviceCreateInfo::builder() .queue_create_infos(std::slice::from_ref(&queue_info)) ...;
Builders have an explicit lifetime, and are marked as #[repr(transparent)]
.
#[repr(transparent)] pub struct DeviceCreateInfoBuilder<'a> { inner: DeviceCreateInfo, marker: ::std::marker::PhantomData<&'a ()>, } impl<'a> DeviceCreateInfoBuilder<'a> { //... pub fn queue_create_infos( mut self, queue_create_infos: &'a [DeviceQueueCreateInfo], ) -> DeviceCreateInfoBuilder<'a> {...} //...
Every reference has to live as long as the builder itself. Builders implement Deref
targeting their corresponding Vulkan struct, so references to builders can be passed directly to Vulkan functions.
Calling .build()
will discard that lifetime because Vulkan structs use raw pointers internally. This should be avoided as much as possible because this can easily lead to dangling pointers. If .build()
has to be called, it should be called as late as possible. Lifetimes of temporaries are extended to the enclosing statement, ensuring they are valid for the duration of a Vulkan call occurring in the same statement.
let mut variable_pointers = vk::PhysicalDeviceVariablePointerFeatures::builder(); let mut corner = vk::PhysicalDeviceCornerSampledImageFeaturesNV::builder(); ; let mut device_create_info = vk::DeviceCreateInfo::builder() .push_next(&mut corner) .push_next(&mut variable_pointers);
Pointer chains in builders differ from raw Vulkan. Instead of chaining every struct manually, you instead use .push_next
on the struct that you are going to pass into the function. Those structs then get prepended into the chain.
push_next
is also type checked, you can only add valid structs to the chain. Both the structs and the builders can be passed into push_next
. Only builders for structs that can be passed into functions will implement a push_next
.
// Bitflag vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE
// Constant vk::PipelineBindPoint::GRAPHICS,
let flag = vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE; println!("Debug: {:?}", flag); println!("Display: {}", flag); // Prints: // Debug: AccessFlags(110000000) // Display: COLOR_ATTACHMENT_READ | COLOR_ATTACHMENT_WRITE
Ash also takes care of loading the function pointers. Function pointers are split into 3 categories.
Instance
and Device
.Device
s it has created.The loader is just one possible implementation:
Custom loaders can be implemented.
Additionally, every Vulkan extension has to be loaded explicitly. You can find all extensions under ash::extensions.
use ash::extensions::khr::Swapchain; let swapchain_loader = Swapchain::new(&instance, &device); let swapchain = swapchain_loader.create_swapchain(&swapchain_create_info).unwrap();
Raw function pointers are available, if something hasn't been exposed yet in the higher level API. Please open an issue if anything is missing.
device.fp_v1_0().destroy_device(...);
use ash::extensions::{Swapchain, XlibSurface, Surface, DebugReport}; #[cfg(all(unix, not(target_os = "android")))] fn extension_names() -> Vec<*const i8> { vec![ Surface::name().as_ptr(), XlibSurface::name().as_ptr(), DebugReport::name().as_ptr() ] }
Handles from Instance or Device are passed implicitly.
pub fn create_command_pool(&self, create_info: &vk::CommandPoolCreateInfo) -> VkResult<vk::CommandPool>; let pool = device.create_command_pool(&pool_create_info).unwrap();
The default loaded
cargo feature will dynamically load the default Vulkan library for the current platform with Entry::load
, meaning that the build environment does not have to have Vulkan development packages installed.
If, on the other hand, your application cannot handle Vulkan being missing at runtime, you can instead enable the linked
feature, which will link your binary with the Vulkan loader directly and expose the infallible Entry::linked
.
You can find the examples here. All examples currently require: the LunarG Validation layers and a Vulkan library that is visible in your PATH
. An easy way to get started is to use the LunarG Vulkan SDK
Make sure that you have a Vulkan ready driver and install the LunarG Vulkan SDK.
Make sure that you have a Vulkan ready driver and install the LunarG Vulkan SDK. You also have to add the library and layers to your path. Have a look at my post if you are unsure how to do that.
Install the LunarG Vulkan SDK. The installer puts the SDK in $HOME/VulkanSDK/<version>
by default. You will need to set the following environment variables when running cargo:
VULKAN_SDK=$HOME/VulkanSDK/<version>/macOS \ DYLD_FALLBACK_LIBRARY_PATH=$VULKAN_SDK/lib \ VK_ICD_FILENAMES=$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json \ VK_LAYER_PATH=$VULKAN_SDK/share/vulkan/explicit_layer.d \ cargo run ...
Displays a triangle with vertex colors.
cd examples cargo run --bin triangle
Displays a texture on a quad.
cd examples cargo run --bin texture