summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/error.rs33
-rw-r--r--src/graphics_permanent.rs728
-rw-r--r--src/main.rs759
3 files changed, 768 insertions, 752 deletions
diff --git a/src/error.rs b/src/error.rs
index f3f1ca9..f034683 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -96,3 +96,36 @@ pub fn ignore_errors(mut body: impl FnMut() -> Result<()>) -> () {
   }
 }
 
+
+pub enum Acceptable<T> {
+  Accepted(T),
+  Rejected(String),
+}
+
+impl<T> Acceptable<T> {
+  #[allow(unused)]
+  pub fn is_accepted(&self) -> bool {
+    if let Acceptable::Accepted(_) = self { true } else { false }
+  }
+
+  #[allow(unused)]
+  pub fn is_rejected(&self) -> bool {
+    if let Acceptable::Rejected(_) = self { true } else { false }
+  }
+
+  #[allow(unused)]
+  pub fn unwrap(self) -> T {
+    if let Acceptable::Accepted(result) = self {
+      result
+    } else {
+      panic!("Unwrapped a rejected Acceptable.");
+    }
+  }
+
+  pub fn require(self) -> Result<T> {
+    match self {
+      Acceptable::Accepted(value) => Ok(value),
+      Acceptable::Rejected(message) => Err(Error { message }),
+    }
+  }
+}
diff --git a/src/graphics_permanent.rs b/src/graphics_permanent.rs
new file mode 100644
index 0000000..a4ece9d
--- /dev/null
+++ b/src/graphics_permanent.rs
@@ -0,0 +1,728 @@
+#![deny(unsafe_code)]
+use crate::error::*;
+
+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,
+                      KhrSurfaceExtensionInstanceCommands };
+use winit::dpi::LogicalSize;
+use winit::event_loop::ActiveEventLoop;
+use winit::window::{ Window, WindowAttributes };
+
+
+const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216);
+
+
+//   The PermanentGraphicsState collects the various windowing-system and
+// Vulkan objects which never need to be regenerated once they're created. The
+// ones which do need that are collected below, in WindowDressing.
+pub struct PermanentGraphicsState {
+  //   The "window" is the usual operating-system concept of a window; it's
+  // provided by winit, and may be X11, Wayland, or some more curious thing.
+  // The way we initialize Vulkan requires us to have at least one of these;
+  // we could have more, but for now, we don't.
+  pub window: Window,
+
+  //   The Vulkan "entry" is the part of the Vulkan library ecosystem that's
+  // responsible for finding and loading the other parts. Once we have the
+  // instance, the entry is never directly used again, but we retain it
+  // because doing otherwise would segfault.
+  #[allow(unused)]
+  entry: Entry,
+
+  //   The Vulkan "instance" is the bulk of the Vulkan library, with most of
+  // the high-level responsibilities around lifecycle management.
+  pub instance: Instance,
+
+  //   The debug messager is a Vulkan object representing our callback which
+  // Vulkan uses to tell us things.
+  //
+  //   Vulkan spells "messager" as "messenger", but this is absurd
+  // over-formality and we don't indulge it.
+  //
+  //    Once we've created this, we never actually need to do anything with
+  // it, but we do need to retain it, so here it is.
+  debug_messager: Option<vk::DebugUtilsMessengerEXT>,
+
+  //   The Vulkan "surface" is the destination that rendering happens into.
+  // It is connected to the window but distinct from it. Since we always have
+  // exactly one window, this is permanent state.
+  pub surface: 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 pick a physical
+  // device during initialization, but only the logical one is used later, so
+  // it's all we track. We'll be referencing the logical device a lot, so we
+  // follow Vulkan's lead and let it have a short variable name.
+  pub device: Device,
+
+  //   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. Alas.
+  pub graphics_queue: vk::Queue,
+  pub presentation_queue: vk::Queue,
+}
+
+
+#[derive(Debug)]
+pub struct QueueFamilyIndices {
+  pub graphics: u32,
+  pub presentation: u32,
+}
+
+//   These structs exist for use in function calling, to remove the potential
+// for accidentally passing or returning one boolean as if it's another.
+struct EnablePortability(bool);
+struct EnableValidation(bool);
+pub struct EnableSwapchain(pub bool);
+
+
+impl PermanentGraphicsState {
+  #[allow(unsafe_code)]
+  pub fn new(event_loop: &ActiveEventLoop)
+      -> Result<(Self, vk::PhysicalDevice, QueueFamilyIndices,
+                 EnableSwapchain)>
+  {
+    let window = Self::init_window(event_loop)?;
+
+    //   There are a few Vulkan features (in the informal sense of
+    // "feature") that we want to be able to run both with and without.
+    // The enable_* values, here and below, are wrapped booleans that describe
+    // those choices.
+    //
+    //   These are only used to communicate between initialization
+    // phases; we don't keep them around after that.
+    let (entry, instance, debug_messager,
+         enable_portability, enable_validation)
+        = Self::init_vulkan(&window)?;
+
+    //   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 (physical_device, device,
+         indices, graphics_queue, presentation_queue,
+         enable_swapchain)
+        = Self::init_vulkan_device(&instance, &surface,
+                                   enable_validation, enable_portability)?;
+
+    Ok((PermanentGraphicsState {
+      window, entry, instance, debug_messager, surface, device,
+      graphics_queue, presentation_queue,
+    }, physical_device, indices, enable_swapchain))
+  }
+
+  #[allow(unsafe_code)]
+  pub fn destroy(self) -> () {
+    unsafe { self.device.destroy_device(None) };
+
+    unsafe { self.instance.destroy_surface_khr(self.surface, 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 {
+      unsafe {
+        self.instance.destroy_debug_utils_messenger_ext(debug_messager, None);
+      }
+    }
+
+    unsafe { self.instance.destroy_instance(None) };
+  }
+
+  fn init_window(event_loop: &ActiveEventLoop) -> Result<Window> {
+    //   Notice that we do this before having a Vulkan instance. The window is
+    // actually a parameter needed to create the instance; see
+    // init_vulkan(), below.
+    let window_attributes = WindowAttributes::default()
+            .with_title("Love, Curiosity, Justice")
+            .with_inner_size(LogicalSize::new(1024, 768));
+
+    Ok(event_loop.create_window(window_attributes)?)
+  }
+
+  #[allow(unsafe_code)]
+  fn init_vulkan(window: &Window)
+      -> Result<(Entry, Instance, Option<vk::DebugUtilsMessengerEXT>,
+                 EnablePortability, EnableValidation)>
+  {
+    let enable_validation = cfg!(feature = "vulkan-validation")
+                            || cfg!(debug_assertions);
+
+    //   Okay, so, a Vulkan "entry" is a small set of functions which are used
+    // to dynamically load all the rest of Vulkan. It's our responsibility to
+    // know how to load the entry, then it will take care of the rest. At
+    // least, that's the theory, but also see flake.nix for all the
+    // FHS-centric assumptions it makes that we have to correct.
+    //
+    //   Anyway, Vulkanalia offers an integration with libloading, which is a
+    // crate that wraps POSIX dlopen(). We use that; it's enabled by
+    // Vulkanalia's "libloading" feature.
+    let loader = unsafe { LibloadingLoader::new(LIBRARY) }?;
+    let entry = unsafe { Entry::new(loader) }?;
+
+    //   Since there's a lot of factors going into our instance creation
+    // request, we'll build up the parameters mutably.
+    let mut flags = vk::InstanceCreateFlags::empty();
+    let mut extensions = Vec::new();
+    let mut layers = Vec::new();
+
+    //   Before we go any further, use Vulkan's introspection to list off
+    // what's available.
+    let mut available_extensions = HashSet::new();
+    for extension in
+            unsafe { entry.enumerate_instance_extension_properties(None) }?
+    {
+      available_extensions.insert(extension.extension_name);
+    }
+    let available_extensions = available_extensions;
+
+    let mut available_layers = HashSet::new();
+    for layer in unsafe { entry.enumerate_instance_layer_properties() }? {
+      available_layers.insert(layer.layer_name);
+    }
+    let available_layers = available_layers;
+
+    //   There are certain extensions which are required by the nature of our
+    // windowing system. Happily, vulanaklia knows how to deal with that based
+    // on the type of window we give it.
+    //
+    //   This is possible because of an integration between Vulkanalia and
+    // winit, which is enabled by Vulkanalia's "window" feature.
+    for extension in vulkanalia::window::get_required_instance_extensions(
+                         window)
+    {
+      extensions.push(extension.as_ptr());
+    }
+
+    //   Deal with Vulkan's thing about opting in to non-conforming
+    // implementations.
+    let enable_portability = if entry.version()?
+                                >= VULKAN_FIRST_PORTABILITY_VERSION
+    {
+      if cfg!(target_os = "macos") {
+        // Vulkan on the Mac is not fully conforming.
+        extensions.push(
+            vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr());
+        extensions.push(
+            vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr());
+        flags.insert(vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR);
+
+        EnablePortability(true)
+      } else {
+        EnablePortability(false)
+      }
+    } else {
+      EnablePortability(false)
+    };
+
+    // Request the LunarG validation layer, when appropriate.
+    let validation_layer_name = vk::ExtensionName::from_bytes(
+                                    b"VK_LAYER_KHRONOS_validation");
+    let enable_validation = if enable_validation {
+      if available_layers.contains(&validation_layer_name) {
+        layers.push(validation_layer_name.as_ptr());
+
+        EnableValidation(true)
+      } else {
+        eprintln!("Vulkan validation requested at build time, \
+                   but no validation layer available.");
+
+        EnableValidation(false)
+      }
+    } else {
+      EnableValidation(false)
+    };
+
+    //   Request the debug extension. This is the first of three bits of code
+    // that deal with this, and has the resonsibility of making sure the
+    // extension is in the list we ask for.
+    let debug_extension_name = vk::EXT_DEBUG_UTILS_EXTENSION.name;
+    if available_extensions.contains(&debug_extension_name) {
+      extensions.push(debug_extension_name.as_ptr());
+    } else {
+      eprintln!("Vulkan debug extension not available; \
+                 this may mean other messages don't show up.");
+    }
+
+    let application_info = ApplicationInfo::builder()
+            .application_name(b"Surreality\0")
+            .application_version(vk::make_version(1, 0, 0))
+            .engine_name(b"Surreality\0")
+            .engine_version(vk::make_version(1, 0, 0))
+            .api_version(vk::make_version(1, 0, 0));
+
+    //   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)
+            .enabled_layer_names(&layers);
+
+    //   Configure the debug extension. This is the middle of three bits of
+    // code that deal with this, and has the responsibility of making sure
+    // the callback will be available during instance creation and
+    // destruction, which is done in a special way that doesn't rely on having
+    // a messager, since there can't be one for those steps.
+    let debug_info = if available_extensions.contains(&debug_extension_name) {
+      let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder()
+              .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all())
+              .message_type(vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
+                            | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
+                            | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE)
+              .user_callback(Some(debug_messager_callback));
+
+      //   Please notice that the reference we pass here will escape Rust's
+      // lifetime checking, since push_next() casts it to a pointer. We don't
+      // get nearly as strong a safety guarantee as one might hope (and as [1]
+      // naively reassures us we do). If we did, the thing we're doing would
+      // actually be forbidden!
+      //
+      // [1] https://kylemayes.github.io/vulkanalia/
+      instance_create_info.push_next(&mut debug_info);
+
+      Some(debug_info)
+    } else { None };
+
+    let instance = unsafe {
+      //   We're promising that every struct referenced here is still alive.
+      // Since it's all pointers, that's... not a thing we statically know. Be
+      // aware. Only you can prevent segfaults.
+      entry.create_instance(&instance_create_info, None)
+    }?;
+
+    //   Configure the debug extension. This is the last of three bits of code
+    // that deal with this, and has the responsibility of asking the instance,
+    // which now exists, to create the debug messager.
+    let debug_messager = if let Some(debug_info) = debug_info {
+      #[allow(unsafe_code)]
+      Some(unsafe {
+        instance.create_debug_utils_messenger_ext(&debug_info, None)
+      }?)
+    } else {
+      None
+    };
+
+    Ok((entry, instance, debug_messager,
+        enable_portability, enable_validation))
+  }
+
+  #[allow(unsafe_code)]
+  fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
+                        enable_validation: EnableValidation,
+                        enable_portability: EnablePortability)
+      -> Result<(vk::PhysicalDevice, Device, QueueFamilyIndices, vk::Queue,
+                 vk::Queue, EnableSwapchain)>
+  {
+    let (physical_device, indices)
+            = Self::pick_vulkan_device(instance, surface)?;
+
+    //   We enumerate the device extensions here so they can inform
+    // configuration. We already did this in score_vulkan_device(), but here
+    // it is again.
+    let mut available_extensions = HashSet::new();
+    for extension in unsafe {
+      instance.enumerate_device_extension_properties(physical_device, None)
+    }? {
+      available_extensions.insert(extension.extension_name);
+    }
+    let available_extensions = available_extensions;
+
+    //   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();
+
+    let validation_layer_name = vk::ExtensionName::from_bytes(
+                                    b"VK_LAYER_KHRONOS_validation");
+    if enable_validation.0 {
+      //   It's not concerning if this isn't supported, because device
+      // layers are ignored on recent versions, they're purely historical.
+      if available_extensions.contains(&validation_layer_name) {
+        layers.push(validation_layer_name.as_ptr());
+      }
+    }
+
+    let portability_extension_name = vk::ExtensionName::from_bytes(
+                                         b"VK_KHR_portability_subset");
+    if enable_portability.0 {
+      //   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.
+      if available_extensions.contains(&portability_extension_name) {
+        extensions.push(portability_extension_name.as_ptr());
+      }
+    }
+
+    let swapchain_extension_name = vk::KHR_SWAPCHAIN_EXTENSION.name;
+    let enable_swapchain = if available_extensions.contains(
+                                  &swapchain_extension_name)
+    {
+      //   It's important that we not call the swapchain extension
+      // functions until we've verified the extension is supported. To
+      // emphasize that, we do it on a separate line.
+      //
+      //   We've done this check once already, in scoring, and now here
+      // we are discarding its results a second time. We'll do it for the
+      // third and last time in swapchain creation.
+      if let Acceptable::Accepted(_)
+             = PermanentGraphicsState::find_device_swapchain_features(
+                   &instance, &surface, &physical_device)?
+      {
+        extensions.push(swapchain_extension_name.as_ptr());
+
+        EnableSwapchain(true)
+      } else {
+        EnableSwapchain(false)
+      }
+    } else {
+      EnableSwapchain(false)
+    };
+
+    //   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)
+            .enabled_layer_names(&layers)
+            .enabled_extension_names(&extensions)
+            .enabled_features(&features);
+
+    let device = unsafe {
+      instance.create_device(physical_device, &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(indices.graphics, 0)
+    };
+
+    let presentation_queue = unsafe {
+      device.get_device_queue(indices.presentation, 0)
+    };
+
+    Ok((physical_device, device,
+        indices, graphics_queue, presentation_queue,
+        enable_swapchain))
+  }
+
+  //   To Vulkan, a "physical" device is the actual GPU, and a "logical"
+  // device is per-process state that represents a connection to the GPU.
+  // Before we can create a logical device, we must choose which physical
+  // device to connect it to.
+  #[allow(unsafe_code)]
+  fn pick_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR)
+      -> Result<(vk::PhysicalDevice, QueueFamilyIndices)>
+  {
+    let mut best_device = None;
+    let mut best_score = None;
+    let mut best_indices = None;
+    let mut rejected = BTreeMap::new();
+
+    for device in unsafe { instance.enumerate_physical_devices() }? {
+      match Self::score_vulkan_device(instance, surface, &device)? {
+        Acceptable::Accepted((new_score, new_indices)) => {
+          if let Some(old_score) = best_score {
+            if new_score > old_score {
+              best_device = Some(device);
+              best_score = Some(new_score);
+              best_indices = Some(new_indices);
+            }
+          } else {
+            best_device = Some(device);
+            best_score = Some(new_score);
+            best_indices = Some(new_indices);
+          }
+        }
+        Acceptable::Rejected(reason) => {
+          let properties = unsafe {
+            instance.get_physical_device_properties(device)
+          };
+
+          let name = properties.device_name.to_string_lossy().into_owned();
+
+          rejected.insert(properties.device_id, (name, reason));
+        }
+      }
+    }
+
+    if let (Some(device), Some(indices)) = (best_device, best_indices) {
+      Ok((device, indices))
+    } else if rejected.is_empty() {
+      Err(Error {
+        message: "The system has no GPUs of any kind.".to_string()
+      })
+    } else {
+      for (_, (name, reason)) in rejected {
+        eprintln!("Can't run on {} because: {}", name, reason);
+      }
+
+      Err(Error {
+        message: "The system has GPUs, but none are acceptable (see above)."
+                 .to_string()
+      })
+    }
+  }
+
+  //   We're doing two tasks: Quantifying how strongly we prefer a device, and
+  // deciding whether it's acceptable at all. If it's unacceptable, it's
+  // possible there will be no acceptable devices, and in that case our caller
+  // will want to print explanations, but otherwise it'll want to be quiet. So
+  // the outer Result is whether we successfully evaluated the device, and the
+  // inner Acceptable is whether we approve of it.
+  //
+  //   In the event that we find the device acceptable, we also return the
+  // queue family indices we'd be using if we ultimately go with it. While
+  // this is not strictly necessary, it's better to return them from here
+  // than to recompute them later on the assumption it'll work out the same.
+  #[allow(unsafe_code)]
+  fn score_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
+                         physical_device: &vk::PhysicalDevice)
+      -> Result<Acceptable<(u64, QueueFamilyIndices)>>
+  {
+    //   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. If we
+    // ultimately use this device, we'll need these, so we make sure to return
+    // them.
+    let indices = match Self::find_device_queue_family_indices(
+                            instance, surface, physical_device)?
+    {
+      Acceptable::Rejected(rationale) => {
+        return Ok(Acceptable::Rejected(rationale));
+      }
+      Acceptable::Accepted(indices) => indices
+    };
+
+    //   At this point we know the device meets our high-level requirements,
+    // so it's just a question of scoring.
+    let properties = unsafe {
+      instance.get_physical_device_properties(*physical_device)
+    };
+
+    let mut score = 0;
+    if properties.device_type == vk::PhysicalDeviceType::DISCRETE_GPU {
+      // If the user has a fancy GPU, they prefer it.
+      score += 128;
+    } else if properties.device_type
+                  == vk::PhysicalDeviceType::INTEGRATED_GPU
+    {
+      // It's still hardware rendering.
+      score += 96;
+    } else if properties.device_type == vk::PhysicalDeviceType::VIRTUAL_GPU {
+      // Whatever it is, the user went to some trouble to set it up.
+      score += 64;
+    } else if properties.device_type == vk::PhysicalDeviceType::CPU {
+      // Software rendering is slow, but at least it's a known quantity.
+      score += 32;
+    }
+    // If it's none of those, we don't have enough information to know if
+    // that's good or bad, so we assume it's bad.
+
+    //   Some of our scoring will depend on what extensions the device
+    // supports, so we enumerate those.
+    let mut available_extensions = HashSet::new();
+    for extension in unsafe {
+      instance.enumerate_device_extension_properties(*physical_device, None)
+    }? {
+      available_extensions.insert(extension.extension_name);
+    }
+    let available_extensions = available_extensions;
+
+    if available_extensions.contains(&vk::KHR_SWAPCHAIN_EXTENSION.name) {
+      //   Double buffering is both quite a nice feature to have, and a good
+      // indicator that this is a "real" graphics card rather than some
+      // trivial weird thing.
+      //
+      //   With that said, however, it only counts if we're able to actually
+      // use it on the surface we have. Let's find out...
+      if let Acceptable::Accepted(_)
+             = PermanentGraphicsState::find_device_swapchain_features(
+                   instance, surface, physical_device)?
+      {
+        //   We don't count it for enough points to override a device type
+        // bracket, but it's good for a lot within the bracket.
+        score += 16;
+      }
+
+      //   This isn't disqualifying, so we don't worry about tracking the
+      // rationale. We'll deal with that later, if the device actually gets
+      // selected.
+    }
+
+    Ok(Acceptable::Accepted((score, indices)))
+  }
+
+  #[allow(unsafe_code)]
+  fn find_device_queue_family_indices(instance: &Instance,
+                                      surface: &vk::SurfaceKHR,
+                                      device: &vk::PhysicalDevice)
+      -> Result<Acceptable<QueueFamilyIndices>>
+  {
+    //   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()))
+    }
+  }
+
+  //   We expect our caller to have already verified that the device supports
+  // the swapchain extension.
+  #[allow(unsafe_code)]
+  pub fn find_device_swapchain_features(instance: &Instance,
+                                        surface: &vk::SurfaceKHR,
+                                        physical_device: &vk::PhysicalDevice)
+      -> Result<Acceptable<(vk::SurfaceCapabilitiesKHR,
+                            Vec<vk::SurfaceFormatKHR>,
+                            Vec<vk::PresentModeKHR>)>>
+  {
+    let capabilities = unsafe {
+      instance.get_physical_device_surface_capabilities_khr(
+          *physical_device, *surface)
+    }?;
+    let formats = unsafe {
+      instance.get_physical_device_surface_formats_khr(
+          *physical_device, *surface)
+    }?;
+    let presentation_modes = unsafe {
+      instance.get_physical_device_surface_present_modes_khr(
+          *physical_device, *surface)
+    }?;
+
+    if formats.is_empty() {
+      Ok(Acceptable::Rejected("No matching surface formats.".to_string()))
+    } else if presentation_modes.is_empty() {
+      Ok(Acceptable::Rejected("No matching presentation modes.".to_string()))
+    } else {
+      Ok(Acceptable::Accepted((capabilities, formats, presentation_modes)))
+    }
+  }
+}
+
+
+#[allow(unsafe_code)]
+extern "system" fn debug_messager_callback(
+    severity: vk::DebugUtilsMessageSeverityFlagsEXT,
+    flags: vk::DebugUtilsMessageTypeFlagsEXT,
+    data: *const vk::DebugUtilsMessengerCallbackDataEXT,
+    _context: *mut c_void) -> vk::Bool32
+{
+  //   Vulkan sends us everything, it's up to us to apply any filtering we
+  // want. The thing about this is that games need to be debuggable by end
+  // users, to diagnose compatibility issues and weird configurations, so we
+  // still want SOMETHING even when we're built in release mode.
+  //
+  //   For now, we'll see if we can get away without providing runtime config
+  // stuff for diagnostics. We set the threshold pretty high in release mode,
+  // on the theory that our own diagnostics should be sufficient.
+  //
+  //   Making this strategy work does rely on us actually checking error
+  // conditions and reporting them in useful ways, so that we only need
+  // Vulkan's messages for things we truly couldn't have anticipated. We do
+  // not take a more-is-better approach to diagnostics; the ideal would be to
+  // provide all the crucial information, and nothing else.
+  let threshold = if cfg!(feature = "vulkan-validation")
+                     || cfg!(debug_assertions)
+  {
+    vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
+  } else {
+    vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
+  };
+
+  if severity >= threshold {
+    let data = unsafe { *data };
+    let text = unsafe { CStr::from_ptr(data.message) }.to_string_lossy();
+
+    let label = if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR {
+      "error"
+    } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING {
+      "warning"
+    } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO {
+      "informational message"
+    } else {
+      "message of unknown, very minor significance"
+    };
+
+    eprintln!("Vulkan {}: {} (flags {:?})", label, text, flags);
+  }
+
+  //   A return value of true would tell the validation layer we're unhappy
+  // with it, for the sake of conformance testing. We're not a conformance
+  // test so anything it does is fine with us.
+  vk::FALSE
+}
diff --git a/src/main.rs b/src/main.rs
index 8254424..b073b7c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,138 +1,34 @@
 #![deny(unsafe_code)]
 use crate::error::*;
