use anyhow::{anyhow, Result}; use vulkanalia::prelude::v1_0::*; use std::ptr::copy_nonoverlapping as memcpy; use std::fs::File; pub type Mat4 = cgmath::Matrix4<f32>; use crate::app_data; use crate::buffer; use crate::command_buffer; pub unsafe fn create_texture_image( instance: &Instance, device: &Device, data: &mut app_data::AppData, ) -> Result<()> { let image = File::open("resources/viking_room.png")?; let decoder = png::Decoder::new(image); let mut reader = decoder.read_info()?; let mut pixels = vec![0; reader.info().raw_bytes()]; reader.next_frame(&mut pixels)?; let size = reader.info().raw_bytes() as u64; let (width, height) = reader.info().size(); data.mip_levels = (width.max(height) as f32).log2().floor() as u32 + 1; if width != 1024 || height != 1024 || reader.info().color_type != png::ColorType::Rgba { panic!("Invalid texture image (use https://kylemayes.github.io/vulkanalia/images/viking_room.png)."); } let (staging_buffer, staging_buffer_memory) = buffer::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(), )?; memcpy(pixels.as_ptr(), memory.cast(), pixels.len()); device.unmap_memory(staging_buffer_memory); let (texture_image, texture_image_memory) = create_image( instance, device, data, width, height, data.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, )?; data.texture_image = texture_image; data.texture_image_memory = texture_image_memory; transition_image_layout( device, data, data.texture_image, vk::Format::R8G8B8A8_SRGB, vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL, data.mip_levels )?; copy_buffer_to_image( device, data, staging_buffer, data.texture_image, width, height, )?; generate_mipmaps( instance, device, data, data.texture_image, vk::Format::R8G8B8A8_SRGB, width, height, data.mip_levels, )?; device.destroy_buffer(staging_buffer, None); device.free_memory(staging_buffer_memory, None); Ok(()) } pub unsafe fn create_image( instance: &Instance, device: &Device, data: &app_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(buffer::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 unsafe fn transition_image_layout( device: &Device, data: &app_data::AppData, image: vk::Image, format: vk::Format, old_layout: vk::ImageLayout, new_layout: vk::ImageLayout, mip_levels: u32, ) -> Result<()> { let command_buffer = command_buffer::begin_single_time_commands(device, data)?; 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 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 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], ); command_buffer::end_single_time_commands(device, data, command_buffer)?; Ok(()) } unsafe fn copy_buffer_to_image( device: &Device, data: &app_data::AppData, buffer: vk::Buffer, image: vk::Image, width: u32, height: u32, ) -> Result<()> { let command_buffer = 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], ); command_buffer::end_single_time_commands(device, data, command_buffer)?; Ok(()) } pub unsafe fn create_image_view( device: &Device, image: vk::Image, format: vk::Format, aspects: vk::ImageAspectFlags, mip_levels: u32, ) -> Result<vk::ImageView> { let subresource_range = vk::ImageSubresourceRange::builder() .aspect_mask(aspects) .base_mip_level(0) .level_count(mip_levels) .base_array_layer(0) .layer_count(1); let info = vk::ImageViewCreateInfo::builder() .image(image) .view_type(vk::ImageViewType::_2D) .format(format) .subresource_range(subresource_range); Ok(device.create_image_view(&info, None)?) } pub unsafe fn create_texture_image_view(device: &Device, data: &mut app_data::AppData) -> Result<()> { data.texture_image_view = create_image_view( device, data.texture_image, vk::Format::R8G8B8A8_SRGB, vk::ImageAspectFlags::COLOR, data.mip_levels )?; Ok(()) } pub unsafe fn create_texture_sampler(device: &Device, data: &mut app_data::AppData) -> Result<()> { let info = vk::SamplerCreateInfo::builder() .mag_filter(vk::Filter::LINEAR) .min_filter(vk::Filter::LINEAR) .address_mode_u(vk::SamplerAddressMode::REPEAT) .address_mode_v(vk::SamplerAddressMode::REPEAT) .address_mode_w(vk::SamplerAddressMode::REPEAT) .anisotropy_enable(true) .max_anisotropy(16.0) .border_color(vk::BorderColor::INT_OPAQUE_BLACK) .unnormalized_coordinates(false) .compare_enable(false) .compare_op(vk::CompareOp::ALWAYS) .mipmap_mode(vk::SamplerMipmapMode::LINEAR) .min_lod(0.0) .max_lod(data.mip_levels as f32) .mip_lod_bias(0.0); data.texture_sampler = device.create_sampler(&info, None)?; Ok(()) } unsafe fn generate_mipmaps( instance: &Instance, device: &Device, data: &app_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 = 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); 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], ); command_buffer::end_single_time_commands(device, data, command_buffer)?; Ok(()) }