use anyhow::{anyhow, Result}; use vulkanalia::prelude::v1_0::*; use crate::{ app::AppData, single_time_cmd::{begin_single_time_commands, end_single_time_commands}, }; // OPTIMIZE pregenerate and store texture file mipmaps alongside the base level to improve loading speed pub(crate) unsafe fn generate_mipmaps( instance: &Instance, device: &Device, data: &AppData, image: vk::Image, format: vk::Format, width: u32, height: u32, mip_levels: u32, ) -> Result<()> { if !instance .get_physical_device_format_properties(data.physical_device, format) .optimal_tiling_features .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR) { return Err(anyhow!( "Texture image format does not support linear blitting!" )); } let command_buffer = begin_single_time_commands(device, data)?; let subresource = vk::ImageSubresourceRange::builder() .aspect_mask(vk::ImageAspectFlags::COLOR) .base_array_layer(0) .layer_count(1) .level_count(1); let mut barrier = vk::ImageMemoryBarrier::builder() .image(image) .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .subresource_range(subresource); let mut mip_width = width; let mut mip_height = height; for i in 1..mip_levels { barrier.subresource_range.base_mip_level = i - 1; barrier.old_layout = vk::ImageLayout::TRANSFER_DST_OPTIMAL; barrier.new_layout = vk::ImageLayout::TRANSFER_SRC_OPTIMAL; barrier.src_access_mask = vk::AccessFlags::TRANSFER_WRITE; barrier.dst_access_mask = vk::AccessFlags::TRANSFER_READ; device.cmd_pipeline_barrier( command_buffer, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::TRANSFER, vk::DependencyFlags::empty(), &[] as &[vk::MemoryBarrier], &[] as &[vk::BufferMemoryBarrier], &[barrier], ); let src_subresource = vk::ImageSubresourceLayers::builder() .aspect_mask(vk::ImageAspectFlags::COLOR) .mip_level(i - 1) .base_array_layer(0) .layer_count(1); let dst_subresource = vk::ImageSubresourceLayers::builder() .aspect_mask(vk::ImageAspectFlags::COLOR) .mip_level(i) .base_array_layer(0) .layer_count(1); let blit = vk::ImageBlit::builder() .src_offsets([ vk::Offset3D { x: 0, y: 0, z: 0 }, vk::Offset3D { x: mip_width as i32, y: mip_height as i32, z: 1, }, ]) .src_subresource(src_subresource) .dst_offsets([ vk::Offset3D { x: 0, y: 0, z: 0 }, vk::Offset3D { x: (if mip_width > 1 { mip_width / 2 } else { 1 }) as i32, y: (if mip_height > 1 { mip_height / 2 } else { 1 }) as i32, z: 1, }, ]) .dst_subresource(dst_subresource); // OPTIMIZE FIX if you are using a dedicated transfer queue, cmd_blit_image must be submitted to a queue with graphics capability. device.cmd_blit_image( command_buffer, image, vk::ImageLayout::TRANSFER_SRC_OPTIMAL, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, &[blit], vk::Filter::LINEAR, ); barrier.old_layout = vk::ImageLayout::TRANSFER_SRC_OPTIMAL; barrier.new_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; barrier.src_access_mask = vk::AccessFlags::TRANSFER_READ; barrier.dst_access_mask = vk::AccessFlags::SHADER_READ; device.cmd_pipeline_barrier( command_buffer, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::FRAGMENT_SHADER, vk::DependencyFlags::empty(), &[] as &[vk::MemoryBarrier], &[] as &[vk::BufferMemoryBarrier], &[barrier], ); if mip_width > 1 { mip_width /= 2; } if mip_height > 1 { mip_height /= 2; } } barrier.subresource_range.base_mip_level = mip_levels - 1; barrier.old_layout = vk::ImageLayout::TRANSFER_DST_OPTIMAL; barrier.new_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; barrier.src_access_mask = vk::AccessFlags::TRANSFER_WRITE; barrier.dst_access_mask = vk::AccessFlags::SHADER_READ; device.cmd_pipeline_barrier( command_buffer, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::FRAGMENT_SHADER, vk::DependencyFlags::empty(), &[] as &[vk::MemoryBarrier], &[] as &[vk::BufferMemoryBarrier], &[barrier], ); end_single_time_commands(device, data, command_buffer)?; Ok(()) }