From 5ee9fc1bd6cd6d9d4460878b4cfa73b64a0a9aa5 Mon Sep 17 00:00:00 2001 From: Serkyo Date: Thu, 30 Apr 2026 14:56:45 +0200 Subject: [PATCH] feat(renderer): implement surface creation and support checks Created the OS surface and ensured that the selected GPU and queue family support presentation to that surface. Updated Drop to properly destroy the surface and improved documentation. --- crates/renderer/Cargo.toml | 1 + crates/renderer/src/lib.rs | 59 +++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 10 deletions(-) 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); } }