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