Convert Figma logo to code with AI

ash-rs logoash

Vulkan bindings for Rust

1,799
186
1,799
69

Top Related Projects

4,471

Safe and rich Rust wrapper around the Vulkan API

1,799

Vulkan bindings for Rust

12,131

A cross-platform, safe, pure-Rust graphics API.

5,351

[maintenance mode] A low-overhead Vulkan-like GPU API for Rust.

4,770

💡 Experimental real-time global illumination renderer 🦀

Open-Source Vulkan C++ API

Quick Overview

Ash is a lightweight Vulkan wrapper for Rust, providing a safe and ergonomic interface to the Vulkan graphics and compute API. It aims to stay close to the original Vulkan API while leveraging Rust's safety features and abstractions.

Pros

  • Provides a safe and idiomatic Rust interface to Vulkan
  • Minimal overhead compared to raw Vulkan bindings
  • Extensive documentation and examples
  • Actively maintained and updated with new Vulkan features

Cons

  • Steeper learning curve compared to higher-level graphics libraries
  • Requires understanding of Vulkan concepts and architecture
  • May have more verbose code compared to more abstracted graphics libraries
  • Limited platform support compared to some other graphics libraries

Code Examples

Creating a Vulkan instance:

use ash::{vk, Entry};

let entry = unsafe { Entry::load().unwrap() };
let app_info = vk::ApplicationInfo::builder()
    .application_name(b"Hello, Ash!\0")
    .application_version(vk::make_api_version(0, 1, 0, 0))
    .engine_name(b"No Engine\0")
    .engine_version(vk::make_api_version(0, 1, 0, 0))
    .api_version(vk::API_VERSION_1_0);

let create_info = vk::InstanceCreateInfo::builder()
    .application_info(&app_info);

let instance = unsafe {
    entry.create_instance(&create_info, None).unwrap()
};

Creating a logical device:

let physical_devices = unsafe { instance.enumerate_physical_devices().unwrap() };
let physical_device = physical_devices[0]; // Choose the first available device

let queue_family_index = 0; // Assume the first queue family supports graphics
let queue_priorities = [1.0];
let queue_create_info = vk::DeviceQueueCreateInfo::builder()
    .queue_family_index(queue_family_index)
    .queue_priorities(&queue_priorities);

let device_create_info = vk::DeviceCreateInfo::builder()
    .queue_create_infos(std::slice::from_ref(&queue_create_info));

let device = unsafe {
    instance.create_device(physical_device, &device_create_info, None).unwrap()
};

Getting Started

  1. Add Ash to your Cargo.toml:

    [dependencies]
    ash = "0.37"
    
  2. In your Rust file, import Ash and create a Vulkan instance:

    use ash::{vk, Entry};
    
    fn main() {
        let entry = unsafe { Entry::load().unwrap() };
        let app_info = vk::ApplicationInfo::builder()
            .application_name(b"My Vulkan App\0")
            .application_version(vk::make_api_version(0, 1, 0, 0))
            .engine_name(b"No Engine\0")
            .engine_version(vk::make_api_version(0, 1, 0, 0))
            .api_version(vk::API_VERSION_1_0);
    
        let create_info = vk::InstanceCreateInfo::builder()
            .application_info(&app_info);
    
        let instance = unsafe {
            entry.create_instance(&create_info, None).unwrap()
        };
    
        // Use the instance for further Vulkan operations
    }
    

Competitor Comparisons

4,471

Safe and rich Rust wrapper around the Vulkan API

Pros of Vulkano

  • Higher-level abstraction, making Vulkan development more accessible
  • Built-in safety features and memory management
  • Automatic synchronization and command buffer generation

Cons of Vulkano

  • Potentially lower performance due to abstraction overhead
  • Less flexibility for fine-grained control over Vulkan operations
  • Steeper learning curve for developers already familiar with raw Vulkan

Code Comparison

Vulkano:

let device = Device::new(physical, &Features::none(), &DeviceExtensions::none(), None)?;
let queue = queues.next().unwrap();
let image = StorageImage::new(device.clone(), Dimensions::Dim2d { width: 1024, height: 1024 }, Format::R8G8B8A8_UNORM, Some(queue.family()))?;

Ash:

let device = unsafe { instance.create_device(physical_device, &device_create_info, None)? };
let queue = unsafe { device.get_device_queue(queue_family_index, 0) };
let image = unsafe { device.create_image(&image_create_info, None)? };
1,799

Vulkan bindings for Rust

Pros of ash

  • More actively maintained with frequent updates
  • Larger community and contributor base
  • Better documentation and examples

Cons of ash

  • Potentially more complex API due to broader feature set
  • May have a steeper learning curve for beginners

Code Comparison

ash:

