diff options
| -rw-r--r-- | src/error.rs | 84 | ||||
| -rw-r--r-- | src/main.rs | 218 |
2 files changed, 212 insertions, 90 deletions
diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..39d52b8 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,84 @@ +#![forbid(unsafe_code)] + +#[derive(Debug)] +pub struct Error { + pub message: String, +} +pub type Result<T> = std::result::Result<T, Error>; + +impl std::fmt::Display for Error { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) + -> std::result::Result<(), std::fmt::Error> + { + fmt.write_str(&self.message) + } +} + +impl From<winit::error::EventLoopError> for Error { + fn from(e: winit::error::EventLoopError) -> Self { + match e { + winit::error::EventLoopError::NotSupported(e) => Self::from(e), + winit::error::EventLoopError::Os(e) => Self::from(e), + winit::error::EventLoopError::RecreationAttempt => Error { + message: + "There may only ever be a single winit event loop.".to_string() + }, + winit::error::EventLoopError::ExitFailure(code) => Error { + message: + format!("Clean unhappy exit with code {} via winit event loop", + code) + } + } + } +} + +impl From<winit::error::NotSupportedError> for Error { + fn from(e: winit::error::NotSupportedError) -> Self { + Error { + message: + format!("The winit backend does not support an operation: {}", + e.to_string()) + } + } +} + +impl From<winit::error::OsError> for Error { + fn from(e: winit::error::OsError) -> Self { + Error { + message: format!("The OS told winit about an error: {}", e.to_string()) + } + } +} + +impl From<libloading::Error> for Error { + fn from(e: libloading::Error) -> Self { + Error { + message: format!("The dynamic object loader reported an error: {}", + e.to_string()) + } + } +} + +impl From<Box<dyn vulkanalia::loader::LoaderError>> for Error { + fn from(e: Box<dyn vulkanalia::loader::LoaderError>) -> Self { + Error { + message: format!("The Vulkan loader reported an error: {}", + e.to_string()) + } + } +} + +impl From<vulkanalia::vk::ErrorCode> for Error { + fn from(e: vulkanalia::vk::ErrorCode) -> Self { + Error { + message: format!("Vulkan gave an error code: {}", e.to_string()) + } + } +} + +pub fn ignore_errors(mut body: impl FnMut() -> Result<()>) -> () { + if let Err(e) = body() { + eprintln!("Error: {}", e); + } +} + diff --git a/src/main.rs b/src/main.rs index 05c8cd5..a42e8da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ #![deny(unsafe_code)] +use crate::error::*; + use std::cell::OnceCell; use std::collections::{ BTreeMap, HashSet }; use std::ffi::{ c_void, CStr }; -use vulkanalia::{ Entry, Instance, Version }; +use vulkanalia::{ Device, Entry, Instance, Version }; use vulkanalia::loader::{ LibloadingLoader, LIBRARY }; use vulkanalia::vk::{ self, HasBuilder, ApplicationInfo, InstanceCreateInfo, - DeviceV1_4, EntryV1_0, InstanceV1_0, + DeviceV1_0, EntryV1_0, InstanceV1_0, ExtDebugUtilsExtensionInstanceCommands }; use winit::dpi::LogicalSize; use winit::application::ApplicationHandler; @@ -14,87 +16,7 @@ use winit::event::WindowEvent; use winit::event_loop::{ ActiveEventLoop, EventLoop }; use winit::window::{ Window, WindowAttributes, WindowId }; -#[derive(Debug)] -struct Error { - message: String, -} -type Result<T> = std::result::Result<T, Error>; - -impl std::fmt::Display for Error { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) - -> std::result::Result<(), std::fmt::Error> - { - fmt.write_str(&self.message) - } -} - -impl From<winit::error::EventLoopError> for Error { - fn from(e: winit::error::EventLoopError) -> Self { - match e { - winit::error::EventLoopError::NotSupported(e) => Self::from(e), - winit::error::EventLoopError::Os(e) => Self::from(e), - winit::error::EventLoopError::RecreationAttempt => Error { - message: - "There may only ever be a single winit event loop.".to_string() - }, - winit::error::EventLoopError::ExitFailure(code) => Error { - message: - format!("Clean unhappy exit with code {} via winit event loop", - code) - } - } - } -} - -impl From<winit::error::NotSupportedError> for Error { - fn from(e: winit::error::NotSupportedError) -> Self { - Error { - message: - format!("The winit backend does not support an operation: {}", - e.to_string()) - } - } -} - -impl From<winit::error::OsError> for Error { - fn from(e: winit::error::OsError) -> Self { - Error { - message: format!("The OS told winit about an error: {}", e.to_string()) - } - } -} - -impl From<libloading::Error> for Error { - fn from(e: libloading::Error) -> Self { - Error { - message: format!("The dynamic object loader reported an error: {}", - e.to_string()) - } - } -} - -impl From<Box<dyn vulkanalia::loader::LoaderError>> for Error { - fn from(e: Box<dyn vulkanalia::loader::LoaderError>) -> Self { - Error { - message: format!("The Vulkan loader reported an error: {}", - e.to_string()) - } - } -} - -impl From<vulkanalia::vk::ErrorCode> for Error { - fn from(e: vulkanalia::vk::ErrorCode) -> Self { - Error { - message: format!("Vulkan gave an error code: {}", e.to_string()) - } - } -} - -fn ignore_errors(mut body: impl FnMut() -> Result<()>) -> () { - if let Err(e) = body() { - eprintln!("Error: {}", e); - } -} +mod error; const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216); @@ -113,6 +35,9 @@ struct Surreality { // we could have more, but for now, we don't. window: OnceCell<Window>, + enable_validation: OnceCell<bool>, + enable_portability: OnceCell<bool>, + // The Vulkan "entry" is the part of the Vulkan library ecosystem that's // responsible for finding and loading the other parts. entry: OnceCell<Entry>, @@ -128,18 +53,30 @@ struct Surreality { // over-formality and we don't indulge it. debug_messager: OnceCell<vk::DebugUtilsMessengerEXT>, - // The Vulkan "device" is the abstraction for a GPU. - device: OnceCell<vk::PhysicalDevice>, + // 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 + // and let it have a short variable name. + physical_device: OnceCell<vk::PhysicalDevice>, + device: OnceCell<Device>, + + // Vulkan has a first-class concept of command queues, but we only need a + // single one. + graphics_queue: OnceCell<vk::Queue>, } impl Surreality { fn new() -> Self { Surreality { window: OnceCell::new(), + enable_validation: OnceCell::new(), + enable_portability: OnceCell::new(), entry: OnceCell::new(), instance: OnceCell::new(), debug_messager: OnceCell::new(), + physical_device: OnceCell::new(), device: OnceCell::new(), + graphics_queue: OnceCell::new(), } } @@ -248,7 +185,13 @@ impl Surreality { extensions.push( vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr()); flags.insert(vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR); + + let _ = self.enable_portability.set(true); + } else { + let _ = self.enable_portability.set(false); } + } else { + let _ = self.enable_portability.set(false); } // Request the LunarG validation layer, when appropriate. @@ -257,10 +200,16 @@ impl Surreality { b"VK_LAYER_KHRONOS_validation"); if available_layers.contains(&layer_name) { layers.push(layer_name.as_ptr()); + + let _ = self.enable_validation.set(enable_validation); } else { eprintln!("Vulkan validation requested at build time, \ but no validation layer available."); + + let _ = self.enable_validation.set(false); } + } else { + let _ = self.enable_validation.set(false); } // Request the debug extension. This is the first of three bits of code @@ -281,7 +230,9 @@ impl Surreality { .engine_version(vk::make_version(1, 0, 0)) .api_version(vk::make_version(1, 0, 0)); - let mut instance_create_info = InstanceCreateInfo::builder() + // Deceptively, this DOES get mutated later, but Vulkanalia doesn't see + // it that way. + let instance_create_info = InstanceCreateInfo::builder() .application_info(&application_info) .flags(flags) .enabled_extension_names(&extensions) @@ -338,8 +289,86 @@ impl Surreality { Ok(()) } + #[allow(unsafe_code)] fn init_vulkan_device(&mut self) -> Result<()> { - let physical_device = self.pick_vulkan_device()?; + if self.physical_device.get().is_none() { + let physical_device = self.pick_vulkan_device()?; + + let _ = self.physical_device.set(physical_device); + } + + 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 + // 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(); + + // 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 + // layers. + let features = vk::PhysicalDeviceFeatures::builder(); + let mut extensions = Vec::new(); + let mut layers = Vec::new(); + + if *self.enable_validation.get().unwrap() { + let layer_name = vk::ExtensionName::from_bytes( + b"VK_LAYER_KHRONOS_validation"); + layers.push(layer_name.as_ptr()); + } + + if *self.enable_portability.get().unwrap() { + // This is untested, since the only scenario where it would come up + // is on a Mac, which we don't actually support. Sorry, and good luck. + let layer_name = vk::ExtensionName::from_bytes( + b"VK_KHR_portability_subset"); + 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]; + + let device_info = vk::DeviceCreateInfo::builder() + .queue_create_infos(&queues) + .enabled_layer_names(&layers) + .enabled_extension_names(&extensions) + .enabled_features(&features); + + let device = unsafe { + instance.create_device(*self.physical_device.get().unwrap(), + &device_info, None) + }?; + + // So, this is a little confusing. Queues are found in queue families. + // The family has an index within the device, and the queue has an index + // within the family. We computed the family index above, and when we + // 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) + }; + + let _ = self.device.set(device); + let _ = self.graphics_queue.set(graphics_queue); + } Ok(()) } @@ -421,7 +450,7 @@ impl Surreality { } if !has_graphics { return Ok(Acceptable::Rejected( - "Doesn't support graphics.".to_string()));; + "Doesn't support graphics.".to_string())); } // At this point we know the device meets our high-level requirements, @@ -468,6 +497,15 @@ impl Surreality { impl Drop for Surreality { #[allow(unsafe_code)] fn drop(&mut self) { + if let Some(device) = self.device.get() { + unsafe { device.destroy_device(None) }; + } + + // Everything but the instance itself should already be destroyed, + // before we destroy the debug messager. The special hook to get debug + // messages while destroying the instance itself only applies to the + // instance and the messager, so if we were to destroy anything we + // shouldn't after this point, we'd miss out on diagnostics. if let Some(debug_messager) = self.debug_messager.get() && let Some(instance) = self.instance.get() { @@ -555,9 +593,9 @@ extern "system" fn debug_messager_callback( let threshold = if cfg!(feature = "vulkan-validation") || cfg!(debug_assertions) { - vk::DebugUtilsMessageSeverityFlagsEXT::empty() - } else { vk::DebugUtilsMessageSeverityFlagsEXT::WARNING + } else { + vk::DebugUtilsMessageSeverityFlagsEXT::ERROR }; if severity >= threshold { |