#vulkan #gpu-rendering #gpu #graphics-rendering #ffi

sys vulkane

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 optional GLSL/WGSL/HLSL→SPIR-V compilation via naga or shaderc. Supports Vulkan 1.2.175 onward — swap vk.xml and rebuild.

13 releases (7 breaking)

Uses new Rust 2024

0.8.0 Apr 21, 2026
0.7.0 Apr 19, 2026
0.6.0 Apr 17, 2026
0.5.0 Apr 16, 2026
0.1.0 Apr 9, 2026

#98 in Graphics APIs

MIT/Apache

720KB
11K SLoC

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.

CI Crates.io docs.rs 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 / DeviceInstance, PhysicalDevice, PhysicalDeviceGroup, Device, Queue, DeviceFeatures builder (1.0 / 1.1 / 1.2 / 1.3 features).
    • MemoryBuffer, 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 helpersBuffer::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.
    • ComputeComputePipeline, PipelineLayout, DescriptorSet, ShaderModule, specialization constants, pipeline cache, push constants.
    • GraphicsRenderPass (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).
    • Extension ergonomics (0.8+) — curated safe wrappers for ray tracing (AccelerationStructure, RayTracingPipeline, cmd_trace_rays), external memory/semaphore interop (DeviceMemory::get_win32_handle / get_fd for CUDA / HIP / DX12 bridging), timeline semaphores, synchronization 2 (memory_barrier2 / image_barrier2 / buffer_barrier2), push descriptors, dynamic rendering (begin_rendering), descriptor buffer, subgroup size control, memory priority, and shader integer dot product properties. Every safe create-info builder now exposes a pnext extension point so any unwrapped extension struct can be layered on without dropping to raw.
    • Generated ergonomic traits (0.8+) — alongside the raw per-command DeviceExt / InstanceExt / CommandBufferRecordingExt traits, the build emits a second tier — DeviceSafeExt, InstanceSafeExt, PhysicalDeviceSafeExt, QueueSafeExt, CommandBufferRecordingSafeExt — with idiomatic signatures (slices for slice-params, Result<Vec<T>> for (count, data) enumerate pairs, references for input structs, return-by-value for single output params). 545 generated ergonomic methods cover ~70% of the extension surface mechanically.
    • Raw escape hatchDevice::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

[dependencies]
vulkane = { version = "0.8", features = ["fetch-spec"] }
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.

Example Technique
device_info Raw API: instance, physical device enumeration, queue families
fill_buffer Safe API: vkCmdFillBuffer round trip
compute_square Compute: SPIR-V, descriptor set, pipeline, dispatch, verify
compute_image_invert Compute: 2D storage image, layout transitions, per-pixel verify
compile_shader Compile GLSL/WGSL → SPIR-V via naga (--features naga)
headless_triangle Graphics: render pass, pipeline, draw, readback
textured_quad Graphics: texture upload, sampler, WGSL fragment shader
windowed_triangle Windowed: winit + surface + swapchain + present loop
buffer_upload Queue::one_shot staging upload pattern
allocator_compute Allocator::create_buffer — 2 lines vs 5
raw_interop Device::dispatch() + .raw() escape hatch
depth_prepass Depth-only pass + color EQUAL — early-Z prepass
instanced_mesh 100 triangles via InputRate::INSTANCE
shadow_map Two-pass shadow mapping: depth bias, comparison sampler, uniform buffers
deferred_shading G-buffer (3 MRT) + fullscreen Phong lighting pass
derive_vertex #[derive(Vertex)] auto-generated vertex layouts (--features derive)
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:

vulkane = { version = "0.4", features = ["fetch-spec", "derive"] }
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).

Precompiled shader registry

For applications that ship precompiled .spv artifacts (embedded with include_bytes! or shipped alongside the binary), vulkane::safe::ShaderRegistry gives you a small, shared abstraction for looking up shaders by name — plus an optional runtime disk override for shader developers iterating without a full rebuild.

use vulkane::safe::{ShaderRegistry, ShaderSource};

// Embedded at compile time. `include_bytes!` resolves paths relative
// to the file that invokes it, so the macro call lives in your crate.
const EMBEDDED: &[ShaderSource] = &[
    ShaderSource { name: "doubler", spv: include_bytes!("shaders/doubler.spv") },
    ShaderSource { name: "reduce",  spv: include_bytes!("shaders/reduce.spv")  },
];

fn app_shaders() -> ShaderRegistry {
    ShaderRegistry::new()
        .with_embedded(EMBEDDED)
        .with_env_override("MY_APP_SHADER_OVERRIDE_DIR")
}

// Later, when you have a Device:
let module = app_shaders().load_module(&device, "doubler")?;

Lookup order when an override var is configured:

  1. $MY_APP_SHADER_OVERRIDE_DIR/doubler.spv on disk (if the directory and file both exist).
  2. Fall back to the embedded table.

ShaderRegistry::load(name) returns the SPIR-V bytes, load_words(name) returns a Vec<u32> ready for ShaderModule::from_spirv, and load_module(&device, name) wraps the whole pipeline. Lookup errors flow through ShaderLoadError into the unified Error::ShaderLoad variant.

Runtime shader compilation

Vulkane offers two optional, independent back-ends for compiling shader source to SPIR-V at runtime. Enable one, the other, or both — they are completely separate modules.

naga — pure Rust (WGSL + subset of GLSL)

Zero external build dependencies. Best choice when:

  • You want a pure-Rust build (no CMake, no C++ toolchain).
  • You are targeting modern GLSL or WGSL.
  • WGSL's combined image-samplers fit your rendering code.
