summary refs log tree commit diff
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-07-22 01:07:29 -0700
committerIrene Knapp <ireneista@irenes.space>2026-07-27 01:58:36 -0700
commit74ebf3d25e94b87a755d0ad0875332f962c8b92f (patch)
treecbd707e70269ee84589fe735cdc4d0042602b809
parent3ac1e5e5a9ebcb4beb2bfe149dd5095f2250f60d (diff)
generate mipmaps
Force-Push: yes
Change-Id: I2683fa6e3f290a812b74e5b64701c3094bb5cbee
-rw-r--r--src/graphics_permanent.rs49
-rw-r--r--src/graphics_window_dressing.rs270
-rw-r--r--src/main.rs9
3 files changed, 254 insertions, 74 deletions
diff --git a/src/graphics_permanent.rs b/src/graphics_permanent.rs
index 63e8994..c19bacd 100644
--- a/src/graphics_permanent.rs
+++ b/src/graphics_permanent.rs
@@ -62,8 +62,6 @@ pub struct PermanentGraphicsState {
   // follow Vulkan's lead and let it have a short variable name.
   pub device: Device,
 
-  pub sampler: vk::Sampler,
-
   //   Vulkan has a first-class concept of command queues. We have two of
   // them, one for graphics drawing commands and one for presentation.
   //
@@ -90,14 +88,15 @@ pub struct QueueFamilyIndices {
 // for accidentally passing or returning one boolean as if it's another.
 struct EnablePortability(bool);
 struct EnableValidation(bool);
-struct EnableAnisotropy(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, EnableSwapchain)>
+      -> Result<(Self, GraphicsStateForReinit, EnableAnisotropy,
+                 EnableSwapchain)>
   {
     let window = init_window(event_loop)?;
 
@@ -130,22 +129,18 @@ impl PermanentGraphicsState {
         = init_vulkan_device(&instance, &surface,
                              enable_validation, enable_portability)?;
 
-    let sampler = init_sampler(&device, &enable_anisotropy)?;
-
     let descriptor_set_layout = init_descriptor_set_layout(&device)?;
 
     Ok((PermanentGraphicsState {
-      window, entry, instance, debug_messager, surface, device, sampler,
+      window, entry, instance, debug_messager, surface, device,
       graphics_queue, presentation_queue
     }, GraphicsStateForReinit {
-      physical_device, indices, descriptor_set_layout
-    }, enable_swapchain))
+      physical_device, indices, descriptor_set_layout,
+    }, enable_anisotropy, enable_swapchain))
   }
 
   #[allow(unsafe_code)]
   pub fn destroy(self) -> () {
-    unsafe { self.device.destroy_sampler(self.sampler, None) };
-
     unsafe { self.device.destroy_device(None) };
 
     unsafe { self.instance.destroy_surface_khr(self.surface, None) };
@@ -551,38 +546,6 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
 
 
 #[allow(unsafe_code)]
-fn init_sampler(device: &Device, enable_anisotropy: &EnableAnisotropy)
-    -> 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(0.0);
-  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_set_layout(device: &Device)
     -> Result<vk::DescriptorSetLayout>
 {
diff --git a/src/graphics_window_dressing.rs b/src/graphics_window_dressing.rs
index e774177..fd6b8d6 100644
--- a/src/graphics_window_dressing.rs
+++ b/src/graphics_window_dressing.rs
@@ -1,7 +1,8 @@
 #![deny(unsafe_code)]
 use crate::error::*;
 use crate::graphics_permanent::{
-  PermanentGraphicsState, GraphicsStateForReinit, QueueFamilyIndices
+  PermanentGraphicsState, GraphicsStateForReinit, QueueFamilyIndices,
+  EnableAnisotropy
 };
 use crate::model_loader::load_model;
 use crate::shader_data::{ Vertex, UniformBlock };
@@ -56,6 +57,8 @@ pub struct WindowDressing {
   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>,
@@ -105,14 +108,14 @@ pub struct Concurrency {
 
 impl WindowDressing {
   pub fn new(permanent: &PermanentGraphicsState,
-             for_reinit: &GraphicsStateForReinit)
+             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 sampler = &permanent.sampler;
     let graphics_queue = &permanent.graphics_queue;
     let physical_device = &for_reinit.physical_device;
     let indices = &for_reinit.indices;
@@ -149,10 +152,12 @@ impl WindowDressing {
             = init_index_buffer(indices, instance, physical_device, device,
                                 graphics_queue, &transient_command_pool)?;
 
-    let (texture_image, texture_image_memory, texture_image_view)
+    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())?;
@@ -163,7 +168,7 @@ impl WindowDressing {
             = init_descriptor_sets(device, descriptor_set_layout,
                                    &uniform_buffers, &descriptor_pool,
                                    swapchain.images.len(),
-                                   &texture_image_view, sampler)?;
+                                   &texture_image_view, &sampler)?;
 
     let command_buffers = init_commands(
             device, &swapchain.extent, &framebuffers, &render_pass,
@@ -190,6 +195,8 @@ impl WindowDressing {
       texture_image,
       texture_image_memory,
       texture_image_view,
+      mip_count,
+      sampler,
       uniform_buffers,
       uniform_buffer_memory,
       descriptor_pool,
@@ -211,7 +218,6 @@ impl WindowDressing {
     let instance = &permanent.instance;
     let surface = &permanent.surface;
     let device = &permanent.device;
-    let sampler = &permanent.sampler;
     let physical_device = &for_reinit.physical_device;
     let indices = &for_reinit.indices;
     let descriptor_set_layout = &for_reinit.descriptor_set_layout;
@@ -250,7 +256,7 @@ impl WindowDressing {
             = init_descriptor_sets(device, descriptor_set_layout,
                                    &uniform_buffers, &descriptor_pool,
                                    swapchain.images.len(),
-                                   &self.texture_image_view, sampler)?;
+                                   &self.texture_image_view, &self.sampler)?;
 
     // Notice that we reused the command pool.
     let command_buffers = init_commands(
@@ -289,11 +295,14 @@ impl WindowDressing {
 
     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) };
+
     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) };
@@ -425,7 +434,7 @@ fn init_swapchain(window: &Window, instance: &Instance,
 
   let mut image_views = Vec::new();
   for image in &images {
-    let view = init_image_view(device, image, format.format,
+    let view = init_image_view(device, image, 1, format.format,
                                vk::ImageAspectFlags::COLOR)?;
     image_views.push(view);
   }
@@ -447,13 +456,13 @@ fn init_depth(instance: &Instance, physical_device: &vk::PhysicalDevice,
 
   let (image, image_memory)
           = allocate_image(instance, physical_device, device,
-                           extent.width, extent.height,
+                           extent.width, extent.height, 1,
                            format,
                            vk::ImageTiling::OPTIMAL,
                            vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
                            vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
 
-  let image_view = init_image_view(device, &image, format,
+  let image_view = init_image_view(device, &image, 1, format,
                                    vk::ImageAspectFlags::DEPTH)?;
 
   Ok((image, image_memory, image_view, format))
@@ -719,7 +728,7 @@ fn init_index_buffer(indices: Vec<u32>, instance: &Instance,
 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)>
+    -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)>
 {
   let png = include_bytes!("../textures/forest_leaves_04_diff.png");
 
@@ -728,6 +737,21 @@ fn init_texture(instance: &Instance,
 
   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)?;
 
@@ -735,14 +759,16 @@ fn init_texture(instance: &Instance,
           = stage_in_buffer(instance, physical_device, device, &pixels)?;
 
   let (image, image_memory)
-          = allocate_image(instance, physical_device, device, width, height,
+          = allocate_image(instance, physical_device, device,
+                           width, height, mip_count,
                            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,
+  change_image_layout(device, queue, command_pool, &image, mip_count,
                       vk::Format::R8G8B8A8_SRGB,
                       vk::ImageLayout::UNDEFINED,
                       vk::ImageLayout::TRANSFER_DST_OPTIMAL)?;
@@ -750,18 +776,19 @@ fn init_texture(instance: &Instance,
   copy_buffer_to_image(device, queue, command_pool, &staging_buffer, &image,
                        width, height)?;
 
-  change_image_layout(device, queue, command_pool, &image,
-                      vk::Format::R8G8B8A8_SRGB,
-                      vk::ImageLayout::TRANSFER_DST_OPTIMAL,
-                      vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)?;
+  // This will also change the layout to SHADER_READ_ONLY_OPTIMAL.
 
-  let view = init_image_view(device, &image, vk::Format::R8G8B8A8_SRGB,
+  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))
+  Ok((image, image_memory, view, mip_count))
 }
 
 
@@ -816,6 +843,39 @@ fn init_buffer<T>(instance: &Instance,
 
 
 #[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>
 {
@@ -1053,8 +1113,8 @@ fn init_concurrency(device: &Device,
 
 
 #[allow(unsafe_code)]
-fn init_image_view(device: &Device, image: &vk::Image, format: vk::Format,
-                   aspects: vk::ImageAspectFlags)
+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
@@ -1068,7 +1128,7 @@ fn init_image_view(device: &Device, image: &vk::Image, format: vk::Format,
   let subresource_range = vk::ImageSubresourceRange::builder()
                               .aspect_mask(aspects)
                               .base_mip_level(0)
-                              .level_count(1)
+                              .level_count(mip_count)
                               .base_array_layer(0)
                               .layer_count(1);
 
@@ -1281,7 +1341,7 @@ fn copy_buffer(device: &Device, queue: &vk::Queue,
 
 #[allow(unsafe_code)]
 fn allocate_image(instance: &Instance, physical_device: &vk::PhysicalDevice,
-                  device: &Device, width: u32, height: u32,
+                  device: &Device, width: u32, height: u32, mip_count: u32,
                   format: vk::Format, tiling: vk::ImageTiling,
                   usage: vk::ImageUsageFlags,
                   memory_flags: vk::MemoryPropertyFlags)
@@ -1290,7 +1350,7 @@ fn allocate_image(instance: &Instance, physical_device: &vk::PhysicalDevice,
   let image_info = vk::ImageCreateInfo::builder()
           .image_type(vk::ImageType::_2D)
           .extent(vk::Extent3D { width, height, depth: 1 })
-          .mip_levels(1)
+          .mip_levels(mip_count)
           .array_layers(1)
           .format(format)
           .tiling(tiling)
@@ -1356,8 +1416,8 @@ fn copy_buffer_to_image(device: &Device, queue: &vk::Queue,
 #[allow(unsafe_code)]
 fn change_image_layout(device: &Device, queue: &vk::Queue,
                        command_pool: &vk::CommandPool, image: &vk::Image,
-                       format: vk::Format, old: vk::ImageLayout,
-                       new: vk::ImageLayout)
+                       mip_count: u32, format: vk::Format,
+                       old: vk::ImageLayout, new: vk::ImageLayout)
     -> Result<()>
 {
   let command_buffer = begin_transient_commands(device, command_pool)?;
@@ -1365,7 +1425,7 @@ fn change_image_layout(device: &Device, queue: &vk::Queue,
   let subresource_range = vk::ImageSubresourceRange::builder()
                               .aspect_mask(vk::ImageAspectFlags::COLOR)
                               .base_mip_level(0)
-                              .level_count(1)
+                              .level_count(mip_count)
                               .base_array_layer(0)
                               .layer_count(1);
 
@@ -1419,6 +1479,162 @@ fn change_image_layout(device: &Device, queue: &vk::Queue,
 }
 
 
+//   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>
diff --git a/src/main.rs b/src/main.rs
index af41b0d..3267d0b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -61,12 +61,13 @@ impl Surreality {
 
   #[allow(unsafe_code)]
   fn init(&mut self, event_loop: &ActiveEventLoop) -> Result<()> {
-    let (permanent, for_reinit, enable_swapchain)
+    let (permanent, for_reinit, enable_anisotropy, enable_swapchain)
         = PermanentGraphicsState::new(event_loop)?;
 
     if enable_swapchain.0 {
       *self.window_dressing.get_mut()
-          = Some(WindowDressing::new(&permanent, &for_reinit)?);
+          = Some(WindowDressing::new(&permanent, &for_reinit,
+                                     enable_anisotropy)?);
     }
 
     *self.permanent.get_mut() = Some(permanent);
@@ -292,11 +293,11 @@ fn render_uniforms(device: &Device, device_memory: &vk::DeviceMemory,
     rotation: Vec4::rotation_quaternion(&Vec3::new(0.0, 1.0, 0.0), time % TAU),
     translation: Vec3::new(0.0, 0.0, 0.0),
   };
-  let view = Transformation::look_at(&Vec3::new(0.0, -3.0, -8.0),
+  let view = Transformation::look_at(&Vec3::new(0.0, -3.0, -10.0),
                                      &Vec3::new(0.0, -1.0, 0.0),
                                      &Vec3::new(0.0, -1.0, 0.0));
   let aspect_ratio = extent.width as f32 / extent.height as f32;
-  let projection = Mat4::perspective(FRAC_PI_4, aspect_ratio, 0.1, 10.0);
+  let projection = Mat4::perspective(FRAC_PI_4, aspect_ratio, 0.1, 100.0);
   let block = UniformBlock::<f32> { scale, model, view, projection };
 
   let size = size_of::<UniformBlock<f32>>() as u64;