summary refs log tree commit diff
path: root/src/graphics
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-08-06 16:49:10 -0700
committerIrene Knapp <ireneista@irenes.space>2026-08-06 16:49:10 -0700
commit26e89267c0d745793d777ad0b1157a5596258515 (patch)
treede3d5f79a06a649a46803ba0ecc88375acc2821c /src/graphics
parent2c8110d93e04a1bfd976fe20c8c7493a41d51eae (diff)
move some modules into a new graphics submodule
Force-Push: yes
Change-Id: I5cbdf59870258f099fc6dd47ff2f1566376c1585
Diffstat (limited to 'src/graphics')
-rw-r--r--src/graphics/mod.rs5
-rw-r--r--src/graphics/permanent.rs842
-rw-r--r--src/graphics/scene.rs118
-rw-r--r--src/graphics/window_dressing.rs1738
4 files changed, 2703 insertions, 0 deletions
diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs
new file mode 100644
index 0000000..f164588
--- /dev/null
+++ b/src/graphics/mod.rs
@@ -0,0 +1,5 @@
+#![deny(unsafe_code)]
+
+pub mod permanent;
+pub mod scene;
+pub mod window_dressing;
diff --git a/src/graphics/permanent.rs b/src/graphics/permanent.rs
new file mode 100644
index 0000000..5282e21
--- /dev/null
+++ b/src/graphics/permanent.rs
@@ -0,0 +1,842 @@
+#![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::bytecode::Bytecode;
+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 EnableAnisotropy(pub bool);
+pub struct EnableSwapchain(pub bool);
+
+
+impl PermanentGraphicsState {
+  #[allow(unsafe_code)]
+  pub fn new(event_loop: &ActiveEventLoop)
+      -> Result<(Self, GraphicsStateForReinit, EnableAnisotropy,
+                 EnableSwapchain)>
+  {
+    let window = 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)
+        = 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, sample_count, graphics_queue,
+         presentation_queue, enable_anisotropy, enable_swapchain)
+        = init_vulkan_device(&instance, &surface,
+                             enable_validation, enable_portability)?;
+
+    let descriptor_set_layout = init_descriptor_set_layout(&device)?;
+
+    Ok((PermanentGraphicsState {
+      window, entry, instance, debug_messager, surface, device,
+      graphics_queue, presentation_queue
+    }, GraphicsStateForReinit {
+      physical_device, indices, sample_count, descriptor_set_layout,
+    }, enable_anisotropy, 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) };
+  }
+
+  //   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)]
+  pub fn load_spirv_shader_module(device: &Device, binary: &[u8])
+      -> Result<vk::ShaderModule>
+  {
+    let bytecode = Bytecode::new(binary)?;
+
+    let module_info = vk::ShaderModuleCreateInfo::builder()
+                          .code(bytecode.code())
+                          .code_size(bytecode.code_size());
+
+    let module = unsafe {
+      device.create_shader_module(&module_info, None)
+    }?;
+
+    Ok(module)
+  }
+}
+
+
+//   The GraphicsStateForReinit connects Vulkan objects which are only needed
+// during the creation of the window-dressing objects. They are used during
+// initial startup, and again any time the window-dressing needs to be
+// reinitialized. Most notably, they are not needed when rendering.
+pub struct GraphicsStateForReinit {
+  pub physical_device: vk::PhysicalDevice,
+  pub indices: QueueFamilyIndices,
+  pub sample_count: vk::SampleCountFlags,
+  pub descriptor_set_layout: vk::DescriptorSetLayout,
+}
+
+
+impl GraphicsStateForReinit {
+  #[allow(unsafe_code)]
+  pub fn destroy(self, device: &Device) -> () {
+    unsafe {
+      device.destroy_descriptor_set_layout(self.descriptor_set_layout, 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::SampleCountFlags, vk::Queue, vk::Queue, EnableAnisotropy,
+               EnableSwapchain)>
+{
+  let (physical_device, indices, sample_count)
+          = 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 available_features = unsafe {
+    instance.get_physical_device_features(physical_device)
+  };
+  let mut 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)
+  };
+
+  let enable_anisotropy = if available_features.sampler_anisotropy
+                             == vk::TRUE
+  {
+    features = features.sampler_anisotropy(true);
+
+    EnableAnisotropy(true)
+  } else {
+    EnableAnisotropy(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, sample_count, graphics_queue,
+      presentation_queue, enable_anisotropy, enable_swapchain))
+}
+
+
+#[allow(unsafe_code)]
+fn init_descriptor_set_layout(device: &Device)
+    -> Result<vk::DescriptorSetLayout>
+{
+  let uniform_block_binding = vk::DescriptorSetLayoutBinding::builder()
+          .binding(0)
+          .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
+          .descriptor_count(1)
+          .stage_flags(vk::ShaderStageFlags::VERTEX);
+
+  let sampler_binding = vk::DescriptorSetLayoutBinding::builder()
+          .binding(1)
+          .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
+          .descriptor_count(1)
+          .stage_flags(vk::ShaderStageFlags::FRAGMENT);
+
+  let bindings = [uniform_block_binding, sampler_binding];
+  let descriptor_set_layout_info
+          = vk::DescriptorSetLayoutCreateInfo::builder()
+                .bindings(&bindings);
+  let descriptor_set_layout = unsafe {
+    device.create_descriptor_set_layout(&descriptor_set_layout_info, None)
+  }?;
+
+  Ok(descriptor_set_layout)
+}
+
+
+
+//   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, vk::SampleCountFlags)>
+{
+  let mut best_device = None;
+  let mut best_score = None;
+  let mut best_indices = None;
+  let mut best_sample_count = None;
+  let mut rejected = BTreeMap::new();
+
+  for device in unsafe { instance.enumerate_physical_devices() }? {
+    match score_vulkan_device(instance, surface, &device)? {
+      Acceptable::Accepted((new_score, new_indices, new_sample_count)) => {
+        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);
+            best_sample_count = Some(new_sample_count);
+          }
+        } else {
+          best_device = Some(device);
+          best_score = Some(new_score);
+          best_indices = Some(new_indices);
+          best_sample_count = Some(new_sample_count);
+        }
+      }
+      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), Some(sample_count))
+             = (best_device, best_indices, best_sample_count)
+  {
+    Ok((device, indices, sample_count))
+  } 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, vk::SampleCountFlags)>>
+{
+  //   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 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.
+  }
+
+  let features = unsafe {
+    instance.get_physical_device_features(*physical_device)
+  };
+  if features.sampler_anisotropy == vk::TRUE {
+    score += 1;
+  }
+
+  let sample_count = properties.limits.framebuffer_color_sample_counts
+                     & properties.limits.framebuffer_depth_sample_counts;
+  //   Happily, these bit flags are arranged in the obvious way, which lets us
+  // do some math on them. The max sample count is 64, so the max score bonus
+  // we give is 4.
+  let shift = sample_count.bits().ilog2() as u64;
+  score += shift;
+  let sample_count = vk::SampleCountFlags::from_bits(1 << shift).unwrap();
+
+  Ok(Acceptable::Accepted((score, indices, sample_count)))
+}
+
+
+#[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()))
+  }
+}
+
+
+#[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/graphics/scene.rs b/src/graphics/scene.rs
new file mode 100644
index 0000000..e59ae89
--- /dev/null
+++ b/src/graphics/scene.rs
@@ -0,0 +1,118 @@
+#![deny(unsafe_code)]
+use crate::error::*;
+use crate::graphics::window_dressing::RenderState;
+use crate::linear_algebra::{ Vec3, Vec4, Transformation };
+use crate::shader_data::VertexPushBlock;
+
+use std::f32::consts::{ TAU };
+use std::mem::size_of;
+
+use vulkanalia::Device;
+use vulkanalia::vk::{ self, HasBuilder, DeviceV1_0 };
+
+
+#[allow(unsafe_code)]
+pub fn generate_scene_commands<'a>(image_index: usize, time: f32,
+                                   render_state: &'a RenderState,
+                                   device: &Device, extent: &vk::Extent2D)
+    -> Result<&'a vk::CommandBuffer>
+{
+  let command_buffer = &render_state.command_buffers[image_index];
+  let framebuffer = &render_state.framebuffers[image_index];
+  let descriptor_set = &render_state.descriptor_sets[image_index];
+
+  let inheritance_info = vk::CommandBufferInheritanceInfo::builder();
+
+  let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder()
+          .flags(vk::CommandBufferUsageFlags::empty())
+          .inheritance_info(&inheritance_info);
+
+  unsafe {
+    device.begin_command_buffer(*command_buffer, &command_buffer_begin_info)
+  }?;
+
+  let render_area = vk::Rect2D::builder()
+          .offset(vk::Offset2D::default())
+          .extent(*extent);
+
+  let color_clear_value = vk::ClearValue {
+    color: vk::ClearColorValue {
+      float32: [0.0, 0.0, 0.0, 1.0]
+    }
+  };
+  let depth_clear_value = vk::ClearValue {
+    depth_stencil: vk::ClearDepthStencilValue {
+      depth: 1.0,
+      stencil: 0,
+    }
+  };
+  let clear_values = [color_clear_value, depth_clear_value];
+
+  let begin_pass_info = vk::RenderPassBeginInfo::builder()
+          .render_pass(render_state.render_pass)
+          .framebuffer(*framebuffer)
+          .render_area(render_area)
+          .clear_values(&clear_values);
+
+  unsafe {
+    device.cmd_begin_render_pass(*command_buffer, &begin_pass_info,
+                                 vk::SubpassContents::INLINE)
+  };
+
+  unsafe {
+    device.cmd_bind_pipeline(*command_buffer,
+                             vk::PipelineBindPoint::GRAPHICS,
+                             render_state.pipeline)
+  };
+
+  unsafe {
+    device.cmd_bind_vertex_buffers(*command_buffer, 0,
+                                   &[render_state.vertex_buffer], &[0])
+  };
+
+  unsafe {
+    device.cmd_bind_index_buffer(*command_buffer, render_state.index_buffer,
+                                 0, vk::IndexType::UINT32)
+  };
+
+  unsafe {
+    device.cmd_bind_descriptor_sets(*command_buffer,
+                                    vk::PipelineBindPoint::GRAPHICS,
+                                    render_state.pipeline_layout,
+                                    0,
+                                    &[*descriptor_set],
+                                    &[])
+  };
+
+  let scale = Vec3::new(1.0, 1.0, 1.0);
+  let model = Transformation {
+    rotation: Vec4::rotation_quaternion(&Vec3::new(0.0, 1.0, 0.0), time % TAU),
+    translation: Vec3::new(0.0, 0.0, 0.0),
+  };
+  let push_block = VertexPushBlock::<f32> { scale, model };
+  let size = size_of::<VertexPushBlock::<f32>>();
+  let push_block_bytes = unsafe {
+    std::slice::from_raw_parts(&push_block as *const VertexPushBlock<f32>
+                                           as *const u8,
+                               size)
+  };
+
+  unsafe {
+    device.cmd_push_constants(*command_buffer, render_state.pipeline_layout,
+                              vk::ShaderStageFlags::VERTEX,
+                              0,
+                              push_block_bytes)
+  };
+
+  unsafe {
+    device.cmd_draw_indexed(*command_buffer, render_state.index_count as u32,
+                            1, 0, 0, 0)
+  };
+
+  unsafe { device.cmd_end_render_pass(*command_buffer) };
+
+  unsafe { device.end_command_buffer(*command_buffer) }?;
+
+  Ok(command_buffer)
+}
+
diff --git a/src/graphics/window_dressing.rs b/src/graphics/window_dressing.rs
new file mode 100644
index 0000000..dc7f47f
--- /dev/null
+++ b/src/graphics/window_dressing.rs
@@ -0,0 +1,1738 @@
+#![deny(unsafe_code)]
+use crate::error::*;
+use crate::graphics::permanent::{
+  PermanentGraphicsState, GraphicsStateForReinit, QueueFamilyIndices,
+  EnableAnisotropy
+};
+use crate::model_loader::load_model;
+use crate::shader_data::{ Vertex, UniformBlock, VertexPushBlock };
+
+use std::collections::BTreeSet;
+use std::io::Cursor;
+use std::mem::size_of;
+use std::ptr::copy_nonoverlapping;
+
+use png::Decoder;
+use vulkanalia::{ Device, Instance };
+use vulkanalia::vk::{ self, Handle, HasBuilder, InstanceV1_0, DeviceV1_0,
+                      KhrSwapchainExtensionDeviceCommands };
+use winit::window::Window;
+
+
+// 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.
+pub const N_SIMULTANEOUS_FRAMES: usize = 5;
+
+
+//   The WindowDressing collects the Vulkan graphics objects which need to be
+// regenerated or modified when the window changes in certain ways, such as
+// resizing, but are not needed during rendering. The ones which don't need to
+// be regenerated are collected in PermanentGraphicsState. The ones which are
+// needed during rendering are collected in RenderState, below.
+#[derive(Debug)]
+pub struct WindowDressing {
+  pub swapchain: Swapchain,
+
+  color_image: vk::Image,
+  color_image_memory: vk::DeviceMemory,
+  color_image_view: vk::ImageView,
+
+  depth_image: vk::Image,
+  depth_image_memory: vk::DeviceMemory,
+  depth_image_view: vk::ImageView,
+  depth_format: vk::Format,
+
+  primary_command_pool: vk::CommandPool,
+  transient_command_pool: vk::CommandPool,
+
+  texture_image: vk::Image,
+  texture_image_memory: vk::DeviceMemory,
+  texture_image_view: vk::ImageView,
+  mip_count: u32,
+  sampler: vk::Sampler,
+
+  uniform_buffers: Vec<vk::Buffer>,
+  pub uniform_buffer_memory: Vec<vk::DeviceMemory>,
+
+  descriptor_pool: vk::DescriptorPool,
+
+  pub concurrency: Concurrency,
+}
+
+//   The RenderState collects the Vulkan graphics objects which need to be
+// regenerated or modified when the window changes, as with WindowDressing,
+// and which are also used as part of rendering.
+#[derive(Debug)]
+pub struct RenderState {
+  pub render_pass: vk::RenderPass,
+
+  pub pipeline: vk::Pipeline,
+  pub pipeline_layout: vk::PipelineLayout,
+
+  pub vertex_buffer: vk::Buffer,
+  vertex_buffer_memory: vk::DeviceMemory,
+
+  pub index_buffer: vk::Buffer,
+  index_buffer_memory: vk::DeviceMemory,
+  pub index_count: usize,
+
+  pub framebuffers: Vec<vk::Framebuffer>,
+  pub command_buffers: Vec<vk::CommandBuffer>,
+  pub descriptor_sets: Vec<vk::DescriptorSet>,
+}
+
+//   A swapchain is the generalized facility that is used to implement
+// double buffering, triple buffering, rendering passes that feed into each
+// other, and other things of that nature. It's a first-class thing but for
+// now, we use at most one of it. We also support running without one.
+#[derive(Debug)]
+pub struct Swapchain {
+  pub swapchain: vk::SwapchainKHR,
+  images: Vec<vk::Image>,
+  image_views: Vec<vk::ImageView>,
+  format: vk::Format,
+  pub extent: vk::Extent2D,
+}
+
+#[derive(Debug)]
+pub struct Concurrency {
+  pub image_available_semaphores: Vec<vk::Semaphore>,
+  pub rendering_finished_semaphores: Vec<vk::Semaphore>,
+
+  //   Okay, the lifetime management on the fences is really subtle. There is
+  // one fence for each frame, and frame_fences holds the authoritative
+  // reference to it.
+  //
+  //   There is one entry in image_fences for each image. The number of images
+  // is not directly related to the number of frames; it will likely be
+  // larger, but may be smaller or the same. At the start of execution, the
+  // entries are all nulls. Each time an image is acquired from the swapchain,
+  // the corresponding entry in image_fences is overwritten with a duplicate
+  // of the frame fence. This happens during rendering of the frame, so the
+  // frame fence is in the "signaled" state. It will be reset right before
+  // submitting the queue, then signaled again when the submission completes.
+  pub frame_fences: Vec<vk::Fence>,
+  pub image_fences: Vec<vk::Fence>,
+}
+
+
+impl WindowDressing {
+  pub fn new(permanent: &PermanentGraphicsState,
+             for_reinit: &GraphicsStateForReinit,
+             enable_anisotropy: EnableAnisotropy)
+      -> Result<Self>
+  {
+    let window = &permanent.window;
+    let instance = &permanent.instance;
+    let surface = &permanent.surface;
+    let device = &permanent.device;
+    let graphics_queue = &permanent.graphics_queue;
+    let physical_device = &for_reinit.physical_device;
+    let sample_count = for_reinit.sample_count;
+    let indices = &for_reinit.indices;
+
+    let swapchain = init_swapchain(
+            window, instance, surface, &physical_device, device, &indices)?;
+
+    let (color_image, color_image_memory, color_image_view)
+            = init_color(instance, &physical_device, device,
+                         &swapchain.extent, sample_count, swapchain.format)?;
+
+    let (depth_image, depth_image_memory, depth_image_view, depth_format)
+            = init_depth(instance, &physical_device, device,
+                         &swapchain.extent, sample_count)?;
+
+    let (primary_command_pool, transient_command_pool)
+            = init_command_pools(device, indices)?;
+
+    let (texture_image, texture_image_memory, texture_image_view, mip_count)
+            = init_texture(instance, physical_device, device,
+                           graphics_queue, &transient_command_pool)?;
+
+    let sampler = init_sampler(&device, &enable_anisotropy, mip_count)?;
+
+    let (uniform_buffers, uniform_buffer_memory)
+            = init_uniform_buffers(instance, physical_device, device,
+                                   swapchain.images.len())?;
+
+    let descriptor_pool
+            = init_descriptor_pool(device, swapchain.images.len())?;
+
+    let concurrency = init_concurrency(device, &swapchain.images)?;
+
+    Ok(WindowDressing {
+      swapchain,
+      color_image,
+      color_image_memory,
+      color_image_view,
+      depth_image,
+      depth_image_memory,
+      depth_image_view,
+      depth_format,
+      texture_image,
+      texture_image_memory,
+      texture_image_view,
+      mip_count,
+      sampler,
+      uniform_buffers,
+      uniform_buffer_memory,
+      descriptor_pool,
+      primary_command_pool,
+      transient_command_pool,
+      concurrency,
+    })
+  }
+
+
+  #[allow(unsafe_code)]
+  pub fn reinit(&mut self, permanent: &PermanentGraphicsState,
+                for_reinit: &GraphicsStateForReinit)
+      -> Result<()>
+  {
+    let window = &permanent.window;
+    let instance = &permanent.instance;
+    let surface = &permanent.surface;
+    let device = &permanent.device;
+    let physical_device = &for_reinit.physical_device;
+    let sample_count = for_reinit.sample_count;
+    let indices = &for_reinit.indices;
+    let descriptor_set_layout = &for_reinit.descriptor_set_layout;
+
+    unsafe { device.device_wait_idle() }.unwrap();
+
+    self.destroy_replaceable(device);
+
+    let swapchain = init_swapchain(
+            window, instance, surface, &physical_device, device, &indices)?;
+
+    let (color_image, color_image_memory, color_image_view)
+            = init_color(instance, &physical_device, device,
+                         &swapchain.extent, sample_count, swapchain.format)?;
+
+    let (depth_image, depth_image_memory, depth_image_view, depth_format)
+            = init_depth(instance, &physical_device, device,
+                         &swapchain.extent, sample_count)?;
+
+    let (uniform_buffers, uniform_buffer_memory)
+            = init_uniform_buffers(instance, physical_device, device,
+                                   swapchain.images.len())?;
+
+    // Notice that we did NOT reuse the descriptor pool.
+    let descriptor_pool
+            = init_descriptor_pool(device, swapchain.images.len())?;
+
+    self.concurrency.image_fences.resize(swapchain.images.len(),
+                                         vk::Fence::null());
+
+    self.swapchain = swapchain;
+    self.color_image = color_image;
+    self.color_image_memory = color_image_memory;
+    self.color_image_view = color_image_view;
+    self.depth_image = depth_image;
+    self.depth_image_memory = depth_image_memory;
+    self.depth_image_view = depth_image_view;
+    self.depth_format = depth_format;
+    self.uniform_buffers = uniform_buffers;
+    self.uniform_buffer_memory = uniform_buffer_memory;
+    self.descriptor_pool = descriptor_pool;
+
+    Ok(())
+  }
+
+
+  //   This relies on its caller to have already waited for the device to be
+  // idle.
+  #[allow(unsafe_code)]
+  pub fn destroy(mut self, device: &Device) {
+    self.destroy_replaceable(device);
+
+    unsafe { device.destroy_image(self.texture_image, None) };
+    unsafe { device.free_memory(self.texture_image_memory, None) };
+    unsafe { device.destroy_image_view(self.texture_image_view, None) };
+    unsafe { device.destroy_sampler(self.sampler, None) };
+
+    for semaphore in self.concurrency.image_available_semaphores {
+      unsafe { device.destroy_semaphore(semaphore, None) };
+    }
+
+    for semaphore in self.concurrency.rendering_finished_semaphores {
+      unsafe { device.destroy_semaphore(semaphore, None) };
+    }
+
+    for fence in self.concurrency.frame_fences {
+      unsafe { device.destroy_fence(fence, None) };
+    }
+
+    //   Notice that destroy_replaceable() freed the buffers in the pools, but
+    // did not destroy the pools.
+    unsafe { device.destroy_command_pool(self.primary_command_pool, None) };
+    unsafe { device.destroy_command_pool(self.transient_command_pool, None) };
+  }
+
+
+  #[allow(unsafe_code)]
+  fn destroy_replaceable(&mut self, device: &Device) {
+    //   While the descriptor pool is also a pool, it has a preallocated size
+    // which will be different next time. So, we destroy it all the way.
+    unsafe { device.destroy_descriptor_pool(self.descriptor_pool, None) };
+
+    //   Notice that, unlike the vertex and index buffers, we destroy and
+    // re-create these on every reinitialization. That's because the number of
+    // them depends on how many images the swapchain has.
+    for buffer in &self.uniform_buffers {
+      unsafe { device.destroy_buffer(*buffer, None) };
+    }
+    for memory in &self.uniform_buffer_memory {
+      unsafe { device.free_memory(*memory, None) };
+    }
+
+    unsafe { device.destroy_image(self.color_image, None) };
+    unsafe { device.free_memory(self.color_image_memory, None) };
+    unsafe { device.destroy_image_view(self.color_image_view, None) };
+    unsafe { device.destroy_image(self.depth_image, None) };
+    unsafe { device.free_memory(self.depth_image_memory, None) };
+    unsafe { device.destroy_image_view(self.depth_image_view, None) };
+
+    for view in &self.swapchain.image_views {
+      unsafe { device.destroy_image_view(*view, None) };
+    }
+
+    unsafe { device.destroy_swapchain_khr(self.swapchain.swapchain, None) };
+  }
+}
+
+
+impl RenderState {
+  pub fn new(permanent: &PermanentGraphicsState,
+             for_reinit: &GraphicsStateForReinit,
+             window_dressing: &WindowDressing)
+      -> Result<Self>
+  {
+    let device = &permanent.device;
+    let instance = &permanent.instance;
+    let graphics_queue = &permanent.graphics_queue;
+    let physical_device = &for_reinit.physical_device;
+    let sample_count = for_reinit.sample_count;
+    let descriptor_set_layout = &for_reinit.descriptor_set_layout;
+    let primary_command_pool = &window_dressing.primary_command_pool;
+    let transient_command_pool = &window_dressing.transient_command_pool;
+    let swapchain = &window_dressing.swapchain;
+    let depth_format = &window_dressing.depth_format;
+    let color_image_view = &window_dressing.color_image_view;
+    let depth_image_view = &window_dressing.depth_image_view;
+    let texture_image_view = &window_dressing.texture_image_view;
+    let uniform_buffers = &window_dressing.uniform_buffers;
+    let descriptor_pool = &window_dressing.descriptor_pool;
+    let sampler = &window_dressing.sampler;
+
+    let render_pass = init_render_pass(device, sample_count,
+                                       &swapchain.format, &depth_format)?;
+
+    let (pipeline_layout, pipeline)
+            = init_pipeline(device, descriptor_set_layout, &swapchain.extent,
+                            sample_count, &render_pass)?;
+
+    let framebuffers = init_framebuffers(
+            device, &swapchain.extent, &swapchain.image_views,
+            &color_image_view, &depth_image_view, &render_pass)?;
+
+    let (vertices, indices) = load_model()?;
+    let index_count = indices.len();
+
+    let command_buffers = init_command_buffers(device, &framebuffers,
+                                               primary_command_pool)?;
+
+    let (vertex_buffer, vertex_buffer_memory)
+            = init_vertex_buffer(vertices, instance, physical_device, device,
+                                 graphics_queue, &transient_command_pool)?;
+    let (index_buffer, index_buffer_memory)
+            = init_index_buffer(indices, instance, physical_device, device,
+                                graphics_queue, &transient_command_pool)?;
+
+    let descriptor_sets
+            = init_descriptor_sets(device, descriptor_set_layout,
+                                   &uniform_buffers, &descriptor_pool,
+                                   swapchain.images.len(),
+                                   &texture_image_view, &sampler)?;
+
+    Ok(RenderState {
+      render_pass,
+      pipeline,
+      pipeline_layout,
+      vertex_buffer,
+      vertex_buffer_memory,
+      index_buffer,
+      index_buffer_memory,
+      index_count,
+      framebuffers,
+      command_buffers,
+      descriptor_sets,
+    })
+  }
+
+  //   This relies on its caller to have already waited for the device to be
+  // idle.
+  pub fn reinit(&mut self, permanent: &PermanentGraphicsState,
+                for_reinit: &GraphicsStateForReinit,
+                window_dressing: &WindowDressing)
+      -> Result<()>
+  {
+    let device = &permanent.device;
+    let sample_count = for_reinit.sample_count;
+    let descriptor_set_layout = &for_reinit.descriptor_set_layout;
+    let primary_command_pool = &window_dressing.primary_command_pool;
+    let swapchain = &window_dressing.swapchain;
+    let depth_format = &window_dressing.depth_format;
+    let color_image_view = &window_dressing.color_image_view;
+    let depth_image_view = &window_dressing.depth_image_view;
+    let texture_image_view = &window_dressing.texture_image_view;
+    let uniform_buffers = &window_dressing.uniform_buffers;
+    let descriptor_pool = &window_dressing.descriptor_pool;
+    let sampler = &window_dressing.sampler;
+
+    self.destroy_replaceable(device, primary_command_pool);
+
+    let render_pass = init_render_pass(device, sample_count,
+                                       &swapchain.format, &depth_format)?;
+
+    let (pipeline_layout, pipeline)
+            = init_pipeline(device, descriptor_set_layout, &swapchain.extent,
+                            sample_count, &render_pass)?;
+
+    let framebuffers = init_framebuffers(
+            device, &swapchain.extent, &swapchain.image_views,
+            &color_image_view, &depth_image_view, &render_pass)?;
+
+    // Notice that we reused the command pool.
+    let command_buffers = init_command_buffers(device, &framebuffers,
+                                               primary_command_pool)?;
+
+    let descriptor_sets
+            = init_descriptor_sets(device, descriptor_set_layout,
+                                   &uniform_buffers, &descriptor_pool,
+                                   swapchain.images.len(),
+                                   texture_image_view, sampler)?;
+
+    self.render_pass = render_pass;
+    self.pipeline = pipeline;
+    self.pipeline_layout = pipeline_layout;
+    self.framebuffers = framebuffers;
+    self.command_buffers = command_buffers;
+    self.descriptor_sets = descriptor_sets;
+
+    Ok(())
+  }
+
+  //   This relies on its caller to have already waited for the device to be
+  // idle.
+  #[allow(unsafe_code)]
+  pub fn destroy(mut self, device: &Device,
+                 window_dressing: &WindowDressing)
+  {
+    self.destroy_replaceable(device, &window_dressing.primary_command_pool);
+
+    unsafe { device.destroy_buffer(self.vertex_buffer, None) };
+    unsafe { device.free_memory(self.vertex_buffer_memory, None) };
+
+    unsafe { device.destroy_buffer(self.index_buffer, None) };
+    unsafe { device.free_memory(self.index_buffer_memory, None) };
+  }
+
+  #[allow(unsafe_code)]
+  fn destroy_replaceable(&mut self, device: &Device,
+                         primary_command_pool: &vk::CommandPool)
+  {
+    for framebuffer in &self.framebuffers {
+      unsafe { device.destroy_framebuffer(*framebuffer, None) };
+    }
+
+    //   Notice that we free the buffers in the pool, but do not destroy the
+    // pool itself. Notice also that we only do this for the primary command
+    // pool, because that's the only one where we've kept track of the
+    // buffers. We promise ourselves to free buffers in the transient pool
+    // immediately after using them.
+    unsafe {
+      device.free_command_buffers(*primary_command_pool,
+                                  &self.command_buffers)
+    };
+
+    unsafe { device.destroy_pipeline(self.pipeline, None) };
+    unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) };
+    unsafe { device.destroy_render_pass(self.render_pass, None) };
+  }
+}
+
+
+#[allow(unsafe_code)]
+fn init_swapchain(window: &Window, instance: &Instance,
+                  surface: &vk::SurfaceKHR,
+                  physical_device: &vk::PhysicalDevice, device: &Device,
+                  indices: &QueueFamilyIndices)
+    -> Result<Swapchain>
+{
+  let (capabilities, formats, presentation_modes)
+          = PermanentGraphicsState::find_device_swapchain_features(
+                instance, surface, physical_device)?.require()?;
+
+  let format = pick_surface_format(&formats)?;
+
+  let presentation_mode
+          = pick_presentation_mode(&presentation_modes)?;
+  let extent = pick_image_extent(window, capabilities)?;
+
+  let mut image_count = capabilities.min_image_count + 1;
+  if capabilities.max_image_count != 0 {
+    image_count
+        = image_count.clamp(0, capabilities.max_image_count);
+  }
+
+  let mut unique_queue_family_indices = BTreeSet::new();
+  unique_queue_family_indices.insert(indices.graphics);
+  unique_queue_family_indices.insert(indices.presentation);
+
+  //   If there's only one queue, we use exclusive sharing mode, which
+  // will allow things to work without locks. Otherwise we use concurrent
+  // mode.
+  let (ordered_indices, sharing_mode)
+          = if unique_queue_family_indices.len() < 2
+  {
+    (vec![indices.graphics], vk::SharingMode::EXCLUSIVE)
+  } else {
+    (vec![indices.graphics, indices.presentation],
+     vk::SharingMode::CONCURRENT)
+  };
+
+  let swapchain_info = vk::SwapchainCreateInfoKHR::builder()
+          .surface(*surface)
+          .min_image_count(image_count)
+          .image_format(format.format)
+          .image_color_space(format.color_space)
+          .image_extent(extent)
+          .image_array_layers(1)
+          .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
+          .image_sharing_mode(sharing_mode)
+          .queue_family_indices(&ordered_indices)
+          .pre_transform(capabilities.current_transform)
+          .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
+          .present_mode(presentation_mode)
+          .clipped(true)
+          .old_swapchain(vk::SwapchainKHR::null());
+
+  let swapchain = unsafe {
+    device.create_swapchain_khr(&swapchain_info, None)
+  }?;
+
+  let images = unsafe {
+    device.get_swapchain_images_khr(swapchain)
+  }?;
+
+  let mut image_views = Vec::new();
+  for image in &images {
+    let view = init_image_view(device, image, 1, format.format,
+                               vk::ImageAspectFlags::COLOR)?;
+    image_views.push(view);
+  }
+
+  Ok(Swapchain {
+    swapchain, images, image_views,
+    format: format.format,
+    extent
+  })
+}
+
+
+#[allow(unsafe_code)]
+fn init_color(instance: &Instance, physical_device: &vk::PhysicalDevice,
+              device: &Device, extent: &vk::Extent2D,
+              sample_count: vk::SampleCountFlags, format: vk::Format)
+    -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView)>
+{
+  let (image, image_memory)
+          = allocate_image(instance, physical_device, device,
+                           extent.width, extent.height, 1, sample_count,
+                           format,
+                           vk::ImageTiling::OPTIMAL,
+                           vk::ImageUsageFlags::COLOR_ATTACHMENT
+                           | vk::ImageUsageFlags::TRANSIENT_ATTACHMENT,
+                           vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
+
+  let image_view = init_image_view(device, &image, 1, format,
+                                   vk::ImageAspectFlags::COLOR)?;
+
+  Ok((image, image_memory, image_view))
+}
+
+
+#[allow(unsafe_code)]
+fn init_depth(instance: &Instance, physical_device: &vk::PhysicalDevice,
+              device: &Device, extent: &vk::Extent2D,
+              sample_count: vk::SampleCountFlags)
+    -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, vk::Format)>
+{
+  let format = pick_depth_format(instance, physical_device)?;
+
+  let (image, image_memory)
+          = allocate_image(instance, physical_device, device,
+                           extent.width, extent.height, 1, sample_count,
+                           format,
+                           vk::ImageTiling::OPTIMAL,
+                           vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
+                           vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
+
+  let image_view = init_image_view(device, &image, 1, format,
+                                   vk::ImageAspectFlags::DEPTH)?;
+
+  Ok((image, image_memory, image_view, format))
+}
+
+
+#[allow(unsafe_code)]
+fn init_render_pass(device: &Device, sample_count: vk::SampleCountFlags,
+                    color_format: &vk::Format, depth_format: &vk::Format)
+    -> Result<vk::RenderPass>
+{
+  let color_attachment = vk::AttachmentDescription::builder()
+          .format(*color_format)
+          .samples(sample_count)
+          .load_op(vk::AttachmentLoadOp::CLEAR)
+          .store_op(vk::AttachmentStoreOp::STORE)
+          .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
+          .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
+          .initial_layout(vk::ImageLayout::UNDEFINED)
+          .final_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
+
+  let color_attachment_reference = vk::AttachmentReference::builder()
+          .attachment(0)
+          .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
+
+  let depth_attachment = vk::AttachmentDescription::builder()
+          .format(*depth_format)
+          .samples(sample_count)
+          .load_op(vk::AttachmentLoadOp::CLEAR)
+          .store_op(vk::AttachmentStoreOp::DONT_CARE)
+          .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
+          .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
+          .initial_layout(vk::ImageLayout::UNDEFINED)
+          .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+
+  let depth_attachment_reference = vk::AttachmentReference::builder()
+          .attachment(1)
+          .layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+
+  let color_resolve_attachment = vk::AttachmentDescription::builder()
+          .format(*color_format)
+          .samples(vk::SampleCountFlags::_1)
+          .load_op(vk::AttachmentLoadOp::DONT_CARE)
+          .store_op(vk::AttachmentStoreOp::STORE)
+          .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
+          .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
+          .initial_layout(vk::ImageLayout::UNDEFINED)
+          .final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
+
+  let color_resolve_attachment_reference = vk::AttachmentReference::builder()
+          .attachment(2)
+          .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
+
+  let color_attachments = [color_attachment_reference];
+  let resolve_attachments = [color_resolve_attachment_reference];
+  let subpass = vk::SubpassDescription::builder()
+          .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
+          .color_attachments(&color_attachments)
+          .depth_stencil_attachment(&depth_attachment_reference)
+          .resolve_attachments(&resolve_attachments);
+
+  let dependency = vk::SubpassDependency::builder()
+          .src_subpass(vk::SUBPASS_EXTERNAL)
+          .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
+                          | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS)
+          .src_access_mask(vk::AccessFlags::empty())
+          .dst_subpass(0)
+          .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
+                          | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS)
+          .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE
+                           | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE);
+
+  let render_attachments = [color_attachment,
+                            depth_attachment,
+                            color_resolve_attachment];
+  let subpasses = [subpass];
+  let dependencies = [dependency];
+  let render_pass_info = vk::RenderPassCreateInfo::builder()
+          .attachments(&render_attachments)
+          .subpasses(&subpasses)
+          .dependencies(&dependencies);
+
+  let render_pass = unsafe {
+    device.create_render_pass(&render_pass_info, None)
+  }?;
+
+  Ok(render_pass)
+}
+
+
+#[allow(unsafe_code)]
+fn init_pipeline(device: &Device,
+                 descriptor_set_layout: &vk::DescriptorSetLayout,
+                 extent: &vk::Extent2D, sample_count: vk::SampleCountFlags,
+                 render_pass: &vk::RenderPass)
+    -> Result<(vk::PipelineLayout, vk::Pipeline)>
+{
+  let vertex_binary = include_bytes!(
+          concat!(env!("OUT_DIR"), "/shader.vert.spv"));
+  let fragment_binary = include_bytes!(
+          concat!(env!("OUT_DIR"), "/shader.frag.spv"));
+
+  let vertex_module = PermanentGraphicsState::load_spirv_shader_module(
+          device, vertex_binary)?;
+  let fragment_module = PermanentGraphicsState::load_spirv_shader_module(
+          device, fragment_binary)?;
+
+  let vertex_stage_info = vk::PipelineShaderStageCreateInfo::builder()
+          .stage(vk::ShaderStageFlags::VERTEX)
+          .module(vertex_module)
+          .name(b"main\0");
+
+  let fragment_stage_info = vk::PipelineShaderStageCreateInfo::builder()
+          .stage(vk::ShaderStageFlags::FRAGMENT)
+          .module(fragment_module)
+          .name(b"main\0");
+
+  let binding_descriptions = [Vertex::<f32>::binding_description()];
+  let attribute_descriptions = Vertex::<f32>::attribute_descriptions();
+  let vertex_input_state_info
+          = vk::PipelineVertexInputStateCreateInfo::builder()
+                .vertex_binding_descriptions(&binding_descriptions)
+                .vertex_attribute_descriptions(&attribute_descriptions);
+
+  let input_assembly_state_info
+          = vk::PipelineInputAssemblyStateCreateInfo::builder()
+                .topology(vk::PrimitiveTopology::TRIANGLE_LIST)
+                .primitive_restart_enable(false);
+
+  let viewport = vk::Viewport::builder()
+          .x(0.0)
+          .y(0.0)
+          .width(extent.width as f32)
+          .height(extent.height as f32)
+          .min_depth(0.0)
+          .max_depth(1.0);
+  let viewports = [viewport];
+
+  let scissor = vk::Rect2D::builder()
+          .offset(vk::Offset2D { x: 0, y: 0 })
+          .extent(*extent);
+  let scissor_list = [scissor];
+
+  let viewport_state_info = vk::PipelineViewportStateCreateInfo::builder()
+          .viewports(&viewports)
+          .scissors(&scissor_list);
+
+  let rasterizer_state_info = vk::PipelineRasterizationStateCreateInfo::builder()
+          .depth_clamp_enable(false)
+          .rasterizer_discard_enable(false)
+          .polygon_mode(vk::PolygonMode::FILL)
+          .line_width(1.0)
+          .cull_mode(vk::CullModeFlags::BACK)
+          .front_face(vk::FrontFace::CLOCKWISE)
+          .depth_bias_enable(false);
+
+  let multisample_state_info
+          = vk::PipelineMultisampleStateCreateInfo::builder()
+                .sample_shading_enable(false)
+                .rasterization_samples(sample_count);
+
+  let depth_state_info = vk::PipelineDepthStencilStateCreateInfo::builder()
+          .depth_test_enable(true)
+          .depth_write_enable(true)
+          .depth_compare_op(vk::CompareOp::LESS)
+          .depth_bounds_test_enable(false)
+          .min_depth_bounds(0.0)
+          .max_depth_bounds(1.0)
+          .stencil_test_enable(false);
+
+  let blend_attachment_info = vk::PipelineColorBlendAttachmentState::builder()
+          .color_write_mask(vk::ColorComponentFlags::all())
+          .blend_enable(false)
+          .src_color_blend_factor(vk::BlendFactor::ONE)
+          .dst_color_blend_factor(vk::BlendFactor::ZERO)
+          .color_blend_op(vk::BlendOp::ADD)
+          .src_alpha_blend_factor(vk::BlendFactor::ONE)
+          .dst_alpha_blend_factor(vk::BlendFactor::ZERO)
+          .alpha_blend_op(vk::BlendOp::ADD);
+  let blend_attachments = [blend_attachment_info];
+
+  let blend_info = vk::PipelineColorBlendStateCreateInfo::builder()
+          .logic_op_enable(false)
+          .logic_op(vk::LogicOp::COPY)
+          .attachments(&blend_attachments)
+          .blend_constants([0.0, 0.0, 0.0, 0.0]);
+
+  let vertex_push_constant_range = vk::PushConstantRange::builder()
+          .stage_flags(vk::ShaderStageFlags::VERTEX)
+          .offset(0)
+          .size(size_of::<VertexPushBlock<f32>>() as u32);
+
+  let layouts = [*descriptor_set_layout];
+  let push_constant_ranges = [vertex_push_constant_range];
+  let pipeline_layout_info = vk::PipelineLayoutCreateInfo::builder()
+                                 .set_layouts(&layouts)
+                                 .push_constant_ranges(&push_constant_ranges);
+
+  let pipeline_layout = unsafe {
+    device.create_pipeline_layout(&pipeline_layout_info, None)
+  }?;
+
+  let stages = [vertex_stage_info, fragment_stage_info];
+  let pipeline_info = vk::GraphicsPipelineCreateInfo::builder()
+          .stages(&stages)
+          .vertex_input_state(&vertex_input_state_info)
+          .input_assembly_state(&input_assembly_state_info)
+          .viewport_state(&viewport_state_info)
+          .rasterization_state(&rasterizer_state_info)
+          .multisample_state(&multisample_state_info)
+          .depth_stencil_state(&depth_state_info)
+          .color_blend_state(&blend_info)
+          .layout(pipeline_layout)
+          .render_pass(*render_pass)
+          .subpass(0);
+
+  let pipeline = unsafe {
+    device.create_graphics_pipelines(vk::PipelineCache::null(),
+                                     &[pipeline_info], None)
+  }?.0[0];
+
+  unsafe {
+    device.destroy_shader_module(vertex_module, None);
+    device.destroy_shader_module(fragment_module, None);
+  };
+
+  Ok((pipeline_layout, pipeline))
+}
+
+
+#[allow(unsafe_code)]
+fn init_framebuffers(device: &Device, extent: &vk::Extent2D,
+                     swapchain_image_views: &Vec<vk::ImageView>,
+                     color_image_view: &vk::ImageView,
+                     depth_image_view: &vk::ImageView,
+                     render_pass: &vk::RenderPass)
+    -> Result<Vec<vk::Framebuffer>>
+{
+  let mut framebuffers = Vec::new();
+
+  for color_resolve_image_view in swapchain_image_views {
+    let attachments = [*color_image_view,
+                       *depth_image_view,
+                       *color_resolve_image_view];
+
+    let framebuffer_info = vk::FramebufferCreateInfo::builder()
+            .render_pass(*render_pass)
+            .attachments(&attachments)
+            .width(extent.width)
+            .height(extent.height)
+            .layers(1);
+
+    let framebuffer = unsafe {
+      device.create_framebuffer(&framebuffer_info, None)
+    }?;
+
+    framebuffers.push(framebuffer);
+  }
+
+  Ok(framebuffers)
+}
+
+
+fn init_vertex_buffer(vertices: Vec<Vertex<f32>>, instance: &Instance,
+                      physical_device: &vk::PhysicalDevice, device: &Device,
+                      queue: &vk::Queue, command_pool: &vk::CommandPool)
+    -> Result<(vk::Buffer, vk::DeviceMemory)>
+{
+  init_buffer(instance, physical_device, device, queue, command_pool,
+              vk::BufferUsageFlags::VERTEX_BUFFER, &vertices)
+}
+
+
+fn init_index_buffer(indices: Vec<u32>, instance: &Instance,
+                     physical_device: &vk::PhysicalDevice, device: &Device,
+                     queue: &vk::Queue, command_pool: &vk::CommandPool)
+    -> Result<(vk::Buffer, vk::DeviceMemory)>
+{
+  init_buffer(instance, physical_device, device, queue, command_pool,
+              vk::BufferUsageFlags::INDEX_BUFFER, &indices)
+}
+
+
+#[allow(unsafe_code)]
+fn init_texture(instance: &Instance,
+                physical_device: &vk::PhysicalDevice, device: &Device,
+                queue: &vk::Queue, command_pool: &vk::CommandPool)
+    -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)>
+{
+  let png = include_bytes!("../../textures/forest_leaves_04_diff.png");
+
+  let decoder = Decoder::new(Cursor::new(png));
+  let mut reader = decoder.read_info()?;
+
+  let (width, height) = reader.info().size();
+
+  let format_properties = unsafe {
+    instance.get_physical_device_format_properties(*physical_device,
+                                                   vk::Format::R8G8B8A8_SRGB)
+  };
+  let has_linear_filter = format_properties
+          .optimal_tiling_features
+          .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR);
+  let mip_count = if has_linear_filter {
+    //   This will generate mips all the way down to 1x1. It is not clear
+    // whether there's a benefit to that.
+    (width.max(height)).ilog2() + 1
+  } else {
+    1
+  };
+
+  let mut pixels = vec![0; reader.info().raw_bytes()];
+  reader.next_frame(&mut pixels)?;
+
+  let (staging_buffer, staging_memory, _byte_size)
+          = stage_in_buffer(instance, physical_device, device, &pixels)?;
+
+  let (image, image_memory)
+          = allocate_image(instance, physical_device, device,
+                           width, height, mip_count, vk::SampleCountFlags::_1,
+                           vk::Format::R8G8B8A8_SRGB,
+                           vk::ImageTiling::OPTIMAL,
+                           vk::ImageUsageFlags::SAMPLED
+                               | vk::ImageUsageFlags::TRANSFER_SRC
+                               | vk::ImageUsageFlags::TRANSFER_DST,
+                           vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
+
+  change_image_layout(device, queue, command_pool, &image, mip_count,
+                      vk::Format::R8G8B8A8_SRGB,
+                      vk::ImageLayout::UNDEFINED,
+                      vk::ImageLayout::TRANSFER_DST_OPTIMAL)?;
+
+  copy_buffer_to_image(device, queue, command_pool, &staging_buffer, &image,
+                       width, height)?;
+
+  // This will also change the layout to SHADER_READ_ONLY_OPTIMAL.
+
+  fill_mip_levels(device, queue, command_pool, &image,
+                  width, height, mip_count)?;
+
+  let view = init_image_view(device, &image, mip_count,
+                             vk::Format::R8G8B8A8_SRGB,
+                             vk::ImageAspectFlags::COLOR)?;
+
+  unsafe { device.destroy_buffer(staging_buffer, None) };
+  unsafe { device.free_memory(staging_memory, None) };
+
+  Ok((image, image_memory, view, mip_count))
+}
+
+
+fn init_uniform_buffers(instance: &Instance,
+                        physical_device: &vk::PhysicalDevice, device: &Device,
+                        count: usize)
+    -> Result<(Vec<vk::Buffer>, Vec<vk::DeviceMemory>)>
+{
+  let mut buffers = Vec::new();
+  let mut all_memory = Vec::new();
+
+  for _ in 0 .. count {
+    let (buffer, memory) = allocate_buffer(
+            instance, physical_device, device,
+            size_of::<UniformBlock<f32>>() as vk::DeviceSize,
+            vk::BufferUsageFlags::UNIFORM_BUFFER,
+            vk::MemoryPropertyFlags::HOST_COHERENT
+            | vk::MemoryPropertyFlags::HOST_VISIBLE)?;
+    buffers.push(buffer);
+    all_memory.push(memory);
+  }
+
+  Ok((buffers, all_memory))
+}
+
+
+#[allow(unsafe_code)]
+fn init_buffer<T>(instance: &Instance,
+                  physical_device: &vk::PhysicalDevice, device: &Device,
+                  queue: &vk::Queue, command_pool: &vk::CommandPool,
+                  usage: vk::BufferUsageFlags, contents: &[T])
+    -> Result<(vk::Buffer, vk::DeviceMemory)>
+{
+  let (staging_buffer, staging_memory, size)
+          = stage_in_buffer(instance, physical_device, device, contents)?;
+
+  let final_usage = vk::BufferUsageFlags::TRANSFER_DST | usage;
+  let final_memory_flags = vk::MemoryPropertyFlags::DEVICE_LOCAL;
+  let (final_buffer, device_memory)
+          = allocate_buffer(instance, physical_device, device,
+                            size as vk::DeviceSize, final_usage,
+                            final_memory_flags)?;
+
+  copy_buffer(device, queue, command_pool, &staging_buffer, &final_buffer,
+              size as vk::DeviceSize)?;
+
+  unsafe { device.destroy_buffer(staging_buffer, None) };
+  unsafe { device.free_memory(staging_memory, None) };
+
+  Ok((final_buffer, device_memory))
+}
+
+
+#[allow(unsafe_code)]
+fn init_sampler(device: &Device, enable_anisotropy: &EnableAnisotropy,
+                mip_count: u32)
+    -> Result<vk::Sampler>
+{
+  let mut sampler_info = vk::SamplerCreateInfo::builder()
+          .mag_filter(vk::Filter::LINEAR)
+          .min_filter(vk::Filter::LINEAR)
+          .address_mode_u(vk::SamplerAddressMode::REPEAT)
+          .address_mode_v(vk::SamplerAddressMode::REPEAT)
+          .address_mode_w(vk::SamplerAddressMode::REPEAT)
+          .border_color(vk::BorderColor::INT_OPAQUE_BLACK)
+          .unnormalized_coordinates(false)
+          .compare_enable(false)
+          .compare_op(vk::CompareOp::ALWAYS)
+          .mipmap_mode(vk::SamplerMipmapMode::LINEAR)
+          .mip_lod_bias(0.0)
+          .min_lod(0.0)
+          .max_lod(mip_count as f32);
+  sampler_info = if enable_anisotropy.0 {
+    sampler_info.anisotropy_enable(true)
+                .max_anisotropy(16.0)
+  } else {
+    sampler_info.anisotropy_enable(false)
+                .max_anisotropy(1.0)
+  };
+
+  let sampler = unsafe { device.create_sampler(&sampler_info, None) }?;
+
+  Ok(sampler)
+}
+
+
+#[allow(unsafe_code)]
+fn init_descriptor_pool(device: &Device, count: usize)
+    -> Result<vk::DescriptorPool>
+{
+  let uniform_block_size = vk::DescriptorPoolSize::builder()
+          .type_(vk::DescriptorType::UNIFORM_BUFFER)
+          .descriptor_count(count as u32);
+
+  let sampler_size = vk::DescriptorPoolSize::builder()
+          .type_(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
+          .descriptor_count(count as u32);
+
+  let sizes = [uniform_block_size, sampler_size];
+  let pool_info = vk::DescriptorPoolCreateInfo::builder()
+          .pool_sizes(&sizes)
+          .max_sets(count as u32);
+  let pool = unsafe { device.create_descriptor_pool(&pool_info, None) }?;
+
+  Ok(pool)
+}
+
+
+#[allow(unsafe_code)]
+fn init_descriptor_sets(device: &Device, layout: &vk::DescriptorSetLayout,
+                        buffers: &Vec<vk::Buffer>, pool: &vk::DescriptorPool,
+                        count: usize, texture_image_view: &vk::ImageView,
+                        sampler: &vk::Sampler)
+    -> Result<Vec<vk::DescriptorSet>>
+{
+  let layouts = vec![*layout; count];
+  let set_info = vk::DescriptorSetAllocateInfo::builder()
+          .descriptor_pool(*pool)
+          .set_layouts(&layouts);
+  let sets = unsafe { device.allocate_descriptor_sets(&set_info) }?;
+
+  for index in 0 .. count {
+    let buffer_info = vk::DescriptorBufferInfo::builder()
+            .buffer(buffers[index])
+            .offset(0)
+            .range(size_of::<UniformBlock<f32>>() as vk::DeviceSize);
+
+    let buffer_info_list = [buffer_info];
+    let uniform_block_write_info = vk::WriteDescriptorSet::builder()
+            .dst_set(sets[index])
+            .dst_binding(0)
+            .dst_array_element(0)
+            .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
+            .buffer_info(&buffer_info_list);
+
+    let image_info = vk::DescriptorImageInfo::builder()
+            .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
+            .image_view(*texture_image_view)
+            .sampler(*sampler);
+    let image_info_list = [image_info];
+    let sampler_write_info = vk::WriteDescriptorSet::builder()
+            .dst_set(sets[index])
+            .dst_binding(1)
+            .dst_array_element(0)
+            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
+            .image_info(&image_info_list);
+
+    let write_info_list = [uniform_block_write_info, sampler_write_info];
+    let copy_info_list: [vk::CopyDescriptorSet; 0] = [];
+
+    unsafe {
+      device.update_descriptor_sets(&write_info_list, &copy_info_list)
+    };
+  }
+
+  Ok(sets)
+}
+
+
+#[allow(unsafe_code)]
+fn init_command_pools(device: &Device, indices: &QueueFamilyIndices)
+    -> Result<(vk::CommandPool, vk::CommandPool)>
+{
+  let command_pool_info = vk::CommandPoolCreateInfo::builder()
+          .flags(vk::CommandPoolCreateFlags::TRANSIENT
+                 | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
+          .queue_family_index(indices.graphics);
+
+  let primary = unsafe {
+    device.create_command_pool(&command_pool_info, None)
+  }?;
+
+  command_pool_info.flags(vk::CommandPoolCreateFlags::TRANSIENT);
+  let transient = unsafe {
+    device.create_command_pool(&command_pool_info, None)
+  }?;
+
+  Ok((primary, transient))
+}
+
+
+#[allow(unsafe_code)]
+fn init_command_buffers(device: &Device,
+                        framebuffers: &Vec<vk::Framebuffer>,
+                        command_pool: &vk::CommandPool)
+    -> Result<Vec<vk::CommandBuffer>>
+{
+  let command_buffer_allocation_info
+          = vk::CommandBufferAllocateInfo::builder()
+                .command_pool(*command_pool)
+                .level(vk::CommandBufferLevel::PRIMARY)
+                .command_buffer_count(framebuffers.len() as u32);
+  let command_buffers = unsafe {
+    device.allocate_command_buffers(&command_buffer_allocation_info)
+  }?;
+
+  Ok(command_buffers)
+}
+
+
+#[allow(unsafe_code)]
+fn init_concurrency(device: &Device,
+                    swapchain_images: &Vec<vk::Image>)
+    -> Result<Concurrency>
+{
+  let semaphore_info = vk::SemaphoreCreateInfo::builder();
+  let fence_info = vk::FenceCreateInfo::builder()
+                       .flags(vk::FenceCreateFlags::SIGNALED);
+
+  let mut image_available_semaphores = Vec::new();
+  let mut rendering_finished_semaphores = Vec::new();
+  let mut frame_fences = Vec::new();
+  for _ in 0 .. N_SIMULTANEOUS_FRAMES {
+    image_available_semaphores.push(unsafe {
+      device.create_semaphore(&semaphore_info, None)
+    }?);
+
+    rendering_finished_semaphores.push(unsafe {
+      device.create_semaphore(&semaphore_info, None)
+    }?);
+
+    frame_fences.push(unsafe {
+      device.create_fence(&fence_info, None)
+    }?);
+  }
+
+  let mut image_fences = Vec::new();
+  for _ in 0 .. swapchain_images.len() {
+    image_fences.push(vk::Fence::null());
+  }
+
+  Ok(Concurrency {
+    image_available_semaphores,
+    rendering_finished_semaphores,
+    frame_fences,
+    image_fences: image_fences,
+  })
+}
+
+
+#[allow(unsafe_code)]
+fn init_image_view(device: &Device, image: &vk::Image, mip_count: u32,
+                   format: vk::Format, aspects: vk::ImageAspectFlags)
+    -> Result<vk::ImageView>
+{
+  //   Component mapping is only for color components (not, for example, depth
+  // or stencil components), so we always just want it like this.
+  let components = vk::ComponentMapping::builder()
+          .r(vk::ComponentSwizzle::IDENTITY)
+          .g(vk::ComponentSwizzle::IDENTITY)
+          .b(vk::ComponentSwizzle::IDENTITY)
+          .a(vk::ComponentSwizzle::IDENTITY);
+
+  let subresource_range = vk::ImageSubresourceRange::builder()
+          .aspect_mask(aspects)
+          .base_mip_level(0)
+          .level_count(mip_count)
+          .base_array_layer(0)
+          .layer_count(1);
+
+  let view_info = vk::ImageViewCreateInfo::builder()
+          .image(*image)
+          .view_type(vk::ImageViewType::_2D)
+          .format(format)
+          .components(components)
+          .subresource_range(subresource_range);
+
+  let view = unsafe {
+    device.create_image_view(&view_info, None)
+  }?;
+
+  Ok(view)
+}
+
+
+fn pick_surface_format(available_formats: &Vec<vk::SurfaceFormatKHR>)
+    -> Result<vk::SurfaceFormatKHR>
+{
+  for format in available_formats {
+    if format.format == vk::Format::B8G8R8A8_SRGB
+       && format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
+    {
+      return Ok(format.clone());
+    }
+  }
+
+  return Ok(available_formats[0].clone());
+}
+
+
+#[allow(unsafe_code)]
+fn pick_depth_format(instance: &Instance,
+                     physical_device: &vk::PhysicalDevice)
+    -> Result<vk::Format>
+{
+  let required_features = vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT;
+
+  for format in [vk::Format::D32_SFLOAT,
+                 vk::Format::D32_SFLOAT_S8_UINT,
+                 vk::Format::D24_UNORM_S8_UINT]
+  {
+    let properties = unsafe {
+      instance.get_physical_device_format_properties(
+                   *physical_device, format)
+    };
+
+    if properties.optimal_tiling_features.contains(required_features) {
+      return Ok(format);
+    }
+  }
+
+  Err(Error {
+    message: "There is no supported depth-buffer sample format.".to_string()
+  })
+}
+
+
+fn pick_presentation_mode(_available_modes: &Vec<vk::PresentModeKHR>)
+    -> Result<vk::PresentModeKHR>
+{
+  // It's guaranteed to have this one.
+  return Ok(vk::PresentModeKHR::FIFO);
+}
+
+
+fn pick_image_extent(window: &Window,
+                     capabilities: vk::SurfaceCapabilitiesKHR)
+    -> Result<vk::Extent2D>
+{
+  if capabilities.current_extent.width != u32::MAX
+     && capabilities.current_extent.height != u32::MAX
+  {
+    Ok(capabilities.current_extent)
+  } else {
+    let window_size = window.inner_size();
+
+    let width = window_size.width
+                           .clamp(capabilities.min_image_extent.width,
+                                  capabilities.max_image_extent.width);
+    let height = window_size.height
+                            .clamp(capabilities.min_image_extent.height,
+                                   capabilities.max_image_extent.height);
+
+    Ok(vk::Extent2D::builder().width(width).height(height).build())
+  }
+}
+
+
+#[allow(unsafe_code)]
+fn pick_memory_type(instance: &Instance,
+                    physical_device: &vk::PhysicalDevice,
+                    properties: &vk::MemoryPropertyFlags,
+                    requirements: &vk::MemoryRequirements)
+    -> Result<u32>
+{
+  let memory_map = unsafe {
+    instance.get_physical_device_memory_properties(*physical_device)
+  };
+
+  //   So. The memory_type_bits field is a map of which indices are suitable,
+  // based on the buffer our caller passed to
+  // get_buffer_memory_requirements(). Yes, that means there's a hard cap on
+  // how many memory types there can be, based on the size of the bitfield.
+  for index in 0 .. memory_map.memory_type_count {
+    if requirements.memory_type_bits & (1 << index) == 0 {
+      continue;
+    }
+
+    let memory_type = memory_map.memory_types[index as usize];
+
+    if memory_type.property_flags.contains(*properties) {
+      return Ok(index);
+    }
+  }
+
+  Err(Error {
+    message: "The system has no suitable memory for a buffer.".to_string()
+  })
+}
+
+
+#[allow(unsafe_code)]
+fn stage_in_buffer<T>(instance: &Instance,
+                      physical_device: &vk::PhysicalDevice, device: &Device,
+                      contents: &[T])
+    -> Result<(vk::Buffer, vk::DeviceMemory, usize)>
+{
+  let size = size_of::<T>() * contents.len();
+
+  let staging_usage = vk::BufferUsageFlags::TRANSFER_SRC;
+  let staging_memory_flags = vk::MemoryPropertyFlags::HOST_COHERENT
+                             | vk::MemoryPropertyFlags::HOST_VISIBLE;
+  let (staging_buffer, staging_memory)
+          = allocate_buffer(instance, physical_device, device,
+                            size as vk::DeviceSize, staging_usage,
+                            staging_memory_flags)?;
+
+  let host_memory = unsafe {
+    device.map_memory(staging_memory, 0, size as vk::DeviceSize,
+                      vk::MemoryMapFlags::empty())
+  }?;
+
+  unsafe {
+    copy_nonoverlapping(contents.as_ptr(), host_memory.cast(), contents.len())
+  };
+
+  unsafe { device.unmap_memory(staging_memory) };
+
+  Ok((staging_buffer, staging_memory, size))
+}
+
+
+#[allow(unsafe_code)]
+fn allocate_buffer(instance: &Instance,
+                   physical_device: &vk::PhysicalDevice, device: &Device,
+                   size: vk::DeviceSize, usage: vk::BufferUsageFlags,
+                   memory_flags: vk::MemoryPropertyFlags)
+    -> Result<(vk::Buffer, vk::DeviceMemory)>
+{
+  let buffer_info = vk::BufferCreateInfo::builder()
+                        .size(size)
+                        .usage(usage)
+                        .sharing_mode(vk::SharingMode::EXCLUSIVE);
+
+  let buffer = unsafe { device.create_buffer(&buffer_info, None) }?;
+
+  //   The requirements are mostly what you'd think: size and alignment. The
+  // bits field is something special; see pick_memory_type() for the
+  // explanation. Despite the simplicity of this data, Vulkan wants to be the
+  // one to tell us about it, and we let it.
+  let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
+
+  let type_index = pick_memory_type(instance, physical_device,
+                                    &memory_flags, &requirements)?;
+
+  let memory_info = vk::MemoryAllocateInfo::builder()
+          .allocation_size(requirements.size)
+          .memory_type_index(type_index);
+
+  let device_memory = unsafe { device.allocate_memory(&memory_info, None) }?;
+
+  unsafe { device.bind_buffer_memory(buffer, device_memory, 0) }?;
+
+  Ok((buffer, device_memory))
+}
+
+
+#[allow(unsafe_code)]
+fn copy_buffer(device: &Device, queue: &vk::Queue,
+               command_pool: &vk::CommandPool, source: &vk::Buffer,
+               destination: &vk::Buffer, size: vk::DeviceSize)
+    -> Result<()>
+{
+  let command_buffer = begin_transient_commands(device, command_pool)?;
+
+  let copy_info = vk::BufferCopy::builder().size(size);
+  unsafe {
+    device.cmd_copy_buffer(command_buffer, *source, *destination,
+                           &[copy_info])
+  };
+
+  end_transient_commands(command_buffer, device, queue, command_pool)?;
+
+  Ok(())
+}
+
+
+#[allow(unsafe_code)]
+fn allocate_image(instance: &Instance, physical_device: &vk::PhysicalDevice,
+                  device: &Device, width: u32, height: u32, mip_count: u32,
+                  sample_count: vk::SampleCountFlags, format: vk::Format,
+                  tiling: vk::ImageTiling, usage: vk::ImageUsageFlags,
+                  memory_flags: vk::MemoryPropertyFlags)
+    -> Result<(vk::Image, vk::DeviceMemory)>
+{
+  let image_info = vk::ImageCreateInfo::builder()
+          .image_type(vk::ImageType::_2D)
+          .extent(vk::Extent3D { width, height, depth: 1 })
+          .mip_levels(mip_count)
+          .samples(sample_count)
+          .array_layers(1)
+          .format(format)
+          .tiling(tiling)
+          .initial_layout(vk::ImageLayout::UNDEFINED)
+          .usage(usage)
+          .sharing_mode(vk::SharingMode::EXCLUSIVE)
+          .flags(vk::ImageCreateFlags::empty());
+  let image = unsafe { device.create_image(&image_info, None) }?;
+
+  let requirements = unsafe { device.get_image_memory_requirements(image) };
+
+  let type_index = pick_memory_type(instance, physical_device,
+                                    &memory_flags, &requirements)?;
+
+  let image_memory_info = vk::MemoryAllocateInfo::builder()
+          .allocation_size(requirements.size)
+          .memory_type_index(type_index);
+  let image_memory = unsafe {
+    device.allocate_memory(&image_memory_info, None)
+  }?;
+
+  unsafe { device.bind_image_memory(image, image_memory, 0) }?;
+
+  Ok((image, image_memory))
+}
+
+
+#[allow(unsafe_code)]
+fn copy_buffer_to_image(device: &Device, queue: &vk::Queue,
+                        command_pool: &vk::CommandPool, source: &vk::Buffer,
+                        destination: &vk::Image, width: u32, height: u32)
+    -> Result<()>
+{
+  let command_buffer = begin_transient_commands(device, command_pool)?;
+
+  let subresource_layers = vk::ImageSubresourceLayers::builder()
+          .aspect_mask(vk::ImageAspectFlags::COLOR)
+          .mip_level(0)
+          .base_array_layer(0)
+          .layer_count(1);
+
+  let copy_info = vk::BufferImageCopy::builder()
+          .buffer_offset(0)
+          .buffer_row_length(0)
+          .buffer_image_height(0)
+          .image_subresource(subresource_layers)
+          .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 })
+          .image_extent(vk::Extent3D { width, height, depth: 1 });
+
+  unsafe {
+    device.cmd_copy_buffer_to_image(command_buffer, *source, *destination,
+                                    vk::ImageLayout::TRANSFER_DST_OPTIMAL,
+                                    &[copy_info])
+  };
+
+  end_transient_commands(command_buffer, device, queue, command_pool)?;
+
+  Ok(())
+}
+
+
+#[allow(unsafe_code)]
+fn change_image_layout(device: &Device, queue: &vk::Queue,
+                       command_pool: &vk::CommandPool, image: &vk::Image,
+                       mip_count: u32, format: vk::Format,
+                       old: vk::ImageLayout, new: vk::ImageLayout)
+    -> Result<()>
+{
+  let command_buffer = begin_transient_commands(device, command_pool)?;
+
+  let subresource_range = vk::ImageSubresourceRange::builder()
+          .aspect_mask(vk::ImageAspectFlags::COLOR)
+          .base_mip_level(0)
+          .level_count(mip_count)
+          .base_array_layer(0)
+          .layer_count(1);
+
+  //   Notionally this is a property that our caller is in a better position
+  // to know than we are, but in practice the nature of the transition
+  // strongly implies a particular phase of the image's lifecycle, so we just
+  // compute it here.
+  let (source_access, source_stage, destination_access, destination_stage)
+          = match (old, new)
+  {
+    (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL)
+        => (vk::AccessFlags::empty(),
+            vk::PipelineStageFlags::TOP_OF_PIPE,
+            vk::AccessFlags::TRANSFER_WRITE,
+            vk::PipelineStageFlags::TRANSFER),
+    (vk::ImageLayout::TRANSFER_DST_OPTIMAL,
+     vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
+        => (vk::AccessFlags::TRANSFER_WRITE,
+            vk::PipelineStageFlags::TRANSFER,
+            vk::AccessFlags::SHADER_READ,
+            vk::PipelineStageFlags::FRAGMENT_SHADER),
+    _ => return Err(Error {
+      message:
+          format!("Don't know how to change from image layout {:?} to {:?}",
+                  old, new)
+    })
+  };
+
+  let barrier_info = vk::ImageMemoryBarrier::builder()
+        .image(*image)
+        .subresource_range(subresource_range)
+        .old_layout(old)
+        .new_layout(new)
+        .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
+        .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
+        .src_access_mask(source_access)
+        .dst_access_mask(destination_access);
+
+  unsafe {
+    device.cmd_pipeline_barrier(command_buffer,
+                                source_stage, destination_stage,
+                                vk::DependencyFlags::empty(),
+                                &[] as &[vk::MemoryBarrier],
+                                &[] as &[vk::BufferMemoryBarrier],
+                                &[barrier_info])
+  };
+
+  end_transient_commands(command_buffer, device, queue, command_pool)?;
+
+  Ok(())
+}
+
+
+//   An Image can store multiple mip levels within it, as one of several kinds
+// of subresource it has. We deal with this by
+#[allow(unsafe_code)]
+fn fill_mip_levels(device: &Device, queue: &vk::Queue,
+                   command_pool: &vk::CommandPool, image: &vk::Image,
+                   original_width: u32, original_height: u32,
+                   mip_count: u32)
+    -> Result<()>
+{
+  let command_buffer = begin_transient_commands(device, command_pool)?;
+
+  //   We'll be mutating these two builders as we loop through the mip levels,
+  // because we need to construct a lot of similar things. Remember, the
+  // builder methods don't mutate in-place, they return a new builder; to
+  // avoid confusion we always assign that result back to the same variable.
+  let mut barrier_subresource_range = vk::ImageSubresourceRange::builder()
+          .aspect_mask(vk::ImageAspectFlags::COLOR)
+          .level_count(1)
+          .base_array_layer(0)
+          .layer_count(1);
+
+  let mut blit_barrier_info = vk::ImageMemoryBarrier::builder()
+          .image(*image)
+          .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
+          .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED);
+
+  //   Now we loop through the mip levels from largest (low numbers) to
+  // smallest (high numbers). Conceptually, the only thing we're doing is a
+  // blit that copies each mip level from the one immediately before. Recall
+  // though that we don't just want to fill in the pixels, we also care about
+  // pixel format and memory sharing. There are additional operations to deal
+  // with that. These are best done together, as detailed below.
+  //
+  //   This loop has a lot of code in it, so we make the "paragraphs" a little
+  // more dense than usual to make sure the logical grouping is clear.
+  let mut source_width = original_width;
+  let mut source_height = original_height;
+  for destination_mip_level in 1 .. mip_count {
+    let source_mip_level = destination_mip_level - 1;
+    let destination_width = (source_width / 2).max(1);
+    let destination_height = (source_height / 2).max(1);
+
+    //   So. The name pipeline_barrier is a little misleading; it does indeed
+    // mean "barrier" in the concurrency sense, but it isn't just initiating
+    // a wait, it's also performing any needed mutation. We do one of them
+    // here, acting on this iteration's source level, to set it up for
+    // reading.
+    barrier_subresource_range = barrier_subresource_range
+        .base_mip_level(source_mip_level as u32);
+    blit_barrier_info = blit_barrier_info
+        .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
+        .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
+        .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
+        .dst_access_mask(vk::AccessFlags::TRANSFER_READ)
+        .subresource_range(barrier_subresource_range);
+    unsafe {
+      device.cmd_pipeline_barrier(command_buffer,
+                                  vk::PipelineStageFlags::TRANSFER,
+                                  vk::PipelineStageFlags::TRANSFER,
+                                  vk::DependencyFlags::empty(),
+                                  &[] as &[vk::MemoryBarrier],
+                                  &[] as &[vk::BufferMemoryBarrier],
+                                  &[blit_barrier_info])
+    };
+
+    //   Now we do the actual blit. Nice and easy, though specifying the
+    // coordinates is a bit verbose.
+    let blit_source_layer_info = vk::ImageSubresourceLayers::builder()
+            .aspect_mask(vk::ImageAspectFlags::COLOR)
+            .mip_level(source_mip_level as u32)
+            .base_array_layer(0)
+            .layer_count(1);
+    let blit_destination_layer_info = vk::ImageSubresourceLayers::builder()
+            .aspect_mask(vk::ImageAspectFlags::COLOR)
+            .mip_level(destination_mip_level as u32)
+            .base_array_layer(0)
+            .layer_count(1);
+    let blit_info = vk::ImageBlit::builder()
+            .src_offsets([vk::Offset3D { x: 0, y: 0, z: 0 },
+                          vk::Offset3D {
+                            x: source_width as i32,
+                            y: source_height as i32,
+                            z: 1
+                          }])
+            .src_subresource(blit_source_layer_info)
+            .dst_offsets([vk::Offset3D { x: 0, y: 0, z: 0 },
+                          vk::Offset3D {
+                            x: destination_width as i32,
+                            y: destination_height as i32,
+                            z: 1
+                          }])
+            .dst_subresource(blit_destination_layer_info);
+    unsafe {
+      device.cmd_blit_image(command_buffer,
+                            *image, vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
+                            *image, vk::ImageLayout::TRANSFER_DST_OPTIMAL,
+                            &[blit_info],
+                            vk::Filter::LINEAR)
+    };
+
+    //   Now we do another pipeline_barrier. We're still acting on this
+    // iteration's source level, not on the destination. We'll never need to
+    // use it again except from the shader, so we set it appropriately for
+    // that.
+    blit_barrier_info = blit_barrier_info
+        .old_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
+        .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
+        .src_access_mask(vk::AccessFlags::TRANSFER_READ)
+        .dst_access_mask(vk::AccessFlags::SHADER_READ);
+    unsafe {
+      device.cmd_pipeline_barrier(command_buffer,
+                                  vk::PipelineStageFlags::TRANSFER,
+                                  vk::PipelineStageFlags::FRAGMENT_SHADER,
+                                  vk::DependencyFlags::empty(),
+                                  &[] as &[vk::MemoryBarrier],
+                                  &[] as &[vk::BufferMemoryBarrier],
+                                  &[blit_barrier_info])
+    };
+
+    source_width = destination_width;
+    source_height = destination_height;
+  }
+
+  let final_mip_level = mip_count - 1;
+
+  //   We need to do one final pipeline_barrier, because the loop didn't do it
+  // to the smallest (last) mip level. We change it to have the same settings
+  // the loop left the rest of them in. The barrier source properties for this
+  // barrier are different from the others because this level was never useds
+  // as a blit source, only as a blit destination. The barrier destination
+  // properties are the same as the rest, so after this all the subresourcess
+  // will be in their fully-ready state.
+  barrier_subresource_range = barrier_subresource_range
+      .base_mip_level(final_mip_level as u32);
+  blit_barrier_info = blit_barrier_info
+      .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
+      .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
+      .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
+      .dst_access_mask(vk::AccessFlags::SHADER_READ)
+      .subresource_range(barrier_subresource_range);
+  unsafe {
+    device.cmd_pipeline_barrier(command_buffer,
+                                vk::PipelineStageFlags::TRANSFER,
+                                vk::PipelineStageFlags::FRAGMENT_SHADER,
+                                vk::DependencyFlags::empty(),
+                                &[] as &[vk::MemoryBarrier],
+                                &[] as &[vk::BufferMemoryBarrier],
+                                &[blit_barrier_info])
+  };
+
+  end_transient_commands(command_buffer, device, queue, command_pool)?;
+
+  Ok(())
+}
+
+
+#[allow(unsafe_code)]
+fn begin_transient_commands(device: &Device, command_pool: &vk::CommandPool)
+    -> Result<vk::CommandBuffer>
+{
+  let command_buffer_allocation_info
+          = vk::CommandBufferAllocateInfo::builder()
+                .command_pool(*command_pool)
+                .level(vk::CommandBufferLevel::PRIMARY)
+                .command_buffer_count(1);
+  let command_buffer = unsafe {
+    device.allocate_command_buffers(&command_buffer_allocation_info)
+  }?[0];
+
+  let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder()
+          .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
+
+  unsafe {
+    device.begin_command_buffer(command_buffer, &command_buffer_begin_info)
+  }?;
+
+  Ok(command_buffer)
+}
+
+
+#[allow(unsafe_code)]
+fn end_transient_commands(command_buffer: vk::CommandBuffer,
+                          device: &Device, queue: &vk::Queue,
+                          command_pool: &vk::CommandPool)
+    -> Result<()>
+{
+  unsafe { device.end_command_buffer(command_buffer) }?;
+
+  let command_buffers = [command_buffer];
+  let submit_info = vk::SubmitInfo::builder()
+          .command_buffers(&command_buffers);
+  unsafe { device.queue_submit(*queue, &[submit_info], vk::Fence::null()) }?;
+
+  unsafe { device.queue_wait_idle(*queue) }?;
+
+  unsafe { device.free_command_buffers(*command_pool, &command_buffers) };
+
+  Ok(())
+}
+