vulkane = { version = "0.4", features = ["naga"] }
use vulkane::safe::naga::compile_glsl;
use naga::ShaderStage;

let spirv = compile_glsl(
    r#"#version 450
       layout(local_size_x = 64) in;
       layout(set = 0, binding = 0, std430) buffer Data { uint v[]; };
       void main() { v[gl_GlobalInvocationID.x] *= 2; }"#,
    ShaderStage::Compute,
)?;
// Pass straight into ShaderModule::from_spirv(&device, &spirv).

shaderc — Khronos glslang (full GLSL + HLSL)

Wraps the Khronos reference compiler. Best choice when:

  • You need full GLSL support (#include, GL_* extensions, older core versions, legacy sample shaders).
  • You are compiling HLSL for a Vulkan target.
  • You want optimization passes (OptimizationLevel::Size or Performance) or explicit macro defines / include resolution.
vulkane = { version = "0.4", features = ["shaderc"] }
use vulkane::safe::shaderc::{compile_glsl, ShaderKind};

let spirv = compile_glsl(
    r#"#version 450
       layout(local_size_x = 64) in;
       void main() {}"#,
    ShaderKind::Compute,
    "doubler.comp",   // virtual file name (used in error messages and #include resolution)
    "main",           // entry-point name
)?;

For fine-grained control — HLSL input, optimization level, macro defines, include callbacks, target Vulkan version — use compile_with_options:

use vulkane::safe::shaderc::{compile_with_options, ShaderKind, SourceLanguage};
use shaderc::OptimizationLevel;

let spirv = compile_with_options(
    hlsl_source,
    ShaderKind::Fragment,
    "shader.hlsl",
    "main",
    |opts| {
        opts.set_source_language(SourceLanguage::HLSL);
        opts.set_optimization_level(OptimizationLevel::Size);
        opts.add_macro_definition("USE_PBR", Some("1"));
    },
)?;

Build requirements for shaderc

shaderc-rs locates libshaderc in this order:

  1. SHADERC_LIB_DIR env var — point to a directory containing libshaderc_combined.
  2. VULKAN_SDK env var — installing the LunarG Vulkan SDK sets this automatically, and ships a prebuilt libshaderc_combined. This is the easiest path.
  3. pkg-config / system libraries (typical on Linux distros that package shaderc).
  4. Source build fallback — compiles glslang from C++ source. Requires CMake ≥ 3.17 (newer CMake may need CMAKE_POLICY_VERSION_MINIMUM=3.5 until shaderc-sys updates its pinned sources), Python 3, and a working C++ toolchain. First build takes 1–3 minutes.

If neither the SDK nor a system package is available and you don't want to build from source, use the naga feature instead.

When to pick which

Need naga shaderc
Pure Rust build (no C++ toolchain)
Modern GLSL (core, no extensions)
Full GLSL (#include, GL_* exts)
HLSL input
WGSL input
Optimization passes
Smallest binary / fastest cold build

Both features can be enabled simultaneously — pick per shader at the call site.

slang — Khronos Slang (modules, generics, autodiff)

Slang is Khronos's modern shading language. Pick it when you need:

  • Modules, generics, and interfaces — real code reuse in shaders.
  • Automatic differentiation — tag a function [Differentiable] and the compiler generates forward (__fwd_diff) and backward (__bwd_diff) variants. A single kernel definition yields both the forward and backward SPIR-V, which is a game-changer for ML compute on Vulkan.
  • Explicit entry points — one .slang file can expose many compute / vertex / fragment entry points; compile each to its own SPIR-V blob from the same parsed module.
vulkane = { version = "0.4", features = ["slang"] }

Slang compilation is stateful — a session holds the SPIR-V target and search paths, modules are loaded by name through those paths, and each entry point is compiled on demand:

use vulkane::safe::slang::SlangSession;

// Given kernels/autodiff.slang with [Differentiable] functions and
// both `forward` and `backward` compute entry points:
let session = SlangSession::with_search_paths(&["kernels"])?;
let module  = session.load_file("autodiff")?;                // parses once
let fwd     = module.compile_entry_point("forward")?;        // Vec<u32> SPIR-V
let bwd     = module.compile_entry_point("backward")?;       // Vec<u32> SPIR-V

Or for a one-off compile:

use vulkane::safe::slang::compile_slang_file;

let spirv = compile_slang_file("kernels", "trivial", "main")?;

Build requirements for slang

shader-slang locates the Slang compiler via:

  1. VULKAN_SDK env var — the LunarG Vulkan SDK ships slangc and the Slang runtime library. Easiest path (same as shaderc).
  2. SLANG_DIR env var — a standalone Slang install from shader-slang/slang releases.
  3. SLANG_INCLUDE_DIR + SLANG_LIB_DIR — split paths.

At runtime, slang.dll / libslang.so / libslang.dylib must be discoverable (same directory as the executable, or on the library search path).

Current limitation

shader-slang 0.1.0 on crates.io only supports loading Slang modules from disk (via search-path resolution). Inline source strings will be supported once a newer shader-slang release exposes them; until then, keep your Slang code in .slang files.

Why Vulkane over ash?

Aspect Vulkane 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 (pure Rust), shaderc (glslang), or slang (Slang + autodiff) feature N/A
Raw escape hatch device.dispatch() + .raw() N/A (always raw)
Maturity New (0.4) Battle-tested

Features

Feature Description
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 (pure Rust)
shaderc compile_glsl / compile_with_options → SPIR-V via Khronos glslang (full GLSL + HLSL)
slang SlangSession / compile_slang_file → SPIR-V via Khronos Slang (modules, generics, autodiff)
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

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:

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.

Dependencies

~0.3–10MB
~210K SLoC