From da0be47b14e5846a8be08f0da721346ccdc8c7fd Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 10 Nov 2024 18:18:59 +0100 Subject: [PATCH] Add logical device struct and surface handling for Vulkan Introduce the VkLogicalDevice struct and add surface creation logic in VkInstance. Also, import necessary extensions and refine Vulkan physical device and window handling. Included a dependency on 'anyhow' for error management. --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/display/app.rs | 89 +++++++++++++++++++------------- src/display/mod.rs | 5 +- src/display/window.rs | 63 ++++++++++++++++++++++ src/main.rs | 6 +-- src/vulkan/mod.rs | 2 + src/vulkan/utils.rs | 74 -------------------------- src/vulkan/utils/layers.rs | 38 ++++++++++++++ src/vulkan/utils/mod.rs | 11 ++++ src/vulkan/vk_instance.rs | 57 ++++++++++++++------ src/vulkan/vk_logical_device.rs | 3 ++ src/vulkan/vk_physical_device.rs | 26 ++-------- src/vulkan/vk_surface.rs | 29 +++++++++++ 14 files changed, 258 insertions(+), 153 deletions(-) create mode 100644 src/display/window.rs delete mode 100644 src/vulkan/utils.rs create mode 100644 src/vulkan/utils/layers.rs create mode 100644 src/vulkan/utils/mod.rs create mode 100644 src/vulkan/vk_logical_device.rs create mode 100644 src/vulkan/vk_surface.rs diff --git a/Cargo.lock b/Cargo.lock index 425c1e2..6b96949 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + [[package]] name = "arrayref" version = "0.3.9" @@ -1066,6 +1072,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" name = "rust_ash_test" version = "0.1.0" dependencies = [ + "anyhow", "ash", "ash-window", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 8c27b75..4ba8b4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Florian RICHER "] publish = false [dependencies] +anyhow = "1.0" winit = { version = "0.30", features = [ "rwh_06" ] } ash = { version = "0.38", default-features = false, features = ["linked", "debug", "std"] } ash-window = "0.13" diff --git a/src/display/app.rs b/src/display/app.rs index 4bcc756..152f409 100644 --- a/src/display/app.rs +++ b/src/display/app.rs @@ -1,52 +1,70 @@ use std::fmt::{Display, Formatter}; -use winit::{ - application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop, raw_window_handle::{HasDisplayHandle, DisplayHandle, HandleError}, window::{Window, WindowId} -}; -use winit::window::WindowAttributes; -use crate::vulkan::VkInstance; +use ash::vk::QueueFlags; +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::ActiveEventLoop; +use winit::window::{WindowId}; +use crate::display::window::Window; +use crate::vulkan::{VkInstance, VkPhysicalDevice}; pub struct App { - window_attributes: WindowAttributes, - window: Option, - instance: Option, + window: Window, + instance: Option } impl App { - pub fn new(window_attributes: WindowAttributes) -> Self { + pub fn new(window: Window) -> Self { Self { - window_attributes, - window: None, - instance: None, + window, + instance: None } } -} -impl HasDisplayHandle for App { - fn display_handle(&self) -> Result, HandleError> { - self.window.as_ref() - .ok_or_else(|| HandleError::Unavailable)? - .display_handle() + fn init_vulkan(&mut self) { + let required_extensions = self.window + .required_extensions() + .expect("Unable to get required required extensions"); + + log::info!("Initializing Vulkan instance"); + let instance = VkInstance::new(&required_extensions); + log::info!("Vulkan instance created"); + log::info!("\t{}", instance); + + let surface = instance.create_surface(&self.window) + .expect("Unable to create surface"); + + let mut physical_devices = instance.get_physical_devices(); + physical_devices.sort_by(|a, b| b.priority().cmp(&a.priority())); + + let (physical_device, queue_family_index) = physical_devices + .iter() + .find_map(|physical_device| { + physical_device.queue_family_properties + .iter() + .enumerate() + .find_map(|(index, queue_family_property)| { + if surface.physical_device_queue_supported(physical_device, index as u32).unwrap_or(false) { + Some((physical_device, index as u32)) + } else { + None + } + }) + }) + .expect("Unable to find suitable device"); + + + self.instance = Some(instance); + } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - self.window = event_loop - .create_window(self.window_attributes.clone()) - .ok(); + self.window.create_window(event_loop) + .map_err(|err| format!("Failed to create window: {}", err)) + .unwrap(); - self.instance = self.window - .as_ref() - .and_then(|w| Some(VkInstance::new(w))); - - if let Some(instance) = self.instance.as_ref() { - let physical_devices = instance.get_physical_devices(); - - log::info!("Physical Devices:"); - for physical_device in physical_devices { - log::info!("{}", physical_device) - } - } + self.init_vulkan(); } fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { @@ -55,10 +73,7 @@ impl ApplicationHandler for App { log::info!("The close button was pressed; stopping"); event_loop.exit(); } - WindowEvent::RedrawRequested => { - self.window.as_ref().unwrap().request_redraw(); - } - _ => (), + _ => self.window.window_event(event_loop, id, event), } } } diff --git a/src/display/mod.rs b/src/display/mod.rs index dc5ff07..9c5c623 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -1,2 +1,5 @@ mod app; -pub use app::App; \ No newline at end of file +mod window; + +pub use app::App; +pub use window::Window; \ No newline at end of file diff --git a/src/display/window.rs b/src/display/window.rs new file mode 100644 index 0000000..f062162 --- /dev/null +++ b/src/display/window.rs @@ -0,0 +1,63 @@ +use std::ffi::c_char; +use winit::event::WindowEvent; +use winit::event_loop::ActiveEventLoop; +use winit::raw_window_handle::HasDisplayHandle; +use winit::window::WindowId; + +pub struct Window { + handle: Option, + window_attributes: winit::window::WindowAttributes +} + +impl Window { + pub fn new(window_attributes: winit::window::WindowAttributes) -> Self { + Self { + handle: None, + window_attributes, + } + } + + pub fn create_window(&mut self, event_loop: &ActiveEventLoop) -> anyhow::Result<()> { + let window = event_loop.create_window(self.window_attributes.clone())?; + + self.handle = Some(window); + + Ok(()) + } + + pub fn required_extensions(&self) -> anyhow::Result> { + let display_handle = self.handle + .as_ref() + .ok_or_else(||anyhow::anyhow!("Window not found"))? + .display_handle()?; + + #[allow(unused_mut)] + let mut extension_names = ash_window::enumerate_required_extensions(display_handle.as_raw())? + .to_vec(); + + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + extension_names.push(ash::khr::portability_enumeration::NAME.as_ptr()); + // Enabling this extension is a requirement when using `VK_KHR_portability_subset` + extension_names.push(ash::khr::get_physical_device_properties2::NAME.as_ptr()); + } + + Ok(extension_names) + } + + pub fn handle(&self) -> Option<&winit::window::Window> { + self.handle.as_ref() + } + + pub fn window_event(&mut self, _event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + match event { + WindowEvent::RedrawRequested => { + match self.handle.as_ref() { + Some(window) => window.request_redraw(), + None => log::warn!("Redraw requested but no window found") + } + } + _ => (), + } + } +} diff --git a/src/main.rs b/src/main.rs index fb42818..66299aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use winit::event_loop::EventLoop; -use winit::window::Window; mod display; mod vulkan; @@ -9,7 +8,7 @@ fn main() { let event_loop = EventLoop::new().unwrap(); - let window_attributes = Window::default_attributes() + let window_attributes = winit::window::Window::default_attributes() .with_title("Rust ASH Test") .with_visible(true) .with_inner_size(winit::dpi::LogicalSize::new( @@ -17,7 +16,8 @@ fn main() { f64::from(600), )); - let mut app = display::App::new(window_attributes); + let window = display::Window::new(window_attributes); + let mut app = display::App::new(window); let _ = event_loop.run_app(&mut app); } diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs index 6ea722f..9d2cfd4 100644 --- a/src/vulkan/mod.rs +++ b/src/vulkan/mod.rs @@ -1,6 +1,8 @@ pub(self) mod vk_instance; pub(self) mod vk_physical_device; +pub(self) mod vk_logical_device; mod utils; +mod vk_surface; pub use vk_instance::VkInstance; pub use vk_physical_device::VkPhysicalDevice; \ No newline at end of file diff --git a/src/vulkan/utils.rs b/src/vulkan/utils.rs deleted file mode 100644 index 682b890..0000000 --- a/src/vulkan/utils.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::ffi::CString; - -pub fn use_layers( - entry: &ash::Entry, - layers_to_select: Vec<&str>, -) -> Vec { - let layers_available = get_layers_available(entry); - - log_layers_available(&layers_available); - - let selected_layers = select_layers(&layers_available, &layers_to_select); - - log_layers_wanted(&layers_to_select, &selected_layers); - - selected_layers - .iter() - .map(|sl| CString::new(sl.clone().as_bytes()).unwrap()) - .collect::>() -} - -fn get_layers_available(entry: &ash::Entry) -> Vec { - unsafe { entry.enumerate_instance_layer_properties().unwrap_or_default() } -} - -fn log_layers_available(layers_available: &[ash::vk::LayerProperties]) { - log::info!("Available layers ({}):", layers_available.len()); - for l in layers_available { - log::info!( - "\t{:?}\tImplementation version: {}\tVulkan Version: {}\tDescription: {:?}", - l.layer_name_as_c_str().unwrap_or_default(), - l.implementation_version, - print_version(l.spec_version), - l.description_as_c_str().unwrap_or_default() - ); - } - log::info!(""); // Add blank line -} - -fn select_layers( - layers_available: &[ash::vk::LayerProperties], - layers_to_select: &[&str], -) -> Vec { - layers_available - .iter() - .filter_map(|l| { - let layer_name = l - .layer_name_as_c_str() - .unwrap_or_default() - .to_string_lossy(); - layers_to_select - .iter() - .find(|&&ln| ln == layer_name) - .map(|_| layer_name.into_owned()) - }) - .collect() -} - -fn log_layers_wanted(layers_to_select: &[&str], selected_layers: &[String]) { - log::info!("Layers wanted ({}):", layers_to_select.len()); - for ol in layers_to_select { - let selected = selected_layers.iter().any(|sl| sl == ol); - log::info!("\t{:?}\tSelected: {}", ol, selected); - } - log::info!(""); // Add blank line -} - -pub fn print_version(version: u32) -> String { - format!( - "{}.{}.{}", - ash::vk::api_version_major(version), - ash::vk::api_version_minor(version), - ash::vk::api_version_patch(version) - ) -} diff --git a/src/vulkan/utils/layers.rs b/src/vulkan/utils/layers.rs new file mode 100644 index 0000000..9a6fa2e --- /dev/null +++ b/src/vulkan/utils/layers.rs @@ -0,0 +1,38 @@ +use std::ffi::CString; + +pub fn use_layers( + entry: &ash::Entry, + layers_to_select: Vec<&str>, +) -> Vec { + let layers_available = get_layers_available(entry); + + let selected_layers = select_layers(&layers_available, &layers_to_select); + + selected_layers + .iter() + .map(|sl| CString::new(sl.clone().as_bytes()).unwrap()) + .collect::>() +} + +fn get_layers_available(entry: &ash::Entry) -> Vec { + unsafe { entry.enumerate_instance_layer_properties().unwrap_or_default() } +} + +fn select_layers( + layers_available: &[ash::vk::LayerProperties], + layers_to_select: &[&str], +) -> Vec { + layers_available + .iter() + .filter_map(|l| { + let layer_name = l + .layer_name_as_c_str() + .unwrap_or_default() + .to_string_lossy(); + layers_to_select + .iter() + .find(|&&ln| ln == layer_name) + .map(|_| layer_name.into_owned()) + }) + .collect() +} diff --git a/src/vulkan/utils/mod.rs b/src/vulkan/utils/mod.rs new file mode 100644 index 0000000..39400d8 --- /dev/null +++ b/src/vulkan/utils/mod.rs @@ -0,0 +1,11 @@ +mod layers; +pub use layers::use_layers; + +pub fn print_version(version: u32) -> String { + format!( + "{}.{}.{}", + ash::vk::api_version_major(version), + ash::vk::api_version_minor(version), + ash::vk::api_version_patch(version) + ) +} diff --git a/src/vulkan/vk_instance.rs b/src/vulkan/vk_instance.rs index 525089b..b949812 100644 --- a/src/vulkan/vk_instance.rs +++ b/src/vulkan/vk_instance.rs @@ -1,7 +1,10 @@ -use std::ffi::CString; +use std::ffi::{c_char, CString}; +use std::fmt::{Display, Formatter}; use ash::{Instance, vk, Entry}; -use winit::raw_window_handle::{HasDisplayHandle}; +use ash::khr::surface; +use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use crate::vulkan::utils::use_layers; +use crate::vulkan::vk_surface::VkSurface; use crate::vulkan::VkPhysicalDevice; pub struct VkInstance { @@ -10,7 +13,9 @@ pub struct VkInstance { } impl VkInstance { - pub fn new(window: &impl HasDisplayHandle) -> Self { + pub fn new( + required_extensions: &Vec<*const c_char>, + ) -> Self { let entry = Entry::linked(); // Layers @@ -21,19 +26,6 @@ impl VkInstance { ]); let layers_raw = layers.iter().map(|s| s.as_ptr()).collect::>(); - // Extensions - let mut extension_names = - ash_window::enumerate_required_extensions(window.display_handle().expect("No display handle").as_raw()) - .unwrap() - .to_vec(); - - #[cfg(any(target_os = "macos", target_os = "ios"))] - { - extension_names.push(ash::khr::portability_enumeration::NAME.as_ptr()); - // Enabling this extension is a requirement when using `VK_KHR_portability_subset` - extension_names.push(ash::khr::get_physical_device_properties2::NAME.as_ptr()); - } - // App Info let app_name = CString::new("VulkanTriangle").unwrap(); let appinfo = vk::ApplicationInfo::default() @@ -53,7 +45,7 @@ impl VkInstance { let create_info = vk::InstanceCreateInfo::default() .application_info(&appinfo) .enabled_layer_names(&layers_raw) - .enabled_extension_names(&extension_names) + .enabled_extension_names(&required_extensions) .flags(create_flags); let instance: Instance = unsafe { @@ -75,6 +67,31 @@ impl VkInstance { .iter().map(|physical_device| VkPhysicalDevice::new(&self.handle, *physical_device)) .collect() } + + pub fn create_surface( + &self, + window: &crate::display::Window + ) -> anyhow::Result { + let window_handle = window.handle() + .ok_or_else(|| anyhow::anyhow!("Window handle is not available."))?; + + let surface_loader = surface::Instance::new(&self.entry, &self.handle); + + let surface = unsafe { + ash_window::create_surface( + &self.entry, + &self.handle, + window_handle.display_handle()?.as_raw(), + window_handle.window_handle()?.as_raw(), + None, + )? + }; + + Ok(VkSurface::new( + surface_loader, + surface, + )) + } } impl Drop for VkInstance { @@ -83,4 +100,10 @@ impl Drop for VkInstance { self.handle.destroy_instance(None); } } +} + +impl Display for VkInstance { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Vulkan Version:") + } } \ No newline at end of file diff --git a/src/vulkan/vk_logical_device.rs b/src/vulkan/vk_logical_device.rs new file mode 100644 index 0000000..5b22cc3 --- /dev/null +++ b/src/vulkan/vk_logical_device.rs @@ -0,0 +1,3 @@ +pub struct VkLogicalDevice { + +} \ No newline at end of file diff --git a/src/vulkan/vk_physical_device.rs b/src/vulkan/vk_physical_device.rs index 753947e..8337d42 100644 --- a/src/vulkan/vk_physical_device.rs +++ b/src/vulkan/vk_physical_device.rs @@ -1,10 +1,11 @@ +use std::cmp::Ordering; use std::fmt::{Display, Formatter}; use ash::vk; -use crate::display::App; +use crate::vulkan::vk_surface::VkSurface; pub struct VkPhysicalDevice { // Vulkan properties - handle: vk::PhysicalDevice, + pub(super) handle: vk::PhysicalDevice, pub properties: vk::PhysicalDeviceProperties, pub features: vk::PhysicalDeviceFeatures, pub queue_family_properties: Vec, @@ -26,33 +27,16 @@ impl VkPhysicalDevice { } pub fn priority(&self) -> usize { - let mut priority = 0; - - let has_graphics_support = self.queue_family_properties.iter().any(|qf| qf.queue_flags.contains(vk::QueueFlags::GRAPHICS)); - let has_compute_support = self.queue_family_properties.iter().any(|qf| qf.queue_flags.contains(vk::QueueFlags::COMPUTE)); - let has_transfer_support = self.queue_family_properties.iter().any(|qf| qf.queue_flags.contains(vk::QueueFlags::TRANSFER)); - let has_sparse_binding_support = self.queue_family_properties.iter().any(|qf| qf.queue_flags.contains(vk::QueueFlags::SPARSE_BINDING)); - let physical_device_type = self.properties.device_type; - - priority |= has_graphics_support as usize; - priority |= (has_sparse_binding_support as usize) << 1; - priority |= (has_transfer_support as usize) << 2; - priority |= (has_compute_support as usize) << 3; - - let weight : usize = match physical_device_type { + match self.properties.device_type { vk::PhysicalDeviceType::CPU => 1, vk::PhysicalDeviceType::VIRTUAL_GPU => 2, vk::PhysicalDeviceType::INTEGRATED_GPU => 3, vk::PhysicalDeviceType::DISCRETE_GPU => 4, _ => 0 - }; - priority |= weight << 4; - - priority + } } } - impl Display for VkPhysicalDevice { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "\tNom: {:?}, Priorité: {}", self.properties.device_name_as_c_str().unwrap_or_default(), self.priority()) diff --git a/src/vulkan/vk_surface.rs b/src/vulkan/vk_surface.rs new file mode 100644 index 0000000..3472ed8 --- /dev/null +++ b/src/vulkan/vk_surface.rs @@ -0,0 +1,29 @@ +use ash::prelude::VkResult; +use crate::vulkan::VkPhysicalDevice; + +pub struct VkSurface { + surface_loader: ash::khr::surface::Instance, + surface: ash::vk::SurfaceKHR, +} + +impl VkSurface { + pub fn new( + surface_loader: ash::khr::surface::Instance, + surface: ash::vk::SurfaceKHR, + ) -> Self { + Self { + surface_loader, + surface + } + } + + pub fn physical_device_queue_supported(&self, physical_device: &VkPhysicalDevice, queue_index: u32) -> VkResult { + unsafe { + self.surface_loader.get_physical_device_surface_support( + physical_device.handle, + queue_index, + self.surface + ) + } + } +}