vulkane 0.4.0

Vulkan API bindings generated entirely from vk.xml, with a complete safe RAII wrapper covering compute and graphics: instance/device/queue, buffer, image, sampler, render pass, framebuffer, graphics + compute pipelines, swapchain, a VMA-style sub-allocator with TLSF + linear pools and defragmentation, sync primitives (fences, binary + timeline semaphores, sync2 barriers), query pools, and an optional naga GLSL/WGSL→SPIR-V feature. Supports Vulkan 1.2.175 onward — swap vk.xml and rebuild.
docs.rs failed to build vulkane-0.4.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: vulkane-0.4.4

Vulkane

Vulkan for Rust: complete bindings generated from the official vk.xml specification, plus a safe RAII wrapper that covers compute and graphics end-to-end (instance, device, buffer, image, sampler, render pass, graphics + compute pipelines, swapchain, a VMA-style sub-allocator with TLSF + linear pools and defragmentation, fences + binary/timeline semaphores, query pools, validation layers, and more).

CI Crates.io License

What is Vulkane?

Vulkane is a Rust crate that generates complete Vulkan API bindings from vk.xml, the official machine-readable Vulkan specification maintained by Khronos. Every type, constant, struct, enum, function pointer, and dispatch table is derived from the XML at build time. Nothing is hardcoded.

To target a different Vulkan version, swap vk.xml (or set the VK_VERSION environment variable) and rebuild — that's the entire upgrade procedure.

Vulkane exposes Vulkan through two complementary APIs:

  • vulkane::raw — direct FFI bindings, exactly as the spec defines them. Maximum control, zero overhead.

  • vulkane::safe — RAII wrappers with automatic cleanup, Result-based error handling, and type-safe enums. Covers the complete compute path and the complete graphics path:

    • Instance / DeviceInstance, InstanceCreateInfo (with optional validation layer + debug-utils messenger), PhysicalDevice, PhysicalDeviceGroup, Device (always backed by a possibly-singleton physical-device group), Queue, DeviceFeatures builder for the Vulkan 1.0/1.1/1.2/1.3 feature chain.
    • MemoryBuffer, Image (2D, color/depth attachment, storage, sampled), ImageView, Sampler, DeviceMemory, plus a VMA-style sub-allocator (Allocator) with TLSF + linear pools, custom user pools, dedicated allocations, persistent mapping, defragmentation, and memory budget queries.
    • ComputeComputePipeline (with specialization constants and pipeline cache), PipelineLayout (with push constants), DescriptorSetLayout, DescriptorPool, DescriptorSet (storage buffer / uniform buffer / storage image / combined image sampler), ShaderModule (takes &[u32] SPIR-V), with an optional naga feature for GLSL → SPIR-V at runtime.
    • GraphicsRenderPass, Framebuffer, GraphicsPipeline (with a focused GraphicsPipelineBuilder), Surface (Win32 / Wayland / Xlib / Xcb / Metal), Swapchain with the standard acquire / submit / present semaphore loop.
    • SynchronizationFence, Semaphore (binary and timeline), command-buffer memory_barrier / image_barrier plus their Synchronization2 64-bit counterparts, QueryPool (timestamps + pipeline statistics), and the Vulkan 1.2 vkGetBufferDeviceAddress path for bindless / pointer-chasing compute kernels.

Why Vulkane?

If you're already using ash, here's the trade-off:

Aspect vulkane ash
Source of truth vk.xml (parsed at build time) Hand-curated bindings module
New Vulkan version support Swap vk.xml, rebuild Wait for crate update
New extension support Automatic on next vk.xml fetch Wait for crate update
Generated lines of Rust ~52,000 ~30,000
Hand-written FFI None Some
Safe-API surface Compute + graphics RAII layer in vulkane::safe (instance, device, buffer, image, sampler, render pass, framebuffer, graphics + compute pipelines, swapchain, allocator, sync, queries) Raw FFI; safe wrappers come from third-party crates (vulkano, vulkanalia)
Sub-allocator TLSF + linear pools, custom user pools, defragmentation, memory budget queries built into vulkane::safe::Allocator None — you BYO via gpu-allocator or vk-mem
GLSL→SPIR-V at runtime Optional naga feature None
Maturity New Battle-tested

Vulkane is the right choice if:

  • you want bindings that always match the Vulkan version your driver supports
  • you want to opt into new extensions the moment Khronos publishes them
  • you don't want to depend on a third party shipping updates
  • you want to read the Vulkan spec and have confidence the bindings reflect it exactly

Ash is the right choice if:

  • you want maximum maturity and a large body of existing example code
  • you prefer ergonomic builders over raw FFI structs
  • you're building higher-level wrappers and want a stable foundation

The two crates are not mutually exclusive. Vulkane targets the same C ABI as the Vulkan spec, so a downstream crate can build a safe wrapper layer on top of either.

First impression — safe API

