Add logical device struct and surface handling for Vulkan
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 0s

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