diff options
Diffstat (limited to 'src/command_buffer/standard/builder/copy.rs')
-rw-r--r-- | src/command_buffer/standard/builder/copy.rs | 3729 |
1 files changed, 3729 insertions, 0 deletions
diff --git a/src/command_buffer/standard/builder/copy.rs b/src/command_buffer/standard/builder/copy.rs new file mode 100644 index 0000000..4d80e2a --- /dev/null +++ b/src/command_buffer/standard/builder/copy.rs @@ -0,0 +1,3729 @@ +// Copyright (c) 2022 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use super::{ + BlitImageInfo, BufferCopy, BufferImageCopy, CommandBufferBuilder, CopyBufferInfo, + CopyBufferToImageInfo, CopyError, CopyErrorResource, CopyImageInfo, CopyImageToBufferInfo, + ImageCopy, ResolveImageInfo, +}; +use crate::{ + buffer::BufferUsage, + command_buffer::{ + allocator::CommandBufferAllocator, ImageBlit, ImageResolve, ResourceInCommand, + ResourceUseRef, + }, + device::{DeviceOwned, QueueFlags}, + format::{Format, FormatFeatures, NumericType}, + image::{ + ImageAccess, ImageAspects, ImageDimensions, ImageLayout, ImageSubresourceLayers, + ImageSubresourceRange, ImageType, ImageUsage, SampleCount, SampleCounts, + }, + sampler::Filter, + sync::PipelineStageAccess, + DeviceSize, Version, VulkanObject, +}; +use smallvec::SmallVec; +use std::cmp::{max, min}; + +impl<L, A> CommandBufferBuilder<L, A> +where + A: CommandBufferAllocator, +{ + /// Copies data from a buffer to another buffer. + /// + /// # Panics + /// + /// - Panics if `src_buffer` or `dst_buffer` were not created from the same device + /// as `self`. + /// + /// # Safety + /// + /// - Appropriate synchronization must be provided for all buffers + /// that are accessed by the command. + #[inline] + pub unsafe fn copy_buffer( + &mut self, + copy_buffer_info: impl Into<CopyBufferInfo>, + ) -> Result<&mut Self, CopyError> { + let copy_buffer_info = copy_buffer_info.into(); + self.validate_copy_buffer(©_buffer_info)?; + + unsafe { Ok(self.copy_buffer_unchecked(copy_buffer_info)) } + } + + fn validate_copy_buffer(&self, copy_buffer_info: &CopyBufferInfo) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyBuffer2-renderpass + if self.builder_state.render_pass.is_some() { + return Err(CopyError::ForbiddenInsideRenderPass); + } + + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdCopyBuffer2-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::TRANSFER | QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + { + return Err(CopyError::NotSupportedByQueueFamily); + } + + let &CopyBufferInfo { + ref src_buffer, + ref dst_buffer, + ref regions, + _ne: _, + } = copy_buffer_info; + + // VUID-VkCopyBufferInfo2-commonparent + assert_eq!(device, src_buffer.device()); + assert_eq!(device, dst_buffer.device()); + + // VUID-VkCopyBufferInfo2-srcBuffer-00118 + if !src_buffer + .buffer() + .usage() + .intersects(BufferUsage::TRANSFER_SRC) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Source, + usage: "transfer_src", + }); + } + + // VUID-VkCopyBufferInfo2-dstBuffer-00120 + if !dst_buffer + .buffer() + .usage() + .intersects(BufferUsage::TRANSFER_DST) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Destination, + usage: "transfer_dst", + }); + } + + let same_buffer = src_buffer.buffer() == dst_buffer.buffer(); + let mut overlap_indices = None; + + for (region_index, region) in regions.iter().enumerate() { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne: _, + } = region; + + // VUID-VkBufferCopy2-size-01988 + assert!(size != 0); + + // VUID-VkCopyBufferInfo2-srcOffset-00113 + // VUID-VkCopyBufferInfo2-size-00115 + if src_offset + size > src_buffer.size() { + return Err(CopyError::RegionOutOfBufferBounds { + resource: CopyErrorResource::Source, + region_index, + offset_range_end: src_offset + size, + buffer_size: src_buffer.size(), + }); + } + + // VUID-VkCopyBufferInfo2-dstOffset-00114 + // VUID-VkCopyBufferInfo2-size-00116 + if dst_offset + size > dst_buffer.size() { + return Err(CopyError::RegionOutOfBufferBounds { + resource: CopyErrorResource::Destination, + region_index, + offset_range_end: dst_offset + size, + buffer_size: dst_buffer.size(), + }); + } + + // VUID-VkCopyBufferInfo2-pRegions-00117 + if same_buffer { + let src_region_index = region_index; + let src_range = + src_buffer.offset() + src_offset..src_buffer.offset() + src_offset + size; + + for (dst_region_index, dst_region) in regions.iter().enumerate() { + let &BufferCopy { dst_offset, .. } = dst_region; + + let dst_range = + dst_buffer.offset() + dst_offset..dst_buffer.offset() + dst_offset + size; + + if src_range.start >= dst_range.end || dst_range.start >= src_range.end { + // The regions do not overlap + continue; + } + + overlap_indices = Some((src_region_index, dst_region_index)); + } + } + } + + // VUID-VkCopyBufferInfo2-pRegions-00117 + if let Some((src_region_index, dst_region_index)) = overlap_indices { + return Err(CopyError::OverlappingRegions { + src_region_index, + dst_region_index, + }); + } + + // TODO: sync check + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn copy_buffer_unchecked( + &mut self, + copy_buffer_info: impl Into<CopyBufferInfo>, + ) -> &mut Self { + let copy_buffer_info = copy_buffer_info.into(); + let CopyBufferInfo { + src_buffer, + dst_buffer, + regions, + _ne: _, + } = copy_buffer_info; + + if regions.is_empty() { + return self; + } + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne, + } = region; + + ash::vk::BufferCopy2 { + src_offset: src_offset + src_buffer.offset(), + dst_offset: dst_offset + dst_buffer.offset(), + size, + ..Default::default() + } + }) + .collect(); + + let copy_buffer_info = ash::vk::CopyBufferInfo2 { + src_buffer: src_buffer.buffer().handle(), + dst_buffer: dst_buffer.buffer().handle(), + region_count: regions.len() as u32, + p_regions: regions.as_ptr(), + ..Default::default() + }; + + if self.device().api_version() >= Version::V1_3 { + (fns.v1_3.cmd_copy_buffer2)(self.handle(), ©_buffer_info); + } else { + (fns.khr_copy_commands2.cmd_copy_buffer2_khr)(self.handle(), ©_buffer_info); + } + } else { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne, + } = region; + + ash::vk::BufferCopy { + src_offset: src_offset + src_buffer.offset(), + dst_offset: dst_offset + dst_buffer.offset(), + size, + } + }) + .collect(); + + (fns.v1_0.cmd_copy_buffer)( + self.handle(), + src_buffer.buffer().handle(), + dst_buffer.buffer().handle(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + let command_index = self.next_command_index; + let command_name = "copy_buffer"; + let src_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }; + let dst_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }; + + for region in regions { + let BufferCopy { + src_offset, + dst_offset, + size, + _ne: _, + } = region; + + let mut src_range = src_offset..src_offset + size; + src_range.start += src_buffer.offset(); + src_range.end += src_buffer.offset(); + self.resources_usage_state.record_buffer_access( + &src_use_ref, + src_buffer.buffer(), + src_range, + PipelineStageAccess::Copy_TransferRead, + ); + + let mut dst_range = dst_offset..dst_offset + size; + dst_range.start += dst_buffer.offset(); + dst_range.end += dst_buffer.offset(); + self.resources_usage_state.record_buffer_access( + &dst_use_ref, + dst_buffer.buffer(), + dst_range, + PipelineStageAccess::Copy_TransferWrite, + ); + } + + self.resources.push(Box::new(src_buffer)); + self.resources.push(Box::new(dst_buffer)); + + self.next_command_index += 1; + self + } + + /// Copies data from an image to another image. + /// + /// There are several restrictions: + /// + /// - The number of samples in the source and destination images must be equal. + /// - The size of the uncompressed element format of the source image must be equal to the + /// compressed element format of the destination. + /// - If you copy between depth, stencil or depth-stencil images, the format of both images + /// must match exactly. + /// - For two-dimensional images, the Z coordinate must be 0 for the image offsets and 1 for + /// the extent. Same for the Y coordinate for one-dimensional images. + /// - For non-array images, the base array layer must be 0 and the number of layers must be 1. + /// + /// If `layer_count` is greater than 1, the copy will happen between each individual layer as + /// if they were separate images. + /// + /// # Panics + /// + /// - Panics if `src_image` or `dst_image` were not created from the same device + /// as `self`. + /// + /// # Safety + /// + /// - Appropriate synchronization must be provided for all images + /// that are accessed by the command. + /// - All images that are accessed by the command must be in the expected image layout. + #[inline] + pub unsafe fn copy_image( + &mut self, + copy_image_info: CopyImageInfo, + ) -> Result<&mut Self, CopyError> { + self.validate_copy_image(©_image_info)?; + + unsafe { Ok(self.copy_image_unchecked(copy_image_info)) } + } + + fn validate_copy_image(&self, copy_image_info: &CopyImageInfo) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyImage2-renderpass + if self.builder_state.render_pass.is_some() { + return Err(CopyError::ForbiddenInsideRenderPass); + } + + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdCopyImage2-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::TRANSFER | QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + { + return Err(CopyError::NotSupportedByQueueFamily); + } + + let &CopyImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = copy_image_info; + + // VUID-VkCopyImageInfo2-srcImageLayout-parameter + src_image_layout.validate_device(device)?; + + // VUID-VkCopyImageInfo2-dstImageLayout-parameter + dst_image_layout.validate_device(device)?; + + // VUID-VkCopyImageInfo2-commonparent + assert_eq!(device, src_image.device()); + assert_eq!(device, dst_image.device()); + + let copy_2d_3d_supported = + device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_maintenance1; + let src_image_inner = src_image.inner(); + let dst_image_inner = dst_image.inner(); + let mut src_image_aspects = src_image.format().aspects(); + let mut dst_image_aspects = dst_image.format().aspects(); + + if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_maintenance1 { + // VUID-VkCopyImageInfo2-srcImage-01995 + if !src_image + .format_features() + .intersects(FormatFeatures::TRANSFER_SRC) + { + return Err(CopyError::MissingFormatFeature { + resource: CopyErrorResource::Source, + format_feature: "transfer_src", + }); + } + + // VUID-VkCopyImageInfo2-dstImage-01996 + if !dst_image + .format_features() + .intersects(FormatFeatures::TRANSFER_DST) + { + return Err(CopyError::MissingFormatFeature { + resource: CopyErrorResource::Destination, + format_feature: "transfer_dst", + }); + } + } + + // VUID-VkCopyImageInfo2-srcImage-00136 + if src_image.samples() != dst_image.samples() { + return Err(CopyError::SampleCountMismatch { + src_sample_count: src_image.samples(), + dst_sample_count: dst_image.samples(), + }); + } + + if !(src_image_aspects.intersects(ImageAspects::COLOR) + || dst_image_aspects.intersects(ImageAspects::COLOR)) + { + // VUID-VkCopyImageInfo2-srcImage-01548 + if src_image.format() != dst_image.format() { + return Err(CopyError::FormatsMismatch { + src_format: src_image.format(), + dst_format: dst_image.format(), + }); + } + } + + // VUID-VkCopyImageInfo2-srcImageLayout-01917 + if !matches!( + src_image_layout, + ImageLayout::TransferSrcOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Source, + image_layout: src_image_layout, + }); + } + + // VUID-VkCopyImageInfo2-dstImageLayout-01395 + if !matches!( + dst_image_layout, + ImageLayout::TransferDstOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Destination, + image_layout: dst_image_layout, + }); + } + + let extent_alignment = match queue_family_properties.min_image_transfer_granularity { + [0, 0, 0] => None, + min_image_transfer_granularity => { + let granularity = move |block_extent: [u32; 3], is_multi_plane: bool| { + if is_multi_plane { + // Assume planes always have 1x1 blocks + min_image_transfer_granularity + } else { + // "The value returned in minImageTransferGranularity has a unit of + // compressed texel blocks for images having a block-compressed format, and + // a unit of texels otherwise." + [ + min_image_transfer_granularity[0] * block_extent[0], + min_image_transfer_granularity[1] * block_extent[1], + min_image_transfer_granularity[2] * block_extent[2], + ] + } + }; + + Some(( + granularity( + src_image.format().block_extent(), + src_image_aspects.intersects(ImageAspects::PLANE_0), + ), + granularity( + dst_image.format().block_extent(), + dst_image_aspects.intersects(ImageAspects::PLANE_0), + ), + )) + } + }; + + if src_image_aspects.intersects(ImageAspects::PLANE_0) { + // VUID-VkCopyImageInfo2-srcImage-01552 + // VUID-VkCopyImageInfo2-srcImage-01553 + src_image_aspects -= ImageAspects::COLOR; + } + + if dst_image_aspects.intersects(ImageAspects::PLANE_0) { + // VUID-VkCopyImageInfo2-dstImage-01554 + // VUID-VkCopyImageInfo2-dstImage-01555 + dst_image_aspects -= ImageAspects::COLOR; + } + + let mut src_image_aspects_used = ImageAspects::empty(); + let mut dst_image_aspects_used = ImageAspects::empty(); + let same_image = src_image_inner.image == dst_image_inner.image; + let mut overlap_subresource_indices = None; + let mut overlap_extent_indices = None; + + for (region_index, region) in regions.iter().enumerate() { + let &ImageCopy { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne, + } = region; + + let check_subresource = |resource: CopyErrorResource, + image: &dyn ImageAccess, + image_aspects: ImageAspects, + subresource: &ImageSubresourceLayers| + -> Result<_, CopyError> { + // VUID-VkCopyImageInfo2-srcSubresource-01696 + // VUID-VkCopyImageInfo2-dstSubresource-01697 + if subresource.mip_level >= image.mip_levels() { + return Err(CopyError::MipLevelsOutOfRange { + resource, + region_index, + mip_levels_range_end: subresource.mip_level + 1, + image_mip_levels: image.mip_levels(), + }); + } + + // VUID-VkImageSubresourceLayers-layerCount-01700 + assert!(!subresource.array_layers.is_empty()); + + // VUID-VkCopyImageInfo2-srcSubresource-01698 + // VUID-VkCopyImageInfo2-dstSubresource-01699 + // VUID-VkCopyImageInfo2-srcImage-04443 + // VUID-VkCopyImageInfo2-dstImage-04444 + if subresource.array_layers.end > image.dimensions().array_layers() { + return Err(CopyError::ArrayLayersOutOfRange { + resource, + region_index, + array_layers_range_end: subresource.array_layers.end, + image_array_layers: image.dimensions().array_layers(), + }); + } + + // VUID-VkImageSubresourceLayers-aspectMask-parameter + subresource.aspects.validate_device(device)?; + + // VUID-VkImageSubresourceLayers-aspectMask-requiredbitmask + assert!(!subresource.aspects.is_empty()); + + // VUID-VkCopyImageInfo2-aspectMask-00142 + // VUID-VkCopyImageInfo2-aspectMask-00143 + if !image_aspects.contains(subresource.aspects) { + return Err(CopyError::AspectsNotAllowed { + resource, + region_index, + aspects: subresource.aspects, + allowed_aspects: image_aspects, + }); + } + + let (subresource_format, subresource_extent) = + if image_aspects.intersects(ImageAspects::PLANE_0) { + // VUID-VkCopyImageInfo2-srcImage-01552 + // VUID-VkCopyImageInfo2-srcImage-01553 + // VUID-VkCopyImageInfo2-dstImage-01554 + // VUID-VkCopyImageInfo2-dstImage-01555 + if subresource.aspects.count() != 1 { + return Err(CopyError::MultipleAspectsNotAllowed { + resource, + region_index, + aspects: subresource.aspects, + }); + } + + if subresource.aspects.intersects(ImageAspects::PLANE_0) { + ( + image.format().planes()[0], + image.dimensions().width_height_depth(), + ) + } else if subresource.aspects.intersects(ImageAspects::PLANE_1) { + ( + image.format().planes()[1], + image + .format() + .ycbcr_chroma_sampling() + .unwrap() + .subsampled_extent(image.dimensions().width_height_depth()), + ) + } else { + ( + image.format().planes()[2], + image + .format() + .ycbcr_chroma_sampling() + .unwrap() + .subsampled_extent(image.dimensions().width_height_depth()), + ) + } + } else { + ( + image.format(), + image + .dimensions() + .mip_level_dimensions(subresource.mip_level) + .unwrap() + .width_height_depth(), + ) + }; + + Ok((subresource_format, subresource_extent)) + }; + + src_image_aspects_used |= src_subresource.aspects; + dst_image_aspects_used |= dst_subresource.aspects; + + let (src_subresource_format, src_subresource_extent) = check_subresource( + CopyErrorResource::Source, + src_image, + src_image_aspects, + src_subresource, + )?; + let (dst_subresource_format, dst_subresource_extent) = check_subresource( + CopyErrorResource::Destination, + dst_image, + dst_image_aspects, + dst_subresource, + )?; + + if !(src_image_aspects.intersects(ImageAspects::PLANE_0) + || dst_image_aspects.intersects(ImageAspects::PLANE_0)) + { + // VUID-VkCopyImageInfo2-srcImage-01551 + if src_subresource.aspects != dst_subresource.aspects { + return Err(CopyError::AspectsMismatch { + region_index, + src_aspects: src_subresource.aspects, + dst_aspects: dst_subresource.aspects, + }); + } + } + + // VUID-VkCopyImageInfo2-srcImage-01548 + // VUID-VkCopyImageInfo2-None-01549 + // Color formats must be size-compatible. + if src_subresource_format.block_size() != dst_subresource_format.block_size() { + return Err(CopyError::FormatsNotCompatible { + src_format: src_subresource_format, + dst_format: dst_subresource_format, + }); + } + + // TODO: + // "When copying between compressed and uncompressed formats the extent members + // represent the texel dimensions of the source image and not the destination." + let mut src_extent = extent; + let mut dst_extent = extent; + let src_layer_count = + src_subresource.array_layers.end - src_subresource.array_layers.start; + let dst_layer_count = + dst_subresource.array_layers.end - dst_subresource.array_layers.start; + + if copy_2d_3d_supported { + match ( + src_image.dimensions().image_type(), + dst_image.dimensions().image_type(), + ) { + (ImageType::Dim2d, ImageType::Dim3d) => { + src_extent[2] = 1; + + // VUID-vkCmdCopyImage-srcImage-01791 + if dst_extent[2] != src_layer_count { + return Err(CopyError::ArrayLayerCountMismatch { + region_index, + src_layer_count, + dst_layer_count: dst_extent[2], + }); + } + } + (ImageType::Dim3d, ImageType::Dim2d) => { + dst_extent[2] = 1; + + // VUID-vkCmdCopyImage-dstImage-01792 + if src_extent[2] != dst_layer_count { + return Err(CopyError::ArrayLayerCountMismatch { + region_index, + src_layer_count: src_extent[2], + dst_layer_count, + }); + } + } + _ => { + // VUID-VkImageCopy2-extent-00140 + if src_layer_count != dst_layer_count { + return Err(CopyError::ArrayLayerCountMismatch { + region_index, + src_layer_count, + dst_layer_count, + }); + } + } + } + } else { + // VUID-VkImageCopy2-extent-00140 + if src_layer_count != dst_layer_count { + return Err(CopyError::ArrayLayerCountMismatch { + region_index, + src_layer_count, + dst_layer_count, + }); + } + }; + + if let Some((src_extent_alignment, dst_extent_alignment)) = extent_alignment { + let check_offset_extent = |resource: CopyErrorResource, + extent_alignment: [u32; 3], + subresource_extent: [u32; 3], + offset: [u32; 3], + extent: [u32; 3]| + -> Result<_, CopyError> { + for i in 0..3 { + // VUID-VkImageCopy2-extent-06668 + // VUID-VkImageCopy2-extent-06669 + // VUID-VkImageCopy2-extent-06670 + assert!(extent[i] != 0); + + // VUID-VkCopyImageInfo2-srcOffset-00144 + // VUID-VkCopyImageInfo2-srcOffset-00145 + // VUID-VkCopyImageInfo2-srcOffset-00147 + // VUID-VkCopyImageInfo2-dstOffset-00150 + // VUID-VkCopyImageInfo2-dstOffset-00151 + // VUID-VkCopyImageInfo2-dstOffset-00153 + if offset[i] + extent[i] > subresource_extent[i] { + return Err(CopyError::RegionOutOfImageBounds { + resource, + region_index, + offset_range_end: [ + offset[0] + extent[0], + offset[1] + extent[1], + offset[2] + extent[2], + ], + subresource_extent, + }); + } + + // VUID-VkCopyImageInfo2-srcImage-01727 + // VUID-VkCopyImageInfo2-dstImage-01731 + // VUID-VkCopyImageInfo2-srcOffset-01783 + // VUID-VkCopyImageInfo2-dstOffset-01784 + if offset[i] % extent_alignment[i] != 0 { + return Err(CopyError::OffsetNotAlignedForImage { + resource, + region_index, + offset, + required_alignment: extent_alignment, + }); + } + + // VUID-VkCopyImageInfo2-srcImage-01728 + // VUID-VkCopyImageInfo2-srcImage-01729 + // VUID-VkCopyImageInfo2-srcImage-01730 + // VUID-VkCopyImageInfo2-dstImage-01732 + // VUID-VkCopyImageInfo2-dstImage-01733 + // VUID-VkCopyImageInfo2-dstImage-01734 + if offset[i] + extent[i] != subresource_extent[i] + && extent[i] % extent_alignment[i] != 0 + { + return Err(CopyError::ExtentNotAlignedForImage { + resource, + region_index, + extent, + required_alignment: extent_alignment, + }); + } + } + + Ok(()) + }; + + check_offset_extent( + CopyErrorResource::Source, + src_extent_alignment, + src_subresource_extent, + src_offset, + src_extent, + )?; + check_offset_extent( + CopyErrorResource::Destination, + dst_extent_alignment, + dst_subresource_extent, + dst_offset, + dst_extent, + )?; + + // VUID-VkCopyImageInfo2-pRegions-00124 + if same_image { + let src_region_index = region_index; + let src_subresource_axes = [ + src_image_inner.first_mipmap_level + src_subresource.mip_level + ..src_image_inner.first_mipmap_level + src_subresource.mip_level + 1, + src_image_inner.first_layer + src_subresource.array_layers.start + ..src_image_inner.first_layer + src_subresource.array_layers.end, + ]; + let src_extent_axes = [ + src_offset[0]..src_offset[0] + extent[0], + src_offset[1]..src_offset[1] + extent[1], + src_offset[2]..src_offset[2] + extent[2], + ]; + + for (dst_region_index, dst_region) in regions.iter().enumerate() { + let &ImageCopy { + ref dst_subresource, + dst_offset, + .. + } = dst_region; + + // For a single-plane image, the aspects must always be identical anyway + if src_image_aspects.intersects(ImageAspects::PLANE_0) + && src_subresource.aspects != dst_subresource.aspects + { + continue; + } + + let dst_subresource_axes = [ + dst_image_inner.first_mipmap_level + dst_subresource.mip_level + ..dst_image_inner.first_mipmap_level + + dst_subresource.mip_level + + 1, + dst_image_inner.first_layer + src_subresource.array_layers.start + ..dst_image_inner.first_layer + src_subresource.array_layers.end, + ]; + + if src_subresource_axes.iter().zip(dst_subresource_axes).any( + |(src_range, dst_range)| { + src_range.start >= dst_range.end || dst_range.start >= src_range.end + }, + ) { + continue; + } + + // If the subresource axes all overlap, then the source and destination must + // have the same layout. + overlap_subresource_indices = Some((src_region_index, dst_region_index)); + + let dst_extent_axes = [ + dst_offset[0]..dst_offset[0] + extent[0], + dst_offset[1]..dst_offset[1] + extent[1], + dst_offset[2]..dst_offset[2] + extent[2], + ]; + + // There is only overlap if all of the axes overlap. + if src_extent_axes.iter().zip(dst_extent_axes).any( + |(src_range, dst_range)| { + src_range.start >= dst_range.end || dst_range.start >= src_range.end + }, + ) { + continue; + } + + overlap_extent_indices = Some((src_region_index, dst_region_index)); + } + } + } else { + // If granularity is `None`, then we can only copy whole subresources. + let check_offset_extent = |resource: CopyErrorResource, + subresource_extent: [u32; 3], + offset: [u32; 3], + extent: [u32; 3]| + -> Result<_, CopyError> { + // VUID-VkCopyImageInfo2-srcImage-01727 + // VUID-VkCopyImageInfo2-dstImage-01731 + // VUID-vkCmdCopyImage-srcOffset-01783 + // VUID-vkCmdCopyImage-dstOffset-01784 + if offset != [0, 0, 0] { + return Err(CopyError::OffsetNotAlignedForImage { + resource, + region_index, + offset, + required_alignment: subresource_extent, + }); + } + + // VUID-VkCopyImageInfo2-srcImage-01728 + // VUID-VkCopyImageInfo2-srcImage-01729 + // VUID-VkCopyImageInfo2-srcImage-01730 + // VUID-VkCopyImageInfo2-dstImage-01732 + // VUID-VkCopyImageInfo2-dstImage-01733 + // VUID-VkCopyImageInfo2-dstImage-01734 + if extent != subresource_extent { + return Err(CopyError::ExtentNotAlignedForImage { + resource, + region_index, + extent, + required_alignment: subresource_extent, + }); + } + + Ok(()) + }; + + check_offset_extent( + CopyErrorResource::Source, + src_subresource_extent, + src_offset, + src_extent, + )?; + check_offset_extent( + CopyErrorResource::Destination, + dst_subresource_extent, + dst_offset, + dst_extent, + )?; + + // VUID-VkCopyImageInfo2-pRegions-00124 + // A simpler version that assumes the region covers the full extent. + if same_image { + let src_region_index = region_index; + let src_axes = [ + src_image_inner.first_mipmap_level + src_subresource.mip_level + ..src_image_inner.first_mipmap_level + src_subresource.mip_level + 1, + src_image_inner.first_layer + src_subresource.array_layers.start + ..src_image_inner.first_layer + src_subresource.array_layers.end, + ]; + + for (dst_region_index, dst_region) in regions.iter().enumerate() { + let &ImageCopy { + ref dst_subresource, + dst_offset: _, + .. + } = dst_region; + + if src_image_aspects.intersects(ImageAspects::PLANE_0) + && src_subresource.aspects != dst_subresource.aspects + { + continue; + } + + let dst_axes = [ + dst_image_inner.first_mipmap_level + dst_subresource.mip_level + ..dst_image_inner.first_mipmap_level + + dst_subresource.mip_level + + 1, + dst_image_inner.first_layer + src_subresource.array_layers.start + ..dst_image_inner.first_layer + src_subresource.array_layers.end, + ]; + + // There is only overlap if all of the axes overlap. + if src_axes.iter().zip(dst_axes).any(|(src_range, dst_range)| { + src_range.start >= dst_range.end || dst_range.start >= src_range.end + }) { + continue; + } + + overlap_extent_indices = Some((src_region_index, dst_region_index)); + } + } + } + } + + // VUID-VkCopyImageInfo2-aspect-06662 + if !(src_image_aspects_used - ImageAspects::STENCIL).is_empty() + && !src_image.usage().intersects(ImageUsage::TRANSFER_SRC) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Source, + usage: "transfer_src", + }); + } + + // VUID-VkCopyImageInfo2-aspect-06663 + if !(dst_image_aspects_used - ImageAspects::STENCIL).is_empty() + && !dst_image.usage().intersects(ImageUsage::TRANSFER_DST) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Destination, + usage: "transfer_dst", + }); + } + + // VUID-VkCopyImageInfo2-aspect-06664 + if src_image_aspects_used.intersects(ImageAspects::STENCIL) + && !src_image + .stencil_usage() + .intersects(ImageUsage::TRANSFER_SRC) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Source, + usage: "transfer_src", + }); + } + + // VUID-VkCopyImageInfo2-aspect-06665 + if dst_image_aspects_used.intersects(ImageAspects::STENCIL) + && !dst_image + .stencil_usage() + .intersects(ImageUsage::TRANSFER_DST) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Destination, + usage: "transfer_dst", + }); + } + + // VUID-VkCopyImageInfo2-pRegions-00124 + if let Some((src_region_index, dst_region_index)) = overlap_extent_indices { + return Err(CopyError::OverlappingRegions { + src_region_index, + dst_region_index, + }); + } + + // VUID-VkCopyImageInfo2-srcImageLayout-00128 + // VUID-VkCopyImageInfo2-dstImageLayout-00133 + if let Some((src_region_index, dst_region_index)) = overlap_subresource_indices { + if src_image_layout != dst_image_layout { + return Err(CopyError::OverlappingSubresourcesLayoutMismatch { + src_region_index, + dst_region_index, + src_image_layout, + dst_image_layout, + }); + } + } + + // TODO: sync check + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn copy_image_unchecked(&mut self, copy_image_info: CopyImageInfo) -> &mut Self { + let CopyImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = copy_image_info; + + if regions.is_empty() { + return self; + } + + let src_image_inner = src_image.inner(); + let dst_image_inner = dst_image.inner(); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageCopy { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + let mut src_subresource = src_subresource.clone(); + src_subresource.array_layers.start += src_image_inner.first_layer; + src_subresource.array_layers.end += src_image_inner.first_layer; + src_subresource.mip_level += src_image_inner.first_mipmap_level; + + let mut dst_subresource = dst_subresource.clone(); + dst_subresource.array_layers.start += dst_image_inner.first_layer; + dst_subresource.array_layers.end += dst_image_inner.first_layer; + dst_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::ImageCopy2 { + src_subresource: src_subresource.into(), + src_offset: ash::vk::Offset3D { + x: src_offset[0] as i32, + y: src_offset[1] as i32, + z: src_offset[2] as i32, + }, + dst_subresource: dst_subresource.into(), + dst_offset: ash::vk::Offset3D { + x: dst_offset[0] as i32, + y: dst_offset[1] as i32, + z: dst_offset[2] as i32, + }, + extent: ash::vk::Extent3D { + width: extent[0], + height: extent[1], + depth: extent[2], + }, + ..Default::default() + } + }) + .collect(); + + let copy_image_info = ash::vk::CopyImageInfo2 { + src_image: src_image_inner.image.handle(), + src_image_layout: src_image_layout.into(), + dst_image: dst_image_inner.image.handle(), + dst_image_layout: dst_image_layout.into(), + region_count: regions.len() as u32, + p_regions: regions.as_ptr(), + ..Default::default() + }; + + if self.device().api_version() >= Version::V1_3 { + (fns.v1_3.cmd_copy_image2)(self.handle(), ©_image_info); + } else { + (fns.khr_copy_commands2.cmd_copy_image2_khr)(self.handle(), ©_image_info); + } + } else { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageCopy { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + let mut src_subresource = src_subresource.clone(); + src_subresource.array_layers.start += src_image_inner.first_layer; + src_subresource.array_layers.end += src_image_inner.first_layer; + src_subresource.mip_level += src_image_inner.first_mipmap_level; + + let mut dst_subresource = dst_subresource.clone(); + dst_subresource.array_layers.start += dst_image_inner.first_layer; + dst_subresource.array_layers.end += dst_image_inner.first_layer; + dst_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::ImageCopy { + src_subresource: src_subresource.into(), + src_offset: ash::vk::Offset3D { + x: src_offset[0] as i32, + y: src_offset[1] as i32, + z: src_offset[2] as i32, + }, + dst_subresource: dst_subresource.into(), + dst_offset: ash::vk::Offset3D { + x: dst_offset[0] as i32, + y: dst_offset[1] as i32, + z: dst_offset[2] as i32, + }, + extent: ash::vk::Extent3D { + width: extent[0], + height: extent[1], + depth: extent[2], + }, + } + }) + .collect(); + + (fns.v1_0.cmd_copy_image)( + self.handle(), + src_image_inner.image.handle(), + src_image_layout.into(), + dst_image_inner.image.handle(), + dst_image_layout.into(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + let command_index = self.next_command_index; + let command_name = "copy_image"; + let src_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }; + let dst_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }; + + for region in regions { + let ImageCopy { + src_subresource, + src_offset: _, + dst_subresource, + dst_offset: _, + extent: _, + _ne: _, + } = region; + + let mut src_subresource_range = ImageSubresourceRange::from(src_subresource); + src_subresource_range.array_layers.start += src_image_inner.first_layer; + src_subresource_range.array_layers.end += src_image_inner.first_layer; + src_subresource_range.mip_levels.start += src_image_inner.first_mipmap_level; + src_subresource_range.mip_levels.end += src_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &src_use_ref, + src_image_inner.image, + src_subresource_range, + PipelineStageAccess::Copy_TransferRead, + src_image_layout, + ); + + let mut dst_subresource_range = ImageSubresourceRange::from(dst_subresource); + dst_subresource_range.array_layers.start += dst_image_inner.first_layer; + dst_subresource_range.array_layers.end += dst_image_inner.first_layer; + dst_subresource_range.mip_levels.start += dst_image_inner.first_mipmap_level; + dst_subresource_range.mip_levels.end += dst_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &dst_use_ref, + dst_image_inner.image, + dst_subresource_range, + PipelineStageAccess::Copy_TransferWrite, + dst_image_layout, + ); + } + + self.resources.push(Box::new(src_image)); + self.resources.push(Box::new(dst_image)); + + self.next_command_index += 1; + self + } + + /// Copies from a buffer to an image. + /// + /// # Safety + /// + /// - Appropriate synchronization must be provided for all buffers and images + /// that are accessed by the command. + /// - All images that are accessed by the command must be in the expected image layout. + #[inline] + pub unsafe fn copy_buffer_to_image( + &mut self, + copy_buffer_to_image_info: CopyBufferToImageInfo, + ) -> Result<&mut Self, CopyError> { + self.validate_copy_buffer_to_image(©_buffer_to_image_info)?; + + unsafe { Ok(self.copy_buffer_to_image_unchecked(copy_buffer_to_image_info)) } + } + + fn validate_copy_buffer_to_image( + &self, + copy_buffer_to_image_info: &CopyBufferToImageInfo, + ) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyBufferToImage2-renderpass + if self.builder_state.render_pass.is_some() { + return Err(CopyError::ForbiddenInsideRenderPass); + } + + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdCopyBufferToImage2-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::TRANSFER | QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + { + return Err(CopyError::NotSupportedByQueueFamily); + } + + let &CopyBufferToImageInfo { + ref src_buffer, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = copy_buffer_to_image_info; + + // VUID-VkCopyBufferToImageInfo2-dstImageLayout-parameter + dst_image_layout.validate_device(device)?; + + // VUID-VkCopyBufferToImageInfo2-commonparent + assert_eq!(device, src_buffer.device()); + assert_eq!(device, dst_image.device()); + + let mut image_aspects = dst_image.format().aspects(); + + // VUID-VkCopyBufferToImageInfo2-commandBuffer-04477 + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::GRAPHICS) + && !image_aspects.intersects(ImageAspects::COLOR) + { + return Err(CopyError::DepthStencilNotSupportedByQueueFamily); + } + + // VUID-VkCopyBufferToImageInfo2-srcBuffer-00174 + if !src_buffer + .buffer() + .usage() + .intersects(BufferUsage::TRANSFER_SRC) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Source, + usage: "transfer_src", + }); + } + + // VUID-VkCopyBufferToImageInfo2-dstImage-00177 + if !dst_image.usage().intersects(ImageUsage::TRANSFER_DST) { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Destination, + usage: "transfer_dst", + }); + } + + if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_maintenance1 { + // VUID-VkCopyBufferToImageInfo2-dstImage-01997 + if !dst_image + .format_features() + .intersects(FormatFeatures::TRANSFER_DST) + { + return Err(CopyError::MissingFormatFeature { + resource: CopyErrorResource::Destination, + format_feature: "transfer_dst", + }); + } + } + + // VUID-VkCopyBufferToImageInfo2-dstImage-00179 + if dst_image.samples() != SampleCount::Sample1 { + return Err(CopyError::SampleCountInvalid { + resource: CopyErrorResource::Destination, + sample_count: dst_image.samples(), + allowed_sample_counts: SampleCounts::SAMPLE_1, + }); + } + + // VUID-VkCopyBufferToImageInfo2-dstImageLayout-01396 + if !matches!( + dst_image_layout, + ImageLayout::TransferDstOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Destination, + image_layout: dst_image_layout, + }); + } + + let extent_alignment = match queue_family_properties.min_image_transfer_granularity { + [0, 0, 0] => None, + min_image_transfer_granularity => { + let granularity = move |block_extent: [u32; 3], is_multi_plane: bool| { + if is_multi_plane { + // Assume planes always have 1x1 blocks + min_image_transfer_granularity + } else { + // "The value returned in minImageTransferGranularity has a unit of + // compressed texel blocks for images having a block-compressed format, and + // a unit of texels otherwise." + [ + min_image_transfer_granularity[0] * block_extent[0], + min_image_transfer_granularity[1] * block_extent[1], + min_image_transfer_granularity[2] * block_extent[2], + ] + } + }; + + Some(granularity( + dst_image.format().block_extent(), + image_aspects.intersects(ImageAspects::PLANE_0), + )) + } + }; + + if image_aspects.intersects(ImageAspects::PLANE_0) { + // VUID-VkCopyBufferToImageInfo2-aspectMask-01560 + image_aspects -= ImageAspects::COLOR; + } + + for (region_index, region) in regions.iter().enumerate() { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + // VUID-VkCopyBufferToImageInfo2-imageSubresource-01701 + if image_subresource.mip_level >= dst_image.mip_levels() { + return Err(CopyError::MipLevelsOutOfRange { + resource: CopyErrorResource::Destination, + region_index, + mip_levels_range_end: image_subresource.mip_level + 1, + image_mip_levels: dst_image.mip_levels(), + }); + } + + // VUID-VkImageSubresourceLayers-layerCount-01700 + // VUID-VkCopyBufferToImageInfo2-baseArrayLayer-00213 + assert!(!image_subresource.array_layers.is_empty()); + + // VUID-VkCopyBufferToImageInfo2-imageSubresource-01702 + // VUID-VkCopyBufferToImageInfo2-baseArrayLayer-00213 + if image_subresource.array_layers.end > dst_image.dimensions().array_layers() { + return Err(CopyError::ArrayLayersOutOfRange { + resource: CopyErrorResource::Destination, + region_index, + array_layers_range_end: image_subresource.array_layers.end, + image_array_layers: dst_image.dimensions().array_layers(), + }); + } + + // VUID-VkImageSubresourceLayers-aspectMask-requiredbitmask + assert!(!image_subresource.aspects.is_empty()); + + // VUID-VkCopyBufferToImageInfo2-aspectMask-00211 + if !image_aspects.contains(image_subresource.aspects) { + return Err(CopyError::AspectsNotAllowed { + resource: CopyErrorResource::Destination, + region_index, + aspects: image_subresource.aspects, + allowed_aspects: image_aspects, + }); + } + + // VUID-VkBufferImageCopy2-aspectMask-00212 + // VUID-VkCopyBufferToImageInfo2-aspectMask-01560 + if image_subresource.aspects.count() != 1 { + return Err(CopyError::MultipleAspectsNotAllowed { + resource: CopyErrorResource::Destination, + region_index, + aspects: image_subresource.aspects, + }); + } + + let (image_subresource_format, image_subresource_extent) = + if image_aspects.intersects(ImageAspects::PLANE_0) { + if image_subresource.aspects.intersects(ImageAspects::PLANE_0) { + ( + dst_image.format().planes()[0], + dst_image.dimensions().width_height_depth(), + ) + } else if image_subresource.aspects.intersects(ImageAspects::PLANE_1) { + ( + dst_image.format().planes()[1], + dst_image + .format() + .ycbcr_chroma_sampling() + .unwrap() + .subsampled_extent(dst_image.dimensions().width_height_depth()), + ) + } else { + ( + dst_image.format().planes()[2], + dst_image + .format() + .ycbcr_chroma_sampling() + .unwrap() + .subsampled_extent(dst_image.dimensions().width_height_depth()), + ) + } + } else { + ( + dst_image.format(), + dst_image + .dimensions() + .mip_level_dimensions(image_subresource.mip_level) + .unwrap() + .width_height_depth(), + ) + }; + + if let Some(extent_alignment) = extent_alignment { + for i in 0..3 { + // VUID-VkBufferImageCopy2-imageExtent-06659 + // VUID-VkBufferImageCopy2-imageExtent-06660 + // VUID-VkBufferImageCopy2-imageExtent-06661 + assert!(image_extent[i] != 0); + + // VUID-VkCopyBufferToImageInfo2-pRegions-06223 + // VUID-VkCopyBufferToImageInfo2-pRegions-06224 + // VUID-VkCopyBufferToImageInfo2-imageOffset-00200 + if image_offset[i] + image_extent[i] > image_subresource_extent[i] { + return Err(CopyError::RegionOutOfImageBounds { + resource: CopyErrorResource::Destination, + region_index, + offset_range_end: [ + image_offset[0] + image_extent[0], + image_offset[1] + image_extent[1], + image_offset[2] + image_extent[2], + ], + subresource_extent: image_subresource_extent, + }); + } + + // VUID-VkCopyBufferToImageInfo2-imageOffset-01793 + // VUID-VkCopyBufferToImageInfo2-imageOffset-00205 + if image_offset[i] % extent_alignment[i] != 0 { + return Err(CopyError::OffsetNotAlignedForImage { + resource: CopyErrorResource::Destination, + region_index, + offset: image_offset, + required_alignment: extent_alignment, + }); + } + + // VUID-VkCopyBufferToImageInfo2-imageOffset-01793 + // VUID-VkCopyBufferToImageInfo2-imageExtent-00207 + // VUID-VkCopyBufferToImageInfo2-imageExtent-00208 + // VUID-VkCopyBufferToImageInfo2-imageExtent-00209 + if image_offset[i] + image_extent[i] != image_subresource_extent[i] + && image_extent[i] % extent_alignment[i] != 0 + { + return Err(CopyError::ExtentNotAlignedForImage { + resource: CopyErrorResource::Destination, + region_index, + extent: image_extent, + required_alignment: extent_alignment, + }); + } + } + } else { + // If granularity is `None`, then we can only copy whole subresources. + + // VUID-VkCopyBufferToImageInfo2-imageOffset-01793 + if image_offset != [0, 0, 0] { + return Err(CopyError::OffsetNotAlignedForImage { + resource: CopyErrorResource::Destination, + region_index, + offset: image_offset, + required_alignment: image_subresource_extent, + }); + } + + // VUID-VkCopyBufferToImageInfo2-imageOffset-01793 + if image_extent != image_subresource_extent { + return Err(CopyError::ExtentNotAlignedForImage { + resource: CopyErrorResource::Destination, + region_index, + extent: image_extent, + required_alignment: image_subresource_extent, + }); + } + } + + // VUID-VkBufferImageCopy2-bufferRowLength-00195 + if !(buffer_row_length == 0 || buffer_row_length >= image_extent[0]) { + return Err(CopyError::BufferRowLengthTooSmall { + resource: CopyErrorResource::Source, + region_index, + row_length: buffer_row_length, + min: image_extent[0], + }); + } + + // VUID-VkBufferImageCopy2-bufferImageHeight-00196 + if !(buffer_image_height == 0 || buffer_image_height >= image_extent[1]) { + return Err(CopyError::BufferImageHeightTooSmall { + resource: CopyErrorResource::Source, + region_index, + image_height: buffer_image_height, + min: image_extent[1], + }); + } + + let image_subresource_block_extent = image_subresource_format.block_extent(); + + // VUID-VkCopyBufferToImageInfo2-bufferRowLength-00203 + if buffer_row_length % image_subresource_block_extent[0] != 0 { + return Err(CopyError::BufferRowLengthNotAligned { + resource: CopyErrorResource::Source, + region_index, + row_length: buffer_row_length, + required_alignment: image_subresource_block_extent[0], + }); + } + + // VUID-VkCopyBufferToImageInfo2-bufferImageHeight-00204 + if buffer_image_height % image_subresource_block_extent[1] != 0 { + return Err(CopyError::BufferImageHeightNotAligned { + resource: CopyErrorResource::Source, + region_index, + image_height: buffer_image_height, + required_alignment: image_subresource_block_extent[1], + }); + } + + // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferImageCopy.html#_description + let image_subresource_block_size = + if image_subresource.aspects.intersects(ImageAspects::STENCIL) { + 1 + } else if image_subresource.aspects.intersects(ImageAspects::DEPTH) { + match image_subresource_format { + Format::D16_UNORM | Format::D16_UNORM_S8_UINT => 2, + Format::D32_SFLOAT + | Format::D32_SFLOAT_S8_UINT + | Format::X8_D24_UNORM_PACK32 + | Format::D24_UNORM_S8_UINT => 4, + _ => unreachable!(), + } + } else { + image_subresource_format.block_size().unwrap() + }; + + // VUID-VkCopyBufferToImageInfo2-pRegions-04725 + // VUID-VkCopyBufferToImageInfo2-pRegions-04726 + if (buffer_row_length / image_subresource_block_extent[0]) as DeviceSize + * image_subresource_block_size + > 0x7FFFFFFF + { + return Err(CopyError::BufferRowLengthTooLarge { + resource: CopyErrorResource::Source, + region_index, + buffer_row_length, + }); + } + + let buffer_offset_alignment = + if image_aspects.intersects(ImageAspects::DEPTH | ImageAspects::STENCIL) { + 4 + } else { + let mut buffer_offset_alignment = image_subresource_block_size; + + // VUID-VkCopyBufferToImageInfo2-commandBuffer-04052 + // Make the alignment a multiple of 4. + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + { + if buffer_offset_alignment % 2 != 0 { + buffer_offset_alignment *= 2; + } + + if buffer_offset_alignment % 4 != 0 { + buffer_offset_alignment *= 2; + } + } + + buffer_offset_alignment + }; + + // VUID-VkCopyBufferToImageInfo2-bufferOffset-00206 + // VUID-VkCopyBufferToImageInfo2-bufferOffset-01558 + // VUID-VkCopyBufferToImageInfo2-bufferOffset-01559 + // VUID-VkCopyBufferToImageInfo2-srcImage-04053 + if (src_buffer.offset() + buffer_offset) % buffer_offset_alignment != 0 { + return Err(CopyError::OffsetNotAlignedForBuffer { + resource: CopyErrorResource::Source, + region_index, + offset: src_buffer.offset() + buffer_offset, + required_alignment: buffer_offset_alignment, + }); + } + + let buffer_copy_size = region.buffer_copy_size(image_subresource_format); + + // VUID-VkCopyBufferToImageInfo2-pRegions-00171 + if buffer_offset + buffer_copy_size > src_buffer.size() { + return Err(CopyError::RegionOutOfBufferBounds { + resource: CopyErrorResource::Source, + region_index, + offset_range_end: buffer_offset + buffer_copy_size, + buffer_size: src_buffer.size(), + }); + } + } + + // VUID-VkCopyBufferToImageInfo2-pRegions-00173 + // Can't occur as long as memory aliasing isn't allowed. + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn copy_buffer_to_image_unchecked( + &mut self, + copy_buffer_to_image_info: CopyBufferToImageInfo, + ) -> &mut Self { + let CopyBufferToImageInfo { + src_buffer, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = copy_buffer_to_image_info; + + if regions.is_empty() { + return self; + } + + let dst_image_inner = dst_image.inner(); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + let mut image_subresource = image_subresource.clone(); + image_subresource.array_layers.start += dst_image_inner.first_layer; + image_subresource.array_layers.end += dst_image_inner.first_layer; + image_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::BufferImageCopy2 { + buffer_offset: buffer_offset + src_buffer.offset(), + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: ash::vk::Offset3D { + x: image_offset[0] as i32, + y: image_offset[1] as i32, + z: image_offset[2] as i32, + }, + image_extent: ash::vk::Extent3D { + width: image_extent[0], + height: image_extent[1], + depth: image_extent[2], + }, + ..Default::default() + } + }) + .collect(); + + let copy_buffer_to_image_info = ash::vk::CopyBufferToImageInfo2 { + src_buffer: src_buffer.buffer().handle(), + dst_image: dst_image_inner.image.handle(), + dst_image_layout: dst_image_layout.into(), + region_count: regions.len() as u32, + p_regions: regions.as_ptr(), + ..Default::default() + }; + + if self.device().api_version() >= Version::V1_3 { + (fns.v1_3.cmd_copy_buffer_to_image2)(self.handle(), ©_buffer_to_image_info); + } else { + (fns.khr_copy_commands2.cmd_copy_buffer_to_image2_khr)( + self.handle(), + ©_buffer_to_image_info, + ); + } + } else { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + let mut image_subresource = image_subresource.clone(); + image_subresource.array_layers.start += dst_image_inner.first_layer; + image_subresource.array_layers.end += dst_image_inner.first_layer; + image_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::BufferImageCopy { + buffer_offset: buffer_offset + src_buffer.offset(), + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: ash::vk::Offset3D { + x: image_offset[0] as i32, + y: image_offset[1] as i32, + z: image_offset[2] as i32, + }, + image_extent: ash::vk::Extent3D { + width: image_extent[0], + height: image_extent[1], + depth: image_extent[2], + }, + } + }) + .collect(); + + (fns.v1_0.cmd_copy_buffer_to_image)( + self.handle(), + src_buffer.buffer().handle(), + dst_image_inner.image.handle(), + dst_image_layout.into(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + let command_index = self.next_command_index; + let command_name = "copy_buffer_to_image"; + let src_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }; + let dst_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }; + + for region in regions { + let buffer_copy_size = region.buffer_copy_size(dst_image.format()); + + let BufferImageCopy { + buffer_offset, + buffer_row_length: _, + buffer_image_height: _, + image_subresource, + image_offset: _, + image_extent: _, + _ne: _, + } = region; + + let mut src_range = buffer_offset..buffer_offset + buffer_copy_size; + src_range.start += src_buffer.offset(); + src_range.end += src_buffer.offset(); + self.resources_usage_state.record_buffer_access( + &src_use_ref, + src_buffer.buffer(), + src_range, + PipelineStageAccess::Copy_TransferRead, + ); + + let mut dst_subresource_range = ImageSubresourceRange::from(image_subresource); + dst_subresource_range.array_layers.start += dst_image_inner.first_layer; + dst_subresource_range.array_layers.end += dst_image_inner.first_layer; + dst_subresource_range.mip_levels.start += dst_image_inner.first_mipmap_level; + dst_subresource_range.mip_levels.end += dst_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &dst_use_ref, + dst_image_inner.image, + dst_subresource_range, + PipelineStageAccess::Copy_TransferWrite, + dst_image_layout, + ); + } + + self.resources.push(Box::new(src_buffer)); + self.resources.push(Box::new(dst_image)); + + self.next_command_index += 1; + self + } + + /// Copies from an image to a buffer. + /// + /// # Safety + /// + /// - Appropriate synchronization must be provided for all buffers and images + /// that are accessed by the command. + /// - All images that are accessed by the command must be in the expected image layout. + #[inline] + pub unsafe fn copy_image_to_buffer( + &mut self, + copy_image_to_buffer_info: CopyImageToBufferInfo, + ) -> Result<&mut Self, CopyError> { + self.validate_copy_image_to_buffer(©_image_to_buffer_info)?; + + unsafe { Ok(self.copy_image_to_buffer_unchecked(copy_image_to_buffer_info)) } + } + + fn validate_copy_image_to_buffer( + &self, + copy_image_to_buffer_info: &CopyImageToBufferInfo, + ) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyImageToBuffer2-renderpass + if self.builder_state.render_pass.is_some() { + return Err(CopyError::ForbiddenInsideRenderPass); + } + + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdCopyImageToBuffer2-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::TRANSFER | QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + { + return Err(CopyError::NotSupportedByQueueFamily); + } + + let &CopyImageToBufferInfo { + ref src_image, + src_image_layout, + ref dst_buffer, + ref regions, + _ne: _, + } = copy_image_to_buffer_info; + + // VUID-VkCopyImageToBufferInfo2-srcImageLayout-parameter + src_image_layout.validate_device(device)?; + + // VUID-VkCopyImageToBufferInfo2-commonparent + assert_eq!(device, dst_buffer.device()); + assert_eq!(device, src_image.device()); + + let mut image_aspects = src_image.format().aspects(); + + // VUID-VkCopyImageToBufferInfo2-srcImage-00186 + if !src_image.usage().intersects(ImageUsage::TRANSFER_SRC) { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Source, + usage: "transfer_src", + }); + } + + // VUID-VkCopyImageToBufferInfo2-dstBuffer-00191 + if !dst_buffer + .buffer() + .usage() + .intersects(BufferUsage::TRANSFER_DST) + { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Destination, + usage: "transfer_dst", + }); + } + + if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_maintenance1 { + // VUID-VkCopyImageToBufferInfo2-srcImage-01998 + if !src_image + .format_features() + .intersects(FormatFeatures::TRANSFER_SRC) + { + return Err(CopyError::MissingFormatFeature { + resource: CopyErrorResource::Source, + format_feature: "transfer_src", + }); + } + } + + // VUID-VkCopyImageToBufferInfo2-srcImage-00188 + if src_image.samples() != SampleCount::Sample1 { + return Err(CopyError::SampleCountInvalid { + resource: CopyErrorResource::Source, + sample_count: src_image.samples(), + allowed_sample_counts: SampleCounts::SAMPLE_1, + }); + } + + // VUID-VkCopyImageToBufferInfo2-srcImageLayout-01397 + if !matches!( + src_image_layout, + ImageLayout::TransferSrcOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Source, + image_layout: src_image_layout, + }); + } + + let extent_alignment = match queue_family_properties.min_image_transfer_granularity { + [0, 0, 0] => None, + min_image_transfer_granularity => { + let granularity = move |block_extent: [u32; 3], is_multi_plane: bool| { + if is_multi_plane { + // Assume planes always have 1x1 blocks + min_image_transfer_granularity + } else { + // "The value returned in minImageTransferGranularity has a unit of + // compressed texel blocks for images having a block-compressed format, and + // a unit of texels otherwise." + [ + min_image_transfer_granularity[0] * block_extent[0], + min_image_transfer_granularity[1] * block_extent[1], + min_image_transfer_granularity[2] * block_extent[2], + ] + } + }; + + Some(granularity( + src_image.format().block_extent(), + image_aspects.intersects(ImageAspects::PLANE_0), + )) + } + }; + + if image_aspects.intersects(ImageAspects::PLANE_0) { + // VUID-VkCopyImageToBufferInfo2-aspectMask-01560 + image_aspects -= ImageAspects::COLOR; + } + + for (region_index, region) in regions.iter().enumerate() { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + // VUID-VkCopyImageToBufferInfo2-imageSubresource-01703 + if image_subresource.mip_level >= src_image.mip_levels() { + return Err(CopyError::MipLevelsOutOfRange { + resource: CopyErrorResource::Source, + region_index, + mip_levels_range_end: image_subresource.mip_level + 1, + image_mip_levels: src_image.mip_levels(), + }); + } + + // VUID-VkImageSubresourceLayers-layerCount-01700 + assert!(!image_subresource.array_layers.is_empty()); + + // VUID-VkCopyImageToBufferInfo2-imageSubresource-01704 + // VUID-VkCopyImageToBufferInfo2-baseArrayLayer-00213 + if image_subresource.array_layers.end > src_image.dimensions().array_layers() { + return Err(CopyError::ArrayLayersOutOfRange { + resource: CopyErrorResource::Source, + region_index, + array_layers_range_end: image_subresource.array_layers.end, + image_array_layers: src_image.dimensions().array_layers(), + }); + } + + // VUID-VkImageSubresourceLayers-aspectMask-requiredbitmask + assert!(!image_subresource.aspects.is_empty()); + + // VUID-VkCopyImageToBufferInfo2-aspectMask-00211 + if !image_aspects.contains(image_subresource.aspects) { + return Err(CopyError::AspectsNotAllowed { + resource: CopyErrorResource::Source, + region_index, + aspects: image_subresource.aspects, + allowed_aspects: image_aspects, + }); + } + + // VUID-VkBufferImageCopy2-aspectMask-00212 + if image_subresource.aspects.count() != 1 { + return Err(CopyError::MultipleAspectsNotAllowed { + resource: CopyErrorResource::Source, + region_index, + aspects: image_subresource.aspects, + }); + } + + let (image_subresource_format, image_subresource_extent) = + if image_aspects.intersects(ImageAspects::PLANE_0) { + if image_subresource.aspects.intersects(ImageAspects::PLANE_0) { + ( + src_image.format().planes()[0], + src_image.dimensions().width_height_depth(), + ) + } else if image_subresource.aspects.intersects(ImageAspects::PLANE_1) { + ( + src_image.format().planes()[1], + src_image + .format() + .ycbcr_chroma_sampling() + .unwrap() + .subsampled_extent(src_image.dimensions().width_height_depth()), + ) + } else { + ( + src_image.format().planes()[2], + src_image + .format() + .ycbcr_chroma_sampling() + .unwrap() + .subsampled_extent(src_image.dimensions().width_height_depth()), + ) + } + } else { + ( + src_image.format(), + src_image + .dimensions() + .mip_level_dimensions(image_subresource.mip_level) + .unwrap() + .width_height_depth(), + ) + }; + + if let Some(extent_alignment) = extent_alignment { + for i in 0..3 { + // VUID-VkBufferImageCopy2-imageExtent-06659 + // VUID-VkBufferImageCopy2-imageExtent-06660 + // VUID-VkBufferImageCopy2-imageExtent-06661 + assert!(image_extent[i] != 0); + + // VUID-VkCopyImageToBufferInfo2-imageOffset-00197 + // VUID-VkCopyImageToBufferInfo2-imageOffset-00198 + // VUID-VkCopyImageToBufferInfo2-imageOffset-00200 + if image_offset[i] + image_extent[i] > image_subresource_extent[i] { + return Err(CopyError::RegionOutOfImageBounds { + resource: CopyErrorResource::Source, + region_index, + offset_range_end: [ + image_offset[0] + image_extent[0], + image_offset[1] + image_extent[1], + image_offset[2] + image_extent[2], + ], + subresource_extent: image_subresource_extent, + }); + } + + // VUID-VkCopyImageToBufferInfo2-imageOffset-01794 + // VUID-VkCopyImageToBufferInfo2-imageOffset-00205 + if image_offset[i] % extent_alignment[i] != 0 { + return Err(CopyError::OffsetNotAlignedForImage { + resource: CopyErrorResource::Source, + region_index, + offset: image_offset, + required_alignment: extent_alignment, + }); + } + + // VUID-VkCopyImageToBufferInfo2-imageOffset-01794 + // VUID-VkCopyImageToBufferInfo2-imageExtent-00207 + // VUID-VkCopyImageToBufferInfo2-imageExtent-00208 + // VUID-VkCopyImageToBufferInfo2-imageExtent-00209 + if image_offset[i] + image_extent[i] != image_subresource_extent[i] + && image_extent[i] % extent_alignment[i] != 0 + { + return Err(CopyError::ExtentNotAlignedForImage { + resource: CopyErrorResource::Source, + region_index, + extent: image_extent, + required_alignment: extent_alignment, + }); + } + } + } else { + // If granularity is `None`, then we can only copy whole subresources. + + // VUID-VkCopyBufferToImageInfo2-imageOffset-01793 + if image_offset != [0, 0, 0] { + return Err(CopyError::OffsetNotAlignedForImage { + resource: CopyErrorResource::Source, + region_index, + offset: image_offset, + required_alignment: image_subresource_extent, + }); + } + + // VUID-VkCopyBufferToImageInfo2-imageOffset-01793 + if image_extent != image_subresource_extent { + return Err(CopyError::ExtentNotAlignedForImage { + resource: CopyErrorResource::Source, + region_index, + extent: image_extent, + required_alignment: image_subresource_extent, + }); + } + } + + // VUID-VkBufferImageCopy2-bufferRowLength-00195 + if !(buffer_row_length == 0 || buffer_row_length >= image_extent[0]) { + return Err(CopyError::BufferRowLengthTooSmall { + resource: CopyErrorResource::Destination, + region_index, + row_length: buffer_row_length, + min: image_extent[0], + }); + } + + // VUID-VkBufferImageCopy2-bufferImageHeight-00196 + if !(buffer_image_height == 0 || buffer_image_height >= image_extent[1]) { + return Err(CopyError::BufferImageHeightTooSmall { + resource: CopyErrorResource::Destination, + region_index, + image_height: buffer_image_height, + min: image_extent[1], + }); + } + + let image_subresource_block_extent = image_subresource_format.block_extent(); + + // VUID-VkCopyImageToBufferInfo2-bufferRowLength-00203 + if buffer_row_length % image_subresource_block_extent[0] != 0 { + return Err(CopyError::BufferRowLengthNotAligned { + resource: CopyErrorResource::Destination, + region_index, + row_length: buffer_row_length, + required_alignment: image_subresource_block_extent[0], + }); + } + + // VUID-VkCopyImageToBufferInfo2-bufferImageHeight-00204 + if buffer_image_height % image_subresource_block_extent[1] != 0 { + return Err(CopyError::BufferImageHeightNotAligned { + resource: CopyErrorResource::Destination, + region_index, + image_height: buffer_image_height, + required_alignment: image_subresource_block_extent[1], + }); + } + + // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferImageCopy.html#_description + let image_subresource_block_size = + if image_subresource.aspects.intersects(ImageAspects::STENCIL) { + 1 + } else if image_subresource.aspects.intersects(ImageAspects::DEPTH) { + match image_subresource_format { + Format::D16_UNORM | Format::D16_UNORM_S8_UINT => 2, + Format::D32_SFLOAT + | Format::D32_SFLOAT_S8_UINT + | Format::X8_D24_UNORM_PACK32 + | Format::D24_UNORM_S8_UINT => 4, + _ => unreachable!(), + } + } else { + image_subresource_format.block_size().unwrap() + }; + + // VUID-VkCopyImageToBufferInfo2-pRegions-04725 + // VUID-VkCopyImageToBufferInfo2-pRegions-04726 + if (buffer_row_length / image_subresource_block_extent[0]) as DeviceSize + * image_subresource_block_size + > 0x7FFFFFFF + { + return Err(CopyError::BufferRowLengthTooLarge { + resource: CopyErrorResource::Destination, + region_index, + buffer_row_length, + }); + } + + let buffer_offset_alignment = + if image_aspects.intersects(ImageAspects::DEPTH | ImageAspects::STENCIL) { + 4 + } else { + let mut buffer_offset_alignment = image_subresource_block_size; + + // VUID-VkCopyImageToBufferInfo2-commandBuffer-04052 + // Make the alignment a multiple of 4. + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + { + if buffer_offset_alignment % 2 != 0 { + buffer_offset_alignment *= 2; + } + + if buffer_offset_alignment % 4 != 0 { + buffer_offset_alignment *= 2; + } + } + + buffer_offset_alignment + }; + + // VUID-VkCopyImageToBufferInfo2-bufferOffset-01558 + // VUID-VkCopyImageToBufferInfo2-bufferOffset-01559 + // VUID-VkCopyImageToBufferInfo2-bufferOffset-00206 + // VUID-VkCopyImageToBufferInfo2-srcImage-04053 + if (dst_buffer.offset() + buffer_offset) % buffer_offset_alignment != 0 { + return Err(CopyError::OffsetNotAlignedForBuffer { + resource: CopyErrorResource::Destination, + region_index, + offset: dst_buffer.offset() + buffer_offset, + required_alignment: buffer_offset_alignment, + }); + } + + let buffer_copy_size = region.buffer_copy_size(image_subresource_format); + + // VUID-VkCopyImageToBufferInfo2-pRegions-00183 + if buffer_offset + buffer_copy_size > dst_buffer.size() { + return Err(CopyError::RegionOutOfBufferBounds { + resource: CopyErrorResource::Destination, + region_index, + offset_range_end: buffer_offset + buffer_copy_size, + buffer_size: dst_buffer.size(), + }); + } + } + + // VUID-VkCopyImageToBufferInfo2-pRegions-00184 + // Can't occur as long as memory aliasing isn't allowed. + + // TODO: sync check + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn copy_image_to_buffer_unchecked( + &mut self, + copy_image_to_buffer_info: CopyImageToBufferInfo, + ) -> &mut Self { + let CopyImageToBufferInfo { + src_image, + src_image_layout, + dst_buffer, + regions, + _ne: _, + } = copy_image_to_buffer_info; + + if regions.is_empty() { + return self; + } + + let src_image_inner = src_image.inner(); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + let mut image_subresource = image_subresource.clone(); + image_subresource.array_layers.start += src_image_inner.first_layer; + image_subresource.array_layers.end += src_image_inner.first_layer; + image_subresource.mip_level += src_image_inner.first_mipmap_level; + + ash::vk::BufferImageCopy2 { + buffer_offset: buffer_offset + dst_buffer.offset(), + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: ash::vk::Offset3D { + x: image_offset[0] as i32, + y: image_offset[1] as i32, + z: image_offset[2] as i32, + }, + image_extent: ash::vk::Extent3D { + width: image_extent[0], + height: image_extent[1], + depth: image_extent[2], + }, + ..Default::default() + } + }) + .collect(); + + let copy_image_to_buffer_info = ash::vk::CopyImageToBufferInfo2 { + src_image: src_image_inner.image.handle(), + src_image_layout: src_image_layout.into(), + dst_buffer: dst_buffer.buffer().handle(), + region_count: regions.len() as u32, + p_regions: regions.as_ptr(), + ..Default::default() + }; + + if self.device().api_version() >= Version::V1_3 { + (fns.v1_3.cmd_copy_image_to_buffer2)(self.handle(), ©_image_to_buffer_info); + } else { + (fns.khr_copy_commands2.cmd_copy_image_to_buffer2_khr)( + self.handle(), + ©_image_to_buffer_info, + ); + } + } else { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + let mut image_subresource = image_subresource.clone(); + image_subresource.array_layers.start += src_image_inner.first_layer; + image_subresource.array_layers.end += src_image_inner.first_layer; + image_subresource.mip_level += src_image_inner.first_mipmap_level; + + ash::vk::BufferImageCopy { + buffer_offset: buffer_offset + dst_buffer.offset(), + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: ash::vk::Offset3D { + x: image_offset[0] as i32, + y: image_offset[1] as i32, + z: image_offset[2] as i32, + }, + image_extent: ash::vk::Extent3D { + width: image_extent[0], + height: image_extent[1], + depth: image_extent[2], + }, + } + }) + .collect(); + + (fns.v1_0.cmd_copy_image_to_buffer)( + self.handle(), + src_image_inner.image.handle(), + src_image_layout.into(), + dst_buffer.buffer().handle(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + let command_index = self.next_command_index; + let command_name = "copy_image_to_buffer"; + let src_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }; + let dst_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }; + + for region in regions { + let buffer_copy_size = region.buffer_copy_size(src_image.format()); + + let BufferImageCopy { + buffer_offset, + buffer_row_length: _, + buffer_image_height: _, + image_subresource, + image_offset: _, + image_extent: _, + _ne: _, + } = region; + + let mut src_subresource_range = ImageSubresourceRange::from(image_subresource); + src_subresource_range.array_layers.start += src_image_inner.first_layer; + src_subresource_range.array_layers.end += src_image_inner.first_layer; + src_subresource_range.mip_levels.start += src_image_inner.first_mipmap_level; + src_subresource_range.mip_levels.end += src_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &src_use_ref, + src_image_inner.image, + src_subresource_range, + PipelineStageAccess::Copy_TransferRead, + src_image_layout, + ); + + let mut dst_range = buffer_offset..buffer_offset + buffer_copy_size; + dst_range.start += dst_buffer.offset(); + dst_range.end += dst_buffer.offset(); + self.resources_usage_state.record_buffer_access( + &dst_use_ref, + dst_buffer.buffer(), + dst_range, + PipelineStageAccess::Copy_TransferWrite, + ); + } + + self.resources.push(Box::new(src_image)); + self.resources.push(Box::new(dst_buffer)); + + self.next_command_index += 1; + self + } + + /// Blits an image to another. + /// + /// A *blit* is similar to an image copy operation, except that the portion of the image that + /// is transferred can be resized. You choose an area of the source and an area of the + /// destination, and the implementation will resize the area of the source so that it matches + /// the size of the area of the destination before writing it. + /// + /// Blit operations have several restrictions: + /// + /// - Blit operations are only allowed on queue families that support graphics operations. + /// - The format of the source and destination images must support blit operations, which + /// depends on the Vulkan implementation. Vulkan guarantees that some specific formats must + /// always be supported. See tables 52 to 61 of the specifications. + /// - Only single-sampled images are allowed. + /// - You can only blit between two images whose formats belong to the same type. The types + /// are: floating-point, signed integers, unsigned integers, depth-stencil. + /// - If you blit between depth, stencil or depth-stencil images, the format of both images + /// must match exactly. + /// - If you blit between depth, stencil or depth-stencil images, only the `Nearest` filter is + /// allowed. + /// - For two-dimensional images, the Z coordinate must be 0 for the top-left offset and 1 for + /// the bottom-right offset. Same for the Y coordinate for one-dimensional images. + /// - For non-array images, the base array layer must be 0 and the number of layers must be 1. + /// + /// If `layer_count` is greater than 1, the blit will happen between each individual layer as + /// if they were separate images. + /// + /// # Panic + /// + /// - Panics if the source or the destination was not created with `device`. + /// + /// # Safety + /// + /// - Appropriate synchronization must be provided for all images + /// that are accessed by the command. + /// - All images that are accessed by the command must be in the expected image layout. + #[inline] + pub unsafe fn blit_image( + &mut self, + blit_image_info: BlitImageInfo, + ) -> Result<&mut Self, CopyError> { + self.validate_blit_image(&blit_image_info)?; + + unsafe { Ok(self.blit_image_unchecked(blit_image_info)) } + } + + fn validate_blit_image(&self, blit_image_info: &BlitImageInfo) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdBlitImage2-renderpass + if self.builder_state.render_pass.is_some() { + return Err(CopyError::ForbiddenInsideRenderPass); + } + + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdBlitImage2-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::GRAPHICS) + { + return Err(CopyError::NotSupportedByQueueFamily); + } + + let &BlitImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + filter, + _ne: _, + } = blit_image_info; + + // VUID-VkBlitImageInfo2-srcImageLayout-parameter + src_image_layout.validate_device(device)?; + + // VUID-VkBlitImageInfo2-dstImageLayout-parameter + dst_image_layout.validate_device(device)?; + + // VUID-VkBlitImageInfo2-filter-parameter + filter.validate_device(device)?; + + let src_image_inner = src_image.inner(); + let dst_image_inner = dst_image.inner(); + + // VUID-VkBlitImageInfo2-commonparent + assert_eq!(device, src_image.device()); + assert_eq!(device, dst_image.device()); + + let src_image_aspects = src_image.format().aspects(); + let dst_image_aspects = dst_image.format().aspects(); + let src_image_type = src_image.dimensions().image_type(); + let dst_image_type = dst_image.dimensions().image_type(); + + // VUID-VkBlitImageInfo2-srcImage-00219 + if !src_image.usage().intersects(ImageUsage::TRANSFER_SRC) { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Source, + usage: "transfer_src", + }); + } + + // VUID-VkBlitImageInfo2-dstImage-00224 + if !dst_image.usage().intersects(ImageUsage::TRANSFER_DST) { + return Err(CopyError::MissingUsage { + resource: CopyErrorResource::Destination, + usage: "transfer_dst", + }); + } + + // VUID-VkBlitImageInfo2-srcImage-01999 + if !src_image + .format_features() + .intersects(FormatFeatures::BLIT_SRC) + { + return Err(CopyError::MissingFormatFeature { + resource: CopyErrorResource::Source, + format_feature: "blit_src", + }); + } + + // VUID-VkBlitImageInfo2-dstImage-02000 + if !dst_image + .format_features() + .intersects(FormatFeatures::BLIT_DST) + { + return Err(CopyError::MissingFormatFeature { + resource: CopyErrorResource::Destination, + format_feature: "blit_dst", + }); + } + + // VUID-VkBlitImageInfo2-srcImage-06421 + if src_image.format().ycbcr_chroma_sampling().is_some() { + return Err(CopyError::FormatNotSupported { + resource: CopyErrorResource::Source, + format: src_image.format(), + }); + } + + // VUID-VkBlitImageInfo2-dstImage-06422 + if dst_image.format().ycbcr_chroma_sampling().is_some() { + return Err(CopyError::FormatNotSupported { + resource: CopyErrorResource::Destination, + format: src_image.format(), + }); + } + + if !(src_image_aspects.intersects(ImageAspects::COLOR) + && dst_image_aspects.intersects(ImageAspects::COLOR)) + { + // VUID-VkBlitImageInfo2-srcImage-00231 + if src_image.format() != dst_image.format() { + return Err(CopyError::FormatsMismatch { + src_format: src_image.format(), + dst_format: dst_image.format(), + }); + } + } else { + // VUID-VkBlitImageInfo2-srcImage-00229 + // VUID-VkBlitImageInfo2-srcImage-00230 + if !matches!( + ( + src_image.format().type_color().unwrap(), + dst_image.format().type_color().unwrap() + ), + ( + NumericType::SFLOAT + | NumericType::UFLOAT + | NumericType::SNORM + | NumericType::UNORM + | NumericType::SSCALED + | NumericType::USCALED + | NumericType::SRGB, + NumericType::SFLOAT + | NumericType::UFLOAT + | NumericType::SNORM + | NumericType::UNORM + | NumericType::SSCALED + | NumericType::USCALED + | NumericType::SRGB, + ) | (NumericType::SINT, NumericType::SINT) + | (NumericType::UINT, NumericType::UINT) + ) { + return Err(CopyError::FormatsNotCompatible { + src_format: src_image.format(), + dst_format: dst_image.format(), + }); + } + } + + // VUID-VkBlitImageInfo2-srcImage-00233 + if src_image.samples() != SampleCount::Sample1 { + return Err(CopyError::SampleCountInvalid { + resource: CopyErrorResource::Destination, + sample_count: dst_image.samples(), + allowed_sample_counts: SampleCounts::SAMPLE_1, + }); + } + + // VUID-VkBlitImageInfo2-dstImage-00234 + if dst_image.samples() != SampleCount::Sample1 { + return Err(CopyError::SampleCountInvalid { + resource: CopyErrorResource::Destination, + sample_count: dst_image.samples(), + allowed_sample_counts: SampleCounts::SAMPLE_1, + }); + } + + // VUID-VkBlitImageInfo2-srcImageLayout-01398 + if !matches!( + src_image_layout, + ImageLayout::TransferSrcOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Source, + image_layout: src_image_layout, + }); + } + + // VUID-VkBlitImageInfo2-dstImageLayout-01399 + if !matches!( + dst_image_layout, + ImageLayout::TransferDstOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Destination, + image_layout: dst_image_layout, + }); + } + + // VUID-VkBlitImageInfo2-srcImage-00232 + if !src_image_aspects.intersects(ImageAspects::COLOR) && filter != Filter::Nearest { + return Err(CopyError::FilterNotSupportedByFormat); + } + + match filter { + Filter::Nearest => (), + Filter::Linear => { + // VUID-VkBlitImageInfo2-filter-02001 + if !src_image + .format_features() + .intersects(FormatFeatures::SAMPLED_IMAGE_FILTER_LINEAR) + { + return Err(CopyError::FilterNotSupportedByFormat); + } + } + Filter::Cubic => { + // VUID-VkBlitImageInfo2-filter-02002 + if !src_image + .format_features() + .intersects(FormatFeatures::SAMPLED_IMAGE_FILTER_CUBIC) + { + return Err(CopyError::FilterNotSupportedByFormat); + } + + // VUID-VkBlitImageInfo2-filter-00237 + if !matches!(src_image.dimensions(), ImageDimensions::Dim2d { .. }) { + return Err(CopyError::FilterNotSupportedForImageType); + } + } + } + + let same_image = src_image_inner.image == dst_image_inner.image; + let mut overlap_subresource_indices = None; + let mut overlap_extent_indices = None; + + for (region_index, region) in regions.iter().enumerate() { + let &ImageBlit { + ref src_subresource, + src_offsets, + ref dst_subresource, + dst_offsets, + _ne: _, + } = region; + + let check_subresource = |resource: CopyErrorResource, + image: &dyn ImageAccess, + image_aspects: ImageAspects, + subresource: &ImageSubresourceLayers| + -> Result<_, CopyError> { + // VUID-VkBlitImageInfo2-srcSubresource-01705 + // VUID-VkBlitImageInfo2-dstSubresource-01706 + if subresource.mip_level >= image.mip_levels() { + return Err(CopyError::MipLevelsOutOfRange { + resource, + region_index, + mip_levels_range_end: subresource.mip_level + 1, + image_mip_levels: image.mip_levels(), + }); + } + + // VUID-VkImageSubresourceLayers-layerCount-01700 + assert!(!subresource.array_layers.is_empty()); + + // VUID-VkBlitImageInfo2-srcSubresource-01707 + // VUID-VkBlitImageInfo2-dstSubresource-01708 + // VUID-VkBlitImageInfo2-srcImage-00240 + if subresource.array_layers.end > image.dimensions().array_layers() { + return Err(CopyError::ArrayLayersOutOfRange { + resource, + region_index, + array_layers_range_end: subresource.array_layers.end, + image_array_layers: image.dimensions().array_layers(), + }); + } + + // VUID-VkImageSubresourceLayers-aspectMask-parameter + subresource.aspects.validate_device(device)?; + + // VUID-VkImageSubresourceLayers-aspectMask-requiredbitmask + assert!(!subresource.aspects.is_empty()); + + // VUID-VkBlitImageInfo2-aspectMask-00241 + // VUID-VkBlitImageInfo2-aspectMask-00242 + if !image_aspects.contains(subresource.aspects) { + return Err(CopyError::AspectsNotAllowed { + resource, + region_index, + aspects: subresource.aspects, + allowed_aspects: image_aspects, + }); + } + + Ok(image + .dimensions() + .mip_level_dimensions(subresource.mip_level) + .unwrap() + .width_height_depth()) + }; + + let src_subresource_extent = check_subresource( + CopyErrorResource::Source, + src_image, + src_image_aspects, + src_subresource, + )?; + let dst_subresource_extent = check_subresource( + CopyErrorResource::Destination, + dst_image, + dst_image_aspects, + dst_subresource, + )?; + + // VUID-VkImageBlit2-aspectMask-00238 + if src_subresource.aspects != dst_subresource.aspects { + return Err(CopyError::AspectsMismatch { + region_index, + src_aspects: src_subresource.aspects, + dst_aspects: dst_subresource.aspects, + }); + } + + let src_layer_count = + src_subresource.array_layers.end - src_subresource.array_layers.start; + let dst_layer_count = + dst_subresource.array_layers.end - dst_subresource.array_layers.start; + + // VUID-VkImageBlit2-layerCount-00239 + // VUID-VkBlitImageInfo2-srcImage-00240 + if src_layer_count != dst_layer_count { + return Err(CopyError::ArrayLayerCountMismatch { + region_index, + src_layer_count, + dst_layer_count, + }); + } + + let check_offset_extent = |resource: CopyErrorResource, + image_type: ImageType, + subresource_extent: [u32; 3], + offsets: [[u32; 3]; 2]| + -> Result<_, CopyError> { + match image_type { + ImageType::Dim1d => { + // VUID-VkBlitImageInfo2-srcImage-00245 + // VUID-VkBlitImageInfo2-dstImage-00250 + if !(offsets[0][1] == 0 && offsets[1][1] == 1) { + return Err(CopyError::OffsetsInvalidForImageType { + resource, + region_index, + offsets: [offsets[0][1], offsets[1][1]], + }); + } + + // VUID-VkBlitImageInfo2-srcImage-00247 + // VUID-VkBlitImageInfo2-dstImage-00252 + if !(offsets[0][2] == 0 && offsets[1][2] == 1) { + return Err(CopyError::OffsetsInvalidForImageType { + resource, + region_index, + offsets: [offsets[0][2], offsets[1][2]], + }); + } + } + ImageType::Dim2d => { + // VUID-VkBlitImageInfo2-srcImage-00247 + // VUID-VkBlitImageInfo2-dstImage-00252 + if !(offsets[0][2] == 0 && offsets[1][2] == 1) { + return Err(CopyError::OffsetsInvalidForImageType { + resource, + region_index, + offsets: [offsets[0][2], offsets[1][2]], + }); + } + } + ImageType::Dim3d => (), + } + + let offset_range_end = [ + max(offsets[0][0], offsets[1][0]), + max(offsets[0][1], offsets[1][1]), + max(offsets[0][2], offsets[1][2]), + ]; + + for i in 0..3 { + // VUID-VkBlitImageInfo2-srcOffset-00243 + // VUID-VkBlitImageInfo2-srcOffset-00244 + // VUID-VkBlitImageInfo2-srcOffset-00246 + // VUID-VkBlitImageInfo2-dstOffset-00248 + // VUID-VkBlitImageInfo2-dstOffset-00249 + // VUID-VkBlitImageInfo2-dstOffset-00251 + if offset_range_end[i] > subresource_extent[i] { + return Err(CopyError::RegionOutOfImageBounds { + resource, + region_index, + offset_range_end, + subresource_extent, + }); + } + } + + Ok(()) + }; + + check_offset_extent( + CopyErrorResource::Source, + src_image_type, + src_subresource_extent, + src_offsets, + )?; + check_offset_extent( + CopyErrorResource::Destination, + dst_image_type, + dst_subresource_extent, + dst_offsets, + )?; + + // VUID-VkBlitImageInfo2-pRegions-00217 + if same_image { + let src_region_index = region_index; + let src_subresource_axes = [ + src_image_inner.first_mipmap_level + src_subresource.mip_level + ..src_image_inner.first_mipmap_level + src_subresource.mip_level + 1, + src_image_inner.first_layer + src_subresource.array_layers.start + ..src_image_inner.first_layer + src_subresource.array_layers.end, + ]; + let src_extent_axes = [ + min(src_offsets[0][0], src_offsets[1][0]) + ..max(src_offsets[0][0], src_offsets[1][0]), + min(src_offsets[0][1], src_offsets[1][1]) + ..max(src_offsets[0][1], src_offsets[1][1]), + min(src_offsets[0][2], src_offsets[1][2]) + ..max(src_offsets[0][2], src_offsets[1][2]), + ]; + + for (dst_region_index, dst_region) in regions.iter().enumerate() { + let &ImageBlit { + ref dst_subresource, + dst_offsets, + .. + } = dst_region; + + let dst_subresource_axes = [ + dst_image_inner.first_mipmap_level + dst_subresource.mip_level + ..dst_image_inner.first_mipmap_level + dst_subresource.mip_level + 1, + dst_image_inner.first_layer + src_subresource.array_layers.start + ..dst_image_inner.first_layer + src_subresource.array_layers.end, + ]; + + if src_subresource_axes.iter().zip(dst_subresource_axes).any( + |(src_range, dst_range)| { + src_range.start >= dst_range.end || dst_range.start >= src_range.end + }, + ) { + continue; + } + + // If the subresource axes all overlap, then the source and destination must + // have the same layout. + overlap_subresource_indices = Some((src_region_index, dst_region_index)); + + let dst_extent_axes = [ + min(dst_offsets[0][0], dst_offsets[1][0]) + ..max(dst_offsets[0][0], dst_offsets[1][0]), + min(dst_offsets[0][1], dst_offsets[1][1]) + ..max(dst_offsets[0][1], dst_offsets[1][1]), + min(dst_offsets[0][2], dst_offsets[1][2]) + ..max(dst_offsets[0][2], dst_offsets[1][2]), + ]; + + if src_extent_axes + .iter() + .zip(dst_extent_axes) + .any(|(src_range, dst_range)| { + src_range.start >= dst_range.end || dst_range.start >= src_range.end + }) + { + continue; + } + + // If the extent axes *also* overlap, then that's an error. + overlap_extent_indices = Some((src_region_index, dst_region_index)); + } + } + } + + // VUID-VkBlitImageInfo2-pRegions-00217 + if let Some((src_region_index, dst_region_index)) = overlap_extent_indices { + return Err(CopyError::OverlappingRegions { + src_region_index, + dst_region_index, + }); + } + + // VUID-VkBlitImageInfo2-srcImageLayout-00221 + // VUID-VkBlitImageInfo2-dstImageLayout-00226 + if let Some((src_region_index, dst_region_index)) = overlap_subresource_indices { + if src_image_layout != dst_image_layout { + return Err(CopyError::OverlappingSubresourcesLayoutMismatch { + src_region_index, + dst_region_index, + src_image_layout, + dst_image_layout, + }); + } + } + + // TODO: sync check + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn blit_image_unchecked(&mut self, blit_image_info: BlitImageInfo) -> &mut Self { + let BlitImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + filter, + _ne: _, + } = blit_image_info; + + if regions.is_empty() { + return self; + } + + let src_image_inner = src_image.inner(); + let dst_image_inner = dst_image.inner(); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageBlit { + ref src_subresource, + src_offsets, + ref dst_subresource, + dst_offsets, + _ne: _, + } = region; + + let mut src_subresource = src_subresource.clone(); + src_subresource.array_layers.start += src_image_inner.first_layer; + src_subresource.array_layers.end += src_image_inner.first_layer; + src_subresource.mip_level += src_image_inner.first_mipmap_level; + + let mut dst_subresource = dst_subresource.clone(); + dst_subresource.array_layers.start += dst_image_inner.first_layer; + dst_subresource.array_layers.end += dst_image_inner.first_layer; + dst_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::ImageBlit2 { + src_subresource: src_subresource.into(), + src_offsets: [ + ash::vk::Offset3D { + x: src_offsets[0][0] as i32, + y: src_offsets[0][1] as i32, + z: src_offsets[0][2] as i32, + }, + ash::vk::Offset3D { + x: src_offsets[1][0] as i32, + y: src_offsets[1][1] as i32, + z: src_offsets[1][2] as i32, + }, + ], + dst_subresource: dst_subresource.into(), + dst_offsets: [ + ash::vk::Offset3D { + x: dst_offsets[0][0] as i32, + y: dst_offsets[0][1] as i32, + z: dst_offsets[0][2] as i32, + }, + ash::vk::Offset3D { + x: dst_offsets[1][0] as i32, + y: dst_offsets[1][1] as i32, + z: dst_offsets[1][2] as i32, + }, + ], + ..Default::default() + } + }) + .collect(); + + let blit_image_info = ash::vk::BlitImageInfo2 { + src_image: src_image_inner.image.handle(), + src_image_layout: src_image_layout.into(), + dst_image: dst_image_inner.image.handle(), + dst_image_layout: dst_image_layout.into(), + region_count: regions.len() as u32, + p_regions: regions.as_ptr(), + filter: filter.into(), + ..Default::default() + }; + + if self.device().api_version() >= Version::V1_3 { + (fns.v1_3.cmd_blit_image2)(self.handle(), &blit_image_info); + } else { + (fns.khr_copy_commands2.cmd_blit_image2_khr)(self.handle(), &blit_image_info); + } + } else { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageBlit { + ref src_subresource, + src_offsets, + ref dst_subresource, + dst_offsets, + _ne: _, + } = region; + + let mut src_subresource = src_subresource.clone(); + src_subresource.array_layers.start += src_image_inner.first_layer; + src_subresource.array_layers.end += src_image_inner.first_layer; + src_subresource.mip_level += src_image_inner.first_mipmap_level; + + let mut dst_subresource = dst_subresource.clone(); + dst_subresource.array_layers.start += dst_image_inner.first_layer; + dst_subresource.array_layers.end += dst_image_inner.first_layer; + dst_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::ImageBlit { + src_subresource: src_subresource.into(), + src_offsets: [ + ash::vk::Offset3D { + x: src_offsets[0][0] as i32, + y: src_offsets[0][1] as i32, + z: src_offsets[0][2] as i32, + }, + ash::vk::Offset3D { + x: src_offsets[1][0] as i32, + y: src_offsets[1][1] as i32, + z: src_offsets[1][2] as i32, + }, + ], + dst_subresource: dst_subresource.into(), + dst_offsets: [ + ash::vk::Offset3D { + x: dst_offsets[0][0] as i32, + y: dst_offsets[0][1] as i32, + z: dst_offsets[0][2] as i32, + }, + ash::vk::Offset3D { + x: dst_offsets[1][0] as i32, + y: dst_offsets[1][1] as i32, + z: dst_offsets[1][2] as i32, + }, + ], + } + }) + .collect(); + + (fns.v1_0.cmd_blit_image)( + self.handle(), + src_image_inner.image.handle(), + src_image_layout.into(), + dst_image_inner.image.handle(), + dst_image_layout.into(), + regions.len() as u32, + regions.as_ptr(), + filter.into(), + ); + } + + let command_index = self.next_command_index; + let command_name = "blit_image"; + let src_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }; + let dst_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }; + + for region in regions { + let ImageBlit { + src_subresource, + src_offsets: _, + dst_subresource, + dst_offsets: _, + _ne: _, + } = region; + + let mut src_subresource_range = ImageSubresourceRange::from(src_subresource); + src_subresource_range.array_layers.start += src_image_inner.first_layer; + src_subresource_range.array_layers.end += src_image_inner.first_layer; + src_subresource_range.mip_levels.start += src_image_inner.first_mipmap_level; + src_subresource_range.mip_levels.end += src_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &src_use_ref, + src_image_inner.image, + src_subresource_range, + PipelineStageAccess::Blit_TransferRead, + src_image_layout, + ); + + let mut dst_subresource_range = ImageSubresourceRange::from(dst_subresource); + dst_subresource_range.array_layers.start += dst_image_inner.first_layer; + dst_subresource_range.array_layers.end += dst_image_inner.first_layer; + dst_subresource_range.mip_levels.start += dst_image_inner.first_mipmap_level; + dst_subresource_range.mip_levels.end += dst_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &dst_use_ref, + dst_image_inner.image, + dst_subresource_range, + PipelineStageAccess::Blit_TransferWrite, + dst_image_layout, + ); + } + + self.resources.push(Box::new(src_image)); + self.resources.push(Box::new(dst_image)); + + self.next_command_index += 1; + self + } + + /// Resolves a multisampled image into a single-sampled image. + /// + /// # Panics + /// + /// - Panics if `src_image` or `dst_image` were not created from the same device + /// as `self`. + /// + /// # Safety + /// + /// - Appropriate synchronization must be provided for all images + /// that are accessed by the command. + /// - All images that are accessed by the command must be in the expected image layout. + #[inline] + pub unsafe fn resolve_image( + &mut self, + resolve_image_info: ResolveImageInfo, + ) -> Result<&mut Self, CopyError> { + self.validate_resolve_image(&resolve_image_info)?; + + unsafe { Ok(self.resolve_image_unchecked(resolve_image_info)) } + } + + fn validate_resolve_image( + &self, + resolve_image_info: &ResolveImageInfo, + ) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdResolveImage2-renderpass + if self.builder_state.render_pass.is_some() { + return Err(CopyError::ForbiddenInsideRenderPass); + } + + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdResolveImage2-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::GRAPHICS) + { + return Err(CopyError::NotSupportedByQueueFamily); + } + + let &ResolveImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = resolve_image_info; + + // VUID-VkResolveImageInfo2-srcImageLayout-parameter + src_image_layout.validate_device(device)?; + + // VUID-VkResolveImageInfo2-dstImageLayout-parameter + dst_image_layout.validate_device(device)?; + + // VUID-VkResolveImageInfo2-commonparent + assert_eq!(device, src_image.device()); + assert_eq!(device, dst_image.device()); + + let src_image_type = src_image.dimensions().image_type(); + let dst_image_type = dst_image.dimensions().image_type(); + + // VUID-VkResolveImageInfo2-srcImage-00257 + if src_image.samples() == SampleCount::Sample1 { + return Err(CopyError::SampleCountInvalid { + resource: CopyErrorResource::Source, + sample_count: dst_image.samples(), + allowed_sample_counts: SampleCounts::SAMPLE_2 + | SampleCounts::SAMPLE_4 + | SampleCounts::SAMPLE_8 + | SampleCounts::SAMPLE_16 + | SampleCounts::SAMPLE_32 + | SampleCounts::SAMPLE_64, + }); + } + + // VUID-VkResolveImageInfo2-dstImage-00259 + if dst_image.samples() != SampleCount::Sample1 { + return Err(CopyError::SampleCountInvalid { + resource: CopyErrorResource::Destination, + sample_count: dst_image.samples(), + allowed_sample_counts: SampleCounts::SAMPLE_1, + }); + } + + // VUID-VkResolveImageInfo2-dstImage-02003 + if !dst_image + .format_features() + .intersects(FormatFeatures::COLOR_ATTACHMENT) + { + return Err(CopyError::MissingFormatFeature { + resource: CopyErrorResource::Destination, + format_feature: "color_attachment", + }); + } + + // VUID-VkResolveImageInfo2-srcImage-01386 + if src_image.format() != dst_image.format() { + return Err(CopyError::FormatsMismatch { + src_format: src_image.format(), + dst_format: dst_image.format(), + }); + } + + // VUID-VkResolveImageInfo2-srcImageLayout-01400 + if !matches!( + src_image_layout, + ImageLayout::TransferSrcOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Source, + image_layout: src_image_layout, + }); + } + + // VUID-VkResolveImageInfo2-dstImageLayout-01401 + if !matches!( + dst_image_layout, + ImageLayout::TransferDstOptimal | ImageLayout::General + ) { + return Err(CopyError::ImageLayoutInvalid { + resource: CopyErrorResource::Destination, + image_layout: dst_image_layout, + }); + } + + // Should be guaranteed by the requirement that formats match, and that the destination + // image format features support color attachments. + debug_assert!( + src_image.format().aspects().intersects(ImageAspects::COLOR) + && dst_image.format().aspects().intersects(ImageAspects::COLOR) + ); + + for (region_index, region) in regions.iter().enumerate() { + let &ImageResolve { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + let check_subresource = |resource: CopyErrorResource, + image: &dyn ImageAccess, + subresource: &ImageSubresourceLayers| + -> Result<_, CopyError> { + // VUID-VkResolveImageInfo2-srcSubresource-01709 + // VUID-VkResolveImageInfo2-dstSubresource-01710 + if subresource.mip_level >= image.mip_levels() { + return Err(CopyError::MipLevelsOutOfRange { + resource, + region_index, + mip_levels_range_end: subresource.mip_level + 1, + image_mip_levels: image.mip_levels(), + }); + } + + // VUID-VkImageSubresourceLayers-layerCount-01700 + // VUID-VkResolveImageInfo2-srcImage-04446 + // VUID-VkResolveImageInfo2-srcImage-04447 + assert!(!subresource.array_layers.is_empty()); + + // VUID-VkResolveImageInfo2-srcSubresource-01711 + // VUID-VkResolveImageInfo2-dstSubresource-01712 + // VUID-VkResolveImageInfo2-srcImage-04446 + // VUID-VkResolveImageInfo2-srcImage-04447 + if subresource.array_layers.end > image.dimensions().array_layers() { + return Err(CopyError::ArrayLayersOutOfRange { + resource: CopyErrorResource::Destination, + region_index, + array_layers_range_end: subresource.array_layers.end, + image_array_layers: image.dimensions().array_layers(), + }); + } + + // VUID-VkImageSubresourceLayers-aspectMask-parameter + subresource.aspects.validate_device(device)?; + + // VUID-VkImageSubresourceLayers-aspectMask-requiredbitmask + // VUID-VkImageResolve2-aspectMask-00266 + if subresource.aspects != (ImageAspects::COLOR) { + return Err(CopyError::AspectsNotAllowed { + resource, + region_index, + aspects: subresource.aspects, + allowed_aspects: ImageAspects::COLOR, + }); + } + + Ok(image + .dimensions() + .mip_level_dimensions(subresource.mip_level) + .unwrap() + .width_height_depth()) + }; + + let src_subresource_extent = + check_subresource(CopyErrorResource::Source, src_image, src_subresource)?; + let dst_subresource_extent = + check_subresource(CopyErrorResource::Destination, dst_image, dst_subresource)?; + + let src_layer_count = + src_subresource.array_layers.end - src_subresource.array_layers.start; + let dst_layer_count = + dst_subresource.array_layers.end - dst_subresource.array_layers.start; + + // VUID-VkImageResolve2-layerCount-00267 + // VUID-VkResolveImageInfo2-srcImage-04446 + // VUID-VkResolveImageInfo2-srcImage-04447 + if src_layer_count != dst_layer_count { + return Err(CopyError::ArrayLayerCountMismatch { + region_index, + src_layer_count, + dst_layer_count, + }); + } + + // No VUID, but it makes sense? + assert!(extent[0] != 0 && extent[1] != 0 && extent[2] != 0); + + let check_offset_extent = |resource: CopyErrorResource, + _image_type: ImageType, + subresource_extent: [u32; 3], + offset: [u32; 3]| + -> Result<_, CopyError> { + for i in 0..3 { + // No VUID, but makes sense? + assert!(extent[i] != 0); + + // VUID-VkResolveImageInfo2-srcOffset-00269 + // VUID-VkResolveImageInfo2-srcOffset-00270 + // VUID-VkResolveImageInfo2-srcOffset-00272 + // VUID-VkResolveImageInfo2-dstOffset-00274 + // VUID-VkResolveImageInfo2-dstOffset-00275 + // VUID-VkResolveImageInfo2-dstOffset-00277 + if offset[i] + extent[i] > subresource_extent[i] { + return Err(CopyError::RegionOutOfImageBounds { + resource, + region_index, + offset_range_end: [ + offset[0] + extent[0], + offset[1] + extent[1], + offset[2] + extent[2], + ], + subresource_extent, + }); + } + } + + Ok(()) + }; + + check_offset_extent( + CopyErrorResource::Source, + src_image_type, + src_subresource_extent, + src_offset, + )?; + check_offset_extent( + CopyErrorResource::Destination, + dst_image_type, + dst_subresource_extent, + dst_offset, + )?; + } + + // VUID-VkResolveImageInfo2-pRegions-00255 + // Can't occur as long as memory aliasing isn't allowed, because `src_image` and + // `dst_image` must have different sample counts and therefore can never be the same image. + + // TODO: sync check + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn resolve_image_unchecked( + &mut self, + resolve_image_info: ResolveImageInfo, + ) -> &mut Self { + let ResolveImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = resolve_image_info; + + if regions.is_empty() { + return self; + } + + let src_image_inner = src_image.inner(); + let dst_image_inner = dst_image.inner(); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageResolve { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + let mut src_subresource = src_subresource.clone(); + src_subresource.array_layers.start += src_image_inner.first_layer; + src_subresource.array_layers.end += src_image_inner.first_layer; + src_subresource.mip_level += src_image_inner.first_mipmap_level; + + let mut dst_subresource = dst_subresource.clone(); + dst_subresource.array_layers.start += dst_image_inner.first_layer; + dst_subresource.array_layers.end += dst_image_inner.first_layer; + dst_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::ImageResolve2 { + src_subresource: src_subresource.into(), + src_offset: ash::vk::Offset3D { + x: src_offset[0] as i32, + y: src_offset[1] as i32, + z: src_offset[2] as i32, + }, + dst_subresource: dst_subresource.into(), + dst_offset: ash::vk::Offset3D { + x: dst_offset[0] as i32, + y: dst_offset[1] as i32, + z: dst_offset[2] as i32, + }, + extent: ash::vk::Extent3D { + width: extent[0], + height: extent[1], + depth: extent[2], + }, + ..Default::default() + } + }) + .collect(); + + let resolve_image_info = ash::vk::ResolveImageInfo2 { + src_image: src_image_inner.image.handle(), + src_image_layout: src_image_layout.into(), + dst_image: dst_image_inner.image.handle(), + dst_image_layout: dst_image_layout.into(), + region_count: regions.len() as u32, + p_regions: regions.as_ptr(), + ..Default::default() + }; + + if self.device().api_version() >= Version::V1_3 { + (fns.v1_3.cmd_resolve_image2)(self.handle(), &resolve_image_info); + } else { + (fns.khr_copy_commands2.cmd_resolve_image2_khr)(self.handle(), &resolve_image_info); + } + } else { + let regions: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageResolve { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + let mut src_subresource = src_subresource.clone(); + src_subresource.array_layers.start += src_image_inner.first_layer; + src_subresource.array_layers.end += src_image_inner.first_layer; + src_subresource.mip_level += src_image_inner.first_mipmap_level; + + let mut dst_subresource = dst_subresource.clone(); + dst_subresource.array_layers.start += dst_image_inner.first_layer; + dst_subresource.array_layers.end += dst_image_inner.first_layer; + dst_subresource.mip_level += dst_image_inner.first_mipmap_level; + + ash::vk::ImageResolve { + src_subresource: src_subresource.into(), + src_offset: ash::vk::Offset3D { + x: src_offset[0] as i32, + y: src_offset[1] as i32, + z: src_offset[2] as i32, + }, + dst_subresource: dst_subresource.into(), + dst_offset: ash::vk::Offset3D { + x: dst_offset[0] as i32, + y: dst_offset[1] as i32, + z: dst_offset[2] as i32, + }, + extent: ash::vk::Extent3D { + width: extent[0], + height: extent[1], + depth: extent[2], + }, + } + }) + .collect(); + + (fns.v1_0.cmd_resolve_image)( + self.handle(), + src_image_inner.image.handle(), + src_image_layout.into(), + dst_image_inner.image.handle(), + dst_image_layout.into(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + let command_index = self.next_command_index; + let command_name = "resolve_image"; + let src_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }; + let dst_use_ref = ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }; + + for region in regions { + let ImageResolve { + src_subresource, + src_offset: _, + dst_subresource, + dst_offset: _, + extent: _, + _ne: _, + } = region; + + let mut src_subresource_range = ImageSubresourceRange::from(src_subresource); + src_subresource_range.array_layers.start += src_image_inner.first_layer; + src_subresource_range.array_layers.end += src_image_inner.first_layer; + src_subresource_range.mip_levels.start += src_image_inner.first_mipmap_level; + src_subresource_range.mip_levels.end += src_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &src_use_ref, + src_image_inner.image, + src_subresource_range, + PipelineStageAccess::Resolve_TransferRead, + src_image_layout, + ); + + let mut dst_subresource_range = ImageSubresourceRange::from(dst_subresource); + dst_subresource_range.array_layers.start += dst_image_inner.first_layer; + dst_subresource_range.array_layers.end += dst_image_inner.first_layer; + dst_subresource_range.mip_levels.start += dst_image_inner.first_mipmap_level; + dst_subresource_range.mip_levels.end += dst_image_inner.first_mipmap_level; + self.resources_usage_state.record_image_access( + &dst_use_ref, + dst_image_inner.image, + dst_subresource_range, + PipelineStageAccess::Resolve_TransferWrite, + dst_image_layout, + ); + } + + self.resources.push(Box::new(src_image)); + self.resources.push(Box::new(dst_image)); + + self.next_command_index += 1; + self + } +} |