+use crate::graphics_permanent::{ PermanentGraphicsState, QueueFamilyIndices };
 
 use std::cell::RefCell;
-use std::collections::{ BTreeMap, BTreeSet, HashSet };
-use std::ffi::{ c_void, CStr };
-use vulkanalia::{ Device, Entry, Instance, Version };
+use std::collections::BTreeSet;
+use vulkanalia::{ Device, Instance };
 use vulkanalia::bytecode::Bytecode;
-use vulkanalia::loader::{ LibloadingLoader, LIBRARY };
-use vulkanalia::vk::{ self, Handle, HasBuilder,
-                      ApplicationInfo, InstanceCreateInfo,
-                      DeviceV1_0, EntryV1_0, InstanceV1_0,
-                      ExtDebugUtilsExtensionInstanceCommands,
-                      KhrSurfaceExtensionInstanceCommands,
+use vulkanalia::vk::{ self, Handle, HasBuilder, DeviceV1_0,
                       KhrSwapchainExtensionDeviceCommands };
-use winit::dpi::LogicalSize;
 use winit::application::ApplicationHandler;
 use winit::event::WindowEvent;
 use winit::event_loop::{ ActiveEventLoop, EventLoop };
-use winit::window::{ Window, WindowAttributes, WindowId };
+use winit::window::{ Window, WindowId };
 
 mod error;
+mod graphics_permanent;
 
 
-const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216);
 // TODO: use VK_KHR_swapchain_maintenance1 to put a fence on the presentation
 // operation. doing that will remove the requirement that we have more
 // simultaneous frames than images.
 const N_SIMULTANEOUS_FRAMES: usize = 5;
 
 
