#![deny(unsafe_code)] use crate::error::*; use crate::graphics::Permanent; 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: &Permanent) -> 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(()) }