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 + ) + } + } +}