-enum Acceptable<T> {
-  Accepted(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 }
-  }
-
-  #[allow(unused)]
-  fn unwrap(self) -> T {
-    if let Acceptable::Accepted(result) = self {
-      result
-    } else {
-      panic!("Unwrapped a rejected Acceptable.");
-    }
-  }
-
-  fn require(self) -> Result<T> {
-    match self {
-      Acceptable::Accepted(value) => Ok(value),
-      Acceptable::Rejected(message) => Err(Error { message }),
-    }
-  }
-}
-
-#[derive(Debug)]
-struct QueueFamilyIndices {
-  graphics: u32,
-  presentation: u32,
-}
-
-
 struct Surreality {
   permanent: RefCell<Option<PermanentGraphicsState>>,
   window_dressing: RefCell<Option<WindowDressing>>,
   frame_index: usize,
 }
 
-//   The PermanentGraphicsState collects the various windowing-system and
-// Vulkan objects which never need to be regenerated once they're created. The
-// ones which do need that are collected below, in WindowDressing.
-struct PermanentGraphicsState {
-  //   The "window" is the usual operating-system concept of a window; it's
-  // provided by winit, and may be X11, Wayland, or some more curious thing.
-  // The way we initialize Vulkan requires us to have at least one of these;
-  // we could have more, but for now, we don't.
-  window: Window,
-
-  //   The Vulkan "entry" is the part of the Vulkan library ecosystem that's
-  // responsible for finding and loading the other parts. Once we have the
-  // instance, the entry is never directly used again, but we retain it
-  // because doing otherwise would segfault.
-  #[allow(unused)]
-  entry: Entry,
-
-  //   The Vulkan "instance" is the bulk of the Vulkan library, with most of
-  // the high-level responsibilities around lifecycle management.
-  instance: Instance,
-
-  //   The debug messager is a Vulkan object representing our callback which
-  // Vulkan uses to tell us things.
-  //
-  //   Vulkan spells "messager" as "messenger", but this is absurd
-  // over-formality and we don't indulge it.
-  //
-  //    Once we've created this, we never actually need to do anything with
-  // it, but we do need to retain it, so here it is.
-  debug_messager: Option<vk::DebugUtilsMessengerEXT>,
-
-  //   The Vulkan "surface" is the destination that rendering happens into.
-  // It is connected to the window but distinct from it. Since we always have
-  // exactly one window, this is permanent state.
-  surface: 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 pick a physical
-  // device during initialization, but only the logical one is used later, so
-  // it's all we track. We'll be referencing the logical device a lot, so we
-  // follow Vulkan's lead and let it have a short variable name.
-  device: Device,
-
-  //   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. Alas.
-  graphics_queue: vk::Queue,
-  presentation_queue: vk::Queue,
-}
-
 //   The WindowDressing collects the Vulkan graphics objects which need to be
 // regenerated or modified when the window changes in certain ways, such as
 // resizing. The ones which don't need that are collected above, in