use vulkane::safe::{
    ApiVersion, Buffer, BufferCreateInfo, BufferUsage, CommandPool, DeviceCreateInfo,
    DeviceMemory, Fence, Instance, InstanceCreateInfo, MemoryPropertyFlags, QueueCreateInfo,
    QueueFlags,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load Vulkan and create an instance — RAII handles cleanup.
    let instance = Instance::new(InstanceCreateInfo {
        application_name: Some("hello-vulkane"),
        api_version: ApiVersion::V1_0,
        ..Default::default()
    })?;

    // Pick a physical device with a transfer-capable queue.
    let physical = instance
        .enumerate_physical_devices()?
        .into_iter()
        .find(|pd| pd.find_queue_family(QueueFlags::TRANSFER).is_some())
        .ok_or("no compatible GPU")?;
    let qf = physical.find_queue_family(QueueFlags::TRANSFER).unwrap();

    // Create a logical device with one queue.
    let device = physical.create_device(DeviceCreateInfo {
        queue_create_infos: &[QueueCreateInfo {
            queue_family_index: qf,
            queue_priorities: vec![1.0],
        }],
        ..Default::default()
    })?;
    let queue = device.get_queue(qf, 0);

    // Allocate a host-visible buffer.
    let buffer = Buffer::new(&device, BufferCreateInfo {
        size: 1024,
        usage: BufferUsage::TRANSFER_DST,
    })?;
    let req = buffer.memory_requirements();
    let mt = physical
        .find_memory_type(req.memory_type_bits,
            MemoryPropertyFlags::HOST_VISIBLE | MemoryPropertyFlags::HOST_COHERENT)
        .unwrap();
    let mut memory = DeviceMemory::allocate(&device, req.size, mt)?;
    buffer.bind_memory(&memory, 0)?;

    // Record vkCmdFillBuffer and submit it to the GPU.
    let pool = CommandPool::new(&device, qf)?;
    let mut cmd = pool.allocate_primary()?;
    cmd.begin()?.fill_buffer(&buffer, 0, 1024, 0xDEADBEEF);
    let fence = Fence::new(&device)?;
    queue.submit(&[&cmd], Some(&fence))?;
    fence.wait(u64::MAX)?;

    // Read it back from the host side.
    let mapped = memory.map()?;
    assert_eq!(&mapped.as_slice()[..4], &0xDEADBEEFu32.to_ne_bytes());
    println!("GPU filled the buffer with 0xDEADBEEF");

    // Everything drops here in the right order — no manual vkDestroy* calls.
    Ok(())
}

Bundled examples

Example What it shows
device_info Raw-API instance creation, physical device enumeration, queue family inspection
fill_buffer Safe-API: full host→GPU→host round trip via vkCmdFillBuffer
compute_square Safe-API: complete compute path — load SPIR-V, descriptor set, compute pipeline, dispatch, verify the GPU squared every element
compute_image_invert Safe-API: 2D storage image compute — RGBA8 round trip with layout transitions, copy buffer↔image, dispatch invert shader, verify per-pixel
compile_shader (--features naga) Compile every *.comp / *.vert / *.frag / *.wgsl under examples/shaders/ to SPIR-V using the optional naga feature
headless_triangle Safe-API: full graphics pipeline — render pass, framebuffer, graphics pipeline, vertex/fragment shaders, draw, copy back, verify pixels were rasterized
textured_quad Safe-API: headless textured quad — upload a 4×4 RGBA8 checkerboard, sample it with a Sampler from a WGSL fragment shader (separated SAMPLED_IMAGE + SAMPLER descriptors), verify the rendered pixels picked up the texture colors
windowed_triangle Safe-API: opens a real OS window via winit, creates a Win32 / Wayland / Xlib / Xcb / Metal surface, builds a swapchain, and runs the standard acquire/submit/present loop with two frames in flight

The compute examples and the headless triangle run in CI on every platform via Mesa Lavapipe; the windowed triangle is built but not run in CI (it requires a display server). To run any of them locally:

cargo run -p vulkane --features fetch-spec --example headless_triangle
cargo run -p vulkane --features fetch-spec --example compute_square
cargo run -p vulkane --features fetch-spec --example windowed_triangle  # opens a window

Quick Start

Add to your Cargo.toml:

[dependencies]
vulkane = "0.1"

If you don't have a local copy of vk.xml (most people don't), enable auto-download:

[dependencies]
vulkane = { version = "0.1", features = ["fetch-spec"] }

Providing vk.xml

The build script resolves the Vulkan specification in this order:

  1. VK_XML_PATH environment variable — point to any local vk.xml file:

    VK_XML_PATH=/path/to/vk.xml cargo build
    
  2. Local copy — place vk.xml at spec/registry/Vulkan-Docs/xml/vk.xml relative to the workspace root.

  3. Auto-download (requires fetch-spec feature) — downloads from the Khronos GitHub repository:

    # Download the latest version
    cargo build --features fetch-spec
    
    # Pin to a specific version
    VK_VERSION=1.3.250 cargo build --features fetch-spec
    

    Pinned versions are cached permanently; the latest is refreshed after 24 hours.

