Trait Layer

Source
pub trait Layer:
    Sync
    + Default
    + 'static {
    type GlobalHooksInfo: GlobalHooksInfo;
    type InstanceInfo: InstanceInfo;
    type DeviceInfo: DeviceInfo;
    type InstanceInfoContainer: Borrow<Self::InstanceInfo> + Sync + Send;
    type DeviceInfoContainer: Borrow<Self::DeviceInfo> + Sync + Send;

    // Required methods
    fn manifest() -> LayerManifest;
    fn global_instance() -> impl Deref<Target = Global<Self>> + 'static;
    fn global_hooks_info(&self) -> &Self::GlobalHooksInfo;
    fn create_instance_info(
        &self,
        create_info: &InstanceCreateInfo,
        allocator: Option<&AllocationCallbacks>,
        instance: Arc<Instance>,
        next_get_instance_proc_addr: PFN_vkGetInstanceProcAddr,
    ) -> Self::InstanceInfoContainer;
    fn create_device_info(
        &self,
        physical_device: PhysicalDevice,
        create_info: &DeviceCreateInfo,
        allocator: Option<&AllocationCallbacks>,
        device: Arc<Device>,
        next_get_device_proc_addr: PFN_vkGetDeviceProcAddr,
    ) -> Self::DeviceInfoContainer;

    // Provided methods
    fn global_hooks(
        &self,
    ) -> <Self::GlobalHooksInfo as GlobalHooksInfo>::HooksRefType<'_> { ... }
    fn hooked_instance_commands(
        &self,
        _instance_info: &Self::InstanceInfo,
    ) -> Box<dyn Iterator<Item = VulkanCommand>> { ... }
    fn hooked_device_commands(
        &self,
        _instance_info: &Self::InstanceInfo,
        _device_info: Option<&Self::DeviceInfo>,
    ) -> Box<dyn Iterator<Item = VulkanCommand>> { ... }
}
Expand description

The Layer trait provides all layer implementation information for the layer framework.

The type that implements Layer provides the following functionalities:

§Initialization

The layer implementation can rely on Layer::default to perform the global initialization. e.g. initialize the logger.

The layer framework guarantees that Layer::default is only called once when Global::default is called, and won’t be called anywhere else. The layer framework doesn’t call Global::default to initialize. Instead, Layer::global_instance is called instead on every Vulkan function entry when the global singleton is needed, and the layer implementation is expected to lazily create the static Global singleton with Global::default in Layer::global_instance. Therefore, if the layer implementation can guarantee that Global::default is called once during the lifetime of the whole process, it is also guaranteed that Layer::default is called once during the lifetime of the whole process. Note that it may be difficult to realize such guarantee, because the Vulkan loader my load and unload the layer library multiple times, and the application my load and unload the Vulkan loader multiple times.

Layer::default must not call into any Global methods and any Vulkan commands exported by the Vulkan loader to avoid an endless recursion.

§Global clean up

It is recommended that the implementor of the Layer trait should have a no-op drop if all VkInstance intercepted by the layer is destroyed. If so, Global has a no-op drop when all VkInstance is destroyed. If the Layer implementor is always trivially-destructible1, it’s great.

It is guaranteed that when Global::drop is called, Layer::drop is called exactly once. However, as a dynamic link library that may be loaded and unloaded to/from the process multiple times, there is no easy way to guarantee that Global::drop is called on every unload. In additoon, in Rust, static items do not call drop at the end of the program2. We can’t expect drop to be called on dynamic link library unload as well. Therefore, we should avoid using Layer::drop to do clean-up to avoid resource leak.

If it is really necessary to store some not trivially-destructible types(note that all Rust std::collections are not trivially-destructible) in side the type that implements Layer, the following techniques may be helpful.

One way to test if a type T leaks heap memory is to run Miri tests with std::mem::forget.

#[derive(Default)]
struct TriviallyDestructible;

