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.
This commit is contained in:
Serkyo 2026-04-30 14:56:45 +02:00
parent 347f868af1
commit 5ee9fc1bd6
2 changed files with 50 additions and 10 deletions

View file

@ -5,6 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
ash = "0.38.0" ash = "0.38.0"
ash-window = "0.13.0"
raw-window-handle = "0.6.2" raw-window-handle = "0.6.2"
thiserror = "2.0.18" thiserror = "2.0.18"
tracing = "0.1.44" tracing = "0.1.44"

View file

@ -1,7 +1,7 @@
pub mod error; pub mod error;
use std::ffi::{CStr, c_char}; 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 raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
@ -25,6 +25,8 @@ pub struct Renderer {
graphics_queue: vk::Queue, graphics_queue: vk::Queue,
/// The index of the graphics queue family. /// The index of the graphics queue family.
graphics_queue_index: u32, graphics_queue_index: u32,
surface_loader: khr::surface::Instance,
surface: vk::SurfaceKHR,
} }
impl Renderer { impl Renderer {
@ -88,10 +90,19 @@ impl Renderer {
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
let (debug_utils, debug_messenger) = (None, vk::DebugUtilsMessengerEXT::null()); 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 // Pick a physical device that supports our surface
let graphics_queue_index = Self::find_graphics_queue_family(&instance, physical_device)?; 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)?; let (device, graphics_queue) = Self::create_logical_device(&instance, physical_device, graphics_queue_index)?;
Ok(Self { Ok(Self {
@ -103,6 +114,8 @@ impl Renderer {
device, device,
graphics_queue, graphics_queue,
graphics_queue_index, graphics_queue_index,
surface_loader,
surface,
}) })
} }
@ -110,14 +123,25 @@ impl Renderer {
/// ///
/// This function filters for devices supporting graphics operations and scores /// This function filters for devices supporting graphics operations and scores
/// them based on their type, preferring discrete GPUs. /// them based on their type, preferring discrete GPUs.
fn pick_physical_device(instance: &Instance) -> Result<vk::PhysicalDevice, RendererError> { fn pick_physical_device(
instance: &Instance,
surface_loader: &khr::surface::Instance,
surface: vk::SurfaceKHR,
) -> Result<vk::PhysicalDevice, RendererError> {
let devices = unsafe { instance.enumerate_physical_devices()? }; let devices = unsafe { instance.enumerate_physical_devices()? };
let selected = devices.into_iter() let selected = devices.into_iter()
.filter(|&device| { .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) }; 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| { .max_by_key(|&device| {
// Score the device based on its type // Score the device based on its type
@ -136,15 +160,24 @@ impl Renderer {
Ok(selected) 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( fn find_graphics_queue_family(
instance: &Instance, instance: &Instance,
physical_device: vk::PhysicalDevice, physical_device: vk::PhysicalDevice,
surface_loader: &khr::surface::Instance,
surface: vk::SurfaceKHR,
) -> Result<u32, RendererError> { ) -> Result<u32, RendererError> {
let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
props.iter().enumerate() 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) .map(|(i, _)| i as u32)
.ok_or(RendererError::NoSuitableGpu) .ok_or(RendererError::NoSuitableGpu)
} }
@ -167,7 +200,7 @@ impl Renderer {
.synchronization2(true); // Cleaner memory barriers and synchronization .synchronization2(true); // Cleaner memory barriers and synchronization
// We need the swapchain extension to present images to the window // 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() let create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(std::slice::from_ref(&queue_info)) .queue_create_infos(std::slice::from_ref(&queue_info))
@ -185,12 +218,18 @@ impl Renderer {
impl Drop for Renderer { impl Drop for Renderer {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
// 1. Destroy the logical device first
self.device.destroy_device(None); 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 { if let Some(utils) = &self.debug_utils {
utils.destroy_debug_utils_messenger(self.debug_messenger, None); utils.destroy_debug_utils_messenger(self.debug_messenger, None);
} }
// 4. Finally, destroy the instance
self.instance.destroy_instance(None); self.instance.destroy_instance(None);
} }
} }