From e2616a0ef5af2ae8ff0d1e351017976f97c7138a Mon Sep 17 00:00:00 2001
From: Florian RICHER <florian.richer@protonmail.com>
Date: Sun, 13 Apr 2025 18:45:33 +0200
Subject: [PATCH] Add vulkan creation from resources

---
 src/core/mod.rs               |   1 +
 src/core/vulkan/context.rs    |  65 ++++++++++++++++
 src/core/vulkan/mod.rs        |  24 ++++++
 src/core/vulkan/utils.rs      | 137 ++++++++++++++++++++++++++++++++++
 src/core/window/mod.rs        |  39 +++++++---
 src/core/window/raw_handle.rs |  18 +++++
 src/game/mod.rs               |   6 +-
 src/main.rs                   |   4 +-
 8 files changed, 281 insertions(+), 13 deletions(-)
 create mode 100644 src/core/vulkan/context.rs
 create mode 100644 src/core/vulkan/mod.rs
 create mode 100644 src/core/vulkan/utils.rs
 create mode 100644 src/core/window/raw_handle.rs

diff --git a/src/core/mod.rs b/src/core/mod.rs
index bd7ce34..abaeeae 100644
--- a/src/core/mod.rs
+++ b/src/core/mod.rs
@@ -1,4 +1,5 @@
 pub mod app;
 pub mod camera;
 pub mod render;
+pub mod vulkan;
 pub mod window;