#[derive(Default)]
struct NotTriviallyDestructible(Box<u32>);

// This passes the Miri test, so TriviallyDestructible won't leak heap memory if drop is not
// called.
{
    let x: TriviallyDestructible = Default::default();
    std::mem::forget(x);
}

// This fails the Miri test, so NotTriviallyDestructible will leak heap memory if drop is not
// called.
{
    let x: NotTriviallyDestructible = Default::default();
    std::mem::forget(x);
}

The drop of Rust std::collections is not no-op even if the collection is empty, so avoid using them directly in the type that implements Layer.

The drop of std::sync::Weak is not no-op even if all strong references to the underlying object don’t exist, so we should avoid using it to construct a type that needs a no-op drop.

The drop of Option::None is no-op even if the underlying T is not trivially destructible, and the drop of Mutex is no-op if the drop of the wrapped T is no-op. Therefore Mutex<Option<T>> is a good basic block to build a type that needs a no-op drop.

§Examples

A simple layer that prints “Hello from the Rust Vulkan layer!” to stdout on every vkCreateDevice.

use ash::{self, vk};
use once_cell::sync::Lazy;
use std::sync::Arc;
use vulkan_layer::{
    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
    }

    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()
    }
}

A layer that initializes the logging infrastructure with env_logger.

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

struct MyLayer(StubGlobalHooks);

impl Default for MyLayer {
    fn default() -> Self {
        env_logger::init();
        Self(Default::default())
    }
}

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
    }

    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 {
        info!("Hello from the Rust Vulkan layer!");
        Default::default()
    }
}

  1. This term is borrowed from C++: if the type itself doesn’t have a destructor and the types of all fields are trivially destructible, this type is trivially destructible. Rust has std::mem::needs_drop, but this function doesn’t have a strong guarantee. 

  2. https://doc.rust-lang.org/reference/items/static-items.html#:~:text=Static%20items%20do%20not%20call%20drop%20at%20the%20end%20of%20the%20program. 

Required Associated Types§

Source

type GlobalHooksInfo: GlobalHooksInfo

The type that provides information about interception of global commands.

If the layer implementation is not interested in intercepting any global commands, StubGlobalHooks can be used.

Source

type InstanceInfo: InstanceInfo

The type that provides information about interception of Vulkan instance functions, global commands not included.

If the layer implementation is not interested in intercepting any Vulkan instance functions (global commands not included), StubInstanceInfo can be used.

Source

type DeviceInfo: DeviceInfo

The type that provides information about interception of Vulkan device functions.

If the layer implementation is not interested in intercepting any Vulkan device functions, StubDeviceInfo can be used.

Source

type InstanceInfoContainer: Borrow<Self::InstanceInfo> + Sync + Send

The type that holds a Layer::InstanceInfo type. Usually just Self::InstanceInfo.

This extra associated type allows the layer to use a smart pointer like Arc or Box to hold the Layer::InstanceInfo.

§Examples

Use Arc to hold the Layer::InstanceInfo.

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

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

impl Layer for MyLayer {
    type InstanceInfo = StubInstanceInfo;
    type InstanceInfoContainer = Arc<StubInstanceInfo>;

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

    // Unrelated required items are omitted.
}
Source

type DeviceInfoContainer: Borrow<Self::DeviceInfo> + Sync + Send

The type that holds a Layer::DeviceInfo type. Usually just Self::DeviceInfo.

This extra associated type allows the layer to use a smart pointer like Arc to hold the Layer::DeviceInfo.

§Examples

Use Arc to hold the Layer::DeviceInfo.

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

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

impl Layer for MyLayer {
    type DeviceInfo = StubDeviceInfo;
    type DeviceInfoContainer = Arc<StubDeviceInfo>;

    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 {
        Arc::default()
    }

    // Unrelated required items are omitted.
}

Required Methods§

Source

fn manifest() -> LayerManifest

Returns the layer manifest to the layer framework.

