#vulkan #vulkan-bindings #structure #create #bit-flags #pointers #manipulate

lava

Rust wrapper to manipulate Vulkan more conveniently than with bindings

15 releases

Uses old Rust 2015

0.4.9 Apr 20, 2020
0.4.7 Jan 27, 2020
0.4.6 Dec 7, 2019
0.4.5 Sep 22, 2019
0.2.0 Sep 25, 2018

#190 in Graphics APIs

30 downloads per month

MIT license

3MB
57K SLoC

Lava

Wrapper to manipulate the Vulkan API in Rust more conveniently than with bindings:

  • removes the need to specify the structure type when sending structures to Vulkan
  • takes care of making double Vulkan calls when necessary (when retrieving a list of Vulkan objects)
  • returns objects retrieved by Vulkan in a Result instead of requiring a user-provided pointer
  • allows to manipulate references, slices and options instead of pointers (in particular, allows to provide slice instead of pointer + length)
  • exposes the API in an object-oriented way (e.g instance.enumerate_physical_devices() instead of enumerate_physical_devices(&instance))
  • removes the extension suffix from function and data-structure names (modules are used instead)
  • exposes bit flags as structures instead of integers
  • provides a default value for all structures, allowing to "auto-complete" structure with optional fields using ..Default::default()
  • manages the calls to vkGetInstanceProcAddr and vkGetDeviceProcAddr to manipulate functions that are not exposed statically
  • provides a generic create_surface method to create surfaces

Lava is entirely generated from the C header files of Vulkan.

Current restrictions

  • no way to provide allocator callbacks
  • no way to set the pNext field of structures

Usage

[dependencies]
lava = "0.4"

Examples

This code creates a Vulkan instance, adds a debug report callback and displays the name of each GPU of the machine:

#[macro_use] extern crate lava;
use lava::*;

fn main() {
    let instance = vk_create_instance(VkInstanceCreateInfo {
        flags: VkInstanceCreateFlags!(),
        application_info: Some(VkApplicationInfo {
            application_name: Some("lava-example"),
            application_version: 1,
            engine_name: None,
            engine_version: 1,
            api_version: VkVersion(1, 0, 0),
        }),
        enabled_layer_names: vec![VK_LAYER_KHRONOS_VALIDATION_NAME],
        enabled_extension_names: vec![VK_EXT_DEBUG_REPORT_EXTENSION_NAME]
    }).expect("Failed to create instance");

    let debug_report_callback = instance.create_debug_report_callback(VkDebugReportCallbackCreateInfo {
        flags: VkDebugReportFlags!(error, warning),
        callback: |data: VkDebugReportCallbackData| {
            println!("{}", data.message);
        }
    }).expect("Failed to create debug callback");

    let physical_devices = instance.enumerate_physical_devices()
        .expect("Failed to retrieve physical devices");

    for physical_device in &physical_devices {
        let properties = physical_device.get_properties();

        println!("{}", properties.device_name);
    }

    debug_report_callback.destroy();
    instance.destroy();
}

This snippet shows how to create a surface from a GLFW window:

// We assume that `window` is a pointer to a GLFWwindow, as described here:
// http://www.glfw.org/docs/latest/group__vulkan.html#ga1a24536bec3f80b08ead18e28e6ae965

let surface = instance.create_surface(
    |handle, allocator, surface| unsafe { glfwCreateWindowSurface(handle, window, allocator, surface) }
).expect("Failed to create surface from glfw window");

Additional usage information

Module partitionning

Data-structures are separated in multiple modules, according to their extension (KHR, EXT, etc). Data-structures that have no extension are in the lava::vk module.

Some constants (e.g validation layer names) are located in the lava::constants module.

Lava re-exports all the members of lava::vk, lava::constants, lava::ext and lava::khr ("use lava::*" makes all data-structures contained in these modules available without needing to prefix them).

Bit flags

Bit flags are represented as structures instead of integers. Moreover all bit flags structures have static none() and all() functions. The typical way of creating a bit flags structure is as following:

// Creates a structure with the `vertex` and `fragment` flag enabled, and all the others disabled
VkShaderStageFlags {
    vertex: true,
    fragment: true,
    ..VkShaderStageFlags::none()
}

Since it can be tedious to write, all bit flags structures have a macro shortcut:

// Same effect as previous snippet
VkShaderStageFlags!(vertex, fragment)

Additionally, all bit flags structures have the following methods:

let no_shader_stage = VkShaderStageFlags::none();
let all_shader_stages = VkShaderStageFlags::all();
let shader_stages = VkShaderStageFlags::from_u32(17);
let shader_stages_int = shader_stages.to_u32();

Results

When relevant, functions return a Result<T, (VkResult, T)>. The return value is Ok(T) if the VkResult returned by the Vulkan function is 0. Otherwise it's Err((VkResult, T)). The first element of the tuple is the error code returned by the Vulkan function. The second element is, in the specific case where the VkResult is not 0 but is not an error either (e.g when calling swapchain.acquire_next_image()), the value produced by the function. Otherwise it's a zeroed value that will most likely crash when used.

Objects destruction and drop

Users are required to manually destroy their objects themselves, instead of Rust doing it automatically when the object is dropped. There are two reasons for that:

  • In the C API some objects must not be destroyed by the user. For example, the VkImage objects of a swapchain are automatically destroyed when the swapchain is destroyed, and attempting to destroy them manually will produce an error. But the user is still expected to destroy the VkImage objects that they create manually. An automatic destruction mechanism would require some context on where the object comes from, and this is out of scope.
  • The order in which objects are dropped has a good chance to not match the oder in which they must be destroyed, especially when structures are dropped.

Manual build

The content of the src/vulkan/ folder is generated from the vulkan_core.h and vk.xml files of the Vulkan documentation repository.

If you wish to re-generate it manually, you can do (requires Node.js):

  • npm install
  • node generate.js [ --tag <version> ]

Where <version> is a branch or tag name of the Vulkan-Docs repository (for example v1.1.80). If omitted, it defaults to master. The script will download the corresponding files in the download/ folder and generate the new source files.

License

MIT

No runtime deps