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.
This commit is contained in:
Florian RICHER 2024-11-10 18:18:59 +01:00
parent 4048937a6c
commit da0be47b14
Signed by: florian.richer
GPG key ID: C73D37CBED7BFC77
14 changed files with 258 additions and 153 deletions

7
Cargo.lock generated
View file

@ -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",

View file

@ -6,6 +6,7 @@ authors = ["Florian RICHER <florian.richer@protonmail.com>"]
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"

View file

@ -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<Window>,
instance: Option<VkInstance>,
window: Window,
instance: Option<VkInstance>
}
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<DisplayHandle<'_>, 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),
}
}
}

View file

@ -1,2 +1,5 @@
mod app;
pub use app::App;
mod window;
pub use app::App;
pub use window::Window;

63
src/display/window.rs Normal file
View file

@ -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<winit::window::Window>,
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<Vec<*const c_char>> {
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")
}
}
_ => (),
}
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -1,74 +0,0 @@
use std::ffi::CString;
pub fn use_layers(
entry: &ash::Entry,
layers_to_select: Vec<&str>,
) -> Vec<CString> {
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::<Vec<_>>()
}
fn get_layers_available(entry: &ash::Entry) -> Vec<ash::vk::LayerProperties> {
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<String> {
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)
)
}

View file

@ -0,0 +1,38 @@
use std::ffi::CString;
pub fn use_layers(
entry: &ash::Entry,
layers_to_select: Vec<&str>,
) -> Vec<CString> {
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::<Vec<_>>()
}
fn get_layers_available(entry: &ash::Entry) -> Vec<ash::vk::LayerProperties> {
unsafe { entry.enumerate_instance_layer_properties().unwrap_or_default() }
}
fn select_layers(
layers_available: &[ash::vk::LayerProperties],
layers_to_select: &[&str],
) -> Vec<String> {
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()
}

11
src/vulkan/utils/mod.rs Normal file
View file

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

View file

@ -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::<Vec<_>>();
// 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<VkSurface> {
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:")
}
}

View file

@ -0,0 +1,3 @@
pub struct VkLogicalDevice {
}

View file

@ -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<vk::QueueFamilyProperties>,
@ -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())

29
src/vulkan/vk_surface.rs Normal file
View file

@ -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<bool> {
unsafe {
self.surface_loader.get_physical_device_surface_support(
physical_device.handle,
queue_index,
self.surface
)
}
}
}