summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/error.rs84
-rw-r--r--src/main.rs218
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 {