@@ -188,12 +84,6 @@ struct Concurrency {
   image_fences: Vec<vk::Fence>,
 }
 
-//   These structs exist for use in function calling, to remove the potential
-// for accidentally passing or returning one boolean as if it's another.
-struct EnablePortability(bool);
-struct EnableValidation(bool);
-struct EnableSwapchain(bool);
-
 
 impl Surreality {
   fn new() -> Self {
@@ -312,589 +202,7 @@ impl Drop for Surreality {
         window_dressing.destroy(&permanent);
       }
 
-      let device = permanent.device;
-      let instance = permanent.instance;
-
-      unsafe { device.destroy_device(None) };
-
-      unsafe { instance.destroy_surface_khr(permanent.surface, 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) = permanent.debug_messager {
-        unsafe {
-          instance.destroy_debug_utils_messenger_ext(debug_messager, None);
-        }
-      }
-
-      unsafe { instance.destroy_instance(None) };
-    }
-  }
-}
-
-impl PermanentGraphicsState {
-  #[allow(unsafe_code)]
-  fn new(event_loop: &ActiveEventLoop)
-      -> Result<(Self, vk::PhysicalDevice, QueueFamilyIndices,
-                 EnableSwapchain)>
-  {
-    let window = Self::init_window(event_loop)?;
-
-    //   There are a few Vulkan features (in the informal sense of
-    // "feature") that we want to be able to run both with and without.
-    // The enable_* values, here and below, are wrapped booleans that describe
-    // those choices.
-    //
-    //   These are only used to communicate between initialization
-    // phases; we don't keep them around after that.
-    let (entry, instance, debug_messager,
-         enable_portability, enable_validation)
-        = Self::init_vulkan(&window)?;
-
-    //   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 (physical_device, device,
-         indices, graphics_queue, presentation_queue,
-         enable_swapchain)
-        = Self::init_vulkan_device(&instance, &surface,
-                                   enable_validation, enable_portability)?;
-
-    Ok((PermanentGraphicsState {
-      window, entry, instance, debug_messager, surface, device,
-      graphics_queue, presentation_queue,
-    }, physical_device, indices, enable_swapchain))
-  }
-
-  fn init_window(event_loop: &ActiveEventLoop) -> Result<Window> {
-    //   Notice that we do this before having a Vulkan instance. The window is
-    // actually a parameter needed to create the instance; see
-    // init_vulkan(), below.
-    let window_attributes = WindowAttributes::default()
-            .with_title("Love, Curiosity, Justice")
-            .with_inner_size(LogicalSize::new(1024, 768));
-
-    Ok(event_loop.create_window(window_attributes)?)
-  }
-
-  #[allow(unsafe_code)]
-  fn init_vulkan(window: &Window)
-      -> Result<(Entry, Instance, Option<vk::DebugUtilsMessengerEXT>,
-                 EnablePortability, EnableValidation)>
-  {
-    let enable_validation = cfg!(feature = "vulkan-validation")
-                            || cfg!(debug_assertions);
-
-    //   Okay, so, a Vulkan "entry" is a small set of functions which are used
-    // to dynamically load all the rest of Vulkan. It's our responsibility to
-    // know how to load the entry, then it will take care of the rest. At
-    // least, that's the theory, but also see flake.nix for all the
-    // FHS-centric assumptions it makes that we have to correct.
-    //
-    //   Anyway, Vulkanalia offers an integration with libloading, which is a
-    // crate that wraps POSIX dlopen(). We use that; it's enabled by
-    // Vulkanalia's "libloading" feature.
-    let loader = unsafe { LibloadingLoader::new(LIBRARY) }?;
-    let entry = unsafe { Entry::new(loader) }?;
-
-    //   Since there's a lot of factors going into our instance creation
-    // request, we'll build up the parameters mutably.
-    let mut flags = vk::InstanceCreateFlags::empty();
-    let mut extensions = Vec::new();
-    let mut layers = Vec::new();
-
-    //   Before we go any further, use Vulkan's introspection to list off
-    // what's available.
-    let mut available_extensions = HashSet::new();
-    for extension in
-            unsafe { entry.enumerate_instance_extension_properties(None) }?
-    {
-      available_extensions.insert(extension.extension_name);
-    }
-    let available_extensions = available_extensions;
-
-    let mut available_layers = HashSet::new();
-    for layer in unsafe { entry.enumerate_instance_layer_properties() }? {
-      available_layers.insert(layer.layer_name);
-    }
-    let available_layers = available_layers;
-
-    //   There are certain extensions which are required by the nature of our
-    // windowing system. Happily, vulanaklia knows how to deal with that based
-    // on the type of window we give it.
-    //
-    //   This is possible because of an integration between Vulkanalia and
-    // winit, which is enabled by Vulkanalia's "window" feature.
-    for extension in vulkanalia::window::get_required_instance_extensions(
-                         window)
-    {
-      extensions.push(extension.as_ptr());
-    }
-
-    //   Deal with Vulkan's thing about opting in to non-conforming
-    // implementations.
-    let enable_portability = if entry.version()?
-                                >= VULKAN_FIRST_PORTABILITY_VERSION
-    {
-      if cfg!(target_os = "macos") {
-        // Vulkan on the Mac is not fully conforming.
-        extensions.push(
-            vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr());
-        extensions.push(
-            vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr());
-        flags.insert(vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR);
-
-        EnablePortability(true)
-      } else {
-        EnablePortability(false)
-      }
-    } else {
-      EnablePortability(false)
-    };
-
-    // Request the LunarG validation layer, when appropriate.
-    let validation_layer_name = vk::ExtensionName::from_bytes(
-                                    b"VK_LAYER_KHRONOS_validation");
-    let enable_validation = if enable_validation {
-      if available_layers.contains(&validation_layer_name) {
-        layers.push(validation_layer_name.as_ptr());
-
-        EnableValidation(true)
-      } else {
-        eprintln!("Vulkan validation requested at build time, \
-                   but no validation layer available.");
-
-        EnableValidation(false)
-      }
-    } else {
-      EnableValidation(false)
-    };
-
-    //   Request the debug extension. This is the first of three bits of code
-    // that deal with this, and has the resonsibility of making sure the
-    // extension is in the list we ask for.
-    let debug_extension_name = vk::EXT_DEBUG_UTILS_EXTENSION.name;
-    if available_extensions.contains(&debug_extension_name) {
-      extensions.push(debug_extension_name.as_ptr());
-    } else {
-      eprintln!("Vulkan debug extension not available; \
-                 this may mean other messages don't show up.");
-    }
-
-    let application_info = ApplicationInfo::builder()
-            .application_name(b"Surreality\0")
-            .application_version(vk::make_version(1, 0, 0))
-            .engine_name(b"Surreality\0")
-            .engine_version(vk::make_version(1, 0, 0))
-            .api_version(vk::make_version(1, 0, 0));
-
-    //   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)
-            .enabled_layer_names(&layers);
-
-    //   Configure the debug extension. This is the middle of three bits of
-    // code that deal with this, and has the responsibility of making sure
-    // the callback will be available during instance creation and
-    // destruction, which is done in a special way that doesn't rely on having
-    // a messager, since there can't be one for those steps.
-    let debug_info = if available_extensions.contains(&debug_extension_name) {
-      let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder()
-              .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all())
-              .message_type(vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
-                            | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
-                            | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE)
-              .user_callback(Some(debug_messager_callback));
-
-      //   Please notice that the reference we pass here will escape Rust's
-      // lifetime checking, since push_next() casts it to a pointer. We don't
-      // get nearly as strong a safety guarantee as one might hope (and as [1]
-      // naively reassures us we do). If we did, the thing we're doing would
-      // actually be forbidden!
-      //
-      // [1] https://kylemayes.github.io/vulkanalia/
-      instance_create_info.push_next(&mut debug_info);
-
-      Some(debug_info)
-    } else { None };
-
-    let instance = unsafe {
-      //   We're promising that every struct referenced here is still alive.
-      // Since it's all pointers, that's... not a thing we statically know. Be
-      // aware. Only you can prevent segfaults.
-      entry.create_instance(&instance_create_info, None)
-    }?;
-
-    //   Configure the debug extension. This is the last of three bits of code
-    // that deal with this, and has the responsibility of asking the instance,
-    // which now exists, to create the debug messager.
-    let debug_messager = if let Some(debug_info) = debug_info {
-      #[allow(unsafe_code)]
-      Some(unsafe {
-        instance.create_debug_utils_messenger_ext(&debug_info, None)
-      }?)
-    } else {
-      None
-    };
-
-    Ok((entry, instance, debug_messager,
-        enable_portability, enable_validation))
-  }
-
-  #[allow(unsafe_code)]
-  fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
-                        enable_validation: EnableValidation,
-                        enable_portability: EnablePortability)
-      -> Result<(vk::PhysicalDevice, Device, QueueFamilyIndices, vk::Queue,
-                 vk::Queue, EnableSwapchain)>
-  {
-    let (physical_device, indices)
-            = Self::pick_vulkan_device(instance, surface)?;
-
-    //   We enumerate the device extensions here so they can inform
-    // configuration. We already did this in score_vulkan_device(), but here
-    // it is again.
-    let mut available_extensions = HashSet::new();
-    for extension in unsafe {
-      instance.enumerate_device_extension_properties(physical_device, None)
-    }? {
-      available_extensions.insert(extension.extension_name);
-    }
-    let available_extensions = available_extensions;
-
-    //   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();
-
-    let validation_layer_name = vk::ExtensionName::from_bytes(
-                                    b"VK_LAYER_KHRONOS_validation");
-    if enable_validation.0 {
-      //   It's not concerning if this isn't supported, because device
-      // layers are ignored on recent versions, they're purely historical.
-      if available_extensions.contains(&validation_layer_name) {
-        layers.push(validation_layer_name.as_ptr());
-      }
-    }
-
-    let portability_extension_name = vk::ExtensionName::from_bytes(
-                                         b"VK_KHR_portability_subset");
-    if enable_portability.0 {
-      //   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.
-      if available_extensions.contains(&portability_extension_name) {
-        extensions.push(portability_extension_name.as_ptr());
-      }
-    }
-
-    let swapchain_extension_name = vk::KHR_SWAPCHAIN_EXTENSION.name;
-    let enable_swapchain = if available_extensions.contains(
-                                  &swapchain_extension_name)
-    {
-      //   It's important that we not call the swapchain extension
-      // functions until we've verified the extension is supported. To
-      // emphasize that, we do it on a separate line.
-      //
-      //   We've done this check once already, in scoring, and now here
-      // we are discarding its results a second time. We'll do it for the
-      // third and last time in swapchain creation.
-      if let Acceptable::Accepted(_)
-             = PermanentGraphicsState::find_device_swapchain_features(
-                   &instance, &surface, &physical_device)?
-      {
-        extensions.push(swapchain_extension_name.as_ptr());
-
-        EnableSwapchain(true)
-      } else {
-        EnableSwapchain(false)
-      }
-    } else {
-      EnableSwapchain(false)
-    };
-
-    //   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)
-            .enabled_layer_names(&layers)
-            .enabled_extension_names(&extensions)
-            .enabled_features(&features);
-
-    let device = unsafe {
-      instance.create_device(physical_device, &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(indices.graphics, 0)
-    };
-
-    let presentation_queue = unsafe {
-      device.get_device_queue(indices.presentation, 0)
-    };
-
-    Ok((physical_device, device,
-        indices, graphics_queue, presentation_queue,
-        enable_swapchain))
-  }
-
-  //   To Vulkan, a "physical" device is the actual GPU, and a "logical"
-  // device is per-process state that represents a connection to the GPU.
-  // Before we can create a logical device, we must choose which physical
-  // device to connect it to.
-  #[allow(unsafe_code)]
-  fn pick_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR)
-      -> Result<(vk::PhysicalDevice, QueueFamilyIndices)>
-  {
-    let mut best_device = None;
-    let mut best_score = None;
-    let mut best_indices = None;
-    let mut rejected = BTreeMap::new();
-
-    for device in unsafe { instance.enumerate_physical_devices() }? {
-      match Self::score_vulkan_device(instance, surface, &device)? {
-        Acceptable::Accepted((new_score, new_indices)) => {
-          if let Some(old_score) = best_score {
-            if new_score > old_score {
-              best_device = Some(device);
-              best_score = Some(new_score);
-              best_indices = Some(new_indices);
-            }
-          } else {
-            best_device = Some(device);
-            best_score = Some(new_score);
-            best_indices = Some(new_indices);
-          }
-        }
-        Acceptable::Rejected(reason) => {
-          let properties = unsafe {
-            instance.get_physical_device_properties(device)
-          };
-
-          let name = properties.device_name.to_string_lossy().into_owned();
-
-          rejected.insert(properties.device_id, (name, reason));
-        }
-      }
-    }
-
-    if let (Some(device), Some(indices)) = (best_device, best_indices) {
-      Ok((device, indices))
-    } else if rejected.is_empty() {
-      Err(Error {
-        message: "The system has no GPUs of any kind.".to_string()
-      })
-    } else {
-      for (_, (name, reason)) in rejected {
-        eprintln!("Can't run on {} because: {}", name, reason);
-      }
-
-      Err(Error {
-        message: "The system has GPUs, but none are acceptable (see above)."
-                 .to_string()
-      })
-    }
-  }
-
-  //   We're doing two tasks: Quantifying how strongly we prefer a device, and
-  // deciding whether it's acceptable at all. If it's unacceptable, it's
-  // possible there will be no acceptable devices, and in that case our caller
-  // will want to print explanations, but otherwise it'll want to be quiet. So
-  // the outer Result is whether we successfully evaluated the device, and the
-  // inner Acceptable is whether we approve of it.
-  //
-  //   In the event that we find the device acceptable, we also return the
-  // queue family indices we'd be using if we ultimately go with it. While
-  // this is not strictly necessary, it's better to return them from here
-  // than to recompute them later on the assumption it'll work out the same.
-  #[allow(unsafe_code)]
-  fn score_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
-                         physical_device: &vk::PhysicalDevice)
-      -> Result<Acceptable<(u64, QueueFamilyIndices)>>
-  {
-    //   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. If we
-    // ultimately use this device, we'll need these, so we make sure to return
-    // them.
-    let indices = match Self::find_device_queue_family_indices(
-                            instance, surface, physical_device)?
-    {
-      Acceptable::Rejected(rationale) => {
-        return Ok(Acceptable::Rejected(rationale));
-      }
-      Acceptable::Accepted(indices) => indices
-    };
-
-    //   At this point we know the device meets our high-level requirements,
-    // so it's just a question of scoring.
-    let properties = unsafe {
-      instance.get_physical_device_properties(*physical_device)
-    };
-
-    let mut score = 0;
-    if properties.device_type == vk::PhysicalDeviceType::DISCRETE_GPU {
-      // If the user has a fancy GPU, they prefer it.
-      score += 128;
-    } else if properties.device_type
-                  == vk::PhysicalDeviceType::INTEGRATED_GPU
-    {
-      // It's still hardware rendering.
-      score += 96;
-    } else if properties.device_type == vk::PhysicalDeviceType::VIRTUAL_GPU {
-      // Whatever it is, the user went to some trouble to set it up.
-      score += 64;
-    } else if properties.device_type == vk::PhysicalDeviceType::CPU {
-      // Software rendering is slow, but at least it's a known quantity.
-      score += 32;
-    }
-    // If it's none of those, we don't have enough information to know if
-    // that's good or bad, so we assume it's bad.
-
-    //   Some of our scoring will depend on what extensions the device
-    // supports, so we enumerate those.
-    let mut available_extensions = HashSet::new();
-    for extension in unsafe {
-      instance.enumerate_device_extension_properties(*physical_device, None)
-    }? {
-      available_extensions.insert(extension.extension_name);
-    }
-    let available_extensions = available_extensions;
-
-    if available_extensions.contains(&vk::KHR_SWAPCHAIN_EXTENSION.name) {
-      //   Double buffering is both quite a nice feature to have, and a good
-      // indicator that this is a "real" graphics card rather than some
-      // trivial weird thing.
-      //
-      //   With that said, however, it only counts if we're able to actually
-      // use it on the surface we have. Let's find out...
-      if let Acceptable::Accepted(_)
-             = PermanentGraphicsState::find_device_swapchain_features(
-                   instance, surface, physical_device)?
-      {
-        //   We don't count it for enough points to override a device type
-        // bracket, but it's good for a lot within the bracket.
-        score += 16;
-      }
-
-      //   This isn't disqualifying, so we don't worry about tracking the
-      // rationale. We'll deal with that later, if the device actually gets
-      // selected.
-    }
-
-    Ok(Acceptable::Accepted((score, indices)))
-  }
-
-  #[allow(unsafe_code)]
-  fn find_device_queue_family_indices(instance: &Instance,
-                                      surface: &vk::SurfaceKHR,
-                                      device: &vk::PhysicalDevice)
-      -> Result<Acceptable<QueueFamilyIndices>>
-  {
-    //   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()))
-    }
-  }
-
-  //   We expect our caller to have already verified that the device supports
-  // the swapchain extension.
-  #[allow(unsafe_code)]
-  fn find_device_swapchain_features(instance: &Instance,
-                                    surface: &vk::SurfaceKHR,
-                                    physical_device: &vk::PhysicalDevice)
-      -> Result<Acceptable<(vk::SurfaceCapabilitiesKHR,
-                            Vec<vk::SurfaceFormatKHR>,
-                            Vec<vk::PresentModeKHR>)>>
-  {
-    let capabilities = unsafe {
-      instance.get_physical_device_surface_capabilities_khr(
-          *physical_device, *surface)
-    }?;
-    let formats = unsafe {
-      instance.get_physical_device_surface_formats_khr(
-          *physical_device, *surface)
-    }?;
-    let presentation_modes = unsafe {
-      instance.get_physical_device_surface_present_modes_khr(
-          *physical_device, *surface)
-    }?;
-
-    if formats.is_empty() {
-      Ok(Acceptable::Rejected("No matching surface formats.".to_string()))
-    } else if presentation_modes.is_empty() {
-      Ok(Acceptable::Rejected("No matching presentation modes.".to_string()))
-    } else {
-      Ok(Acceptable::Accepted((capabilities, formats, presentation_modes)))
+      permanent.destroy();
     }
   }
 }
