use std::{ collections::HashSet, ffi::{c_char, c_void, CStr, CString}, sync::Arc, }; use ash::{ext::debug_utils, vk}; use winit::raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle}; use crate::{ error::{GfxHalError, Result}, physical_device::PhysicalDevice, surface::Surface, }; unsafe extern "system" fn vulkan_debug_callback( message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, message_type: vk::DebugUtilsMessageTypeFlagsEXT, p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, _p_user_data: *mut c_void, ) -> vk::Bool32 { let callback_data = *p_callback_data; let message_id_number: i32 = callback_data.message_id_number; let message_id_name = if callback_data.p_message_id_name.is_null() { std::borrow::Cow::from("") } else { CStr::from_ptr(callback_data.p_message_id_name).to_string_lossy() }; let message = if callback_data.p_message.is_null() { std::borrow::Cow::from("") } else { CStr::from_ptr(callback_data.p_message).to_string_lossy() }; let severity = match message_severity { vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => "[VERBOSE]", vk::DebugUtilsMessageSeverityFlagsEXT::INFO => "[INFO]", vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => "[WARNING]", vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => "[ERROR]", _ => "[UNKNOWN SEVERITY]", }; let ty = match message_type { vk::DebugUtilsMessageTypeFlagsEXT::GENERAL => "[GENERAL]", vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION => "[VALIDATION]", vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE => "[PERFORMANCE]", _ => "[UNKNOWN TYPE]", }; // Use the tracing crate for output match message_severity { vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { tracing::debug!( "{} {} ({}:{}) {}", severity, ty, message_id_name, message_id_number, message ); } vk::DebugUtilsMessageSeverityFlagsEXT::INFO => { tracing::info!( "{} {} ({}:{}) {}", severity, ty, message_id_name, message_id_number, message ); } vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => { tracing::warn!( "{} {} ({}:{}) {}", severity, ty, message_id_name, message_id_number, message ); } // Treat ERROR and higher as errors _ => { tracing::error!( "{} {} ({}:{}) {}", severity, ty, message_id_name, message_id_number, message ); } } vk::FALSE // Standard return value } #[derive(Clone, Debug)] pub struct InstanceConfig { pub application_name: String, pub engine_name: String, pub application_version: u32, pub engine_version: u32, /// Enable Vulkan validation layers pub enable_validation: bool, /// Additional required instance extensions beyond surface/debug. pub required_extensions: Vec<&'static CStr>, } impl Default for InstanceConfig { fn default() -> Self { InstanceConfig { application_name: "Defualt App".to_string(), engine_name: "Default Engine".to_string(), application_version: vk::make_api_version(0, 1, 0, 0), engine_version: vk::make_api_version(0, 1, 0, 0), enable_validation: cfg!(debug_assertions), required_extensions: Vec::new(), } } } /// Represents the Vulkan API Instance /// /// Owns the `ash::Entry`, `ash::Instance` and potentially the debug messenger. /// This is the starting point for interacting with Vulkan pub struct Instance { entry: ash::Entry, instance: ash::Instance, debug_utils: Option, debug_messenger: Option, } impl Instance { /// Creates a new Vulkan `Instance` /// /// # Arguments /// * `config` - Configuration settings for the instance /// * `display_handle` - Raw display handle for the windowing system /// * `external_extensions` - A slice of `CString` representing additional required instance extensions, /// typically provided by integration libraries like `egui-ash` pub fn new( config: &InstanceConfig, display_handle: &dyn HasDisplayHandle, external_extentions: &[CString], ) -> Result> { let entry = unsafe { ash::Entry::load()? }; let app_name = CString::new(config.application_name.clone())?; let engine_name = CString::new(config.engine_name.clone())?; let app_info = vk::ApplicationInfo::default() .application_name(&app_name) .application_version(config.application_version) .engine_name(&engine_name) .engine_version(config.engine_version) .api_version(vk::API_VERSION_1_3); let validation_layers = [c"VK_LAYER_KHRONOS_validation"]; let enabled_layer_names_raw: Vec<*const c_char> = if config.enable_validation && Self::check_validation_layer_support(&entry, &validation_layers)? { tracing::info!("Validation layers enabled."); validation_layers.iter().map(|name| name.as_ptr()).collect() } else { if config.enable_validation { tracing::warn!("Validation layers requested but not supported. Disabling."); } Vec::new() }; let display_handle: DisplayHandle = display_handle.display_handle()?; let surface_extensions_ptrs = ash_window::enumerate_required_extensions(display_handle.into())?; let mut required_cstrs_for_check: Vec<&CStr> = Vec::new(); let surface_extensions_cstrs: Vec<&CStr> = surface_extensions_ptrs .iter() .map(|&ptr| unsafe { CStr::from_ptr(ptr) }) .collect(); required_cstrs_for_check.extend(&surface_extensions_cstrs); required_cstrs_for_check.extend(external_extentions.iter().map(|cs| cs.as_c_str())); if config.enable_validation { required_cstrs_for_check.push(ash::ext::debug_utils::NAME); } required_cstrs_for_check.sort_unstable(); required_cstrs_for_check.dedup(); Self::check_instance_extension_support(&entry, &required_cstrs_for_check)?; tracing::info!( "Required instance extensions supported: {:?}", required_cstrs_for_check ); let mut enabled_extension_names_raw: Vec<*const c_char> = Vec::new(); enabled_extension_names_raw.extend(surface_extensions_ptrs); enabled_extension_names_raw.extend(external_extentions.iter().map(|cs| cs.as_ptr())); if config.enable_validation { enabled_extension_names_raw.push(ash::ext::debug_utils::NAME.as_ptr()); } enabled_extension_names_raw.sort_unstable(); enabled_extension_names_raw.dedup(); let mut debug_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 mut instance_create_info = vk::InstanceCreateInfo::default() .application_info(&app_info) .enabled_layer_names(&enabled_layer_names_raw) .enabled_extension_names(&enabled_extension_names_raw); if config.enable_validation { instance_create_info = instance_create_info.push_next(&mut debug_create_info); } let instance = unsafe { entry.create_instance(&instance_create_info, None)? }; tracing::info!("Vulkan instance created succesfully."); let (debug_utils, debug_messenger) = if config.enable_validation { let utils = ash::ext::debug_utils::Instance::new(&entry, &instance); let messenger = unsafe { utils.create_debug_utils_messenger(&debug_create_info, None)? }; tracing::debug!("Debug messenger created."); (Some(utils), Some(messenger)) } else { (None, None) }; Ok(Arc::new(Self { entry, instance, debug_utils, debug_messenger, })) } /// Provides access to the loaded Vulkan entry points. pub fn entry(&self) -> &ash::Entry { &self.entry } /// Provides access to the raw `ash::Instance`. pub fn ash_instance(&self) -> &ash::Instance { &self.instance } /// Provides access to the DebugUtils extension loader, if enabled. pub fn debug_utils(&self) -> Option<&debug_utils::Instance> { self.debug_utils.as_ref() } /// Checks if the requested validation layers are available. fn check_validation_layer_support( entry: &ash::Entry, required_layers: &[&CStr], ) -> Result { let available_layers = unsafe { entry.enumerate_instance_layer_properties()? }; let available_names: HashSet<&CStr> = available_layers .iter() .map(|layer| unsafe { CStr::from_ptr(layer.layer_name.as_ptr()) }) .collect(); for layer in required_layers { if !available_names.contains(layer) { tracing::warn!("Required validation layer {:?} not found.", layer); return Ok(false); } } Ok(true) } /// Checks if the requested instance extensions are available. /// Takes a deduplicated list of required extension names (&CStr). fn check_instance_extension_support( entry: &ash::Entry, required_extensions: &[&CStr], ) -> Result<()> { let available_extensions = unsafe { entry.enumerate_instance_extension_properties(None)? }; let available_names: HashSet<&CStr> = available_extensions .iter() .map(|ext| unsafe { CStr::from_ptr(ext.extension_name.as_ptr()) }) .collect(); tracing::debug!("Available instance extensions: {:?}", available_names); for ext in required_extensions { if !available_names.contains(ext) { tracing::error!("Missing required instance extension: {:?}", ext); return Err(GfxHalError::MissingExtension( ext.to_string_lossy().into_owned(), )); } } Ok(()) } /// Enumerates all physical devices available to this instance. /// /// # Safety /// The `Instance` must be kept alive while the returned `PhysicalDevices`s are in use. /// This is ensured by returning `PhysicalDevice`s holding an `Arc`. pub unsafe fn enumerate_physical_devices(self: &Arc) -> Result> { let physical_device_handles = self.instance.enumerate_physical_devices()?; if physical_device_handles.is_empty() { return Err(GfxHalError::NoSuitableGpu( "No Vulkan-compatibile GPUs found.".to_string(), )); } let devices = physical_device_handles .into_iter() .map(|handle| PhysicalDevice::new(Arc::clone(self), handle)) .collect(); Ok(devices) } /// Creates a vulkan surface for the given window /// /// # Safety /// The `window_handle_trait_obj` must point to a valid window/display managed by caller. /// The `Instance` must be kept alive longer than the returned `Surface` pub unsafe fn create_surface( self: &Arc, window_handle_trait_obj: &(impl HasWindowHandle + HasDisplayHandle), ) -> Result> { Surface::new(Arc::clone(self), window_handle_trait_obj) } } impl Drop for Instance { fn drop(&mut self) { unsafe { if let (Some(utils), Some(messenger)) = (&self.debug_utils, self.debug_messenger) { tracing::debug!("Destroying debug messenger..."); utils.destroy_debug_utils_messenger(messenger, None); } tracing::debug!("Destroying Vulkan instance..."); self.instance.destroy_instance(None); tracing::debug!("Vulkan instance destroyed"); } } }