Crate vulkan_layer

Source
Expand description

This crate provides a convenient framework to develop Vulkan layers in Rust on top of the ash crate. If you are not familiar how to write a Vulkan layer, this C++ tutorial by Baldur Karlsson is a good reference to start with.

Key features provided by this crate includes:

  • Support the look-up map fashion of implementing a Vulkan layer.

    The look-up maps for VkDevice and VkInstance are handled by this crate.

  • Implement vkGet*ProcAddr automatically.

    This is a non-trivial work to comply with the spec, because of extensions and the required/supported Vulkan API level. See the spec of vkGetInstanceProc for details.

  • Handle dispatch tables, vkCreateInstance and vkCreateDevice.

    This mainly includes using the vkGet*ProcAddr function pointer correctly, and advancing the VkLayer*CreateInfo::u::pLayerInfo link list. One common mistake in layer implementation in vkCreateDevice is to call getInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDevice") to obtain the function pointer to vkCreateDevice. According to the spec, getInstanceProcAddr should return NULL. This framework helps you avoid bugs like this.

Note that object wrapping is not supported by this crate, and we don’t plan such support, because object wrapping requires us to intercept ALL Vulkan commands related to one handle type, so it can’t handle unknown commands. In addition, object wrapping is more complicated because it is required to call loader callback on every dispatchable handle creation and destruction.

§Overview

The primary types in this crate are the Layer trait and the Global struct. The user implements the Layer trait for a type T, and the Global<T> will include all necessary functions needed to export from this dynamic link library. With the help of the declare_introspection_queries macro, the user can export those functions in oneline.

use ash::{self, vk};
use once_cell::sync::Lazy;
use std::sync::Arc;
use vulkan_layer::{
    declare_introspection_queries, Global, Layer, LayerManifest, StubDeviceInfo,
    StubGlobalHooks, StubInstanceInfo,
};

// Define the layer type.
#[derive(Default)]
struct MyLayer(StubGlobalHooks);

// Implement the Layer trait.
impl Layer for MyLayer {
    type GlobalHooksInfo = StubGlobalHooks;
    type InstanceInfo = StubInstanceInfo;
    type DeviceInfo = StubDeviceInfo;
    type InstanceInfoContainer = StubInstanceInfo;
    type DeviceInfoContainer = StubDeviceInfo;

    fn global_instance() -> impl std::ops::Deref<Target = Global<Self>> + 'static {
        static GLOBAL: Lazy<Global<MyLayer>> = Lazy::new(Default::default);
        &*GLOBAL
    }

    fn manifest() -> LayerManifest {
        Default::default()
    }

    fn global_hooks_info(&self) -> &Self::GlobalHooksInfo {
        &self.0
    }

    fn create_instance_info(
        &self,
        _: &vk::InstanceCreateInfo,
        _: Option<&vk::AllocationCallbacks>,
        _: Arc<ash::Instance>,
        _next_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr,
    ) -> Self::InstanceInfoContainer {
        Default::default()
    }

    fn create_device_info(
        &self,
        _: vk::PhysicalDevice,
        _: &vk::DeviceCreateInfo,
        _: Option<&vk::AllocationCallbacks>,
        _: Arc<ash::Device>,
        _next_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr,
    ) -> Self::DeviceInfoContainer {
        Default::default()
    }
}

// Define the global type from the layer type.
type MyGlobal = Global<MyLayer>;
// Export C functions.
declare_introspection_queries!(MyGlobal);

The user can provide their own types that implement different traits to intercept different commands and custom the layer behavior:

§Usage

You can check a live example in examples/hello-world.

First, create a Rust lib crate.

$ mkdir vulkan-layer-rust-example
$ cd vulkan-layer-rust-example
$ cargo init --lib
     Created library package

Second, modify the crate type to cdylib and set the panic behavior to abort in Cargo.toml.

[lib]
crate-type = ["cdylib"]

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

We need to set panic to abort to avoid unwinding from Rust to the caller(most likely C/C++), because unwinding into other language from Rust is undefined behavior.

If you want to try the layer on Android, also modify the crate name to VkLayer_vendor_rust_example, because Android requires that the layer shared object library follow a specific name convention.

[lib]
name = "VkLayer_vendor_rust_example"

Third, set up the dependency in Cargo.toml. In my case, I checkout the project repository in the same directory where the vulkan-layer-rust-example folder lives.

