use anyhow::{anyhow, Result}; use vulkanalia::{ prelude::v1_0::*, vk::{DeviceMemory, Image}, }; use crate::{ app::AppData, generate_mipmaps::generate_mipmaps, single_time_cmd::{begin_single_time_commands, end_single_time_commands}, vertex_buffer::create_buffer, vertex_buffer::get_memory_type_index, }; pub(crate) type MipLevels = u32; pub(crate) unsafe fn create_texture_image( instance: &Instance, device: &Device, data: &mut AppData, pixels: &Vec, size: u64, width: u32, height: u32, ) -> Result<(Image, DeviceMemory, MipLevels)> { let mip_levels = (width.max(height) as f32).log2().floor() as u32 + 1; let (staging_buffer, staging_buffer_memory) = create_buffer( instance, device, data, size, vk::BufferUsageFlags::TRANSFER_SRC, vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, )?; let memory = device.map_memory(staging_buffer_memory, 0, size, vk::MemoryMapFlags::empty())?; std::ptr::copy_nonoverlapping(pixels.as_ptr(), memory.cast(), pixels.len()); device.unmap_memory(staging_buffer_memory); let (image, image_memory) = create_image( instance, device, data, width, height, mip_levels, vk::SampleCountFlags::_1, vk::Format::R8G8B8A8_SRGB, vk::ImageTiling::OPTIMAL, vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::TRANSFER_SRC, vk::MemoryPropertyFlags::DEVICE_LOCAL, )?; transition_image_layout( device, data, image, vk::Format::R8G8B8A8_SRGB, vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL, mip_levels, )?; copy_buffer_to_image(device, data, staging_buffer, image, width, height)?; generate_mipmaps( instance, device, data, image, vk::Format::R8G8B8A8_SRGB, width, height, mip_levels, )?; device.destroy_buffer(staging_buffer, None); device.free_memory(staging_buffer_memory, None); Ok((image, image_memory, mip_levels)) } pub(crate) unsafe fn create_image( instance: &Instance, device: &Device, data: &AppData, width: u32, height: u32, mip_levels: u32, samples: vk::SampleCountFlags, format: vk::Format, tiling: vk::ImageTiling, usage: vk::ImageUsageFlags, properties: vk::MemoryPropertyFlags, ) -> Result<(vk::Image, vk::DeviceMemory)> { let info = vk::ImageCreateInfo::builder() .image_type(vk::ImageType::_2D) .extent(vk::Extent3D { width, height, depth: 1, }) .mip_levels(mip_levels) .array_layers(1) .format(format) .tiling(tiling) .initial_layout(vk::ImageLayout::UNDEFINED) .usage(usage) .samples(samples) .sharing_mode(vk::SharingMode::EXCLUSIVE); let image = device.create_image(&info, None)?; let requirements = device.get_image_memory_requirements(image); let info = vk::MemoryAllocateInfo::builder() .allocation_size(requirements.size) .memory_type_index(get_memory_type_index( instance, data, properties, requirements, )?); let image_memory = device.allocate_memory(&info, None)?; device.bind_image_memory(image, image_memory, 0)?; Ok((image, image_memory)) } pub(crate) unsafe fn transition_image_layout( device: &Device, data: &AppData, image: vk::Image, format: vk::Format, old_layout: vk::ImageLayout, new_layout: vk::ImageLayout, mip_levels: u32, ) -> Result<()> { let aspect_mask = if new_layout == vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL { match format { vk::Format::D32_SFLOAT_S8_UINT | vk::Format::D24_UNORM_S8_UINT => { vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL } _ => vk::ImageAspectFlags::DEPTH, } } else { vk::ImageAspectFlags::COLOR }; let (src_access_mask, dst_access_mask, src_stage_mask, dst_stage_mask) = match (old_layout, new_layout) { (vk::ImageLayout::UNDEFINED, vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL) => ( vk::AccessFlags::empty(), vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, vk::PipelineStageFlags::TOP_OF_PIPE, vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS, ), (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) => ( vk::AccessFlags::empty(), vk::AccessFlags::TRANSFER_WRITE, vk::PipelineStageFlags::TOP_OF_PIPE, vk::PipelineStageFlags::TRANSFER, ), (vk::ImageLayout::TRANSFER_DST_OPTIMAL, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) => ( vk::AccessFlags::TRANSFER_WRITE, vk::AccessFlags::SHADER_READ, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::FRAGMENT_SHADER, ), _ => return Err(anyhow!("Unsupported image layout transition!")), }; let command_buffer = begin_single_time_commands(device, data)?; let subresource = vk::ImageSubresourceRange::builder() .aspect_mask(aspect_mask) .base_mip_level(0) .level_count(mip_levels) .base_array_layer(0) .layer_count(1); let barrier = vk::ImageMemoryBarrier::builder() .old_layout(old_layout) .new_layout(new_layout) .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .image(image) .subresource_range(subresource) .src_access_mask(src_access_mask) .dst_access_mask(dst_access_mask); device.cmd_pipeline_barrier( command_buffer, src_stage_mask, dst_stage_mask, vk::DependencyFlags::empty(), &[] as &[vk::MemoryBarrier], &[] as &[vk::BufferMemoryBarrier], &[barrier], ); end_single_time_commands(device, data, command_buffer)?; Ok(()) } unsafe fn copy_buffer_to_image( device: &Device, data: &AppData, buffer: vk::Buffer, image: vk::Image, width: u32, height: u32, ) -> Result<()> { let command_buffer = begin_single_time_commands(device, data)?; let subresource = vk::ImageSubresourceLayers::builder() .aspect_mask(vk::ImageAspectFlags::COLOR) .mip_level(0) .base_array_layer(0) .layer_count(1); let region = vk::BufferImageCopy::builder() .buffer_offset(0) .buffer_row_length(0) .buffer_image_height(0) .image_subresource(subresource) .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 }) .image_extent(vk::Extent3D { width, height, depth: 1, }); device.cmd_copy_buffer_to_image( command_buffer, buffer, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, &[region], ); end_single_time_commands(device, data, command_buffer)?; Ok(()) }