diff --git a/src/core/vulkan/context.rs b/src/core/vulkan/context.rs
new file mode 100644
index 0000000..b4e9763
--- /dev/null
+++ b/src/core/vulkan/context.rs
@@ -0,0 +1,65 @@
+use std::sync::Arc;
+
+use bevy_ecs::system::Resource;
+use vulkano::{
+    command_buffer::allocator::StandardCommandBufferAllocator,
+    descriptor_set::allocator::StandardDescriptorSetAllocator,
+    device::{Device, Queue},
+    instance::Instance,
+    memory::allocator::StandardMemoryAllocator,
+    swapchain::Surface,
+};
+
+use crate::core::{app::App, window::raw_handle::DisplayHandleWrapper};
+
+use super::utils;
+
+#[derive(Resource)]
+pub struct VulkanContext {
+    pub instance: Arc<Instance>,
+    pub device: Arc<Device>,
+    pub graphics_queue: Arc<Queue>,
+
+    pub memory_allocator: Arc<StandardMemoryAllocator>,
+    pub command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
+    pub descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
+}
+
+impl From<&App> for VulkanContext {
+    fn from(app: &App) -> Self {
+        let library = utils::load_library();
+
+        let world = app.world();
+
+        let display_handle: &DisplayHandleWrapper =
+            world.get_resource::<DisplayHandleWrapper>().unwrap();
+
+        let enabled_extensions = Surface::required_extensions(&display_handle.0).unwrap();
+
+        let instance = utils::create_instance(library.clone(), enabled_extensions);
+
+        let (device, mut queues) = utils::pick_graphics_device(&instance, &display_handle.0);
+        let graphics_queue = queues.next().unwrap();
+
+        let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
+
+        let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
+            device.clone(),
+            Default::default(),
+        ));
+
+        let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
+            device.clone(),
+            Default::default(),
+        ));
+
+        Self {
+            instance: instance.clone(),
+            device,
+            graphics_queue,
+            memory_allocator,
+            command_buffer_allocator,
+            descriptor_set_allocator,
+        }
+    }
+}
diff --git a/src/core/vulkan/mod.rs b/src/core/vulkan/mod.rs
new file mode 100644
index 0000000..e80d56b
--- /dev/null
+++ b/src/core/vulkan/mod.rs
@@ -0,0 +1,24 @@
+use context::VulkanContext;
+
+use super::app::App;
+
+mod context;
+mod utils;
+
+#[derive(Debug, thiserror::Error)]
+pub enum VulkanError {
+    #[error("Failed to create vulkan context")]
+    FailedToCreateVulkanContext,
+}
+
+pub struct Vulkan;
+
+impl Vulkan {
+    pub fn new(app: &mut App) -> Result<(), VulkanError> {
+        let vulkan_context = VulkanContext::from(app as &App);
+
+        app.world_mut().insert_resource(vulkan_context);
+
+        Ok(())
+    }
+}
diff --git a/src/core/vulkan/utils.rs b/src/core/vulkan/utils.rs
new file mode 100644
index 0000000..046528f
--- /dev/null
+++ b/src/core/vulkan/utils.rs
@@ -0,0 +1,137 @@
+use std::sync::Arc;
+
+use vulkano::{
+    Version, VulkanLibrary,
+    device::{
+        Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo,
+        QueueFlags,
+        physical::{PhysicalDevice, PhysicalDeviceType},
+    },
+    instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions},
+};
+use winit::raw_window_handle::HasDisplayHandle;
+
+pub(super) fn load_library() -> Arc<VulkanLibrary> {
+    let library = VulkanLibrary::new().unwrap();
+
+    log::debug!("Available layer:");
+    for layer in library.layer_properties().unwrap() {
+        log::debug!(
+            "\t - Layer name: {}, Description: {}, Implementation Version: {}, Vulkan Version: {}",
+            layer.name(),
+            layer.description(),
+            layer.implementation_version(),
+            layer.vulkan_version()
+        );
+    }
+
+    library
+}
+
+pub(super) fn create_instance(
+    library: Arc<VulkanLibrary>,
+    required_extensions: InstanceExtensions,
+) -> Arc<Instance> {
+    Instance::new(
+        library,
+        InstanceCreateInfo {
+            // Enable enumerating devices that use non-conformant Vulkan implementations.
+            // (e.g. MoltenVK)
+            flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
+            enabled_extensions: required_extensions,
+            enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")],
+            ..Default::default()
+        },
+    )
+    .unwrap()
+}
+
+pub(super) fn find_physical_device_queue_family_indexes(
+    physical_device: &Arc<PhysicalDevice>,
+    display_handle: &impl HasDisplayHandle,
+) -> Option<u32> {
+    let mut graphic_queue_family_index = None;
+
+    for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() {
+        if queue_family_property
+            .queue_flags
+            .intersects(QueueFlags::GRAPHICS)
+            && physical_device
+                .presentation_support(i as u32, display_handle)
+                .unwrap()
+        {
+            graphic_queue_family_index = Some(i as u32);
+        }
+    }
+
+    graphic_queue_family_index
+}
+
+pub(super) fn pick_physical_device_and_queue_family_indexes(
+    instance: &Arc<Instance>,
+    display_handle: &impl HasDisplayHandle,
+    device_extensions: &DeviceExtensions,
+) -> Option<(Arc<PhysicalDevice>, u32)> {
+    instance
+        .enumerate_physical_devices()
+        .unwrap()
+        .filter(|p| {
+            p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering
+        })
+        .filter(|p| p.supported_extensions().contains(device_extensions))
+        .filter_map(|p| {
+            find_physical_device_queue_family_indexes(&p, display_handle)
+                .and_then(|indexes| Some((p, indexes)))
+        })
+        .min_by_key(|(p, _)| match p.properties().device_type {
+            PhysicalDeviceType::DiscreteGpu => 0,
+            PhysicalDeviceType::IntegratedGpu => 1,
+            PhysicalDeviceType::VirtualGpu => 2,
+            PhysicalDeviceType::Cpu => 3,
+            PhysicalDeviceType::Other => 4,
+            _ => 5,
+        })
+}
+
+pub(super) fn pick_graphics_device(
+    instance: &Arc<Instance>,
+    display_handle: &impl HasDisplayHandle,
+) -> (Arc<Device>, impl ExactSizeIterator<Item = Arc<Queue>>) {
+    let mut device_extensions = DeviceExtensions {
+        khr_swapchain: true,
+        ..DeviceExtensions::empty()
+    };
+
+    let (physical_device, graphics_family_index) =
+        pick_physical_device_and_queue_family_indexes(instance, display_handle, &device_extensions)
+            .unwrap();
+
+    log::debug!(
+        "Using device: {} (type: {:?})",
+        physical_device.properties().device_name,
+        physical_device.properties().device_type,
+    );
+
+    if physical_device.api_version() < Version::V1_3 {
+        device_extensions.khr_dynamic_rendering = true;
+    }
+
+    log::debug!("Using device extensions: {:#?}", device_extensions);
+
+    Device::new(
+        physical_device,
+        DeviceCreateInfo {
+            queue_create_infos: vec![QueueCreateInfo {
+                queue_family_index: graphics_family_index,
+                ..Default::default()
+            }],
+            enabled_extensions: device_extensions,
+            enabled_features: DeviceFeatures {
+                dynamic_rendering: true,
+                ..DeviceFeatures::empty()
+            },
+            ..Default::default()
+        },
+    )
+    .unwrap()
+}
diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs
index e600120..4368310 100644
--- a/src/core/window/mod.rs
+++ b/src/core/window/mod.rs
@@ -1,23 +1,44 @@
 use config::WindowConfig;