Supported Vulkan Versions

Vulkane supports Vulkan specification versions 1.2.175 through the latest release.

Version 1.2.175 is the minimum because it's the first version that introduced the VK_MAKE_API_VERSION / VK_API_VERSION_* macro family, which replaced the deprecated VK_MAKE_VERSION / VK_VERSION_* macros. Vulkane transpiles these macros from C to Rust const fn at build time, so they must be present in the specification.

Vulkan version Status
1.2.175 (minimum) Tested
1.3.250 Tested
1.4.348 Tested
latest (main branch) Tested

What Gets Generated

Everything in this table is derived entirely from vk.xml at build time:

Category Count (recent vk.xml) Description
Structs ~1,478 #[repr(C)] with correct pointer/array/const field types
Unions ~14 #[repr(C)] pub union with unsafe { mem::zeroed() } defaults
Type aliases ~1,343 Dispatchable handles (*mut c_void), non-dispatchable handles (u64), bitmasks, base types
Rust enums ~148 #[repr(i32)] with extension values merged from all extensions
Constants ~3,064 Including bitmask flag values emitted as pub const
Function pointer typedefs ~657 unsafe extern "system" fn(...) for every Vulkan command
Dispatch tables 3 Entry, instance, and device tables generated from first-parameter classification
Version functions All vk_make_api_version, vk_api_version_major, etc. transpiled from C macros
Doc comments ~960 Harvested from vk.xml <comment> and comment attributes

Features

Feature Description
build-support (default) Enables XML parsing and code generation during build
fetch-spec Enables automatic download of vk.xml from the Khronos GitHub repository
naga Pulls in naga 29 with glsl-in + wgsl-in + spv-out only. Exposes vulkane::safe::naga::compile_glsl(source, stage) and compile_wgsl(source) for runtime GLSL/WGSL → SPIR-V compilation. Disabled by default — users with their own SPIR-V pay nothing.

Loader API

The runtime loader is a thin wrapper around libloading:

use vulkane::raw::VulkanLibrary;

let library = VulkanLibrary::new()?;             // dlopen vulkan-1.dll / libvulkan.so.1
let entry = unsafe { library.load_entry() };     // global functions
let inst  = unsafe { library.load_instance(instance) };       // instance functions
let dev   = unsafe { library.load_device(instance, device) }; // device functions

Each dispatch table contains every relevant Vulkan command as an Option<fn_ptr> field. Functions that the loaded Vulkan implementation doesn't support are None.

Result helpers

use vulkane::raw::VkResultExt;

unsafe { vkCreateInstance(&info, std::ptr::null(), &mut instance) }
    .into_result()?;  // VkResult::SUCCESS -> Ok(()), anything else -> Err(VkResult)

VkResult implements std::error::Error so you can use ? directly with Box<dyn Error> return types.

Architecture

       vk.xml
         │
         ▼
  vulkan_gen crate
   ┌──────────────┐
   │ tree_parser  │  (roxmltree DOM parser)
   │      │       │
   │      ▼       │
   │  vk_types    │  (parsed data structures)
   │      │       │
   │      ▼       │
   │   codegen    │  (8 generator modules + assembler)
   └──────┬───────┘
          │
          ▼
    vulkane crate
   ┌─────────────────────────┐
   │  build.rs               │  resolves vk.xml, calls
   │                         │  vulkan_gen::generate_bindings
   │                         │
   │  raw/                   │
   │   bindings.rs (~52k LoC, generated from vk.xml)
   │   loader.rs   (VulkanLibrary, dispatch tables)
   │   result.rs   (VkResultExt, vk_check!)
   │                         │
   │  safe/                  │
   │   instance.rs / device.rs / physical.rs
   │   buffer.rs / image.rs / memory.rs / sampler
   │   render_pass.rs / graphics_pipeline.rs
   │   pipeline.rs (compute) / descriptor.rs / shader.rs
   │   surface.rs / swapchain.rs
   │   command.rs / sync.rs / query.rs
   │   features.rs / debug.rs
   │   allocator/ (TLSF + linear + dedicated, defrag,
   │              custom user pools, statistics, budget)
   │   naga.rs (optional GLSL → SPIR-V via the `naga` feature)
   └─────────────────────────┘

The parser uses DOM-based XML parsing (roxmltree) to correctly handle nested elements like <member> and <param> that contain mixed text and child element content. The safe wrapper layer is hand-written on top of the generated raw bindings — every safe type is a thin RAII shell around a VkFoo handle plus an Arc<DeviceInner> (or Arc<InstanceInner>) for parent lifetime tracking.

License

Licensed under either of:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in vulkane by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.