Implementors should always return the same value during the lifetime of the process, and must match the manifest json file with the layer. Implementors should avoid calling into any Global methods because this function can be called to implement Vulkan introspection queries before any VkInstance is created.

The layer framework will use this value to:

  • Implement Vulkan introspection queries, including vkEnumerateInstanceLayerProperties, vkEnumerateDeviceLayerProperties, vkEnumerateInstanceExtensionProperties and vkEnumerateDeviceExtensionProperties.
  • Remove the layer device extensions from VkDeviceCreateInfo::ppEnabledExtensionNames in vkCreateDevice if present.
Source

fn global_instance() -> impl Deref<Target = Global<Self>> + 'static

Provide the reference to the global singleton of Global.

Implementors must always return the reference pointing to the same Global object. Implementors can use LazyLock, OnceLock, or Lazy from the once_cell crate to implement. Implementors should use Global::default to create the Global object. More information on initialization can be found here.

§Examples

Use Lazy provided by the once_cell crate to implement.

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

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

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

    // Unrelated required items are omitted.
}
Source

fn global_hooks_info(&self) -> &Self::GlobalHooksInfo

Returns a reference of the underlying GlobalHooksInfo object.

Source

fn create_instance_info( &self, create_info: &InstanceCreateInfo, allocator: Option<&AllocationCallbacks>, instance: Arc<Instance>, next_get_instance_proc_addr: PFN_vkGetInstanceProcAddr, ) -> Self::InstanceInfoContainer

The factory method for the InstanceInfo type.

This function is called by the layer framework in vkCreateInstance, after the vkCreateInstance of the next layer returns with success.

§Arguments
  • create_info is a pointer to a VkInstanceCreateInfo structure controlling creation of the instance. The VkLayerInstanceLink linked list in the pNext chain is already advanced by the layer framework. This is the original VkInstanceCreateInfo passed in by the application or the previous layer(except the advanced VkLayerInstanceLink linked list), even if the layer implementation passes a different VkInstanceCreateInfo to the next layer in GlobalHooks::create_instance.
  • allocator controls host memory allocation as described in the Memory Allocation chapter of the Vulkan spec.
  • instance contains the loaded instance dispatch table of the next layer. The layer implementation can just clone this Arc if it is needed to keep the instance dispatch table.
  • next_get_instance_proc_addr is the vkGetInstanceProcAddr function pointer of the next layer obtained from the VkLayerInstanceLink linked list.
Source

fn create_device_info( &self, physical_device: PhysicalDevice, create_info: &DeviceCreateInfo, allocator: Option<&AllocationCallbacks>, device: Arc<Device>, next_get_device_proc_addr: PFN_vkGetDeviceProcAddr, ) -> Self::DeviceInfoContainer

The factory method for the DeviceInfo type.

This function is called by the layer framework in vkCreateDevice, after the vkCreateDevice of the next layer returns with success.

§Arguments
  • physical_device is one of the device handles returned from a call to vkEnumeratePhysicalDevices that is passed to vkCreateDevice by the application or the previous layer.
  • create_info is a pointer to a VkDeviceCreateInfo structure containing information about how to create the device. The VkLayerDeviceLink linked list in the pNext chain is already advanced by the layer framework. This is the original VkDeviceCreateInfo passed in by the application or the previous layer(except the advanced VkLayerDeviceLink linked list), even if the layer implementation passes a different VkDeviceCreateInfo to the next layer in InstanceHooks::create_device.
  • allocator controls host memory allocation as described in the Memory Allocation chapter of the Vulkan spec.
  • device contains the loaded device dispatch table of the next layer. The layer implementation can just clone this Arc if it is needed to keep the device dispatch table.
  • next_get_device_proc_addr is the vkGetDeviceProcAddr function pointer of the next layer obtained from the VkLayerDeviceLink linked list.

Provided Methods§

Source

fn global_hooks( &self, ) -> <Self::GlobalHooksInfo as GlobalHooksInfo>::HooksRefType<'_>