[dependencies]
vulkan-layer = { path = "../vk-layer-for-rust/vulkan-layer" }

Other dependencies.

cargo add ash once_cell

Fourth, implement the layer trait in lib.rs.

use ash::{self, vk};
use once_cell::sync::Lazy;
use std::sync::Arc;
use vulkan_layer::{
    declare_introspection_queries, Global, Layer, LayerManifest, StubDeviceInfo,
    StubGlobalHooks, StubInstanceInfo,
};

#[derive(Default)]
struct MyLayer(StubGlobalHooks);

impl Layer for MyLayer {
    type GlobalHooksInfo = StubGlobalHooks;
    type InstanceInfo = StubInstanceInfo;
    type DeviceInfo = StubDeviceInfo;
    type InstanceInfoContainer = StubInstanceInfo;
    type DeviceInfoContainer = StubDeviceInfo;

    fn global_instance() -> impl std::ops::Deref<Target = Global<Self>> + 'static {
        static GLOBAL: Lazy<Global<MyLayer>> = Lazy::new(Default::default);
        &*GLOBAL
    }

    fn manifest() -> LayerManifest {
        let mut manifest = LayerManifest::default();
        manifest.name = "VK_LAYER_VENDOR_rust_example";
        manifest.spec_version = vk::API_VERSION_1_1;
        manifest.description = "Rust test layer";
        manifest
    }

    fn global_hooks_info(&self) -> &Self::GlobalHooksInfo {
        &self.0
    }

    fn create_instance_info(
        &self,
        _: &vk::InstanceCreateInfo,
        _: Option<&vk::AllocationCallbacks>,
        _: Arc<ash::Instance>,
        _next_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr,
    ) -> Self::InstanceInfoContainer {
        Default::default()
    }

    fn create_device_info(
        &self,
        _: vk::PhysicalDevice,
        _: &vk::DeviceCreateInfo,
        _: Option<&vk::AllocationCallbacks>,
        _: Arc<ash::Device>,
        _next_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr,
    ) -> Self::DeviceInfoContainer {
        println!("Hello from the Rust Vulkan layer!");
        Default::default()
    }
}

Fifth, export functions through the declare_introspection_queries macro

declare_introspection_queries!(Global::<MyLayer>);

Sixth, build and check the exported symbol of the build artifacts, and we should see Vulkan introspection APIs are exported.

On Windows, use dumpbin

$ dumpbin /exports .\target\debug\VkLayer_vendor_rust_example.dll
Microsoft (R) COFF/PE Dumper Version 14.36.32537.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file .\target\debug\VkLayer_vendor_rust_example.dll

File Type: DLL

  Section contains the following exports for VkLayer_vendor_rust_example.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           6 number of functions
           6 number of names

    ordinal hint RVA      name

          1    0 000F1840 vkEnumerateDeviceExtensionProperties = vkEnumerateDeviceExtensionProperties
          2    1 000F1820 vkEnumerateDeviceLayerProperties = vkEnumerateDeviceLayerProperties
          3    2 000F1800 vkEnumerateInstanceExtensionProperties = vkEnumerateInstanceExtensionProperties
          4    3 000F17E0 vkEnumerateInstanceLayerProperties = vkEnumerateInstanceLayerProperties
          5    4 000F1890 vkGetDeviceProcAddr = vkGetDeviceProcAddr
          6    5 000F1870 vkGetInstanceProcAddr = vkGetInstanceProcAddr

  Summary

        1000 .data
       11000 .pdata
       37000 .rdata
        2000 .reloc
      171000 .text

On Linux, use objdump.

$ objdump -TC target/debug/libVkLayer_vendor_rust_example.so

target/debug/libVkLayer_vendor_rust_example.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
(omit some irrelevant symbols...)
00000000000fad20 g    DF .text  0000000000000022  Base        vkEnumerateDeviceExtensionProperties
00000000000face0 g    DF .text  000000000000001c  Base        vkEnumerateInstanceExtensionProperties
00000000000fad50 g    DF .text  0000000000000018  Base        vkGetInstanceProcAddr
00000000000facc0 g    DF .text  0000000000000018  Base        vkEnumerateInstanceLayerProperties
00000000000fad00 g    DF .text  000000000000001c  Base        vkEnumerateDeviceLayerProperties
00000000000fad70 g    DF .text  0000000000000018  Base        vkGetDeviceProcAddr