+use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper};
 use state::WindowState;
 use winit::event_loop::EventLoop;
 
 use super::app::{App, AppExit};
 
 pub mod config;
+pub mod raw_handle;
 pub mod state;
 
-pub fn init(app: &mut App, window_config: WindowConfig) {
-    let world = app.world_mut();
-    world.insert_resource(window_config);
-
-    let mut event_loop_builder = EventLoop::with_user_event();
-    let event_loop = event_loop_builder.build().unwrap();
-
-    app.set_runner(Box::new(move |app| runner(app, event_loop)));
+#[derive(Debug, thiserror::Error)]
+pub enum WindowError {
+    #[error("Failed to create event loop")]
+    FailedToCreateEventLoop,
 }
 
-fn runner(app: App, event_loop: EventLoop<()>) -> AppExit {
+pub struct Window;
+
+impl Window {
+    pub fn new(app: &mut App, window_config: WindowConfig) -> Result<(), WindowError> {
+        let world = app.world_mut();
+        world.insert_resource(window_config);
+
+        let mut event_loop_builder = EventLoop::with_user_event();
+        let event_loop = event_loop_builder
+            .build()
+            .map_err(|_| WindowError::FailedToCreateEventLoop)?;
+
+        world.insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle()));
+
+        app.set_runner(Box::new(move |app| runner(app, event_loop)));
+
+        Ok(())
+    }
+}
+
+fn runner(mut app: App, event_loop: EventLoop<()>) -> AppExit {
+    app.world_mut()
+        .insert_resource(EventLoopProxyWrapper::new(event_loop.create_proxy()));
+
     let mut window_state = WindowState::new(app);
 
     match event_loop.run_app(&mut window_state) {
diff --git a/src/core/window/raw_handle.rs b/src/core/window/raw_handle.rs
new file mode 100644
index 0000000..16b2178
--- /dev/null
+++ b/src/core/window/raw_handle.rs
@@ -0,0 +1,18 @@
+use bevy_ecs::system::Resource;
+use winit::event_loop::EventLoopProxy;
+
+#[derive(Resource)]
+pub struct EventLoopProxyWrapper<T: 'static>(EventLoopProxy<T>);
+
+impl<T: 'static> EventLoopProxyWrapper<T> {
+    pub fn new(event_loop: EventLoopProxy<T>) -> Self {
+        Self(event_loop)
+    }
+
+    pub fn proxy(&self) -> &EventLoopProxy<T> {
+        &self.0
+    }
+}
+
+#[derive(Resource)]
+pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle);
diff --git a/src/game/mod.rs b/src/game/mod.rs
index ffee10f..0b7787d 100644
--- a/src/game/mod.rs
+++ b/src/game/mod.rs
@@ -1,6 +1,7 @@
 use crate::core::{
     app::App,
-    window::{self, config::WindowConfig},
+    vulkan::Vulkan,
+    window::{Window, config::WindowConfig},
 };
 
 pub fn init(app: &mut App) {
@@ -10,5 +11,6 @@ pub fn init(app: &mut App) {
         height: 600,
     };
 
-    window::init(app, window_config);
+    Window::new(app, window_config).unwrap();
+    Vulkan::new(app).unwrap();
 }
diff --git a/src/main.rs b/src/main.rs
index c981713..8297797 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,8 +8,8 @@ pub mod old_app;
 fn main() -> Result<(), impl Error> {
     env_logger::init();
 
-    // run_new_app()
-    run_old_app()
+    run_new_app()
+    // run_old_app()
 }
 
 fn run_new_app() -> Result<(), impl Error> {