# 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 — from instance creation through shadow mapping and deferred
shading.
[](https://github.com/ciresnave/vulkane/actions/workflows/ci.yml)
[](https://crates.io/crates/vulkane)
[](https://docs.rs/vulkane)
[](#license)
## What is Vulkane?
Vulkane generates **complete** Vulkan API bindings from `vk.xml`, the
official machine-readable specification maintained by Khronos. Every
type, constant, struct, enum, function pointer, and dispatch table is
derived at build time. **Nothing is hardcoded.**
To target a different Vulkan version, swap `vk.xml` and rebuild.
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, typed flags, and convenience
helpers. Covers compute **and** graphics:
- **Instance / Device** — `Instance`, `PhysicalDevice`,
`PhysicalDeviceGroup`, `Device`, `Queue`, `DeviceFeatures`
builder (1.0 / 1.1 / 1.2 / 1.3 features).
- **Memory** — `Buffer`, `Image`, `ImageView`, `Sampler`,
`DeviceMemory`, plus a **VMA-style sub-allocator** (`Allocator`)
with TLSF + linear pools, custom pools, dedicated allocations,
persistent mapping, defragmentation, and budget queries.
- **Convenience helpers** — `Buffer::new_bound`,
`Image::new_2d_bound`, `Queue::upload_buffer<T>`,
`Queue::upload_image_rgba`, `Queue::one_shot` — collapse the
5-step allocate-bind pattern into one call.
- **Compute** — `ComputePipeline`, `PipelineLayout`,
`DescriptorSet`, `ShaderModule`, specialization constants,
pipeline cache, push constants.
- **Graphics** — `RenderPass` (with `simple_color` shortcut),
`Framebuffer`, `GraphicsPipelineBuilder` (depth bias, `CompareOp`,
`InputRate`, multi-attachment blend, dynamic viewport/scissor),
`Surface` (Win32 / Wayland / Xlib / Xcb / Metal), `Swapchain`.
- **Synchronization** — typed `PipelineStage` / `AccessFlags`
(plus 64-bit `PipelineStage2` / `AccessFlags2` for Sync2),
`Fence`, `Semaphore` (binary + timeline), `ImageBarrier::color`
/ `::depth`, `ClearValue`, `QueryPool`.
- **Derive macros** — `#[derive(Vertex)]` auto-generates vertex
input layouts from `#[repr(C)]` structs (optional `derive`
feature).
- **Raw escape hatch** — `Device::dispatch()` /
`Instance::dispatch()` expose the full dispatch tables so you
can drop to raw Vulkan for anything the safe wrapper doesn't
cover yet.
## Quick Start
```toml
[dependencies]
vulkane = { version = "0.4", features = ["fetch-spec"] }
```
```rust
use vulkane::safe::{
ApiVersion, Buffer, BufferCreateInfo, BufferUsage, CommandPool,
DeviceCreateInfo, DeviceMemory, Fence, Instance, InstanceCreateInfo,
MemoryPropertyFlags, PipelineStage, AccessFlags, QueueCreateInfo,
QueueFlags,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let instance = Instance::new(InstanceCreateInfo {
application_name: Some("hello-vulkane"),
api_version: ApiVersion::V1_0,
..Default::default()
})?;
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();
let device = physical.create_device(DeviceCreateInfo {
queue_create_infos: &[QueueCreateInfo::single(qf)],
..Default::default()
})?;
let queue = device.get_queue(qf, 0);
// One-call buffer allocation (no manual memory_requirements + find_type + bind).
let (buffer, mut memory) = Buffer::new_bound(
&device, &physical,
BufferCreateInfo { size: 1024, usage: BufferUsage::TRANSFER_DST },
MemoryPropertyFlags::HOST_VISIBLE | MemoryPropertyFlags::HOST_COHERENT,
)?;
// One-shot command recording + submit + wait.
queue.one_shot(&device, qf, |rec| {
rec.fill_buffer(&buffer, 0, 1024, 0xDEADBEEF);
rec.memory_barrier(
PipelineStage::TRANSFER, PipelineStage::HOST,
AccessFlags::TRANSFER_WRITE, AccessFlags::HOST_READ,
);
Ok(())
})?;
let mapped = memory.map()?;
assert_eq!(&mapped.as_slice()[..4], &0xDEADBEEFu32.to_ne_bytes());
println!("GPU filled the buffer with 0xDEADBEEF — it works!");
Ok(())
}
```
## Bundled Examples
15 examples ship with the crate, from basic compute through advanced
graphics techniques. All are headless (runnable in CI) except
`windowed_triangle`.
| [`device_info`](vulkane/examples/device_info.rs) | Raw API: instance, physical device enumeration, queue families |
| [`fill_buffer`](vulkane/examples/fill_buffer.rs) | Safe API: `vkCmdFillBuffer` round trip |
| [`compute_square`](vulkane/examples/compute_square.rs) | Compute: SPIR-V, descriptor set, pipeline, dispatch, verify |
| [`compute_image_invert`](vulkane/examples/compute_image_invert.rs) | Compute: 2D storage image, layout transitions, per-pixel verify |
| [`compile_shader`](vulkane/examples/compile_shader.rs) | Compile GLSL/WGSL → SPIR-V via naga (`--features naga`) |
| [`headless_triangle`](vulkane/examples/headless_triangle.rs) | Graphics: render pass, pipeline, draw, readback |
| [`textured_quad`](vulkane/examples/textured_quad.rs) | Graphics: texture upload, sampler, WGSL fragment shader |
| [`windowed_triangle`](vulkane/examples/windowed_triangle.rs) | Windowed: winit + surface + swapchain + present loop |
| [`buffer_upload`](vulkane/examples/buffer_upload.rs) | `Queue::one_shot` staging upload pattern |
| [`allocator_compute`](vulkane/examples/allocator_compute.rs) | `Allocator::create_buffer` — 2 lines vs 5 |
| [`raw_interop`](vulkane/examples/raw_interop.rs) | `Device::dispatch()` + `.raw()` escape hatch |
| [`depth_prepass`](vulkane/examples/depth_prepass.rs) | Depth-only pass + color EQUAL — early-Z prepass |
| [`instanced_mesh`](vulkane/examples/instanced_mesh.rs) | 100 triangles via `InputRate::INSTANCE` |
| [`shadow_map`](vulkane/examples/shadow_map.rs) | Two-pass shadow mapping: depth bias, comparison sampler, uniform buffers |
| [`deferred_shading`](vulkane/examples/deferred_shading.rs) | G-buffer (3 MRT) + fullscreen Phong lighting pass |
| [`derive_vertex`](vulkane/examples/derive_vertex.rs) | `#[derive(Vertex)]` auto-generated vertex layouts (`--features derive`) |
```bash
cargo run -p vulkane --features fetch-spec --example headless_triangle
cargo run -p vulkane --features fetch-spec --example shadow_map
cargo run -p vulkane --features fetch-spec,derive --example derive_vertex
```
## `#[derive(Vertex)]`
Enable the `derive` feature to auto-generate vertex input layouts:
```toml
vulkane = { version = "0.4", features = ["fetch-spec", "derive"] }
```
```rust
use vulkane::Vertex;
#[derive(Vertex, Clone, Copy)]
#[repr(C)]
struct MyVertex {
position: [f32; 3], // R32G32B32_SFLOAT, location 0
normal: [f32; 3], // R32G32B32_SFLOAT, location 1
uv: [f32; 2], // R32G32_SFLOAT, location 2
}
// In pipeline setup:
let bindings = [MyVertex::binding(0)];
let attributes = MyVertex::attributes(0);
builder.vertex_input(&bindings, &attributes)
```
Strides, offsets, and Vulkan format enums are computed at compile time.
Supports `f32`, `[f32; 2..4]`, `u32`, `[u32; 2..4]`, `i32`,
`[i32; 2..3]`, `[u8; 4]`, `u16`, `i16`. For per-instance data, use
`MyStruct::instance_binding(n)` instead of `::binding(n)`.
## Why Vulkane over ash?
| Source of truth | `vk.xml` at build time | Hand-curated bindings |
| New Vulkan version | Swap vk.xml, rebuild | Wait for crate update |
| Safe wrapper | Built-in: compute + graphics + allocator + sync | Raw FFI only |
| Sub-allocator | TLSF + linear pools + defrag built in | BYO (`gpu-allocator`) |
| Vertex layout | `#[derive(Vertex)]` | Manual |
| Pipeline builder | Depth bias, CompareOp, multi-attach, dynamic viewport | N/A (raw structs) |
| Allocation helpers | `Buffer::new_bound`, `Queue::upload_buffer<T>` | N/A |
| GLSL/WGSL→SPIR-V | Optional `naga` feature | N/A |
| Raw escape hatch | `device.dispatch()` + `.raw()` | N/A (always raw) |
| Maturity | New (0.4) | Battle-tested |
## Features
| `build-support` (default) | XML parsing and code generation during build |
| `fetch-spec` | Auto-download vk.xml from Khronos GitHub |
| `naga` | `compile_glsl` + `compile_wgsl` → SPIR-V at runtime |
| `derive` | `#[derive(Vertex)]` proc macro for vertex layouts |
## Providing vk.xml
1. **`VK_XML_PATH`** env var — point to any local `vk.xml`
2. **Local copy** at `spec/registry/Vulkan-Docs/xml/vk.xml`
3. **Auto-download** (`--features fetch-spec`), optionally pinned
with `VK_VERSION=1.3.250`
## Supported Vulkan Versions
**1.2.175** through the latest release. The minimum is set by the
`VK_MAKE_API_VERSION` macros introduced in that version.
## Architecture
```text
vk.xml → vulkan_gen (roxmltree parser + codegen) → vulkane crate
├── raw/ (generated FFI bindings)
├── safe/ (RAII wrapper)
└── vulkane_derive (proc macros)
```
## License
Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT License ([LICENSE-MIT](LICENSE-MIT))
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.