diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index bd3e81d..27e7b4e 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] ash = "0.38.0" +ash-window = "0.13.0" raw-window-handle = "0.6.2" thiserror = "2.0.18" tracing = "0.1.44" diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index b28b21a..743b512 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -1,7 +1,7 @@ pub mod error; use std::ffi::{CStr, c_char}; -use ash::{Entry, Instance, ext, vk, Device}; +use ash::{Device, Entry, Instance, ext, khr, vk}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use tracing::{debug, error, info, warn}; @@ -25,6 +25,8 @@ pub struct Renderer { graphics_queue: vk::Queue, /// The index of the graphics queue family. graphics_queue_index: u32, + surface_loader: khr::surface::Instance, + surface: vk::SurfaceKHR, } impl Renderer { @@ -88,10 +90,19 @@ impl Renderer { #[cfg(not(debug_assertions))] let (debug_utils, debug_messenger) = (None, vk::DebugUtilsMessengerEXT::null()); - let physical_device = Self::pick_physical_device(&instance)?; + // Create the OS surface + let surface = unsafe { + ash_window::create_surface(&entry, &instance, display_handle, window_handle, None)? + }; + let surface_loader = khr::surface::Instance::new(&entry, &instance); - // Find the graphics queue and create the logical device - let graphics_queue_index = Self::find_graphics_queue_family(&instance, physical_device)?; + // Pick a physical device that supports our surface + let physical_device = Self::pick_physical_device(&instance, &surface_loader, surface)?; + + // Find the graphics queue family (which must also support presentation) + let graphics_queue_index = Self::find_graphics_queue_family(&instance, physical_device, &surface_loader, surface)?; + + // Create the logical device let (device, graphics_queue) = Self::create_logical_device(&instance, physical_device, graphics_queue_index)?; Ok(Self { @@ -103,6 +114,8 @@ impl Renderer { device, graphics_queue, graphics_queue_index, + surface_loader, + surface, }) } @@ -110,14 +123,25 @@ impl Renderer { /// /// This function filters for devices supporting graphics operations and scores /// them based on their type, preferring discrete GPUs. - fn pick_physical_device(instance: &Instance) -> Result { + fn pick_physical_device( + instance: &Instance, + surface_loader: &khr::surface::Instance, + surface: vk::SurfaceKHR, + ) -> Result { let devices = unsafe { instance.enumerate_physical_devices()? }; let selected = devices.into_iter() .filter(|&device| { - // Check if the device has a queue family that supports graphics + // Check if the device has a queue family that supports graphics AND presentation let props = unsafe { instance.get_physical_device_queue_family_properties(device) }; - props.iter().any(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS)) + props.iter().enumerate().any(|(i, p)| { + let supports_graphics = p.queue_flags.contains(vk::QueueFlags::GRAPHICS); + let supports_present = unsafe { + surface_loader.get_physical_device_surface_support(device, i as u32, surface) + .unwrap_or(false) + }; + supports_graphics && supports_present + }) }) .max_by_key(|&device| { // Score the device based on its type @@ -136,15 +160,24 @@ impl Renderer { Ok(selected) } - /// Finds the index of the first queue family that supports graphics operations. + /// Finds the index of the first queue family that supports both graphics and presentation. 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) }; props.iter().enumerate() - .find(|(_, p)| p.queue_flags.contains(vk::QueueFlags::GRAPHICS)) + .find(|&(i, p)| { + let supports_graphics = p.queue_flags.contains(vk::QueueFlags::GRAPHICS); + let supports_present = unsafe { + surface_loader.get_physical_device_surface_support(physical_device, i as u32, surface) + .unwrap_or(false) + }; + supports_graphics && supports_present + }) .map(|(i, _)| i as u32) .ok_or(RendererError::NoSuitableGpu) } @@ -167,7 +200,7 @@ impl Renderer { .synchronization2(true); // Cleaner memory barriers and synchronization // We need the swapchain extension to present images to the window - let device_extensions = [ash::khr::swapchain::NAME.as_ptr()]; + let device_extensions = [khr::swapchain::NAME.as_ptr()]; let create_info = vk::DeviceCreateInfo::default() .queue_create_infos(std::slice::from_ref(&queue_info)) @@ -185,12 +218,18 @@ impl Renderer { impl Drop for Renderer { fn drop(&mut self) { unsafe { + // 1. Destroy the logical device first self.device.destroy_device(None); + // 2. Destroy the surface + self.surface_loader.destroy_surface(self.surface, None); + + // 3. Destroy the debug messenger if we created one if let Some(utils) = &self.debug_utils { utils.destroy_debug_utils_messenger(self.debug_messenger, None); } + // 4. Finally, destroy the instance self.instance.destroy_instance(None); } }