summary refs log tree commit diff
path: root/src/graphics/texture.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/graphics/texture.rs')
-rw-r--r--src/graphics/texture.rs376
1 files changed, 376 insertions, 0 deletions
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(())
+}
+