diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 7b1da1c..8e437ae 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -1,7 +1,7 @@ pub mod error; use std::ffi::{CStr, c_char}; -use ash::{Entry, Instance, ext, vk}; +use ash::{Entry, Instance, ext, vk, Device}; use tracing::{debug, error, info, warn}; pub use error::RendererError; @@ -18,10 +18,20 @@ pub struct Renderer { debug_messenger: vk::DebugUtilsMessengerEXT, /// The selected physical device (GPU). physical_device: vk::PhysicalDevice, + /// The logical device handle. + device: Device, + /// The handle to the graphics queue. + graphics_queue: vk::Queue, + /// The index of the graphics queue family. + graphics_queue_index: u32, } impl Renderer { /// Initializes the Vulkan renderer. + /// + /// This function loads the Vulkan library, creates an instance, selects a GPU, + /// and initializes a logical device with a graphics queue. + /// /// `required_extensions` are raw C-strings (pointers) provided by the windowing system. pub fn new(required_extensions: &[*const c_char]) -> Result { let entry = unsafe { Entry::load() }?; @@ -74,12 +84,19 @@ impl Renderer { let physical_device = Self::pick_physical_device(&instance)?; + // Find the graphics queue and create the logical device + let graphics_queue_index = Self::find_graphics_queue_family(&instance, physical_device)?; + let (device, graphics_queue) = Self::create_logical_device(&instance, physical_device, graphics_queue_index)?; + Ok(Self { _entry: entry, instance, debug_utils, debug_messenger, physical_device, + device, + graphics_queue, + graphics_queue_index, }) } @@ -112,6 +129,50 @@ impl Renderer { Ok(selected) } + + /// Finds the index of the first queue family that supports graphics operations. + fn find_graphics_queue_family( + instance: &Instance, + physical_device: vk::PhysicalDevice, + ) -> Result { + let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; + + props.iter().enumerate() + .find(|(_, p)| p.queue_flags.contains(vk::QueueFlags::GRAPHICS)) + .map(|(i, _)| i as u32) + .ok_or(RendererError::NoSuitableGpu) + } + + /// Creates a logical device and retrieves the handle to its graphics queue. + fn create_logical_device( + instance: &Instance, + physical_device: vk::PhysicalDevice, + queue_index: u32, + ) -> Result<(Device, vk::Queue), RendererError> { + // Request a single queue from the graphics family + let queue_priorities = [1.0]; + let queue_info = vk::DeviceQueueCreateInfo::default() + .queue_family_index(queue_index) + .queue_priorities(&queue_priorities); + + // Enable modern Vulkan 1.3 features + let mut features_13 = vk::PhysicalDeviceVulkan13Features::default() + .dynamic_rendering(true) // Removes the need for RenderPasses/Framebuffers + .synchronization2(true); // Cleaner memory barriers and synchronization + + // We need the swapchain extension to present images to the window + let device_extensions = [ash::khr::swapchain::NAME.as_ptr()]; + + let create_info = vk::DeviceCreateInfo::default() + .queue_create_infos(std::slice::from_ref(&queue_info)) + .enabled_extension_names(&device_extensions) + .push_next(&mut features_13); + + let device = unsafe { instance.create_device(physical_device, &create_info, None)? }; + let graphics_queue = unsafe { device.get_device_queue(queue_index, 0) }; + + Ok((device, graphics_queue)) + } } /// The callback function invoked by Vulkan's validation layers.