Returns a reference of the underlying GlobalHooks object.

The layer framework relies on this function to obtain GlobalHooks, and implementors should avoid overriding this method.

Source

fn hooked_instance_commands( &self, _instance_info: &Self::InstanceInfo, ) -> Box<dyn Iterator<Item = VulkanCommand>>

Returns an iterator of Vulkan instance functions (global commands not included) that the layer implementation needs to intercept.

This function allows the layer implementation to decide the commands to intercept dynamically after the VkInstance is created. By default it just returns all commands returned by InstanceInfo::hooked_commands.

§Arguments
  • _instance_info is the relevant instance.
§Examples

A layer that intercepts vkDestroySurfaceKHR only if the VK_KHR_win32_surface extension is enabled.

use ash::vk;
use once_cell::sync::Lazy;
use std::{ffi::CStr, sync::Arc};
use vulkan_layer::{
    Global, InstanceHooks, InstanceInfo, Layer, LayerManifest, LayerResult,
    LayerVulkanCommand as VulkanCommand, StubDeviceInfo, StubGlobalHooks,
};

struct MyLayerInstanceInfo {
    is_win32_surface_enabled: bool,
}

impl InstanceHooks for MyLayerInstanceInfo {
    fn destroy_surface_khr(
        &self,
        _surface: vk::SurfaceKHR,
        _p_allocator: Option<&vk::AllocationCallbacks>,
    ) -> LayerResult<()> {
        LayerResult::Unhandled
    }
}

impl InstanceInfo for MyLayerInstanceInfo {
    type HooksType = Self;
    type HooksRefType<'a> = &'a Self;

    fn hooked_commands() -> &'static [VulkanCommand] {
        &[VulkanCommand::DestroySurfaceKhr]
    }

    fn hooks(&self) -> Self::HooksRefType<'_> {
        self
    }
}

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

impl Layer for MyLayer {
    // ...
    type InstanceInfo = MyLayerInstanceInfo;
    type InstanceInfoContainer = MyLayerInstanceInfo;

    fn create_instance_info(
        &self,
        create_info: &vk::InstanceCreateInfo,
        _: Option<&vk::AllocationCallbacks>,
        _: Arc<ash::Instance>,
        _next_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr,
    ) -> Self::InstanceInfoContainer {
        let enabled_extensions = if create_info.enabled_extension_count > 0 {
            unsafe {
                std::slice::from_raw_parts(
                    create_info.pp_enabled_extension_names,
                    create_info.enabled_extension_count as usize,
                )
            }
        } else {
            &[]
        };
        let is_win32_surface_enabled = enabled_extensions
            .iter()
            .find(|extension_name| {
                (unsafe { CStr::from_ptr(**extension_name) })
                    == ash::extensions::khr::Win32Surface::name()
            })
            .is_some();
        MyLayerInstanceInfo {
            is_win32_surface_enabled,
        }
    }

    fn hooked_instance_commands(
        &self,
        instance_info: &Self::InstanceInfo,
    ) -> Box<dyn Iterator<Item = VulkanCommand>> {
        let should_hook_destroy_surface = instance_info.is_win32_surface_enabled;
        Box::new(
            Self::InstanceInfo::hooked_commands()
                .iter()
                .cloned()
                .filter(move |command| match command {
                    VulkanCommand::DestroySurfaceKhr => should_hook_destroy_surface,
                    _ => true,
                }),
        )
    }
}
Source

fn hooked_device_commands( &self, _instance_info: &Self::InstanceInfo, _device_info: Option<&Self::DeviceInfo>, ) -> Box<dyn Iterator<Item = VulkanCommand>>

Returns an iterator of Vulkan device functions that the layer implementation needs to intercept.

This function allows the layer implementation to decide the commands to intercept dynamically after the VkInstance or VkDevice is created. By default it just returns all commands returned by DeviceInfo::hooked_commands.

