rotex-vulkan 0.1.1

A Vulkan backend for rotex_core
Documentation
use std::ffi::{CStr, CString, c_void};

use ash::vk;

use crate::backend::vulkan::Adapter;
use crate::error::{Error, ErrorKind, vk_error};

#[derive(Debug, Clone)]
pub struct InstanceOptions {
    pub application_name: &'static str,
    pub engine_name: &'static str,
    pub enable_validation: bool,
    pub enable_debug_utils: bool,
}

impl Default for InstanceOptions {
    fn default() -> Self {
        Self {
            application_name: "rotex_vulkan",
            engine_name: "rotex_vulkan",
            enable_validation: false,
            enable_debug_utils: false,
        }
    }
}

pub struct Instance {
    entry: ash::Entry,
    instance: ash::Instance,
}

impl Instance {
    pub fn new_with_options(
        options: &InstanceOptions,
        extensions: &[*const i8],
    ) -> Result<(Self, Option<DebugMessenger>), Error> {
        let entry = unsafe { ash::Entry::load() }
            .map_err(|_| Error::fatal(ErrorKind::Unsupported("Failed to load Vulkan entry")))?;

        let app_name = CString::new(options.application_name)
            .map_err(|_| Error::fatal(ErrorKind::Unsupported("Application name contains NUL")))?;
        let engine_name = CString::new(options.engine_name)
            .map_err(|_| Error::fatal(ErrorKind::Unsupported("Engine name contains NUL")))?;

        let app_info = vk::ApplicationInfo::default()
            .application_name(&app_name)
            .engine_name(&engine_name)
            .api_version(vk::API_VERSION_1_0);

        let mut enabled_extensions = extensions.to_vec();
        if options.enable_debug_utils
            && !enabled_extensions.contains(&ash::ext::debug_utils::NAME.as_ptr())
        {
            enabled_extensions.push(ash::ext::debug_utils::NAME.as_ptr());
        }

        let mut enabled_layers = Vec::new();
        if options.enable_validation {
            enabled_layers.push(c"VK_LAYER_KHRONOS_validation".as_ptr());
        }

        let create_info = vk::InstanceCreateInfo::default()
            .application_info(&app_info)
            .enabled_extension_names(&enabled_extensions)
            .enabled_layer_names(&enabled_layers);

        let instance = unsafe { entry.create_instance(&create_info, None) }.map_err(vk_error)?;

        let debug = if options.enable_debug_utils {
            Some(DebugMessenger::new(&entry, &instance)?)
        } else {
            None
        };

        Ok((Self { entry, instance }, debug))
    }

    pub fn entry(&self) -> &ash::Entry {
        &self.entry
    }

    pub fn instance(&self) -> &ash::Instance {
        &self.instance
    }

    pub fn enumerate_adapters(&self) -> Vec<Adapter> {
        let Ok(physical_devices) = (unsafe { self.instance.enumerate_physical_devices() }) else {
            return Vec::new();
        };

        physical_devices
            .into_iter()
            .map(|handle| {
                let properties =
                    unsafe { self.instance.get_physical_device_properties(handle) };
                let name = unsafe { CStr::from_ptr(properties.device_name.as_ptr()) }
                    .to_string_lossy()
                    .into_owned();
                Adapter::new(handle, name, properties.device_type, properties.limits)
            })
            .collect()
    }

    pub fn destroy(self) {
        unsafe {
            self.instance.destroy_instance(None);
        }
    }
}

pub struct DebugMessenger {
    loader: ash::ext::debug_utils::Instance,
    messenger: vk::DebugUtilsMessengerEXT,
}

impl DebugMessenger {
    fn new(entry: &ash::Entry, instance: &ash::Instance) -> Result<Self, Error> {
        let loader = ash::ext::debug_utils::Instance::new(entry, instance);
        let create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
            .message_severity(
                vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
                    | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
            )
            .message_type(
                vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
                    | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
                    | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
            )
            .pfn_user_callback(Some(vulkan_debug_callback));
        let messenger =
            unsafe { loader.create_debug_utils_messenger(&create_info, None) }.map_err(vk_error)?;
        Ok(Self { loader, messenger })
    }

    pub fn destroy(self) {
        unsafe {
            self.loader
                .destroy_debug_utils_messenger(self.messenger, None);
        }
    }
}

unsafe extern "system" fn vulkan_debug_callback(
    _message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
    _message_type: vk::DebugUtilsMessageTypeFlagsEXT,
    callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
    _user_data: *mut c_void,
) -> vk::Bool32 {
    if !callback_data.is_null() {
        let message_ptr = unsafe { (*callback_data).p_message };
        if !message_ptr.is_null() {
            let message = unsafe { CStr::from_ptr(message_ptr) };
            eprintln!("[vulkan] {}", message.to_string_lossy());
        }
    }
    vk::FALSE
}