summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/graphics/mod.rs1
-rw-r--r--src/graphics/model.rs6
-rw-r--r--src/graphics/permanent.rs52
-rw-r--r--src/graphics/render_state.rs33
-rw-r--r--src/graphics/texture.rs376
-rw-r--r--src/graphics/util.rs78
-rw-r--r--src/graphics/window_dressing.rs458
-rw-r--r--src/main.rs22
8 files changed, 538 insertions, 488 deletions
diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs
index 7007429..3d03f9c 100644
--- a/src/graphics/mod.rs
+++ b/src/graphics/mod.rs
@@ -3,6 +3,7 @@
 pub mod model;
 pub mod permanent;
 pub mod render_state;
+pub mod texture;
 pub mod scene;
 pub mod util;
 pub mod window_dressing;
diff --git a/src/graphics/model.rs b/src/graphics/model.rs
index 01e8731..7b912b6 100644
--- a/src/graphics/model.rs
+++ b/src/graphics/model.rs
@@ -3,7 +3,6 @@ use crate::error::*;
 use crate::graphics::permanent::PermanentGraphicsState;
 use crate::graphics::render_state::RenderState;
 use crate::graphics::util::init_buffer;
-use crate::graphics::window_dressing::WindowDressing;
 use crate::linear_algebra::{ Vec3, Vec4, Transformation };
 use crate::shader_data::{ Vertex, VertexPushBlock };
 
