use std::{ cmp::Ordering, ffi::{CStr, CString}, mem::MaybeUninit, ops::Deref, sync::Arc, }; use ash::{Entry, ext, khr, vk}; use raw_window_handle::RawDisplayHandle; use crate::{ Error, PhysicalDeviceFeatures, PhysicalDeviceInfo, SurfaceCapabilities, device::{Extension, get_extensions, get_layers}, get_physical_device_features, get_physical_device_properties, make_extension, swapchain::Surface, }; pub struct DebugUtilsCreateInfo { pub severity: vk::DebugUtilsMessageSeverityFlagsEXT, pub message_type: vk::DebugUtilsMessageTypeFlagsEXT, } pub(crate) struct InstanceExtensions { pub(crate) get_surface_capabilities2: Option, pub(crate) surface_maintenance1: Option<()>, } pub struct InstanceInner { pub raw: ash::Instance, pub(crate) extensions: InstanceExtensions, pub entry: Entry, pub(crate) _debug_utils: DebugUtils, } #[derive(Clone)] pub struct Instance { pub(crate) inner: Arc, } impl Deref for Instance { type Target = InstanceInner; fn deref(&self) -> &Self::Target { &self.inner } } impl core::fmt::Debug for Instance { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Instance") .field("raw", &self.inner.raw.handle()) .finish_non_exhaustive() } } #[derive(Debug)] pub struct InstanceDesc<'a> { pub app_name: Option<&'a str>, pub app_version: u32, pub instance_extensions: &'a [Extension<'a>], pub layer_settings: &'a [vk::LayerSettingEXT<'a>], pub layers: &'a [&'a CStr], pub display_handle: Option, } pub const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation"; #[cfg(not(debug_assertions))] const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[]; #[cfg(debug_assertions)] const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[ vk::LayerSettingEXT { p_layer_name: VALIDATION_LAYER.as_ptr(), p_setting_name: c"validate_best_practices".as_ptr(), value_count: 1, p_values: &[1u8; 1] as *const u8 as _, ty: vk::LayerSettingTypeEXT::BOOL32, _marker: core::marker::PhantomData, }, vk::LayerSettingEXT { p_layer_name: VALIDATION_LAYER.as_ptr(), p_setting_name: c"validate_best_practices_amd".as_ptr(), value_count: 1, p_values: &[1u8; 1] as *const u8 as _, ty: vk::LayerSettingTypeEXT::BOOL32, _marker: core::marker::PhantomData, }, vk::LayerSettingEXT { p_layer_name: VALIDATION_LAYER.as_ptr(), p_setting_name: c"validate_sync".as_ptr(), value_count: 1, p_values: &[1u8; 1] as *const u8 as _, ty: vk::LayerSettingTypeEXT::BOOL32, _marker: core::marker::PhantomData, }, ]; impl Default for InstanceDesc<'_> { fn default() -> Self { Self { app_name: None, app_version: vk::make_api_version(0, 1, 0, 0), instance_extensions: &[make_extension!(khr::surface)], layer_settings: DEBUG_LAYER_SETTINGS, layers: &[VALIDATION_LAYER], display_handle: None, } } } impl Instance { pub fn new<'a>(desc: &InstanceDesc<'a>) -> crate::Result { let entry = unsafe { ash::Entry::load()? }; let app_name = desc .app_name .and_then(|name| CString::new(name).ok()) .unwrap_or(c"ShooterGame".to_owned()); let version = unsafe { entry.try_enumerate_instance_version()? }.unwrap_or(vk::API_VERSION_1_0); let app_info = vk::ApplicationInfo::default() .api_version(if version < vk::API_VERSION_1_1 { vk::API_VERSION_1_0 } else { // ash doesn't support 1.4 yet vk::API_VERSION_1_3 }) .application_name(&app_name) .application_version(desc.app_version) .engine_name(c"Bevy Engine") .engine_version(vk::make_api_version(0, 0, 1, 0)); let mut validation_info = vk::LayerSettingsCreateInfoEXT::default().settings(desc.layer_settings); let layers = match get_layers(&entry, desc.layers.to_vec()) { Ok(layers) => layers, Err((supported, unsupported)) => { tracing::error!( "Some requested layers were not supported by instance:\nSupported: {:?}\nUnsupported: {:?}", supported, unsupported ); supported } }; let mut requested_extensions = desc.instance_extensions.to_vec(); requested_extensions.push(make_extension!(ext::debug_utils as 1)); #[cfg(debug_assertions)] requested_extensions.push(make_extension!(ext::layer_settings)); // These are wanted for surface capabilities querying, but not strictly // required, and are only supported on newer devices, especially on // NVIDIA cards. requested_extensions.push(make_extension!(khr::get_surface_capabilities2)); requested_extensions.push(make_extension!(ext::surface_maintenance1)); let (extensions, unsupported_extensions) = get_extensions(&entry, &layers, requested_extensions, desc.display_handle)?; if !unsupported_extensions.is_empty() { tracing::error!( "extensions were requested but not supported by instance: {:?}", unsupported_extensions ); } let layers = layers .iter() .map(|layer| layer.as_ptr()) .collect::>(); let extension_names = extensions .iter() .map(|ext| ext.name.as_ptr()) .collect::>(); let create_info = vk::InstanceCreateInfo::default() .application_info(&app_info) .enabled_extension_names(&extension_names) .enabled_layer_names(&layers) .push_next(&mut validation_info); tracing::debug!( "Creating instance:\napp_info: {app_info:#?}\ncreate_info: {create_info:#?}" ); let instance = unsafe { entry.create_instance(&create_info, None)? }; let debug_utils = { let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default() .message_severity( vk::DebugUtilsMessageSeverityFlagsEXT::ERROR | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, ) .message_type( vk::DebugUtilsMessageTypeFlagsEXT::GENERAL | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, ) .pfn_user_callback(Some(crate::debug::debug_callback)); let instance = ext::debug_utils::Instance::new(&entry, &instance); let messenger = unsafe { instance.create_debug_utils_messenger(&debug_info, None)? }; crate::DebugUtils { instance, messenger, } }; let extensions = InstanceExtensions { get_surface_capabilities2: extensions .contains(&make_extension!(khr::get_surface_capabilities2)) .then(|| khr::get_surface_capabilities2::Instance::new(&entry, &instance)), surface_maintenance1: extensions .contains(&make_extension!(ext::surface_maintenance1)) .then_some(()), }; let instance = Arc::new(InstanceInner { raw: instance, extensions, _debug_utils: debug_utils, entry, }); Ok(Self { inner: instance }) } fn choose_adapter( &self, mut discriminator: impl FnMut(&PhysicalDeviceInfo, &PhysicalDeviceInfo) -> Ordering, ) -> crate::Result { let pdevs = unsafe { self.inner.raw.enumerate_physical_devices()? }; let mut pdevs = pdevs .into_iter() .map(|pdev| -> crate::Result { self.expose_adapter(pdev) }) .filter_map(Result::ok) .collect::>(); pdevs.sort_unstable_by(&mut discriminator); pdevs.pop().ok_or(Error::NoAdapter) } /// Queries the surface capabilities of the given physical device and /// surface, including supported formats and present modes. /// If a present mode is provided, and the /// `VK_KHR_get_surface_capabilities2` and `VK_EXT_surface_maintenance1` /// extensions are available, it will also query present mode compatibility /// information to filter the present modes based on the provided initial /// mode. pub(crate) fn get_adapter_surface_capabilities( &self, pdev: vk::PhysicalDevice, surface: &Surface, present_mode: Option, ) -> crate::Result { let formats = unsafe { surface .functor .get_physical_device_surface_formats(pdev, surface.raw)? }; let present_modes = unsafe { surface .functor .get_physical_device_surface_present_modes(pdev, surface.raw)? }; let generic_caps = unsafe { surface .functor .get_physical_device_surface_capabilities(pdev, surface.raw)? }; let (capabilities, present_modes) = if let Some(ref functor) = self.extensions.get_surface_capabilities2 && self.extensions.surface_maintenance1.is_some() { let mut capabilities = vk::SurfaceCapabilities2KHR::default(); let mut surface_info = vk::PhysicalDeviceSurfaceInfo2KHR::default().surface(surface.raw()); let present_mode = present_mode.unwrap_or(present_modes[0]); let mut surface_present_mode = vk::SurfacePresentModeEXT::default().present_mode(present_mode); surface_info = surface_info.push_next(&mut surface_present_mode); let mut present_modes = MaybeUninit::<[vk::PresentModeKHR; 8]>::uninit(); let mut present_mode_info = vk::SurfacePresentModeCompatibilityEXT::default() .present_modes(unsafe { present_modes.assume_init_mut() }); capabilities = capabilities.push_next(&mut present_mode_info); unsafe { functor.get_physical_device_surface_capabilities2( pdev, &surface_info, &mut capabilities, )?; } (capabilities.surface_capabilities, unsafe { let num_present_modes = present_mode_info.present_mode_count as usize; present_modes.assume_init()[..num_present_modes].to_vec() }) } else { (generic_caps, present_modes) }; // surface.functor // .get_physical_device_surface_capabilities2(pdev, surface.raw, present_mode)?; Ok(SurfaceCapabilities { capabilities, min_image_count: generic_caps.min_image_count, formats, present_modes, }) } pub(crate) fn expose_adapter( &self, pdev: vk::PhysicalDevice, ) -> crate::Result { let properties = get_physical_device_properties(&self.inner, pdev)?; let features = get_physical_device_features(&self.inner, pdev, &properties); Ok(PhysicalDeviceInfo { pdev, properties, features, }) } pub(crate) fn choose_adapter_default( &self, surface: Option<&Surface>, required_extensions: &[Extension], required_features: Option<&PhysicalDeviceFeatures>, ) -> crate::Result { self.choose_adapter(|a, b| { // Extensions: we definitely need swapchain. match a .properties .supports_extension(make_extension!(khr::swapchain)) .cmp( &b.properties .supports_extension(make_extension!(khr::swapchain)), ) { Ordering::Equal => {} other => return other, } for ext in required_extensions { match a.properties.supports_extension(*ext) { true => {} false => return Ordering::Less, } } // Check surface compatibility // TODO _ = surface; if let Some(ref required_features) = required_features { if b.features.superset_of(&required_features) && !a.features.superset_of(&required_features) { return Ordering::Less; } } // check specific features that we need match a .features .core13 .synchronization2 .cmp(&b.features.core13.synchronization2) { Ordering::Equal => {} other => return other, } match a .features .core13 .dynamic_rendering .cmp(&b.features.core13.dynamic_rendering) { Ordering::Equal => {} other => return other, } match a .features .core13 .maintenance4 .cmp(&b.features.core13.maintenance4) { Ordering::Equal => {} other => return other, } // Prefer discrete GPUs let a_discrete = a.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU; let b_discrete = b.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU; match (a_discrete, b_discrete) { (true, false) => return Ordering::Greater, (false, true) => return Ordering::Less, _ => {} } // Then prefer newer APIs match a .properties .core .api_version .cmp(&b.properties.core.api_version) { Ordering::Equal => {} other => return other, } // Prefer larger memory match a .properties .get_device_local_memory_count() .cmp(&b.properties.get_device_local_memory_count()) { Ordering::Equal => {} other => return other, } // Prefer larger texture size match a .properties .core .limits .max_image_dimension2_d .cmp(&b.properties.core.limits.max_image_dimension2_d) { Ordering::Equal => {} other => return other, } Ordering::Equal }) } /// So... basically this probably doesn't matter because the graphics queue will always be a present queue as well... pub(crate) fn query_presentation_support( &self, pdev: vk::PhysicalDevice, queue_family: u32, display_handle: RawDisplayHandle, ) -> bool { unsafe { match display_handle { RawDisplayHandle::Xlib(display) => { let surface = ash::khr::xlib_surface::Instance::new(&self.inner.entry, &self.inner.raw); surface.get_physical_device_xlib_presentation_support( pdev, queue_family, display.display.unwrap().as_ptr() as _, display.screen as _, ) } RawDisplayHandle::Xcb(_xcb_display_handle) => todo!("xcb"), RawDisplayHandle::Wayland(wayland_display_handle) => { let surface = ash::khr::wayland_surface::Instance::new( &self.inner.entry, &self.inner.raw, ); surface.get_physical_device_wayland_presentation_support( pdev, queue_family, wayland_display_handle.display.cast().as_mut(), ) } RawDisplayHandle::Drm(_) => { // idk ? true } RawDisplayHandle::Windows(_) => { ash::khr::win32_surface::Instance::new(&self.inner.entry, &self.inner.raw) .get_physical_device_win32_presentation_support(pdev, queue_family) } _ => panic!("unsupported platform"), } } } } pub(crate) struct DebugUtils { pub instance: ext::debug_utils::Instance, pub messenger: vk::DebugUtilsMessengerEXT, } impl Drop for DebugUtils { fn drop(&mut self) { unsafe { self.instance .destroy_debug_utils_messenger(self.messenger, None); } } }