Arguments

  • _instance_info is the relevant instance.
  • _device_info is an optional relevant device. If _device_info is None, the layer framework is querying all possible intercepted device functions to implement vkGetInstanceProcAddr with the pName parameter referring to a device functions, i.e. implementations must guarantee that the returned commands with a None _device_info is the superset of all possible _device_info. Otherwise, the application that just uses the function pointers returned by vkGetInstanceProcAddr may skip the current layer.
§Examples

A layer that intercepts the vkCreateImage only if the ASTC LDR feature is enabled.

use ash::{self, prelude::VkResult, vk};
use once_cell::sync::Lazy;
use std::sync::Arc;
use vulkan_layer::{
    DeviceHooks, DeviceInfo, Global, Layer, LayerManifest, LayerResult,
    LayerVulkanCommand as VulkanCommand, StubGlobalHooks, StubInstanceInfo,
    VulkanBaseInStructChain,
};

struct MyLayerDeviceInfo {
    is_astc_enabled: bool,
}

impl DeviceHooks for MyLayerDeviceInfo {
    fn create_image(
        &self,
        create_info: &vk::ImageCreateInfo,
        _p_allocator: Option<&vk::AllocationCallbacks>,
    ) -> LayerResult<VkResult<vk::Image>> {
        if create_info.format == vk::Format::ASTC_4X4_UNORM_BLOCK {
            println!("ASTC 4x4 UNORM image created.");
        }
        LayerResult::Unhandled
    }
}

impl DeviceInfo for MyLayerDeviceInfo {
    type HooksType = Self;
    type HooksRefType<'a> = &'a Self;

    fn hooked_commands() -> &'static [VulkanCommand] {
        &[VulkanCommand::CreateImage]
    }

    fn hooks(&self) -> Self::HooksRefType<'_> {
        self
    }
}

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

impl Layer for MyLayer {
    // ...
    type DeviceInfo = MyLayerDeviceInfo;
    type DeviceInfoContainer = MyLayerDeviceInfo;
    fn create_device_info(
        &self,
        _: vk::PhysicalDevice,
        create_info: &vk::DeviceCreateInfo,
        _: Option<&vk::AllocationCallbacks>,
        _: Arc<ash::Device>,
        _next_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr,
    ) -> Self::DeviceInfoContainer {
        let mut p_next_chain: VulkanBaseInStructChain =
            unsafe { (create_info.p_next as *const vk::BaseInStructure).as_ref() }.into();
        let is_astc_enabled = p_next_chain
            .find_map(|p_next| {
                let p_next = p_next as *const vk::BaseInStructure;
                unsafe {
                    ash::match_in_struct!(match p_next {
                        features2 @ vk::PhysicalDeviceFeatures2 => {
                            Some(&features2.features)
                        }
                        _ => {
                            None
                        }
                    })
                }
            })
            .or_else(|| unsafe { create_info.p_enabled_features.as_ref() })
            .map(|features| features.texture_compression_astc_ldr == vk::TRUE)
            .unwrap_or(false);
        MyLayerDeviceInfo { is_astc_enabled }
    }

    fn hooked_device_commands(
        &self,
        _instance_info: &Self::InstanceInfo,
        device_info: Option<&Self::DeviceInfo>,
    ) -> Box<dyn Iterator<Item = VulkanCommand>> {
        let should_hook_create_image = device_info
            .map(|device_info| device_info.is_astc_enabled)
            // Always hook the vkCreateImage function in the function pointers returned by
            // vkGetInstanceProcAddr: we don't know if the to-be-created VkDevice will be
            // created with the ASTC feature enabled.
            .unwrap_or(true);
        Box::new(
            Self::DeviceInfo::hooked_commands()
                .iter()
                .cloned()
                .filter(move |command| match command {
                    VulkanCommand::CreateImage => should_hook_create_image,
                    _ => true,
                }),
        )
    }
}

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§