#![deny(unsafe_code)] use crate::error::*; use crate::graphics::permanent::{ PermanentGraphicsState, GraphicsStateForReinit, QueueFamilyIndices, EnableAnisotropy }; use crate::graphics::util::{ allocate_buffer, stage_in_buffer, pick_memory_type, begin_transient_commands, end_transient_commands }; use crate::shader_data::UniformBlock; use std::collections::BTreeSet; use std::io::Cursor; use std::mem::size_of; use png::Decoder; use vulkanalia::{ Device, Instance }; use vulkanalia::vk::{ self, Handle, HasBuilder, InstanceV1_0, DeviceV1_0, KhrSwapchainExtensionDeviceCommands }; use winit::window::Window; // TODO: use VK_KHR_swapchain_maintenance1 to put a fence on the presentation // operation. doing that will remove the requirement that we have more // simultaneous frames than images. pub const N_SIMULTANEOUS_FRAMES: usize = 5; // The WindowDressing collects the Vulkan graphics objects which need to be // regenerated or modified when the window changes in certain ways, such as // resizing, but are not needed during rendering. The ones which don't need to // be regenerated are collected in PermanentGraphicsState. The ones which are // needed during rendering are collected in RenderState, below. #[derive(Debug)] pub struct WindowDressing { pub swapchain: Swapchain, color_image: vk::Image, color_image_memory: vk::DeviceMemory, pub color_image_view: vk::ImageView, depth_image: vk::Image, depth_image_memory: vk::DeviceMemory, pub depth_image_view: vk::ImageView, pub depth_format: vk::Format, pub primary_command_pool: vk::CommandPool, pub transient_command_pool: vk::CommandPool, texture_image: vk::Image, texture_image_memory: vk::DeviceMemory, pub texture_image_view: vk::ImageView, pub sampler: vk::Sampler, pub uniform_buffers: Vec, pub uniform_buffer_memory: Vec, pub descriptor_pool: vk::DescriptorPool, pub concurrency: Concurrency, } // A swapchain is the generalized facility that is used to implement // double buffering, triple buffering, rendering passes that feed into each // other, and other things of that nature. It's a first-class thing but for // now, we use at most one of it. We also support running without one. #[derive(Debug)] pub struct Swapchain { pub swapchain: vk::SwapchainKHR, pub images: Vec, pub image_views: Vec, pub format: vk::Format, pub extent: vk::Extent2D, } #[derive(Debug)] pub struct Concurrency { pub image_available_semaphores: Vec, pub rendering_finished_semaphores: Vec, // Okay, the lifetime management on the fences is really subtle. There is // one fence for each frame, and frame_fences holds the authoritative // reference to it. // // There is one entry in image_fences for each image. The number of images // is not directly related to the number of frames; it will likely be // larger, but may be smaller or the same. At the start of execution, the // entries are all nulls. Each time an image is acquired from the swapchain, // the corresponding entry in image_fences is overwritten with a duplicate // of the frame fence. This happens during rendering of the frame, so the // frame fence is in the "signaled" state. It will be reset right before // submitting the queue, then signaled again when the submission completes. pub frame_fences: Vec, pub image_fences: Vec, } impl WindowDressing { pub fn new(permanent: &PermanentGraphicsState, for_reinit: &GraphicsStateForReinit, enable_anisotropy: EnableAnisotropy) -> Result { let window = &permanent.window; let instance = &permanent.instance; let surface = &permanent.surface; let device = &permanent.device; let graphics_queue = &permanent.graphics_queue; let physical_device = &for_reinit.physical_device; let sample_count = for_reinit.sample_count; let indices = &for_reinit.indices; let swapchain = init_swapchain( window, instance, surface, &physical_device, device, &indices)?; let (color_image, color_image_memory, color_image_view) = init_color(instance, &physical_device, device, &swapchain.extent, sample_count, swapchain.format)?; let (depth_image, depth_image_memory, depth_image_view, depth_format) = init_depth(instance, &physical_device, device, &swapchain.extent, sample_count)?; let (primary_command_pool, transient_command_pool) = init_command_pools(device, indices)?; let (texture_image, texture_image_memory, texture_image_view, mip_count) = init_texture(instance, physical_device, device, graphics_queue, &transient_command_pool)?; let sampler = init_sampler(&device, &enable_anisotropy, mip_count)?; let (uniform_buffers, uniform_buffer_memory) = init_uniform_buffers(instance, physical_device, device, swapchain.images.len())?; let descriptor_pool = init_descriptor_pool(device, swapchain.images.len())?; let concurrency = init_concurrency(device, &swapchain.images)?; Ok(WindowDressing { swapchain, color_image, color_image_memory, color_image_view, depth_image, depth_image_memory, depth_image_view, depth_format, texture_image, texture_image_memory, texture_image_view, sampler, uniform_buffers, uniform_buffer_memory, descriptor_pool, primary_command_pool, transient_command_pool, concurrency, }) } #[allow(unsafe_code)] pub fn reinit(&mut self, permanent: &PermanentGraphicsState, for_reinit: &GraphicsStateForReinit) -> Result<()> { let window = &permanent.window; let instance = &permanent.instance; let surface = &permanent.surface; let device = &permanent.device; let physical_device = &for_reinit.physical_device; let sample_count = for_reinit.sample_count; let indices = &for_reinit.indices; unsafe { device.device_wait_idle() }.unwrap(); self.destroy_replaceable(device); let swapchain = init_swapchain( window, instance, surface, &physical_device, device, &indices)?; let (color_image, color_image_memory, color_image_view) = init_color(instance, &physical_device, device, &swapchain.extent, sample_count, swapchain.format)?; let (depth_image, depth_image_memory, depth_image_view, depth_format) = init_depth(instance, &physical_device, device, &swapchain.extent, sample_count)?; let (uniform_buffers, uniform_buffer_memory) = init_uniform_buffers(instance, physical_device, device, swapchain.images.len())?; // Notice that we did NOT reuse the descriptor pool. let descriptor_pool = init_descriptor_pool(device, swapchain.images.len())?; self.concurrency.image_fences.resize(swapchain.images.len(), vk::Fence::null()); self.swapchain = swapchain; self.color_image = color_image; self.color_image_memory = color_image_memory; self.color_image_view = color_image_view; self.depth_image = depth_image; self.depth_image_memory = depth_image_memory; self.depth_image_view = depth_image_view; self.depth_format = depth_format; self.uniform_buffers = uniform_buffers; self.uniform_buffer_memory = uniform_buffer_memory; self.descriptor_pool = descriptor_pool; Ok(()) } // This relies on its caller to have already waited for the device to be // idle. #[allow(unsafe_code)] pub fn destroy(mut self, device: &Device) { self.destroy_replaceable(device); unsafe { device.destroy_image(self.texture_image, None) }; unsafe { device.free_memory(self.texture_image_memory, None) }; unsafe { device.destroy_image_view(self.texture_image_view, None) }; unsafe { device.destroy_sampler(self.sampler, None) }; for semaphore in self.concurrency.image_available_semaphores { unsafe { device.destroy_semaphore(semaphore, None) }; } for semaphore in self.concurrency.rendering_finished_semaphores { unsafe { device.destroy_semaphore(semaphore, None) }; } for fence in self.concurrency.frame_fences { unsafe { device.destroy_fence(fence, None) }; } // Notice that destroy_replaceable() freed the buffers in the pools, but // did not destroy the pools. unsafe { device.destroy_command_pool(self.primary_command_pool, None) }; unsafe { device.destroy_command_pool(self.transient_command_pool, None) }; } #[allow(unsafe_code)] fn destroy_replaceable(&mut self, device: &Device) { // While the descriptor pool is also a pool, it has a preallocated size // which will be different next time. So, we destroy it all the way. unsafe { device.destroy_descriptor_pool(self.descriptor_pool, None) }; // Notice that, unlike the vertex and index buffers, we destroy and // re-create these on every reinitialization. That's because the number of // them depends on how many images the swapchain has. for buffer in &self.uniform_buffers { unsafe { device.destroy_buffer(*buffer, None) }; } for memory in &self.uniform_buffer_memory { unsafe { device.free_memory(*memory, None) }; } unsafe { device.destroy_image(self.color_image, None) }; unsafe { device.free_memory(self.color_image_memory, None) }; unsafe { device.destroy_image_view(self.color_image_view, None) }; unsafe { device.destroy_image(self.depth_image, None) }; unsafe { device.free_memory(self.depth_image_memory, None) }; unsafe { device.destroy_image_view(self.depth_image_view, None) }; for view in &self.swapchain.image_views { unsafe { device.destroy_image_view(*view, None) }; } unsafe { device.destroy_swapchain_khr(self.swapchain.swapchain, None) }; } } #[allow(unsafe_code)] fn init_swapchain(window: &Window, instance: &Instance, surface: &vk::SurfaceKHR, physical_device: &vk::PhysicalDevice, device: &Device, indices: &QueueFamilyIndices) -> Result { let (capabilities, formats, presentation_modes) = PermanentGraphicsState::find_device_swapchain_features( instance, surface, physical_device)?.require()?; let format = pick_surface_format(&formats)?; let presentation_mode = pick_presentation_mode(&presentation_modes)?; let extent = pick_image_extent(window, capabilities)?; let mut image_count = capabilities.min_image_count + 1; if capabilities.max_image_count != 0 { image_count = image_count.clamp(0, capabilities.max_image_count); } let mut unique_queue_family_indices = BTreeSet::new(); unique_queue_family_indices.insert(indices.graphics); unique_queue_family_indices.insert(indices.presentation); // If there's only one queue, we use exclusive sharing mode, which // will allow things to work without locks. Otherwise we use concurrent // mode. let (ordered_indices, sharing_mode) = if unique_queue_family_indices.len() < 2 { (vec![indices.graphics], vk::SharingMode::EXCLUSIVE) } else { (vec![indices.graphics, indices.presentation], vk::SharingMode::CONCURRENT) }; let swapchain_info = vk::SwapchainCreateInfoKHR::builder() .surface(*surface) .min_image_count(image_count) .image_format(format.format) .image_color_space(format.color_space) .image_extent(extent) .image_array_layers(1) .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) .image_sharing_mode(sharing_mode) .queue_family_indices(&ordered_indices) .pre_transform(capabilities.current_transform) .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) .present_mode(presentation_mode) .clipped(true) .old_swapchain(vk::SwapchainKHR::null()); let swapchain = unsafe { device.create_swapchain_khr(&swapchain_info, None) }?; let images = unsafe { device.get_swapchain_images_khr(swapchain) }?; let mut image_views = Vec::new(); for image in &images { let view = init_image_view(device, image, 1, format.format, vk::ImageAspectFlags::COLOR)?; image_views.push(view); } Ok(Swapchain { swapchain, images, image_views, format: format.format, extent }) } #[allow(unsafe_code)] fn init_color(instance: &Instance, physical_device: &vk::PhysicalDevice, device: &Device, extent: &vk::Extent2D, sample_count: vk::SampleCountFlags, format: vk::Format) -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView)> { let (image, image_memory) = allocate_image(instance, physical_device, device, extent.width, extent.height, 1, sample_count, format, vk::ImageTiling::OPTIMAL, vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSIENT_ATTACHMENT, vk::MemoryPropertyFlags::DEVICE_LOCAL)?; let image_view = init_image_view(device, &image, 1, format, vk::ImageAspectFlags::COLOR)?; Ok((image, image_memory, image_view)) } #[allow(unsafe_code)] fn init_depth(instance: &Instance, physical_device: &vk::PhysicalDevice, device: &Device, extent: &vk::Extent2D, sample_count: vk::SampleCountFlags) -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, vk::Format)> { let format = pick_depth_format(instance, physical_device)?; let (image, image_memory) = allocate_image(instance, physical_device, device, extent.width, extent.height, 1, sample_count, format, vk::ImageTiling::OPTIMAL, vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT, vk::MemoryPropertyFlags::DEVICE_LOCAL)?; let image_view = init_image_view(device, &image, 1, format, vk::ImageAspectFlags::DEPTH)?; Ok((image, image_memory, image_view, format)) } #[allow(unsafe_code)] fn init_texture(instance: &Instance, physical_device: &vk::PhysicalDevice, device: &Device, queue: &vk::Queue, command_pool: &vk::CommandPool) -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)> { 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, physical_device, device, &pixels)?; let (image, image_memory) = allocate_image(instance, physical_device, 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)) } fn init_uniform_buffers(instance: &Instance, physical_device: &vk::PhysicalDevice, device: &Device, count: usize) -> Result<(Vec, Vec)> { let mut buffers = Vec::new(); let mut all_memory = Vec::new(); for _ in 0 .. count { let (buffer, memory) = allocate_buffer( instance, physical_device, device, size_of::>() as vk::DeviceSize, vk::BufferUsageFlags::UNIFORM_BUFFER, vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE)?; buffers.push(buffer); all_memory.push(memory); } Ok((buffers, all_memory)) } #[allow(unsafe_code)] fn init_sampler(device: &Device, enable_anisotropy: &EnableAnisotropy, mip_count: u32) -> Result { let mut sampler_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) .border_color(vk::BorderColor::INT_OPAQUE_BLACK) .unnormalized_coordinates(false) .compare_enable(false) .compare_op(vk::CompareOp::ALWAYS) .mipmap_mode(vk::SamplerMipmapMode::LINEAR) .mip_lod_bias(0.0) .min_lod(0.0) .max_lod(mip_count as f32); sampler_info = if enable_anisotropy.0 { sampler_info.anisotropy_enable(true) .max_anisotropy(16.0) } else { sampler_info.anisotropy_enable(false) .max_anisotropy(1.0) }; let sampler = unsafe { device.create_sampler(&sampler_info, None) }?; Ok(sampler) } #[allow(unsafe_code)] fn init_descriptor_pool(device: &Device, count: usize) -> Result { let uniform_block_size = vk::DescriptorPoolSize::builder() .type_(vk::DescriptorType::UNIFORM_BUFFER) .descriptor_count(count as u32); let sampler_size = vk::DescriptorPoolSize::builder() .type_(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .descriptor_count(count as u32); let sizes = [uniform_block_size, sampler_size]; let pool_info = vk::DescriptorPoolCreateInfo::builder() .pool_sizes(&sizes) .max_sets(count as u32); let pool = unsafe { device.create_descriptor_pool(&pool_info, None) }?; Ok(pool) } #[allow(unsafe_code)] fn init_command_pools(device: &Device, indices: &QueueFamilyIndices) -> Result<(vk::CommandPool, vk::CommandPool)> { let command_pool_info = vk::CommandPoolCreateInfo::builder() .flags(vk::CommandPoolCreateFlags::TRANSIENT | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) .queue_family_index(indices.graphics); let primary = unsafe { device.create_command_pool(&command_pool_info, None) }?; command_pool_info.flags(vk::CommandPoolCreateFlags::TRANSIENT); let transient = unsafe { device.create_command_pool(&command_pool_info, None) }?; Ok((primary, transient)) } #[allow(unsafe_code)] fn init_concurrency(device: &Device, swapchain_images: &Vec) -> Result { let semaphore_info = vk::SemaphoreCreateInfo::builder(); let fence_info = vk::FenceCreateInfo::builder() .flags(vk::FenceCreateFlags::SIGNALED); let mut image_available_semaphores = Vec::new(); let mut rendering_finished_semaphores = Vec::new(); let mut frame_fences = Vec::new(); for _ in 0 .. N_SIMULTANEOUS_FRAMES { image_available_semaphores.push(unsafe { device.create_semaphore(&semaphore_info, None) }?); rendering_finished_semaphores.push(unsafe { device.create_semaphore(&semaphore_info, None) }?); frame_fences.push(unsafe { device.create_fence(&fence_info, None) }?); } let mut image_fences = Vec::new(); for _ in 0 .. swapchain_images.len() { image_fences.push(vk::Fence::null()); } Ok(Concurrency { image_available_semaphores, rendering_finished_semaphores, frame_fences, image_fences: image_fences, }) } #[allow(unsafe_code)] fn init_image_view(device: &Device, image: &vk::Image, mip_count: u32, format: vk::Format, aspects: vk::ImageAspectFlags) -> Result { // Component mapping is only for color components (not, for example, depth // or stencil components), so we always just want it like this. let components = vk::ComponentMapping::builder() .r(vk::ComponentSwizzle::IDENTITY) .g(vk::ComponentSwizzle::IDENTITY) .b(vk::ComponentSwizzle::IDENTITY) .a(vk::ComponentSwizzle::IDENTITY); let subresource_range = vk::ImageSubresourceRange::builder() .aspect_mask(aspects) .base_mip_level(0) .level_count(mip_count) .base_array_layer(0) .layer_count(1); let view_info = vk::ImageViewCreateInfo::builder() .image(*image) .view_type(vk::ImageViewType::_2D) .format(format) .components(components) .subresource_range(subresource_range); let view = unsafe { device.create_image_view(&view_info, None) }?; Ok(view) } fn pick_surface_format(available_formats: &Vec) -> Result { for format in available_formats { if format.format == vk::Format::B8G8R8A8_SRGB && format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR { return Ok(format.clone()); } } return Ok(available_formats[0].clone()); } #[allow(unsafe_code)] fn pick_depth_format(instance: &Instance, physical_device: &vk::PhysicalDevice) -> Result { let required_features = vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT; for format in [vk::Format::D32_SFLOAT, vk::Format::D32_SFLOAT_S8_UINT, vk::Format::D24_UNORM_S8_UINT] { let properties = unsafe { instance.get_physical_device_format_properties( *physical_device, format) }; if properties.optimal_tiling_features.contains(required_features) { return Ok(format); } } Err(Error { message: "There is no supported depth-buffer sample format.".to_string() }) } fn pick_presentation_mode(_available_modes: &Vec) -> Result { // It's guaranteed to have this one. return Ok(vk::PresentModeKHR::FIFO); } fn pick_image_extent(window: &Window, capabilities: vk::SurfaceCapabilitiesKHR) -> Result { if capabilities.current_extent.width != u32::MAX && capabilities.current_extent.height != u32::MAX { Ok(capabilities.current_extent) } else { let window_size = window.inner_size(); let width = window_size.width .clamp(capabilities.min_image_extent.width, capabilities.max_image_extent.width); let height = window_size.height .clamp(capabilities.min_image_extent.height, capabilities.max_image_extent.height); Ok(vk::Extent2D::builder().width(width).height(height).build()) } } #[allow(unsafe_code)] fn allocate_image(instance: &Instance, physical_device: &vk::PhysicalDevice, device: &Device, width: u32, height: u32, mip_count: u32, sample_count: vk::SampleCountFlags, format: vk::Format, tiling: vk::ImageTiling, usage: vk::ImageUsageFlags, memory_flags: vk::MemoryPropertyFlags) -> Result<(vk::Image, vk::DeviceMemory)> { let image_info = vk::ImageCreateInfo::builder() .image_type(vk::ImageType::_2D) .extent(vk::Extent3D { width, height, depth: 1 }) .mip_levels(mip_count) .samples(sample_count) .array_layers(1) .format(format) .tiling(tiling) .initial_layout(vk::ImageLayout::UNDEFINED) .usage(usage) .sharing_mode(vk::SharingMode::EXCLUSIVE) .flags(vk::ImageCreateFlags::empty()); let image = unsafe { device.create_image(&image_info, None) }?; let requirements = unsafe { device.get_image_memory_requirements(image) }; let type_index = pick_memory_type(instance, physical_device, &memory_flags, &requirements)?; let image_memory_info = vk::MemoryAllocateInfo::builder() .allocation_size(requirements.size) .memory_type_index(type_index); let image_memory = unsafe { device.allocate_memory(&image_memory_info, None) }?; unsafe { device.bind_image_memory(image, image_memory, 0) }?; Ok((image, image_memory)) } #[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(()) } #[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(()) } // 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(()) }