Seventh, create the layer manifest file named rust_example_layer.json right beside the built artifact. If targeting Android, the json manifest file is not needed.

For Windows,

{
    "file_format_version" : "1.2.1",
    "layer": {
        "name": "VK_LAYER_VENDOR_rust_example",
        "type": "INSTANCE",
        "library_path": ".\\VkLayer_vendor_rust_example.dll",
        "library_arch" : "64",
        "api_version" : "1.1.0",
        "implementation_version" : "0",
        "description" : "Rust test layer"
    }
}

For Linux,

{
    "file_format_version" : "1.2.1",
    "layer": {
        "name": "VK_LAYER_VENDOR_rust_example",
        "type": "INSTANCE",
        "library_path": "./libVkLayer_vendor_rust_example.so",
        "library_arch" : "64",
        "api_version" : "1.1.0",
        "implementation_version" : "0",
        "description" : "Rust test layer"
    }
}

This json file will define an explicit layer named VK_LAYER_VENDOR_rust_example.

Eighth, use VkConfig (i.e. Vulkan Configurator) to force enable this explicit layer, and launch the vkcube application through VkConfig. In the log view, we should see the "Hello from the Rust Vulkan layer!" log line. For Android, follow this instruction to enable the layer. println! won’t write to logcat, so one needs to change the layer implementation to write to the logcat.

§Global initialization and clean up

See the document of Layer for details.

§Extensions

TODO

§Synchronization

TODO

§Safety

TODO

§API stability

TODO

Modules§

test_utils_test
Utilities used in the integration tests of this crate.
unstable_apiunstable
Vulkan layer utilities that are used in the integration tests to implement mock layers. The utilities may benefit the layer implementation, but there is no stability guarantee on those APIs.

Macros§

declare_introspection_queries
Declare the required introspection queries for Android given an instantiated vulkan_layer::Global type.

Structs§

ExtensionProperties
A wrapper for VkExtensionProperties. Structure specifying an extension properties.
Global
A struct that implements all necessarily functions for a layer given a type that implements Layer.
LayerManifest
A Rust bindings of the layer manifest file.
StubDeviceInfo
A stub struct that intercept no commands, which implements DeviceHooks and DeviceInfo.
StubGlobalHooks
A stub struct that intercept no commands, which implements GlobalHooks and GlobalHooksInfo.
StubInstanceInfo
A stub struct that intercept no commands, which implements InstanceHooks and InstanceInfo.
VulkanBaseInStructChain
A chain of VkBaseInStructure.
VulkanBaseOutStructChain
A chain of VkBaseOutStructure.

Enums§

Extension
LayerResult
The return value of an intercepted Vulkan command by the layer implementation.
LayerVulkanCommand

Traits§

DeviceHooks
DeviceInfo
A trait for the layer implementation to provide metadata of DeviceHooks for the layer framework.
GlobalHooks
A trait for the layer implementation to provide the implementation of the intercepted global commands.
GlobalHooksInfo
A trait for the layer implementation to provide metadata of GlobalHooks for the layer framework.
InstanceHooks
InstanceInfo
A trait for the layer implementation to provide metadata of InstanceHooks for the layer framework.
Layer
The Layer trait provides all layer implementation information for the layer framework.

Functions§

fill_vk_out_array
Clone the slice to a C style out array with proper VkResult return value.

Type Aliases§

VkLayerDeviceLink
A list node that contains the next entity’s vkGetInstanceProcAddr and vkGetDeviceProcAddr used by a layer. One possible payload of VkLayerDeviceCreateInfo
VkLayerInstanceLink
A list node that contains the next entity’s vkGetInstanceProcAddr used by a layer. One possible payload of VkLayerInstanceCreateInfo.

Attribute Macros§

auto_deviceinfo_impl
Derive the implementation of the vulkan_layer::DeviceInfo trait from the implementation of the vulkan_layer::DeviceHooks trait.
auto_globalhooksinfo_impl
Derive the implementation of the vulkan_layer::GlobalHooksInfo trait from the implementation of the vulkan_layer::GlobalHooks trait.
auto_instanceinfo_impl
Derive the implementation of the vulkan_layer::InstanceInfo trait from the implementation of the vulkan_layer::InstanceHooks.