// SPDX-License-Identifier: AGPL-3.0-only //! Logic for selecting physical devices and creating logical devices. use crate::error::RendererError; use ash::{Device, Instance, khr, vk}; /// Picks a physical device (GPU) that supports the required features and extensions. /// /// # Errors /// /// Returns [`RendererError::VulkanError`] if physical devices cannot be enumerated, or [`RendererError::NoSuitableGpu`] if none meets the requirements. pub fn pick_physical_device( instance: &Instance, surface_loader: &khr::surface::Instance, surface: vk::SurfaceKHR, ) -> Result { let devices = unsafe { instance.enumerate_physical_devices()? }; for device in devices { if is_device_suitable(instance, device, surface_loader, surface) { let props = unsafe { instance.get_physical_device_properties(device) }; let name = unsafe { std::ffi::CStr::from_ptr(props.device_name.as_ptr()).to_string_lossy() }; tracing::info!("Selected GPU: \"{name}\""); return Ok(device); } } Err(RendererError::NoSuitableGpu) } /// Creates a logical device and retrieves the graphics queue. /// /// # Errors /// /// Returns [`RendererError::VulkanError`] if the device cannot be created. pub fn create_logical_device( instance: &Instance, physical_device: vk::PhysicalDevice, queue_family_index: u32, ) -> Result<(Device, vk::Queue), RendererError> { let priorities = [1.0]; let queue_info = vk::DeviceQueueCreateInfo::default() .queue_family_index(queue_family_index) .queue_priorities(&priorities); let device_extensions = [khr::swapchain::NAME.as_ptr()]; // Enable Vulkan 1.3 features let mut synchronization2_features = vk::PhysicalDeviceSynchronization2Features::default().synchronization2(true); let mut dynamic_rendering_features = vk::PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true); // `fillModeNonSolid` unlocks the `POINT` and `LINE` polygon modes used by the debug render modes. `largePoints` permits a shader-written point size above 1.0, without which debug points rasterise as single pixels. let enabled_features = vk::PhysicalDeviceFeatures::default() .fill_mode_non_solid(true) .large_points(true); let create_info = vk::DeviceCreateInfo::default() .queue_create_infos(std::slice::from_ref(&queue_info)) .enabled_extension_names(&device_extensions) .enabled_features(&enabled_features) .push_next(&mut synchronization2_features) .push_next(&mut dynamic_rendering_features); let device = unsafe { instance.create_device(physical_device, &create_info, None)? }; let graphics_queue = unsafe { device.get_device_queue(queue_family_index, 0) }; Ok((device, graphics_queue)) } /// Finds a queue family that supports both graphics commands and presentation. /// /// # Errors /// /// Returns [`RendererError::VulkanError`] if surface-support queries fail, or [`RendererError::NoSuitableGpu`] if no family supports both graphics and presentation. pub fn find_graphics_queue_family( instance: &Instance, physical_device: vk::PhysicalDevice, surface_loader: &khr::surface::Instance, surface: vk::SurfaceKHR, ) -> Result { let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; for (index, prop) in props.iter().enumerate() { #[expect( clippy::expect_used, reason = "a physical device's queue-family count never approaches u32::MAX" )] let index = u32::try_from(index).expect("Queue family index exceeds u32 range"); let graphics = prop.queue_flags.contains(vk::QueueFlags::GRAPHICS); let present = unsafe { surface_loader.get_physical_device_surface_support(physical_device, index, surface)? }; if graphics && present { return Ok(index); } } Err(RendererError::NoSuitableGpu) } fn is_device_suitable( instance: &Instance, device: vk::PhysicalDevice, surface_loader: &khr::surface::Instance, surface: vk::SurfaceKHR, ) -> bool { let extensions = unsafe { instance .enumerate_device_extension_properties(device) .unwrap_or_default() }; let has_swapchain = extensions.iter().any(|ext| unsafe { std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) == khr::swapchain::NAME }); let formats = unsafe { surface_loader .get_physical_device_surface_formats(device, surface) .unwrap_or_default() }; let present_modes = unsafe { surface_loader .get_physical_device_surface_present_modes(device, surface) .unwrap_or_default() }; has_swapchain && !formats.is_empty() && !present_modes.is_empty() }