use ash::vk;

let instance = entry.create_instance(&create_info, None)?;
let physical_device = instance.enumerate_physical_devices()?[0];
let device = instance.create_device(physical_device, &device_create_info, None)?;

ash>:

use ash::vk;

let instance = entry.create_instance(&create_info, None)?;
let physical_device = instance.enumerate_physical_devices()?[0];
let device = instance.create_device(physical_device, &device_create_info, None)?;

The code comparison shows that both libraries have similar syntax and usage patterns for basic Vulkan operations. However, ash may offer more advanced features and options in its API that are not immediately apparent in this simple example.

12,131

A cross-platform, safe, pure-Rust graphics API.

Pros of wgpu

  • Higher-level abstraction, easier to use for beginners
  • Cross-platform support (Web, Desktop, Mobile)
  • Built-in safety features and error handling

Cons of wgpu

  • Less fine-grained control over GPU operations
  • Potentially higher overhead due to abstraction layer
  • May not expose all Vulkan features directly

Code Comparison

wgpu example:

let device = adapter.request_device(&Default::default(), None).await?;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
    label: None,
    source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
});

ash example:

let device = unsafe {
    instance.create_device(physical_device, &device_create_info, None)?
};
let shader_module = unsafe {
    device.create_shader_module(&vk::ShaderModuleCreateInfo::builder()
        .code(include_bytes!("shader.spv"))
        .build(), None)?
};

wgpu provides a more straightforward API with built-in async support and WGSL shaders, while ash offers direct Vulkan bindings requiring manual safety handling and SPIR-V shaders.

5,351

[maintenance mode] A low-overhead Vulkan-like GPU API for Rust.

Pros of gfx

  • Higher-level abstraction, simplifying cross-platform graphics development
  • Supports multiple backends (Vulkan, Metal, DX12, OpenGL)
  • More Rust-idiomatic API design

Cons of gfx

  • Potentially lower performance due to abstraction overhead
  • Less direct control over low-level graphics API features
  • Steeper learning curve for developers familiar with Vulkan

Code Comparison

gfx:

let mut encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into();
encoder.clear(&data.out, [0.1, 0.2, 0.3, 1.0]);
encoder.draw(&slice, &pso, &data);

ash:

let clear_values = [vk::ClearValue::default()];
let render_pass_begin_info = vk::RenderPassBeginInfo::builder()
    .render_pass(render_pass)
    .framebuffer(framebuffer)
    .render_area(render_area)
    .clear_values(&clear_values);

gfx provides a higher-level API with more abstraction, while ash offers direct access to Vulkan commands. gfx simplifies cross-platform development but may sacrifice some performance and fine-grained control. ash provides a thin wrapper around Vulkan, offering more direct control but requiring more verbose code and platform-specific implementations.

4,770

💡 Experimental real-time global illumination renderer 🦀

Pros of Kajiya

  • Focuses on real-time path tracing and advanced rendering techniques
  • Provides a complete rendering engine with built-in features like denoising and temporal accumulation
  • Offers a higher-level abstraction for graphics programming

Cons of Kajiya

  • More specialized and less flexible than Ash for general-purpose Vulkan development
  • Steeper learning curve for developers not familiar with path tracing concepts
  • Less mature and potentially less stable compared to Ash

Code Comparison

Ash (low-level Vulkan binding):

let instance = ash::Instance::new(&create_info, None)?;
let physical_device = instance.enumerate_physical_devices()?.pop().unwrap();
let device = instance.create_device(physical_device, &device_create_info, None)?;

Kajiya (high-level rendering engine):

let mut renderer = Renderer::new(&window)?;
renderer.set_scene(&scene);
renderer.render()?;

Summary

Ash provides low-level Vulkan bindings for Rust, offering flexibility and control over graphics programming. Kajiya, on the other hand, is a specialized rendering engine focused on real-time path tracing, providing higher-level abstractions and built-in features. While Ash is more suitable for general-purpose Vulkan development, Kajiya excels in advanced rendering techniques but with a narrower scope and potentially steeper learning curve.

Open-Source Vulkan C++ API

Pros of Vulkan-Hpp

  • Provides a more C++-friendly API with RAII wrappers and type safety
  • Officially maintained by Khronos Group, ensuring up-to-date compatibility
  • Extensive documentation and examples available

Cons of Vulkan-Hpp

  • Limited to C++ development, not suitable for Rust projects
  • May introduce slight overhead due to C++ abstractions
  • Steeper learning curve for developers new to C++ or modern C++ features

Code Comparison

Vulkan-Hpp:

vk::Instance instance;
vk::InstanceCreateInfo createInfo;
vk::Result result = vk::createInstance(&createInfo, nullptr, &instance);

ash:

let entry = ash::Entry::new()?;
let instance = entry.create_instance(&create_info, None)?;

Summary

Vulkan-Hpp offers a more idiomatic C++ experience with strong type safety and RAII principles, while ash provides a Rust-native interface to Vulkan. Vulkan-Hpp benefits from official Khronos support and extensive documentation, but is limited to C++ development. ash, on the other hand, integrates seamlessly with Rust's ecosystem and safety features. The choice between the two largely depends on the target language and project requirements.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Ash

A very lightweight wrapper around Vulkan

Crates.io Version Documentation Build Status LICENSE LICENSE Join the chat at https://gitter.im/MaikKlein/ash MSRV

Overview

  • A true Vulkan API without compromises
  • Convenience features without limiting functionality
  • Additional type safety
  • Device local function pointer loading
  • No validation, everything is unsafe
  • Lifetime-safety on structs created with the builder pattern
  • Generated from vk.xml
  • Support for Vulkan 1.1, 1.2, 1.3
  • no_std support

⚠️ Semver compatibility warning

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).

Features

Explicit returns with 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 slices

pub 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.

Slices

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<'_>]);

Strongly typed handles

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.

Builder pattern

let queue_info = [vk::DeviceQueueCreateInfo::default()
    .queue_family_index(queue_family_index)
    .queue_priorities(&priorities)];

let device_create_info = vk::DeviceCreateInfo::default()
    .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();

Pointer chains

Use base.push_next(ext) to insert ext at the front of the pointer chain attached to base.

let mut variable_pointers = vk::PhysicalDeviceVariablePointerFeatures::default();
let mut corner = vk::PhysicalDeviceCornerSampledImageFeaturesNV::default();

let mut device_create_info = vk::DeviceCreateInfo::default()
    .push_next(&mut corner)
    .push_next(&mut variable_pointers);

The generic argument of .push_next() only allows valid structs to extend a given struct (known as structextends in the Vulkan registry, mapped to Extends* traits). Only structs that are listed one or more times in any structextends will implement a .push_next().

Flags and constants as associated constants

// Bitflag
vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE
// Constant
vk::PipelineBindPoint::GRAPHICS,

Debug/Display for Flags

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

Function pointer loading

Ash also takes care of loading the function pointers. Function pointers are split into 3 categories.

  • Entry: Loads the Vulkan library. Needs to outlive Instance and Device.
  • Instance: Loads instance level functions. Needs to outlive the Devices it has created.
  • Device: Loads device local functions.

The loader is just one possible implementation:

  • Device level functions are retrieved on a per device basis.
  • Everything is loaded by default, functions that failed to load are initialized to a function that always panics.
  • Do not call Vulkan 1.1 functions if you have created a 1.0 instance. Doing so will result in a panic.

Custom loaders can be implemented.

Extension loading

Additionally, every Vulkan extension has to be loaded explicitly. You can find all extensions directly under ash::* in a module with their prefix (e.g. khr or ext).

use ash::khr;
let swapchain_loader = khr::swapchain::Device::new(&instance, &device);
let swapchain = swapchain_loader.create_swapchain(&swapchain_create_info).unwrap();

Raw function pointers

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(...);

Support for extension names

use ash::{ext, khr};
#[cfg(all(unix, not(target_os = "android")))]
fn extension_names() -> Vec<*const i8> {
    vec![
        khr::surface::NAME.as_ptr(),
        khr::xlib_surface::NAME.as_ptr(),
        ext::debug_utils::NAME.as_ptr(),
    ]
}

Implicit handles

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();

Optional linking

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.

Use in no_std environments

Ash can be used in no_std environments (with alloc) by disabling the std feature.

Example

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

Windows

Make sure that you have a Vulkan ready driver and install the LunarG Vulkan SDK.

Linux

Install a Vulkan driver for your graphics hardware of choice, and (optionally) the Validation Layers via your package manager:

macOS

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 ...

Triangle

Displays a triangle with vertex colors.

cargo run -p ash-examples --bin triangle

screenshot

Texture

Displays a texture on a quad.

cargo run -p ash-examples --bin texture

texture

Useful resources

Examples

Utility libraries

  • vk-sync - Simplified Vulkan synchronization logic, written in rust.
  • vk-mem-rs - This crate provides an FFI layer and idiomatic rust wrappers for the excellent AMD Vulkan Memory Allocator (VMA) C/C++ library.
  • gpu-allocator - GPU Memory allocator written in pure Rust for Vulkan and DirectX 12.
  • lahar - Tools for asynchronously uploading data to a Vulkan device.

Libraries that use ash

  • gfx-rs - gfx-rs is a low-level, cross-platform graphics abstraction library in Rust.

A thanks to

Footnotes

  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.