@@ -1477,56 +785,3 @@ fn main() -> std::process::ExitCode {
   }
 }
 
-
-#[allow(unsafe_code)]
-extern "system" fn debug_messager_callback(
-    severity: vk::DebugUtilsMessageSeverityFlagsEXT,
-    flags: vk::DebugUtilsMessageTypeFlagsEXT,
-    data: *const vk::DebugUtilsMessengerCallbackDataEXT,
-    _context: *mut c_void) -> vk::Bool32
-{
-  //   Vulkan sends us everything, it's up to us to apply any filtering we
-  // want. The thing about this is that games need to be debuggable by end
-  // users, to diagnose compatibility issues and weird configurations, so we
-  // still want SOMETHING even when we're built in release mode.
-  //
-  //   For now, we'll see if we can get away without providing runtime config
-  // stuff for diagnostics. We set the threshold pretty high in release mode,
-  // on the theory that our own diagnostics should be sufficient.
-  //
-  //   Making this strategy work does rely on us actually checking error
-  // conditions and reporting them in useful ways, so that we only need
-  // Vulkan's messages for things we truly couldn't have anticipated. We do
-  // not take a more-is-better approach to diagnostics; the ideal would be to
-  // provide all the crucial information, and nothing else.
-  let threshold = if cfg!(feature = "vulkan-validation")
-                     || cfg!(debug_assertions)
-  {
-    vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
-  } else {
-    vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
-  };
-
-  if severity >= threshold {
-    let data = unsafe { *data };
-    let text = unsafe { CStr::from_ptr(data.message) }.to_string_lossy();
-
-    let label = if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR {
-      "error"
-    } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING {
-      "warning"
-    } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO {
-      "informational message"
-    } else {
-      "message of unknown, very minor significance"
-    };
-
-    eprintln!("Vulkan {}: {} (flags {:?})", label, text, flags);
-  }
-
-  //   A return value of true would tell the validation layer we're unhappy
-  // with it, for the sake of conformance testing. We're not a conformance
-  // test so anything it does is fine with us.
-  vk::FALSE
-}
-