@@ -26,14 +25,13 @@ pub struct Model {
 
 impl Model {
   pub fn new(vertices: Vec<Vertex<f32>>, indices: Vec<u32>,
-             permanent: &PermanentGraphicsState,
-             window_dressing: &WindowDressing)
+             permanent: &PermanentGraphicsState)
       -> Result<Self>
   {
     let device = &permanent.device;
     let instance = &permanent.instance;
     let graphics_queue = &permanent.graphics_queue;
-    let transient_command_pool = &window_dressing.transient_command_pool;
+    let transient_command_pool = &permanent.transient_command_pool;
 
     let index_count = indices.len();
 
diff --git a/src/graphics/permanent.rs b/src/graphics/permanent.rs
index beda3fe..f359dbd 100644
--- a/src/graphics/permanent.rs
+++ b/src/graphics/permanent.rs
@@ -75,6 +75,14 @@ pub struct PermanentGraphicsState {
   // concerns, which have a tendency to defeat optimizations. Alas.
   pub graphics_queue: vk::Queue,
   pub presentation_queue: vk::Queue,
+
+  //   A command pool is an object from which command buffers are allocated.
+  // Our command pools are permanent, but none of the actual buffers are,
+  // those are all managed elsewhere. Lifecycle operations on Permanent
+  // require all the command-buffer lifecycle stuff to have already been dealt
+  // with.
+  pub primary_command_pool: vk::CommandPool,
+  pub transient_command_pool: vk::CommandPool,
 }
 
 
@@ -130,9 +138,13 @@ impl PermanentGraphicsState {
 
     let descriptor_set_layout = init_descriptor_set_layout(&device)?;
 
+    let (primary_command_pool, transient_command_pool)
+            = init_command_pools(&device, &indices)?;
+
     Ok((PermanentGraphicsState {
       window, entry, instance, debug_messager, surface, device,
-      graphics_queue, presentation_queue
+      graphics_queue, presentation_queue,
+      primary_command_pool, transient_command_pool,
     }, GraphicsStateForReinit {
       indices, sample_count, descriptor_set_layout,
     }, enable_anisotropy, enable_swapchain))
@@ -140,9 +152,17 @@ impl PermanentGraphicsState {
 
   #[allow(unsafe_code)]
   pub fn destroy(self) -> () {
-    unsafe { self.device.destroy_device(None) };
+    let device = self.device;
+    let instance = self.instance;
+
+    //   Notice that we rely on the assumption any command buffers in the
+    // pools have already been freed.
+    unsafe { device.destroy_command_pool(self.primary_command_pool, None) };
+    unsafe { device.destroy_command_pool(self.transient_command_pool, None) };
+
+    unsafe { device.destroy_device(None) };
 
-    unsafe { self.instance.destroy_surface_khr(self.surface, None) };
+    unsafe { 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
@@ -151,11 +171,11 @@ impl PermanentGraphicsState {
     // 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);
+        instance.destroy_debug_utils_messenger_ext(debug_messager, None);
       }
     }
 
-    unsafe { self.instance.destroy_instance(None) };
+    unsafe { instance.destroy_instance(None) };
   }
 
   //   We expect our caller to have already verified that the device supports
@@ -573,6 +593,28 @@ fn init_descriptor_set_layout(device: &Device)
 
 
 
+#[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))
+}
+
+
 //   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
diff --git a/src/graphics/render_state.rs b/src/graphics/render_state.rs
index d918e9a..5848ac8 100644
--- a/src/graphics/render_state.rs
+++ b/src/graphics/render_state.rs
@@ -4,6 +4,7 @@ use crate::graphics::permanent::{
   PermanentGraphicsState, GraphicsStateForReinit
 };
 use crate::graphics::model::Model;
+use crate::graphics::texture::Texture;
 use crate::graphics::window_dressing::WindowDressing;
 use crate::shader_data::{ Vertex, UniformBlock, VertexPushBlock };
 
@@ -34,18 +35,17 @@ pub struct RenderState {
 impl RenderState {
   pub fn new(permanent: &PermanentGraphicsState,
              for_reinit: &GraphicsStateForReinit,
-             window_dressing: &WindowDressing)
+             window_dressing: &WindowDressing, texture: &Texture)
       -> Result<Self>
   {
     let device = &permanent.device;
+    let primary_command_pool = &permanent.primary_command_pool;
     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;
@@ -68,7 +68,7 @@ impl RenderState {
             = init_descriptor_sets(device, descriptor_set_layout,
                                    &uniform_buffers, &descriptor_pool,
                                    swapchain.images.len(),
-                                   &texture_image_view, &sampler)?;
+                                   &texture.image_view, &sampler)?;
 
     let model = None;
 
@@ -87,24 +87,23 @@ impl RenderState {
   // idle.
   pub fn reinit(&mut self, permanent: &PermanentGraphicsState,
                 for_reinit: &GraphicsStateForReinit,
-                window_dressing: &WindowDressing)
+                window_dressing: &WindowDressing, texture: &Texture)
       -> Result<()>
   {
+    self.destroy_replaceable(permanent);
+
     let device = &permanent.device;
+    let primary_command_pool = &permanent.primary_command_pool;
     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)?;
 
@@ -124,7 +123,7 @@ impl RenderState {
             = init_descriptor_sets(device, descriptor_set_layout,
                                    &uniform_buffers, &descriptor_pool,
                                    swapchain.images.len(),
-                                   texture_image_view, sampler)?;
+                                   &texture.image_view, sampler)?;
 
     self.render_pass = render_pass;
     self.pipeline = pipeline;
@@ -139,20 +138,20 @@ impl RenderState {
   //   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)
+  pub fn destroy(mut self, permanent: &PermanentGraphicsState)
   {
-    self.destroy_replaceable(device, &window_dressing.primary_command_pool);
+    self.destroy_replaceable(permanent);
 
     if let Some(model) = self.model {
-      model.destroy(device);
+      model.destroy(&permanent.device);
     }
   }
 
   #[allow(unsafe_code)]
-  fn destroy_replaceable(&mut self, device: &Device,
-                         primary_command_pool: &vk::CommandPool)
+  fn destroy_replaceable(&mut self, permanent: &PermanentGraphicsState)
   {
+    let device = &permanent.device;
+
     for framebuffer in &self.framebuffers {
       unsafe { device.destroy_framebuffer(*framebuffer, None) };
     }
@@ -163,7 +162,7 @@ impl RenderState {
     // buffers. We promise ourselves to free buffers in the transient pool
     // immediately after using them.
     unsafe {
-      device.free_command_buffers(*primary_command_pool,
+      device.free_command_buffers(permanent.primary_command_pool,
                                   &self.command_buffers)
     };
 
diff --git a/src/graphics/texture.rs b/src/graphics/texture.rs
new file mode 100644
index 0000000..cd4dd7a
--- /dev/null
+++ b/src/graphics/texture.rs
@@ -0,0 +1,376 @@
+#![deny(unsafe_code)]
+use crate::error::*;
+use crate::graphics::permanent::PermanentGraphicsState;
+use crate::graphics::util::{
+  stage_in_buffer, allocate_image, init_image_view,
+  begin_transient_commands, end_transient_commands
+};
+
+use std::io::Cursor;
+
+use png::Decoder;
+use vulkanalia::{ Device, Instance };
+use vulkanalia::vk::{ self, HasBuilder, InstanceV1_0, DeviceV1_0 };
+
+
+#[derive(Debug)]
+pub struct Texture {
+  image: vk::Image,
+  image_memory: vk::DeviceMemory,
+  pub image_view: vk::ImageView,
+}
+
+
+impl Texture {
+  pub fn new(permanent: &PermanentGraphicsState) -> Result<(Self, u32)> {
+    let graphics_queue = &permanent.graphics_queue;
+    let instance = &permanent.instance;
+    let device = &permanent.device;
+    let transient_command_pool = &permanent.transient_command_pool;
+
+    let (image, image_memory, image_view, mip_count)
+            = init_texture(instance, device, graphics_queue,
+                           &transient_command_pool)?;
+
+    Ok((Texture {
+      image,
+      image_memory,
+      image_view,
+    }, mip_count))
+  }
+
+  //   This relies on its caller to have already waited for the device to be
+  // idle.
+  #[allow(unsafe_code)]
+  pub fn destroy(self, device: &Device) {
+    unsafe { device.destroy_image(self.image, None) };
+    unsafe { device.free_memory(self.image_memory, None) };
+    unsafe { device.destroy_image_view(self.image_view, None) };
+  }
+}
+
+
+#[allow(unsafe_code)]
+fn init_texture(instance: &Instance, device: &Device, queue: &vk::Queue,
+                command_pool: &vk::CommandPool)
+    -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)>
+{
+  let physical_device = device.physical_device();
+
+  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, device, &pixels)?;
+
+  let (image, image_memory)
+          = allocate_image(instance, 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::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))
+}
+
+
+#[allow(unsafe_code)]
+fn change_image_layout(device: &Device, queue: &vk::Queue,
+                       command_pool: &vk::CommandPool, image: &vk::Image,
+                       mip_count: u32, 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(())
+}
+
+
+
+#[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(())
+}
+
+
+//   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(())
+}
+
diff --git a/src/graphics/util.rs b/src/graphics/util.rs
index c970d18..d09d144 100644
--- a/src/graphics/util.rs
+++ b/src/graphics/util.rs
@@ -151,6 +151,84 @@ pub fn pick_memory_type(instance: &Instance,
 
 
 #[allow(unsafe_code)]
+pub fn allocate_image(instance: &Instance, 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 physical_device = device.physical_device();
+
+  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)]
+pub 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)
+}
+
+
+#[allow(unsafe_code)]
 pub fn begin_transient_commands(device: &Device,
                                 command_pool: &vk::CommandPool)
     -> Result<vk::CommandBuffer>
diff --git a/src/graphics/window_dressing.rs b/src/graphics/window_dressing.rs
index 09410bc..9afb03e 100644
--- a/src/graphics/window_dressing.rs
+++ b/src/graphics/window_dressing.rs
@@ -5,17 +5,13 @@ use crate::graphics::permanent::{
   EnableAnisotropy
 };
 use crate::graphics::util::{
-  allocate_buffer, stage_in_buffer,
-  pick_memory_type,
-  begin_transient_commands, end_transient_commands
+  allocate_buffer, allocate_image, init_image_view
 };
 use crate::shader_data::UniformBlock;
 
 use std::collections::BTreeSet;
-use std::io::Cursor;
 use std::mem::size_of;
 
-use png::Decoder;
 use vulkanalia::{ Device, Instance };
 use vulkanalia::vk::{ self, Handle, HasBuilder, InstanceV1_0, DeviceV1_0,
                       KhrSwapchainExtensionDeviceCommands };
@@ -46,12 +42,6 @@ pub struct WindowDressing {
   pub depth_image_view: vk::ImageView,
   pub depth_format: vk::Format,
 
-  pub primary_command_pool: vk::CommandPool,
-  pub transient_command_pool: vk::CommandPool,
-
-  texture_image: vk::Image,
-  texture_image_memory: vk::DeviceMemory,
-  pub texture_image_view: vk::ImageView,
   pub sampler: vk::Sampler,
 
   pub uniform_buffers: Vec<vk::Buffer>,
@@ -101,14 +91,13 @@ pub struct Concurrency {
 impl WindowDressing {
   pub fn new(permanent: &PermanentGraphicsState,
              for_reinit: &GraphicsStateForReinit,
-             enable_anisotropy: EnableAnisotropy)
+             enable_anisotropy: EnableAnisotropy, mip_count: u32)
       -> 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 sample_count = for_reinit.sample_count;
     let indices = &for_reinit.indices;
 
@@ -122,13 +111,6 @@ impl WindowDressing {
     let (depth_image, depth_image_memory, depth_image_view, depth_format)
             = init_depth(instance, 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, device, graphics_queue,
-                           &transient_command_pool)?;
-
     let sampler = init_sampler(&device, &enable_anisotropy, mip_count)?;
 
     let (uniform_buffers, uniform_buffer_memory)
@@ -148,15 +130,10 @@ impl WindowDressing {
       depth_image_memory,
       depth_image_view,
       depth_format,
-      texture_image,
-      texture_image_memory,
-      texture_image_view,
       sampler,
       uniform_buffers,
       uniform_buffer_memory,
       descriptor_pool,
-      primary_command_pool,
-      transient_command_pool,
       concurrency,
     })
   }
@@ -220,9 +197,6 @@ impl WindowDressing {
   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 {
@@ -236,11 +210,6 @@ impl WindowDressing {
     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) };
   }
 
 
@@ -400,74 +369,6 @@ fn init_depth(instance: &Instance, device: &Device, extent: &vk::Extent2D,
 }
 
 
-#[allow(unsafe_code)]
-fn init_texture(instance: &Instance, device: &Device, queue: &vk::Queue,
-                command_pool: &vk::CommandPool)
-    -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)>
-{
-  let physical_device = device.physical_device();
-
-  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, device, &pixels)?;
-
-  let (image, image_memory)
-          = allocate_image(instance, 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::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, device: &Device, count: usize)
     -> Result<(Vec<vk::Buffer>, Vec<vk::DeviceMemory>)>
 {
@@ -545,28 +446,6 @@ fn init_descriptor_pool(device: &Device, count: usize)
 
 
 #[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_concurrency(device: &Device,
                     swapchain_images: &Vec<vk::Image>)
     -> Result<Concurrency>
@@ -606,41 +485,6 @@ fn init_concurrency(device: &Device,
 }
 
 
-#[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>
 {
@@ -713,301 +557,3 @@ fn pick_image_extent(window: &Window,
   }
 }
 
-
-#[allow(unsafe_code)]
-fn allocate_image(instance: &Instance, 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 physical_device = device.physical_device();
-
-  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, 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(())
-}
-
diff --git a/src/main.rs b/src/main.rs
index 9fb89bc..6601982 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
 #![deny(unsafe_code)]
 use crate::error::*;
 use crate::graphics::model::Model;
+use crate::graphics::texture::Texture;
 use crate::graphics::permanent::{
   PermanentGraphicsState, GraphicsStateForReinit
 };
@@ -37,6 +38,7 @@ struct Surreality {
   for_reinit: RefCell<Option<GraphicsStateForReinit>>,
   window_dressing: RefCell<Option<WindowDressing>>,
   render_state: RefCell<Option<RenderState>>,
+  texture: RefCell<Option<Texture>>,
   is_minimized: bool,
   is_reinit_queued: bool,
   frame_index: usize,
@@ -54,6 +56,7 @@ impl Surreality {
       for_reinit: RefCell::new(None),
       window_dressing: RefCell::new(None),
       render_state: RefCell::new(None),
+      texture: RefCell::new(None),
       is_minimized: false,
       is_reinit_queued: false,
       frame_index: 0,
@@ -69,19 +72,21 @@ impl Surreality {
     let (permanent, for_reinit, enable_anisotropy, enable_swapchain)
         = PermanentGraphicsState::new(event_loop)?;
 
+    let (texture, mip_count) = Texture::new(&permanent)?;
+
     if enable_swapchain.0 {
       let window_dressing = WindowDressing::new(&permanent, &for_reinit,
-                                                enable_anisotropy)?;
+                                                enable_anisotropy, mip_count)?;
       let mut render_state = RenderState::new(&permanent, &for_reinit,
-                                              &window_dressing)?;
+                                              &window_dressing, &texture)?;
       let (vertices, indices) = load_obj()?;
-      render_state.set_model(Model::new(vertices, indices,
-                                        &permanent, &window_dressing)?);
+      render_state.set_model(Model::new(vertices, indices, &permanent)?);
 
       *self.window_dressing.get_mut() = Some(window_dressing);
       *self.render_state.get_mut() = Some(render_state);
     }
 
+    *self.texture.get_mut() = Some(texture);
     *self.permanent.get_mut() = Some(permanent);
     *self.for_reinit.get_mut() = Some(for_reinit);
 
@@ -94,9 +99,10 @@ impl Surreality {
        && let Some(window_dressing)
               = self.window_dressing.borrow_mut().as_mut()
        && let Some(render_state) = self.render_state.borrow_mut().as_mut()
+       && let Some(texture) = self.texture.borrow_mut().as_mut()
     {
       window_dressing.reinit(permanent, for_reinit)?;
-      render_state.reinit(permanent, for_reinit, &window_dressing)?;
+      render_state.reinit(permanent, for_reinit, &window_dressing, &texture)?;
     }
 
     Ok(())
@@ -221,12 +227,16 @@ impl Drop for Surreality {
 
       if let Some(window_dressing) = self.window_dressing.replace(None) {
         if let Some(render_state) = self.render_state.replace(None) {
-          render_state.destroy(&permanent.device, &window_dressing);
+          render_state.destroy(&permanent);
         }
 
         window_dressing.destroy(&permanent.device);
       }
 
+      if let Some(texture) = self.texture.replace(None) {
+        texture.destroy(&permanent.device);
+      }
+
       if let Some(for_reinit) = self.for_reinit.replace(None) {
         for_reinit.destroy(&permanent.device);
       }