vk
Vulkan in Rust. Native bindings, cached dispatch and an integrated allocator.
Registry-generated Vulkan bindings for graphics, games and GPU compute,
including the low-level building blocks for AI and ML workloads. The Cargo
package is vkrust; the Rust library and import namespace are vk.
Realm · Developer guide · Rust API reference
Registry coverage, enabled device capabilities and runtime qualification are separate; see the qualification and limitations.
Install
[]
= { = "vkrust", = "0.13" }
No Vulkan SDK, C compiler, C++ library or consumer-side generator is needed.
The default loader feature loads the system Vulkan library at runtime.
Library and Vulkan versions
The package version is 0.13.1+1.4.365:
| Version | Meaning |
|---|---|
0.13.1 |
This Rust library's API release; Git tag v0.13.1 |
+1.4.365 |
Generated from Vulkan 1.4 headers, revision 365 |
ApplicationInfo::api_version |
The Vulkan API version your application requests at runtime |
Cargo treats the suffix as build metadata, so the dependency requirement stays
version = "0.13". The suffix identifies the pinned API snapshot; it is not a
Vulkan 1.4 driver requirement or runtime qualification claim. Applications can
request Vulkan 1.0, 1.1 or 1.2 and use the commands/features admitted by their
actual implementation. The examples below request Vulkan 1.0.
vk::HEADER_VERSION and vk::HEADER_VERSION_COMPLETE expose the corresponding
native header constants. See the
registry provenance
for the exact pinned Khronos commit.
Two calling styles, one implementation
Use free functions when you prefer Vulkan's C-style flow, or associated methods when you prefer an object-oriented, Python-like spelling. Both use the same cached PFNs and canonical operation body.
Prefer Type::new(...).set_*() to prepare records throughout the bindings and
vk::mem. This is the shared construction style for both calling APIs;
borrowed setters retain the backing host storage needed by native arguments.
| Operation | Free function | Constructor or method |
|---|---|---|
| Create instance | vk::create_instance(&entry, &info, None) |
vk::Instance::new(&entry, &info, None) |
| Enumerate GPUs | vk::enumerate_physical_devices(&instance) |
instance.enumerate_physical_devices() |
| Create device | vk::create_device(&instance, physical_device, &info, None) |
vk::Device::new(&instance, physical_device, &info, None) |
| Retrieve queue | vk::get_device_queue(&device, family, index) |
device.get_device_queue(family, index) |
| Record dispatch | vk::cmd_dispatch(&device, commands, x, y, z) |
device.cmd_dispatch(commands, x, y, z) |
| Destroy device | vk::destroy_device(device, None) |
device.destroy(None) |
Free functions take their dispatch context explicitly. There is no mutable global dispatch or per-call symbol lookup. Both styles retain Vulkan's unsafe valid-usage and synchronization requirements; method syntax is not a safe API or a Python binding.
Free functions
Create an instance, enumerate physical devices, then explicitly destroy it. The enumeration result is propagated after cleanup, including on query failure.
Constructors and methods
The equivalent instance lifecycle through Instance:
If native creation succeeds but dispatch initialization cannot recover or
destroy the object, CallError::InitializationFailed retains its handle and
library. Applications must handle that variant explicitly to recover ownership;
the short examples propagate it to their caller.
Borrowed record construction
Use Type::new().set_*() to prepare native arguments. Allocator records use
the same construction style, with required arguments passed to new(...) where
needed. Type::default() produces the same initial state for default-backed records.
This is the default construction approach, available without an additional
feature flag. It works with both free functions and methods;
these setters construct the same Vulkan records passed to either calling style.
Direct native fields remain available, and stored borrowed records carry
lifetime parameters.
Borrowed setters keep backing strings, records and arrays live through their
last use. Array setters derive checked native counts; reviewed parallel arrays
are set together. push_next admits registry-compatible chain types and retains
an exclusive borrow of each inserted node:
/// The caller supplies a compatible physical device and Vulkan 1.2 query context.
unsafe
#
These methods prepare the same native records. They do not create GPU objects,
validate all Vulkan usage, enable features or prove GPU completion. Public raw
pointers remain an unsafe-call obligation; borrowing a pointer array keeps the
array alive, not the objects behind its pointers. Fluent field setters use the
set_* prefix on the semantic field name, while existing bitfield getters and
set_* accessors retain their spelling.
Generated records and unions carry complete construction coverage: byte payloads, SPIR-V words, optional parallel arrays, initialized writable outputs and reviewed parent/cross-record capacities. Opaque addresses and foreign objects without a known host extent use explicit unsafe pointer setters. See the changelog for compatibility changes.
For direct-field construction, prefer Type { field: value, ..Default::default() }
over exhaustive literals. Declare stored borrowed records as Type<'a>. Records
retaining exclusive chain borrows no longer implement Copy or Clone; create
fresh array elements with std::array::from_fn and borrow single records with
std::slice::from_ref. The native union
AccelerationStructureGeometryDataKHR uses ManuallyDrop for its non-Copy
record members. Native field layout remains subject to the independent C ABI
gate. Raw handle lifetimes and explicit destruction requirements are unchanged.
Mix styles when recording commands
An existing compute recording can use either spelling without creating another device or dispatch table:
/// # Safety
/// `commands` must be recording outside a render pass, from a pool on this
/// device's compute-capable queue family. `pipeline` must be a compatible live
/// compute pipeline; descriptor sets, push constants and resource barriers
/// required by its shader must already be valid for this dispatch.
unsafe
Recording does not submit or wait. The application owns command-buffer state, feature enablement, object lifetimes and host/GPU synchronization.
Wrappers and raw handles
Instance and Device are the two main dispatch wrappers. They combine the
native identity with loaded commands, library lifetime retention and optional
owned creation-capability metadata. This is what lets device.cmd_dispatch(...)
know which native handle and PFN table to use.
| Rust type | Meaning |
|---|---|
vk::Instance |
Instance wrapper with cached instance dispatch |
vk::InstanceHandle |
Raw native VkInstance identity |
vk::Device |
Device wrapper with cached device dispatch |
vk::DeviceHandle |
Raw native VkDevice identity |
Handles are copyable identities, not owners or dispatch contexts. PFN signatures
use the raw handle types to preserve the Vulkan ABI. Other native handles keep
their familiar names: PhysicalDevice, Queue, Buffer, Image and so on.
Import existing instances/devices with unsafe new_from_handle or
new_from_handle_with_capabilities; importing must not create duplicate owners.
Native destruction remains explicit. Drop never destroys Vulkan objects,
submits work or waits. Destroy children and complete outstanding work before
destroying their device or instance, using callbacks compatible with creation.
Native Vulkan, Rust spelling
The namespace is flat: bindings, StdVideo definitions, dispatch tables, free
functions and the Instance / Device wrappers live directly under vk::.
The two public subsystem modules are vk::load for library loading and
vk::mem for the Rust-native allocator.
| Item | Rust convention | Native → Rust example |
|---|---|---|
| Types | PascalCase |
VkApplicationInfo → vk::ApplicationInfo |
| Constants | SCREAMING_SNAKE_CASE |
VK_SUCCESS → vk::Result::SUCCESS |
| Functions and methods | snake_case |
vkCmdDispatch → vk::cmd_dispatch / device.cmd_dispatch |
| Struct members | snake_case |
pApplicationInfo → p_application_info |
| Fluent setters | set_* + semantic name |
pApplicationInfo → set_application_info |
| Raw instance/device handles | Explicit Handle suffix |
VkDevice → vk::DeviceHandle |
Vulkan acronyms, vendor suffixes, StdVideo* names and PFN_vk* aliases remain
recognizable. C-compatible representations preserve layout independently of
field spelling.
let info = new;
assert_eq!;
assert!;
assert!;
Search the API reference using native names such as VkDevice,
vkCreateInstance, VK_SUCCESS or pApplicationInfo; generated rustdoc search
aliases lead to the Rust spellings. Vulkan declarations link to Khronos
reference pages. StdVideo definitions and registry-only constants link to their
pinned Khronos registry source. Reference pages follow Khronos's latest
specification; the bindings follow the
pinned registry snapshot.
The development ABI map retains both C and Rust field names for independent
layout checks; renaming fields does not reorder or change their native storage.
Loading and capabilities
vk::Entry::new() opens the OS Vulkan loader. FnEntry, FnInstance and
FnDevice cache nullable native function pointers. Created wrappers retain
version, extension and enabled-feature provenance; known disabled commands are
filtered from dispatch at construction. Raw imports without metadata report
unknown provenance. Resolving a pointer does not establish feature enablement.
Command-specific arguments, feature requirements and pointer chains remain the caller's responsibility. The API reference documents the Rust contracts alongside links to the native Vulkan requirements.
Integrated allocator
Enable features = ["allocator"] for the Rust-native, VMA-derived allocator:
[]
= { = "vkrust", = "0.13", = ["allocator"] }
vk::mem owns allocator operations and configuration; vk::Allocator is the
same type as vk::mem::Allocator. This keeps allocator-backed creation distinct
from the native binding vk::create_buffer, which creates an unbound buffer.
Use use vk::mem as vma; for a familiar local alias without another crate,
wrapper or runtime cost. Both spellings are equally valid; the canonical module
name in this repository is mem.
Use new(...).set_*() for allocator record construction too.
Every public field on a plain vk::mem record has a chainable set_* method,
including requests, configuration, callbacks, import metadata, cache ranges,
budgets and diagnostic snapshots. Default-backed records use new().set_*().
AllocRequest::new(size, alignment, memory_type_bits),
ImportMemoryInfo::new(allocation_size, memory_type_bits) and
CacheRange::new(&allocation) retain their required inputs. Setters consume and
return the same record; public fields and struct literals remain available.
Editing a snapshot changes only that record. Live resources keep their checked
operation APIs, and operations still own validation.
Allocator free functions and methods share one implementation, just like the
binding API. They take the allocator explicitly and retain Rust naming rather
than adding a second set of vma* symbols:
| Operation | Free function | Method |
|---|---|---|
| Create, allocate and bind buffer | vk::mem::create_buffer_with_options(&mut allocator, &info, &options) |
allocator.create_buffer_with_options(&info, &options) |
| Create, allocate and bind image | vk::mem::create_image_with_options(&mut allocator, &info, &options) |
allocator.create_image_with_options(&info, &options) |
| Allocate existing buffer's memory | vk::mem::allocate_for_buffer(&mut allocator, buffer, &options) |
allocator.allocate_for_buffer(buffer, &options) |
| Map an allocation | vk::mem::map(&mut allocator, &allocation) |
allocator.map(&allocation) |
| Inspect an allocation | vk::mem::allocation_info(&allocator, &allocation) |
allocator.allocation_info(&allocation) |
| Destroy managed buffer and free memory | vk::mem::destroy_buffer(&mut allocator, buffer, &mut allocation) |
allocator.destroy_buffer(buffer, &mut allocation) |
The following helper returns both objects to its caller for explicit cleanup.
The caller supplies an allocator constructed for its device, preferably with
Allocator::new_from_physical_device(instance, physical_device, device):
#
#
#
The allocator implements managed buffers/non-disjoint images, mixed-resource
TLSF/linear pools, automatic memory preferences, mapping and cache operations,
budgets, callbacks, statistics, virtual placement and explicit multi-pass
relocation. SynchronizedAllocator exposes direct &self methods. Its map,
unmap and bind calls use a stable mutex per native backing, so another backing
can allocate/free or map/bind concurrently, including within the same pool.
Placement remains serialized per owner; borrowed pool/type scopes provide
independent placement owners. Borrowed results, relocation and global services
use exclusive with_allocator access and wait for active native calls.
SynchronizedVirtualBlock optionally serializes host-only placement; VMA's
virtual blocks themselves require external synchronization. Ordinary Allocator
remains exclusive and non-Send/non-Sync. The new_retained constructor
family produces Allocator<'static> backed by an owned Arc<Device> clone,
letting an allocator outlive its construction scope and finish on a dedicated
cleanup thread; SynchronizedAllocator::new_owned adopts it directly.
The pinned VMA implementation is a development oracle; consumers need no C++
allocator or FFI bridge. The broad allocator service surface is implemented and
hardware-qualified on the recorded baseline. Full VMA equivalence, wider
concurrent stress, multi-driver execution and additional platforms remain
unqualified.
Allocations and native resources need explicit cleanup. Host locks do not order
GPU work or protect escaped mapped bytes; operations and Drop never submit or
wait for the GPU. See qualification and limitations
for the exact scope.
Features
| Cargo feature | Compiled functionality |
|---|---|
loader (default) |
Dynamic loading, instance/device wrappers and native operations |
allocator |
Rust-native allocator in vk::mem; enables loader |
platform-* |
Matching platform-specific ABI declarations |
provisional |
Provisional registry declarations |
Set default-features = false for ABI declarations and raw dispatch tables
without the dynamic loader. Cargo features do not enable Vulkan device
extensions or features.
This build still uses Rust std. Android/iOS do not require no_std; see
platform and standard-library scope
for loader paths, portability setup and cross-build limits.
Performance
Selected host-latency results on Intel Iris Xe (Surface Laptop 5, November 15, 2026), with 20 fresh-process samples per backend/workload. Below 1 favors vk. Allocation rows combine allocation/free and native create/bind/destroy work; 128 live allocations unless noted. Intervals crossing 1 do not establish a lead. 15 of 44 measured workloads complete below 1.0× VMA, including every virtual-placement row; the worst measured gap is pooled TLSF shuffled free at 1.162×.
| Workload | vk / VMA time (95% confidence interval) |
|---|---|
| Virtual TLSF, minimum-offset churn | 0.624× (0.621–0.629) |
| Virtual linear, LIFO | 0.708× (0.699–0.737) |
| Virtual linear, churn | 0.813× (0.788–0.845) |
| Pooled buffers, TLSF, reverse, single live | 0.937× (0.914–1.016) |
| Separate buffers, LIFO | 1.070× (1.027–1.087) |
| Separate images, LIFO | 1.072× (1.058–1.089) |
| Pooled images, TLSF, LIFO | 1.076× (1.063–1.098) |
| Pooled buffers, TLSF, LIFO | 1.006× (0.984–1.043) |
| Pooled buffers, TLSF, churn, 32 live | 1.147× (1.139–1.164) |
| Pooled buffers, TLSF, shuffled free | 1.162× (1.129–1.196) |
| Pooled buffer map + 8-byte write + unmap | 1.017× (1.007–1.031) |
These exploratory runs used unlocked clocks and shared workstation load. Native VMA uses its default internal locks; ordinary vk uses exclusive access. The full benchmark report includes all 44 allocator configurations, subsequent candidate passes, Ash/Vulkano history, methodology and retained evidence.
Development
Runtime source lives in src/rs, generation in tool/gen, and tests in
test/rs. Binding source and tests are flat; load and mem have their own
subdirectories. Generated files use singular *.gen.rs names. Dev/test builds
disable incremental compilation and debug information.
The optional SDK isolates Ash/Vulkano and
C++ VMA comparison dependencies from the library. It prepares explicit hardware
profiles, fresh-process benchmarks and an offscreen Vulkan 1.3 triangle using
the same new().set_*() records and allocator methods. The current
Surface Laptop 5 baseline
records 32-profile core/synchronization validation, the graphics oracle and
paired Ash and VMA confirmation
host-latency ratios on Intel Iris Xe. Broader qualification remains open.
License
MIT. See license texts and third-party notices for Khronos registry and AMD allocator provenance. Realm animation shared with OA.