diff options
Diffstat (limited to 'src/graphics/texture.rs')
| -rw-r--r-- | src/graphics/texture.rs | 376 |
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(()) +} + |