diff options
| -rw-r--r-- | src/main.rs | 209 |
1 files changed, 166 insertions, 43 deletions
diff --git a/src/main.rs b/src/main.rs index a42e8da..9056e98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,15 @@ use crate::error::*; use std::cell::OnceCell; -use std::collections::{ BTreeMap, HashSet }; +use std::collections::{ BTreeMap, BTreeSet, HashSet }; use std::ffi::{ c_void, CStr }; use vulkanalia::{ Device, Entry, Instance, Version }; use vulkanalia::loader::{ LibloadingLoader, LIBRARY }; use vulkanalia::vk::{ self, HasBuilder, ApplicationInfo, InstanceCreateInfo, DeviceV1_0, EntryV1_0, InstanceV1_0, - ExtDebugUtilsExtensionInstanceCommands }; + ExtDebugUtilsExtensionInstanceCommands, + KhrSurfaceExtensionInstanceCommands }; use winit::dpi::LogicalSize; use winit::application::ApplicationHandler; use winit::event::WindowEvent; @@ -27,6 +28,31 @@ enum Acceptable<T> { Rejected(String), } +impl<T> Acceptable<T> { + #[allow(unused)] + fn is_accepted(&self) -> bool { + if let Acceptable::Accepted(_) = self { true } else { false } + } + + #[allow(unused)] + fn is_rejected(&self) -> bool { + if let Acceptable::Rejected(_) = self { true } else { false } + } + + fn unwrap(self) -> T { + if let Acceptable::Accepted(result) = self { + result + } else { + panic!("Unwrapped a rejected Acceptable."); + } + } +} + +struct QueueFamilyIndices { + graphics: u32, + presentation: u32, +} + struct Surreality { // The "window" is the usual operating-system concept of a window; it's @@ -53,6 +79,10 @@ struct Surreality { // over-formality and we don't indulge it. debug_messager: OnceCell<vk::DebugUtilsMessengerEXT>, + // The Vulkan "surface" is the destination that rendering happens into. + // It is connected to the window but distinct from it. + surface: OnceCell<vk::SurfaceKHR>, + // The Vulkan "device" is the abstraction for a GPU. A physical one is the // actual GPU, and a logical one is our connection to it. We track both. // We'll be referencing the logical device a lot, so we follow Vulkan's lead @@ -60,9 +90,19 @@ struct Surreality { physical_device: OnceCell<vk::PhysicalDevice>, device: OnceCell<Device>, - // Vulkan has a first-class concept of command queues, but we only need a - // single one. + // Vulkan has a first-class concept of command queues. We have two of + // them, one for graphics drawing commands and one for presentation. + // + // While these are often the same queue, there is no guarantee of that; + // sometimes there's no queue family that supports both operations together. + // For simplicity's sake we treat them as if they're separate, though the + // handles will alias each other when the initialization logic was able to + // find a family that does both. + // + // Yes, this means the compiler has to deal with pointer aliasing + // concerns, which have a tendency to defeat optimizations. graphics_queue: OnceCell<vk::Queue>, + presentation_queue: OnceCell<vk::Queue>, } impl Surreality { @@ -74,9 +114,11 @@ impl Surreality { entry: OnceCell::new(), instance: OnceCell::new(), debug_messager: OnceCell::new(), + surface: OnceCell::new(), physical_device: OnceCell::new(), device: OnceCell::new(), graphics_queue: OnceCell::new(), + presentation_queue: OnceCell::new(), } } @@ -93,6 +135,10 @@ impl Surreality { self.init_vulkan_instance()?; } + if self.surface.get().is_none() { + self.init_vulkan_surface()?; + } + if self.device.get().is_none() { self.init_vulkan_device()?; } @@ -290,6 +336,28 @@ impl Surreality { } #[allow(unsafe_code)] + fn init_vulkan_surface(&mut self) -> Result<()> { + let window = self.window.get().unwrap(); + let instance = self.instance.get().unwrap(); + + // Conveniently, Vulkanalia's "window" feature allows it to get the + // platform-specific stuff directly out of winit for us. This wrapper does + // not correspond 1:1 to a Vulkan function; rather, it picks the Vulkan + // function from the appropriate platform-specific extension. + // + // The reason it takes the window twice is that that first one is + // actually there to reference the display (in the x11 sense of "display" + // meaning the connection to the windowing system). + let surface = unsafe { + vulkanalia::window::create_surface(&instance, &window, &window) + }?; + + let _ = self.surface.set(surface); + + Ok(()) + } + + #[allow(unsafe_code)] fn init_vulkan_device(&mut self) -> Result<()> { if self.physical_device.get().is_none() { let physical_device = self.pick_vulkan_device()?; @@ -298,24 +366,18 @@ impl Surreality { } if self.device.get().is_none() { - let instance = self.instance.get().unwrap(); - let physical_device = self.physical_device.get().unwrap(); - - // Compute the queue family index of the first queue family that - // supports graphics. - let mut graphics_index = None; - for (index, queue) in (unsafe { - instance.get_physical_device_queue_family_properties(*physical_device) - }).into_iter().enumerate() { - if queue.queue_flags.contains(vk::QueueFlags::GRAPHICS) { - graphics_index = Some(index as u32); - break; - } - } - // We already did this check in score_vulkan_device(), so if it fails + // Usually we're content to borrow these, but here, that would give us + // ownership problems. In reality they are smart pointer objects that do + // not directly contain their substantive pieces, so we can freely clone + // them, and in this case it's convenient to do that. + let instance = self.instance.get().unwrap().clone(); + let physical_device = self.physical_device.get().unwrap().clone(); + + // We already did the check in score_vulkan_device(), so if it fails // this second time, that's our own bug and we don't need to explain it // to our users. - let graphics_index = graphics_index.unwrap(); + let indices = self.find_device_queue_family_indices(&physical_device)? + .unwrap(); // Old versions of Vulkan want layers to be enabled at the device // level as well. Newer ones will ignore this and just use the instance @@ -338,13 +400,24 @@ impl Surreality { extensions.push(layer_name.as_ptr()); } - // Passing the priorities vector also implicitly sets the count of how - // many queues we are creating. This nicety is one of the fun things - // Vulkanalia's builders do for us. - let queue_info = vk::DeviceQueueCreateInfo::builder() - .queue_family_index(graphics_index) - .queue_priorities(&[1.0]); - let queues = vec![queue_info]; + // We have one or more queue family indices; we don't know a priori + // how many, because it's possible some of them are the same. We only + // want to create one queue per distinct family, so we find the unique + // indices... + let mut unique_queue_family_indices = BTreeSet::new(); + unique_queue_family_indices.insert(indices.graphics); + unique_queue_family_indices.insert(indices.presentation); + + // ... then add a queue create info struct for each. + let mut queues = Vec::new(); + for index in unique_queue_family_indices { + // Passing the priorities vector also implicitly sets the count of + // how many queues we are creating within the family. This nicety is + // one of the fun things Vulkanalia's builders do for us. + queues.push(vk::DeviceQueueCreateInfo::builder() + .queue_family_index(index) + .queue_priorities(&[1.0])); + } let device_info = vk::DeviceCreateInfo::builder() .queue_create_infos(&queues) @@ -363,11 +436,16 @@ impl Surreality { // created the device we told it to create just a single queue in that // family. Now we pass both indices to find the actual queue object. let graphics_queue = unsafe { - device.get_device_queue(graphics_index, 0) + device.get_device_queue(indices.graphics, 0) + }; + + let presentation_queue = unsafe { + device.get_device_queue(indices.presentation, 0) }; let _ = self.device.set(device); let _ = self.graphics_queue.set(graphics_queue); + let _ = self.presentation_queue.set(presentation_queue); } Ok(()) @@ -436,23 +514,18 @@ impl Surreality { fn score_vulkan_device(&mut self, device: &vk::PhysicalDevice) -> Result<Acceptable<u64>> { - let instance = self.instance.get().unwrap(); - - // Not all devices support graphics. We find out by checking whether any - // of their queue families do. - let mut has_graphics = false; - for queue in unsafe { - instance.get_physical_device_queue_family_properties(*device) - } { - if queue.queue_flags.contains(vk::QueueFlags::GRAPHICS) { - has_graphics = true; - } - } - if !has_graphics { - return Ok(Acceptable::Rejected( - "Doesn't support graphics.".to_string())); + // Not all devices support graphics, and not all devices support + // presenting to any given surface. We check whether this one is suitable + // by looking up the indices of the queue families we would use, though + // we ignore the actual values and recompute them later. + if let Acceptable::Rejected(rationale) + = self.find_device_queue_family_indices(device)? + { + return Ok(Acceptable::Rejected(rationale)); } + let instance = self.instance.get().unwrap(); + // At this point we know the device meets our high-level requirements, // so it's just a question of scoring. let properties = unsafe { @@ -481,6 +554,50 @@ impl Surreality { Ok(Acceptable::Accepted(score)) } + #[allow(unsafe_code)] + fn find_device_queue_family_indices(&mut self, device: &vk::PhysicalDevice) + -> Result<Acceptable<QueueFamilyIndices>> + { + let instance = self.instance.get().unwrap(); + let surface = self.surface.get().unwrap(); + + // We need a queue family that supports graphics drawing commands, and a + // queue family that supports presentation commands. These may or may not + // be the same family. + let mut graphics = None; + let mut presentation = None; + + for (index, queue_family) in (unsafe { + instance.get_physical_device_queue_family_properties(*device) + }).into_iter().enumerate() { + if graphics.is_none() + && queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS) + { + graphics = Some(index as u32); + } + + if presentation.is_none() && unsafe { + instance.get_physical_device_surface_support_khr( + *device, index as u32, *surface) + }? { + presentation = Some(index as u32); + } + } + + if let Some(graphics) = graphics { + if let Some(presentation) = presentation { + Ok(Acceptable::Accepted(QueueFamilyIndices { + graphics, presentation + })) + } else { + Ok(Acceptable::Rejected( + "Doesn't support presenting to our window.".to_string())) + } + } else { + Ok(Acceptable::Rejected("Doesn't support graphics.".to_string())) + } + } + fn render(&mut self, window_id: WindowId) -> Result<()> { if let Some(window) = self.window.get() && window_id == window.id() @@ -497,6 +614,12 @@ impl Surreality { impl Drop for Surreality { #[allow(unsafe_code)] fn drop(&mut self) { + if let Some(surface) = self.surface.get() + && let Some(instance) = self.instance.get() + { + unsafe { instance.destroy_surface_khr(*surface, None) }; + } + if let Some(device) = self.device.get() { unsafe { device.destroy_device(None) }; } |