diff options
Diffstat (limited to 'src/command_buffer/commands/copy.rs')
-rw-r--r-- | src/command_buffer/commands/copy.rs | 5404 |
1 files changed, 5404 insertions, 0 deletions
diff --git a/src/command_buffer/commands/copy.rs b/src/command_buffer/commands/copy.rs new file mode 100644 index 0000000..1911713 --- /dev/null +++ b/src/command_buffer/commands/copy.rs @@ -0,0 +1,5404 @@ +// 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 crate::{ + buffer::{BufferUsage, Subbuffer}, + command_buffer::{ + allocator::CommandBufferAllocator, + synced::{Command, Resource, SyncCommandBufferBuilder, SyncCommandBufferBuilderError}, + sys::UnsafeCommandBufferBuilder, + AutoCommandBufferBuilder, ResourceInCommand, ResourceUseRef, + }, + device::{DeviceOwned, QueueFlags}, + format::{Format, FormatFeatures, NumericType}, + image::{ + ImageAccess, ImageAspects, ImageDimensions, ImageLayout, ImageSubresourceLayers, ImageType, + ImageUsage, SampleCount, SampleCounts, + }, + sampler::Filter, + sync::{AccessFlags, PipelineMemoryAccess, PipelineStages}, + DeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanObject, +}; +use smallvec::{smallvec, SmallVec}; +use std::{ + cmp::{max, min}, + error::Error, + fmt::{Display, Error as FmtError, Formatter}, + mem::size_of, + sync::Arc, +}; + +/// # Commands to transfer data between resources. +impl<L, A> AutoCommandBufferBuilder<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`. + pub 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 { + self.inner.copy_buffer(copy_buffer_info)?; + } + + Ok(self) + } + + fn validate_copy_buffer(&self, copy_buffer_info: &CopyBufferInfo) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyBuffer2-renderpass + if self.render_pass_state.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, + }); + } + + Ok(()) + } + + /// 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`. + pub fn copy_image(&mut self, copy_image_info: CopyImageInfo) -> Result<&mut Self, CopyError> { + self.validate_copy_image(©_image_info)?; + + unsafe { + self.inner.copy_image(copy_image_info)?; + } + + Ok(self) + } + + fn validate_copy_image(&self, copy_image_info: &CopyImageInfo) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyImage2-renderpass + if self.render_pass_state.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, + }); + } + } + + Ok(()) + } + + /// Copies from a buffer to an image. + pub 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 { + self.inner.copy_buffer_to_image(copy_buffer_to_image_info)?; + } + + Ok(self) + } + + fn validate_copy_buffer_to_image( + &self, + copy_buffer_to_image_info: &CopyBufferToImageInfo, + ) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyBufferToImage2-renderpass + if self.render_pass_state.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(()) + } + + /// Copies from an image to a buffer. + pub 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 { + self.inner.copy_image_to_buffer(copy_image_to_buffer_info)?; + } + + Ok(self) + } + + fn validate_copy_image_to_buffer( + &self, + copy_image_to_buffer_info: &CopyImageToBufferInfo, + ) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdCopyImageToBuffer2-renderpass + if self.render_pass_state.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. + + Ok(()) + } + + /// 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. + /// + /// # Panics + /// + /// - Panics if the source or the destination was not created with `device`. + pub fn blit_image(&mut self, blit_image_info: BlitImageInfo) -> Result<&mut Self, CopyError> { + self.validate_blit_image(&blit_image_info)?; + + unsafe { + self.inner.blit_image(blit_image_info)?; + } + + Ok(self) + } + + fn validate_blit_image(&self, blit_image_info: &BlitImageInfo) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdBlitImage2-renderpass + if self.render_pass_state.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, + }); + } + } + + Ok(()) + } + + /// 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`. + pub fn resolve_image( + &mut self, + resolve_image_info: ResolveImageInfo, + ) -> Result<&mut Self, CopyError> { + self.validate_resolve_image(&resolve_image_info)?; + + unsafe { + self.inner.resolve_image(resolve_image_info)?; + } + + Ok(self) + } + + fn validate_resolve_image( + &self, + resolve_image_info: &ResolveImageInfo, + ) -> Result<(), CopyError> { + let device = self.device(); + + // VUID-vkCmdResolveImage2-renderpass + if self.render_pass_state.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. + + Ok(()) + } +} + +impl SyncCommandBufferBuilder { + /// Calls `vkCmdCopyBuffer` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_buffer( + &mut self, + copy_buffer_info: CopyBufferInfo, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + copy_buffer_info: CopyBufferInfo, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "copy_buffer" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.copy_buffer(&self.copy_buffer_info); + } + } + + let CopyBufferInfo { + src_buffer, + dst_buffer, + regions, + _ne: _, + } = ©_buffer_info; + + let command_index = self.commands.len(); + let command_name = "copy_buffer"; + let resources: SmallVec<[_; 8]> = regions + .iter() + .flat_map(|region| { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne: _, + } = region; + + [ + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: src_buffer.clone(), + range: src_offset..src_offset + size, + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_READ, + exclusive: false, + }, + }, + ), + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: dst_buffer.clone(), + range: dst_offset..dst_offset + size, + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_WRITE, + exclusive: true, + }, + }, + ), + ] + }) + .collect(); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { copy_buffer_info })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdCopyImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_image( + &mut self, + copy_image_info: CopyImageInfo, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + copy_image_info: CopyImageInfo, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "copy_buffer_to_image" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.copy_image(&self.copy_image_info); + } + } + + let &CopyImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = ©_image_info; + + let command_index = self.commands.len(); + let command_name = "copy_image"; + let resources: SmallVec<[_; 8]> = regions + .iter() + .flat_map(|region| { + let &ImageCopy { + ref src_subresource, + src_offset: _, + ref dst_subresource, + dst_offset: _, + extent: _, + _ne: _, + } = region; + + [ + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }, + Resource::Image { + image: src_image.clone(), + subresource_range: src_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_READ, + exclusive: false, + }, + start_layout: src_image_layout, + end_layout: src_image_layout, + }, + ), + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }, + Resource::Image { + image: dst_image.clone(), + subresource_range: dst_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_WRITE, + exclusive: true, + }, + start_layout: dst_image_layout, + end_layout: dst_image_layout, + }, + ), + ] + }) + .collect(); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { copy_image_info })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdCopyBufferToImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_buffer_to_image( + &mut self, + copy_buffer_to_image_info: CopyBufferToImageInfo, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + copy_buffer_to_image_info: CopyBufferToImageInfo, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "copy_buffer_to_image" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.copy_buffer_to_image(&self.copy_buffer_to_image_info); + } + } + + let &CopyBufferToImageInfo { + ref src_buffer, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = ©_buffer_to_image_info; + + let command_index = self.commands.len(); + let command_name = "copy_buffer_to_image"; + let resources: SmallVec<[_; 8]> = regions + .iter() + .flat_map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length: _, + buffer_image_height: _, + ref image_subresource, + image_offset: _, + image_extent: _, + _ne: _, + } = region; + + [ + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: src_buffer.clone(), + range: buffer_offset + ..buffer_offset + region.buffer_copy_size(dst_image.format()), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_READ, + exclusive: false, + }, + }, + ), + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }, + Resource::Image { + image: dst_image.clone(), + subresource_range: image_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_WRITE, + exclusive: true, + }, + start_layout: dst_image_layout, + end_layout: dst_image_layout, + }, + ), + ] + }) + .collect(); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { + copy_buffer_to_image_info, + })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdCopyImageToBuffer` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_image_to_buffer( + &mut self, + copy_image_to_buffer_info: CopyImageToBufferInfo, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + copy_image_to_buffer_info: CopyImageToBufferInfo, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "copy_image_to_buffer" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.copy_image_to_buffer(&self.copy_image_to_buffer_info); + } + } + + let &CopyImageToBufferInfo { + ref src_image, + src_image_layout, + ref dst_buffer, + ref regions, + _ne: _, + } = ©_image_to_buffer_info; + + let command_index = self.commands.len(); + let command_name = "copy_image_to_buffer"; + let resources: SmallVec<[_; 8]> = regions + .iter() + .flat_map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length: _, + buffer_image_height: _, + ref image_subresource, + image_offset: _, + image_extent: _, + _ne: _, + } = region; + + [ + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }, + Resource::Image { + image: src_image.clone(), + subresource_range: image_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_READ, + exclusive: false, + }, + start_layout: src_image_layout, + end_layout: src_image_layout, + }, + ), + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: dst_buffer.clone(), + range: buffer_offset + ..buffer_offset + region.buffer_copy_size(src_image.format()), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_WRITE, + exclusive: true, + }, + }, + ), + ] + }) + .collect(); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { + copy_image_to_buffer_info, + })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdBlitImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn blit_image( + &mut self, + blit_image_info: BlitImageInfo, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + blit_image_info: BlitImageInfo, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "blit_image" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.blit_image(&self.blit_image_info); + } + } + + let &BlitImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + filter: _, + _ne: _, + } = &blit_image_info; + + let command_index = self.commands.len(); + let command_name = "blit_image"; + let resources: SmallVec<[_; 8]> = regions + .iter() + .flat_map(|region| { + let &ImageBlit { + ref src_subresource, + src_offsets: _, + ref dst_subresource, + dst_offsets: _, + _ne: _, + } = region; + + [ + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }, + Resource::Image { + image: src_image.clone(), + subresource_range: src_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_READ, + exclusive: false, + }, + start_layout: src_image_layout, + end_layout: src_image_layout, + }, + ), + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }, + Resource::Image { + image: dst_image.clone(), + subresource_range: dst_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_WRITE, + exclusive: true, + }, + start_layout: dst_image_layout, + end_layout: dst_image_layout, + }, + ), + ] + }) + .collect(); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { blit_image_info })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdResolveImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn resolve_image( + &mut self, + resolve_image_info: ResolveImageInfo, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + resolve_image_info: ResolveImageInfo, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "resolve_image" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.resolve_image(&self.resolve_image_info); + } + } + + let &ResolveImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = &resolve_image_info; + + let command_index = self.commands.len(); + let command_name = "resolve_image"; + let resources: SmallVec<[_; 8]> = regions + .iter() + .flat_map(|region| { + let &ImageResolve { + ref src_subresource, + src_offset: _, + ref dst_subresource, + dst_offset: _, + extent: _, + _ne: _, + } = region; + + [ + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Source, + secondary_use_ref: None, + }, + Resource::Image { + image: src_image.clone(), + subresource_range: src_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_READ, + exclusive: false, + }, + start_layout: src_image_layout, + end_layout: src_image_layout, + }, + ), + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::Destination, + secondary_use_ref: None, + }, + Resource::Image { + image: dst_image.clone(), + subresource_range: dst_subresource.clone().into(), + memory: PipelineMemoryAccess { + stages: PipelineStages::ALL_TRANSFER, + access: AccessFlags::TRANSFER_WRITE, + exclusive: true, + }, + start_layout: dst_image_layout, + end_layout: dst_image_layout, + }, + ), + ] + }) + .collect(); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { resolve_image_info })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } +} + +impl UnsafeCommandBufferBuilder { + /// Calls `vkCmdCopyBuffer` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_buffer(&mut self, copy_buffer_info: &CopyBufferInfo) { + let CopyBufferInfo { + src_buffer, + dst_buffer, + regions, + _ne: _, + } = copy_buffer_info; + + if regions.is_empty() { + return; + } + + 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(), + ); + } + } + + /// Calls `vkCmdCopyImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_image(&mut self, copy_image_info: &CopyImageInfo) { + let &CopyImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = copy_image_info; + + if regions.is_empty() { + return; + } + + 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 + .into_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 + .into_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(), + ); + } + } + + /// Calls `vkCmdCopyBufferToImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_buffer_to_image( + &mut self, + copy_buffer_to_image_info: &CopyBufferToImageInfo, + ) { + let &CopyBufferToImageInfo { + ref src_buffer, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = copy_buffer_to_image_info; + + if regions.is_empty() { + return; + } + + 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(), + ); + } + } + + /// Calls `vkCmdCopyImageToBuffer` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_image_to_buffer( + &mut self, + copy_image_to_buffer_info: &CopyImageToBufferInfo, + ) { + let &CopyImageToBufferInfo { + ref src_image, + src_image_layout, + ref dst_buffer, + ref regions, + _ne: _, + } = copy_image_to_buffer_info; + + if regions.is_empty() { + return; + } + + 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(), + ); + } + } + + /// Calls `vkCmdBlitImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn blit_image(&mut self, blit_image_info: &BlitImageInfo) { + let &BlitImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + filter, + _ne, + } = blit_image_info; + + if regions.is_empty() { + return; + } + + 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 + .into_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 + .into_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(), + ); + } + } + + /// Calls `vkCmdResolveImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn resolve_image(&mut self, resolve_image_info: &ResolveImageInfo) { + let &ResolveImageInfo { + ref src_image, + src_image_layout, + ref dst_image, + dst_image_layout, + ref regions, + _ne: _, + } = resolve_image_info; + + if regions.is_empty() { + return; + } + + 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 + .into_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 + .into_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(), + ); + } + } +} + +/// Parameters to copy data from a buffer to another buffer. +/// +/// The fields of `regions` represent bytes. +#[derive(Clone, Debug)] +pub struct CopyBufferInfo { + /// The buffer to copy from. + /// + /// There is no default value. + pub src_buffer: Subbuffer<[u8]>, + + /// The buffer to copy to. + /// + /// There is no default value. + pub dst_buffer: Subbuffer<[u8]>, + + /// The regions of both buffers to copy between, specified in bytes. + /// + /// The default value is a single region, with zero offsets and a `size` equal to the smallest + /// of the two buffers. + pub regions: SmallVec<[BufferCopy; 1]>, + + pub _ne: crate::NonExhaustive, +} + +impl CopyBufferInfo { + /// Returns a `CopyBufferInfo` with the specified `src_buffer` and `dst_buffer`. + #[inline] + pub fn buffers(src_buffer: Subbuffer<impl ?Sized>, dst_buffer: Subbuffer<impl ?Sized>) -> Self { + let region = BufferCopy { + src_offset: 0, + dst_offset: 0, + size: min(src_buffer.size(), dst_buffer.size()), + ..Default::default() + }; + + Self { + src_buffer: src_buffer.into_bytes(), + dst_buffer: dst_buffer.into_bytes(), + regions: smallvec![region], + _ne: crate::NonExhaustive(()), + } + } +} + +/// Parameters to copy data from a buffer to another buffer, with type information. +/// +/// The fields of `regions` represent elements of `T`. +#[derive(Clone, Debug)] +pub struct CopyBufferInfoTyped<T> { + /// The buffer to copy from. + /// + /// There is no default value. + pub src_buffer: Subbuffer<[T]>, + + /// The buffer to copy to. + /// + /// There is no default value. + pub dst_buffer: Subbuffer<[T]>, + + /// The regions of both buffers to copy between, specified in elements of `T`. + /// + /// The default value is a single region, with zero offsets and a `size` equal to the smallest + /// of the two buffers. + pub regions: SmallVec<[BufferCopy; 1]>, + + pub _ne: crate::NonExhaustive, +} + +impl<T> CopyBufferInfoTyped<T> { + /// Returns a `CopyBufferInfoTyped` with the specified `src_buffer` and `dst_buffer`. + pub fn buffers(src_buffer: Subbuffer<[T]>, dst_buffer: Subbuffer<[T]>) -> Self { + let region = BufferCopy { + size: min(src_buffer.len(), dst_buffer.len()), + ..Default::default() + }; + + Self { + src_buffer, + dst_buffer, + regions: smallvec![region], + _ne: crate::NonExhaustive(()), + } + } +} + +impl<T> From<CopyBufferInfoTyped<T>> for CopyBufferInfo { + fn from(typed: CopyBufferInfoTyped<T>) -> Self { + let CopyBufferInfoTyped { + src_buffer, + dst_buffer, + mut regions, + _ne: _, + } = typed; + + for region in &mut regions { + region.src_offset *= size_of::<T>() as DeviceSize; + region.dst_offset *= size_of::<T>() as DeviceSize; + region.size *= size_of::<T>() as DeviceSize; + } + + Self { + src_buffer: src_buffer.as_bytes().clone(), + dst_buffer: dst_buffer.as_bytes().clone(), + regions, + _ne: crate::NonExhaustive(()), + } + } +} + +/// A region of data to copy between buffers. +#[derive(Clone, Debug)] +pub struct BufferCopy { + /// The offset in bytes or elements from the start of `src_buffer` that copying will + /// start from. + /// + /// The default value is `0`. + pub src_offset: DeviceSize, + + /// The offset in bytes or elements from the start of `dst_buffer` that copying will + /// start from. + /// + /// The default value is `0`. + pub dst_offset: DeviceSize, + + /// The number of bytes or elements to copy. + /// + /// The default value is `0`, which must be overridden. + pub size: DeviceSize, + + pub _ne: crate::NonExhaustive, +} + +impl Default for BufferCopy { + #[inline] + fn default() -> Self { + Self { + src_offset: 0, + dst_offset: 0, + size: 0, + _ne: crate::NonExhaustive(()), + } + } +} + +/// Parameters to copy data from an image to another image. +#[derive(Clone, Debug)] +pub struct CopyImageInfo { + /// The image to copy from. + /// + /// There is no default value. + pub src_image: Arc<dyn ImageAccess>, + + /// The layout used for `src_image` during the copy operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferSrcOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferSrcOptimal`]. + pub src_image_layout: ImageLayout, + + /// The image to copy to. + /// + /// There is no default value. + pub dst_image: Arc<dyn ImageAccess>, + + /// The layout used for `dst_image` during the copy operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferDstOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferDstOptimal`]. + pub dst_image_layout: ImageLayout, + + /// The regions of both images to copy between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers and extent of the two images. All aspects of each image are selected, or + /// `plane0` if the image is multi-planar. + pub regions: SmallVec<[ImageCopy; 1]>, + + pub _ne: crate::NonExhaustive, +} + +impl CopyImageInfo { + /// Returns a `CopyImageInfo` with the specified `src_image` and `dst_image`. + #[inline] + pub fn images(src_image: Arc<dyn ImageAccess>, dst_image: Arc<dyn ImageAccess>) -> Self { + let min_array_layers = src_image + .dimensions() + .array_layers() + .min(dst_image.dimensions().array_layers()); + let region = ImageCopy { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + }, + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + }, + extent: { + let src_extent = src_image.dimensions().width_height_depth(); + let dst_extent = dst_image.dimensions().width_height_depth(); + + [ + src_extent[0].min(dst_extent[0]), + src_extent[1].min(dst_extent[1]), + src_extent[2].min(dst_extent[2]), + ] + }, + ..Default::default() + }; + + Self { + src_image, + src_image_layout: ImageLayout::TransferSrcOptimal, + dst_image, + dst_image_layout: ImageLayout::TransferDstOptimal, + regions: smallvec![region], + _ne: crate::NonExhaustive(()), + } + } +} + +/// A region of data to copy between images. +#[derive(Clone, Debug)] +pub struct ImageCopy { + /// The subresource of `src_image` to copy from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `src_image` that copying will start from. + /// + /// The default value is `[0; 3]`. + pub src_offset: [u32; 3], + + /// The subresource of `dst_image` to copy to. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` that copying will start from. + /// + /// The default value is `[0; 3]`. + pub dst_offset: [u32; 3], + + /// The extent of texels to copy. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub extent: [u32; 3], + + pub _ne: crate::NonExhaustive, +} + +impl Default for ImageCopy { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offset: [0; 3], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offset: [0; 3], + extent: [0; 3], + _ne: crate::NonExhaustive(()), + } + } +} + +/// Parameters to copy data from a buffer to an image. +#[derive(Clone, Debug)] +pub struct CopyBufferToImageInfo { + /// The buffer to copy from. + /// + /// There is no default value. + pub src_buffer: Subbuffer<[u8]>, + + /// The image to copy to. + /// + /// There is no default value. + pub dst_image: Arc<dyn ImageAccess>, + + /// The layout used for `dst_image` during the copy operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferDstOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferDstOptimal`]. + pub dst_image_layout: ImageLayout, + + /// The regions of the buffer and image to copy between. + /// + /// The default value is a single region, covering all of the buffer and the first mip level of + /// the image. All aspects of the image are selected, or `plane0` if the image is multi-planar. + pub regions: SmallVec<[BufferImageCopy; 1]>, + + pub _ne: crate::NonExhaustive, +} + +impl CopyBufferToImageInfo { + /// Returns a `CopyBufferToImageInfo` with the specified `src_buffer` and + /// `dst_image`. + #[inline] + pub fn buffer_image( + src_buffer: Subbuffer<impl ?Sized>, + dst_image: Arc<dyn ImageAccess>, + ) -> Self { + let region = BufferImageCopy { + image_subresource: dst_image.subresource_layers(), + image_extent: dst_image.dimensions().width_height_depth(), + ..Default::default() + }; + + Self { + src_buffer: src_buffer.into_bytes(), + dst_image, + dst_image_layout: ImageLayout::TransferDstOptimal, + regions: smallvec![region], + _ne: crate::NonExhaustive(()), + } + } +} + +/// Parameters to copy data from an image to a buffer. +#[derive(Clone, Debug)] +pub struct CopyImageToBufferInfo { + /// The image to copy from. + /// + /// There is no default value. + pub src_image: Arc<dyn ImageAccess>, + + /// The layout used for `src_image` during the copy operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferSrcOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferSrcOptimal`]. + pub src_image_layout: ImageLayout, + + /// The buffer to copy to. + /// + /// There is no default value. + pub dst_buffer: Subbuffer<[u8]>, + + /// The regions of the image and buffer to copy between. + /// + /// The default value is a single region, covering all of the buffer and the first mip level of + /// the image. All aspects of the image are selected, or `plane0` if the image is multi-planar. + pub regions: SmallVec<[BufferImageCopy; 1]>, + + pub _ne: crate::NonExhaustive, +} + +impl CopyImageToBufferInfo { + /// Returns a `CopyImageToBufferInfo` with the specified `src_image` and + /// `dst_buffer`. + #[inline] + pub fn image_buffer( + src_image: Arc<dyn ImageAccess>, + dst_buffer: Subbuffer<impl ?Sized>, + ) -> Self { + let region = BufferImageCopy { + image_subresource: src_image.subresource_layers(), + image_extent: src_image.dimensions().width_height_depth(), + ..Default::default() + }; + + Self { + src_image, + src_image_layout: ImageLayout::TransferSrcOptimal, + dst_buffer: dst_buffer.into_bytes(), + regions: smallvec![region], + _ne: crate::NonExhaustive(()), + } + } +} + +/// A region of data to copy between a buffer and an image. +#[derive(Clone, Debug)] +pub struct BufferImageCopy { + /// The offset in bytes from the start of the buffer that copying will start from. + /// + /// The default value is `0`. + pub buffer_offset: DeviceSize, + + /// The number of texels between successive rows of image data in the buffer. + /// + /// If set to `0`, the width of the image is used. + /// + /// The default value is `0`. + pub buffer_row_length: u32, + + /// The number of rows between successive depth slices of image data in the buffer. + /// + /// If set to `0`, the height of the image is used. + /// + /// The default value is `0`. + pub buffer_image_height: u32, + + /// The subresource of the image to copy from/to. + /// + /// The default value is empty, which must be overridden. + pub image_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of the image that copying will start from. + /// + /// The default value is `[0; 3]`. + pub image_offset: [u32; 3], + + /// The extent of texels in the image to copy. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub image_extent: [u32; 3], + + pub _ne: crate::NonExhaustive, +} + +impl Default for BufferImageCopy { + #[inline] + fn default() -> Self { + Self { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + image_offset: [0; 3], + image_extent: [0; 3], + _ne: crate::NonExhaustive(()), + } + } +} + +impl BufferImageCopy { + // Following + // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap20.html#copies-buffers-images-addressing + pub(crate) fn buffer_copy_size(&self, format: Format) -> DeviceSize { + let &BufferImageCopy { + buffer_offset: _, + mut buffer_row_length, + mut buffer_image_height, + ref image_subresource, + image_offset: _, + mut image_extent, + _ne: _, + } = self; + + if buffer_row_length == 0 { + buffer_row_length = image_extent[0]; + } + + if buffer_image_height == 0 { + buffer_image_height = image_extent[1]; + } + + // Scale down from texels to texel blocks, rounding up if needed. + let block_extent = format.block_extent(); + buffer_row_length = (buffer_row_length + block_extent[0] - 1) / block_extent[0]; + buffer_image_height = (buffer_image_height + block_extent[1] - 1) / block_extent[1]; + + for i in 0..3 { + image_extent[i] = (image_extent[i] + block_extent[i] - 1) / block_extent[i]; + } + + // Only one of these is greater than 1, take the greater number. + image_extent[2] = max( + image_extent[2], + image_subresource.array_layers.end - image_subresource.array_layers.start, + ); + + let blocks_to_last_slice = (image_extent[2] as DeviceSize - 1) + * buffer_image_height as DeviceSize + * buffer_row_length as DeviceSize; + let blocks_to_last_row = + (image_extent[1] as DeviceSize - 1) * buffer_row_length as DeviceSize; + let num_blocks = blocks_to_last_slice + blocks_to_last_row + image_extent[0] as DeviceSize; + + // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferImageCopy.html#_description + let block_size = if image_subresource.aspects.intersects(ImageAspects::STENCIL) { + 1 + } else if image_subresource.aspects.intersects(ImageAspects::DEPTH) { + match 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 { + format.block_size().unwrap() + }; + + num_blocks * block_size + } +} + +/// Parameters to blit image data. +#[derive(Clone, Debug)] +pub struct BlitImageInfo { + /// The image to blit from. + /// + /// There is no default value. + pub src_image: Arc<dyn ImageAccess>, + + /// The layout used for `src_image` during the blit operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferSrcOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferSrcOptimal`]. + pub src_image_layout: ImageLayout, + + /// The image to blit to. + /// + /// There is no default value. + pub dst_image: Arc<dyn ImageAccess>, + + /// The layout used for `dst_image` during the blit operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferDstOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferDstOptimal`]. + pub dst_image_layout: ImageLayout, + + /// The regions of both images to blit between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers of the two images. The whole extent of each image is covered, scaling if + /// necessary. All aspects of each image are selected, or `plane0` if the image is multi-planar. + pub regions: SmallVec<[ImageBlit; 1]>, + + /// The filter to use for sampling `src_image` when the `src_extent` and + /// `dst_extent` of a region are not the same size. + /// + /// The default value is [`Filter::Nearest`]. + pub filter: Filter, + + pub _ne: crate::NonExhaustive, +} + +impl BlitImageInfo { + /// Returns a `BlitImageInfo` with the specified `src_image` and `dst_image`. + #[inline] + pub fn images(src_image: Arc<dyn ImageAccess>, dst_image: Arc<dyn ImageAccess>) -> Self { + let min_array_layers = src_image + .dimensions() + .array_layers() + .min(dst_image.dimensions().array_layers()); + let region = ImageBlit { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + }, + src_offsets: [[0; 3], src_image.dimensions().width_height_depth()], + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + }, + dst_offsets: [[0; 3], dst_image.dimensions().width_height_depth()], + ..Default::default() + }; + + Self { + src_image, + src_image_layout: ImageLayout::TransferSrcOptimal, + dst_image, + dst_image_layout: ImageLayout::TransferDstOptimal, + regions: smallvec![region], + filter: Filter::Nearest, + _ne: crate::NonExhaustive(()), + } + } +} + +/// A region of data to blit between images. +#[derive(Clone, Debug)] +pub struct ImageBlit { + /// The subresource of `src_image` to blit from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offsets from the zero coordinate of `src_image`, defining two corners of the region + /// to blit from. + /// If the ordering of the two offsets differs between source and destination, the image will + /// be flipped. + /// + /// The default value is `[[0; 3]; 2]`, which must be overridden. + pub src_offsets: [[u32; 3]; 2], + + /// The subresource of `dst_image` to blit to. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` defining two corners of the + /// region to blit to. + /// If the ordering of the two offsets differs between source and destination, the image will + /// be flipped. + /// + /// The default value is `[[0; 3]; 2]`, which must be overridden. + pub dst_offsets: [[u32; 3]; 2], + + pub _ne: crate::NonExhaustive, +} + +impl Default for ImageBlit { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offsets: [[0; 3]; 2], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offsets: [[0; 3]; 2], + _ne: crate::NonExhaustive(()), + } + } +} + +/// Parameters to resolve image data. +#[derive(Clone, Debug)] +pub struct ResolveImageInfo { + /// The multisampled image to resolve from. + /// + /// There is no default value. + pub src_image: Arc<dyn ImageAccess>, + + /// The layout used for `src_image` during the resolve operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferSrcOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferSrcOptimal`]. + pub src_image_layout: ImageLayout, + + /// The non-multisampled image to resolve into. + /// + /// There is no default value. + pub dst_image: Arc<dyn ImageAccess>, + + /// The layout used for `dst_image` during the resolve operation. + /// + /// The following layouts are allowed: + /// - [`ImageLayout::TransferDstOptimal`] + /// - [`ImageLayout::General`] + /// + /// The default value is [`ImageLayout::TransferDstOptimal`]. + pub dst_image_layout: ImageLayout, + + /// The regions of both images to resolve between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers and extent of the two images. All aspects of each image are selected, or + /// `plane0` if the image is multi-planar. + pub regions: SmallVec<[ImageResolve; 1]>, + + pub _ne: crate::NonExhaustive, +} + +impl ResolveImageInfo { + /// Returns a `ResolveImageInfo` with the specified `src_image` and `dst_image`. + #[inline] + pub fn images(src_image: Arc<dyn ImageAccess>, dst_image: Arc<dyn ImageAccess>) -> Self { + let min_array_layers = src_image + .dimensions() + .array_layers() + .min(dst_image.dimensions().array_layers()); + let region = ImageResolve { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + }, + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + }, + extent: { + let src_extent = src_image.dimensions().width_height_depth(); + let dst_extent = dst_image.dimensions().width_height_depth(); + + [ + src_extent[0].min(dst_extent[0]), + src_extent[1].min(dst_extent[1]), + src_extent[2].min(dst_extent[2]), + ] + }, + ..Default::default() + }; + + Self { + src_image, + src_image_layout: ImageLayout::TransferSrcOptimal, + dst_image, + dst_image_layout: ImageLayout::TransferDstOptimal, + regions: smallvec![region], + _ne: crate::NonExhaustive(()), + } + } +} + +/// A region of data to resolve between images. +#[derive(Clone, Debug)] +pub struct ImageResolve { + /// The subresource of `src_image` to resolve from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `src_image` that resolving will start from. + /// + /// The default value is `[0; 3]`. + pub src_offset: [u32; 3], + + /// The subresource of `dst_image` to resolve into. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` that resolving will start from. + /// + /// The default value is `[0; 3]`. + pub dst_offset: [u32; 3], + + /// The extent of texels to resolve. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub extent: [u32; 3], + + pub _ne: crate::NonExhaustive, +} + +impl Default for ImageResolve { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offset: [0; 3], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offset: [0; 3], + extent: [0; 3], + _ne: crate::NonExhaustive(()), + } + } +} + +/// Error that can happen when recording a copy command. +#[derive(Clone, Debug)] +pub enum CopyError { + SyncCommandBufferBuilderError(SyncCommandBufferBuilderError), + + RequirementNotMet { + required_for: &'static str, + requires_one_of: RequiresOneOf, + }, + + /// Operation forbidden inside of a render pass. + ForbiddenInsideRenderPass, + + /// The queue family doesn't allow this operation. + NotSupportedByQueueFamily, + + /// The array layer counts of the source and destination subresource ranges of a region do not + /// match. + ArrayLayerCountMismatch { + region_index: usize, + src_layer_count: u32, + dst_layer_count: u32, + }, + + /// The end of the range of accessed array layers of the subresource range of a region is + /// greater than the number of array layers in the image. + ArrayLayersOutOfRange { + resource: CopyErrorResource, + region_index: usize, + array_layers_range_end: u32, + image_array_layers: u32, + }, + + /// The aspects of the source and destination subresource ranges of a region do not match. + AspectsMismatch { + region_index: usize, + src_aspects: ImageAspects, + dst_aspects: ImageAspects, + }, + + /// The aspects of the subresource range of a region contain aspects that are not present + /// in the image, or that are not allowed. + AspectsNotAllowed { + resource: CopyErrorResource, + region_index: usize, + aspects: ImageAspects, + allowed_aspects: ImageAspects, + }, + + /// The buffer image height of a region is not a multiple of the required buffer alignment. + BufferImageHeightNotAligned { + resource: CopyErrorResource, + region_index: usize, + image_height: u32, + required_alignment: u32, + }, + + /// The buffer image height of a region is smaller than the image extent height. + BufferImageHeightTooSmall { + resource: CopyErrorResource, + region_index: usize, + image_height: u32, + min: u32, + }, + + /// The buffer row length of a region is not a multiple of the required buffer alignment. + BufferRowLengthNotAligned { + resource: CopyErrorResource, + region_index: usize, + row_length: u32, + required_alignment: u32, + }, + + /// The buffer row length of a region specifies a row of texels that is greater than 0x7FFFFFFF + /// bytes in size. + BufferRowLengthTooLarge { + resource: CopyErrorResource, + region_index: usize, + buffer_row_length: u32, + }, + + /// The buffer row length of a region is smaller than the image extent width. + BufferRowLengthTooSmall { + resource: CopyErrorResource, + region_index: usize, + row_length: u32, + min: u32, + }, + + /// Depth/stencil images are not supported by the queue family of this command buffer; a + /// graphics queue family is required. + DepthStencilNotSupportedByQueueFamily, + + /// The image extent of a region is not a multiple of the required image alignment. + ExtentNotAlignedForImage { + resource: CopyErrorResource, + region_index: usize, + extent: [u32; 3], + required_alignment: [u32; 3], + }, + + /// The chosen filter type does not support the dimensionality of the source image. + FilterNotSupportedForImageType, + + /// The chosen filter type does not support the format of the source image. + FilterNotSupportedByFormat, + + /// The format of an image is not supported for this operation. + FormatNotSupported { + resource: CopyErrorResource, + format: Format, + }, + + /// The format of the source image does not match the format of the destination image. + FormatsMismatch { + src_format: Format, + dst_format: Format, + }, + + /// The format of the source image subresource is not compatible with the format of the + /// destination image subresource. + FormatsNotCompatible { + src_format: Format, + dst_format: Format, + }, + + /// A specified image layout is not valid for this operation. + ImageLayoutInvalid { + resource: CopyErrorResource, + image_layout: ImageLayout, + }, + + /// The end of the range of accessed mip levels of the subresource range of a region is greater + /// than the number of mip levels in the image. + MipLevelsOutOfRange { + resource: CopyErrorResource, + region_index: usize, + mip_levels_range_end: u32, + image_mip_levels: u32, + }, + + /// An image does not have a required format feature. + MissingFormatFeature { + resource: CopyErrorResource, + format_feature: &'static str, + }, + + /// A resource did not have a required usage enabled. + MissingUsage { + resource: CopyErrorResource, + usage: &'static str, + }, + + /// A subresource range of a region specifies multiple aspects, but only one aspect can be + /// selected for the image. + MultipleAspectsNotAllowed { + resource: CopyErrorResource, + region_index: usize, + aspects: ImageAspects, + }, + + /// The buffer offset of a region is not a multiple of the required buffer alignment. + OffsetNotAlignedForBuffer { + resource: CopyErrorResource, + region_index: usize, + offset: DeviceSize, + required_alignment: DeviceSize, + }, + + /// The image offset of a region is not a multiple of the required image alignment. + OffsetNotAlignedForImage { + resource: CopyErrorResource, + region_index: usize, + offset: [u32; 3], + required_alignment: [u32; 3], + }, + + /// The image offsets of a region are not the values required for that axis ([0, 1]) for the + /// type of the image. + OffsetsInvalidForImageType { + resource: CopyErrorResource, + region_index: usize, + offsets: [u32; 2], + }, + + /// The source bounds of a region overlap with the destination bounds of a region. + OverlappingRegions { + src_region_index: usize, + dst_region_index: usize, + }, + + /// The source subresources of a region overlap with the destination subresources of a region, + /// but the source image layout does not equal the destination image layout. + OverlappingSubresourcesLayoutMismatch { + src_region_index: usize, + dst_region_index: usize, + src_image_layout: ImageLayout, + dst_image_layout: ImageLayout, + }, + + /// The end of the range of accessed byte offsets of a region is greater than the size of the + /// buffer. + RegionOutOfBufferBounds { + resource: CopyErrorResource, + region_index: usize, + offset_range_end: DeviceSize, + buffer_size: DeviceSize, + }, + + /// The end of the range of accessed texel offsets of a region is greater than the extent of + /// the selected subresource of the image. + RegionOutOfImageBounds { + resource: CopyErrorResource, + region_index: usize, + offset_range_end: [u32; 3], + subresource_extent: [u32; 3], + }, + + /// An image has a sample count that is not valid for this operation. + SampleCountInvalid { + resource: CopyErrorResource, + sample_count: SampleCount, + allowed_sample_counts: SampleCounts, + }, + + /// The source image has a different sample count than the destination image. + SampleCountMismatch { + src_sample_count: SampleCount, + dst_sample_count: SampleCount, + }, +} + +impl Error for CopyError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::SyncCommandBufferBuilderError(err) => Some(err), + _ => None, + } + } +} + +impl Display for CopyError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + match self { + Self::SyncCommandBufferBuilderError(_) => write!(f, "a SyncCommandBufferBuilderError"), + Self::RequirementNotMet { + required_for, + requires_one_of, + } => write!( + f, + "a requirement was not met for: {}; requires one of: {}", + required_for, requires_one_of, + ), + Self::ForbiddenInsideRenderPass => { + write!(f, "operation forbidden inside of a render pass") + } + Self::NotSupportedByQueueFamily => { + write!(f, "the queue family doesn't allow this operation") + } + Self::ArrayLayerCountMismatch { + region_index, + src_layer_count, + dst_layer_count, + } => write!( + f, + "the array layer counts of the source and destination subresource ranges of region \ + {} do not match (source: {}; destination: {})", + region_index, src_layer_count, dst_layer_count, + ), + Self::ArrayLayersOutOfRange { + resource, + region_index, + array_layers_range_end, + image_array_layers, + } => write!( + f, + "the end of the range of accessed array layers ({}) of the {} subresource range of \ + region {} is greater than the number of array layers in the {} image ({})", + array_layers_range_end, resource, region_index, resource, image_array_layers, + ), + Self::AspectsMismatch { + region_index, + src_aspects, + dst_aspects, + } => write!( + f, + "the aspects of the source and destination subresource ranges of region {} do not \ + match (source: {:?}; destination: {:?})", + region_index, src_aspects, dst_aspects, + ), + Self::AspectsNotAllowed { + resource, + region_index, + aspects, + allowed_aspects, + } => write!( + f, + "the aspects ({:?}) of the {} subresource range of region {} contain aspects that \ + are not present in the {} image, or that are not allowed ({:?})", + aspects, resource, region_index, resource, allowed_aspects, + ), + Self::BufferImageHeightNotAligned { + resource, + region_index, + image_height, + required_alignment, + } => write!( + f, + "the {} buffer image height ({}) of region {} is not a multiple of the required {} \ + buffer alignment ({})", + resource, image_height, region_index, resource, required_alignment, + ), + Self::BufferRowLengthTooLarge { + resource, + region_index, + buffer_row_length, + } => write!( + f, + "the {} buffer row length ({}) of region {} specifies a row of texels that is \ + greater than 0x7FFFFFFF bytes in size", + resource, buffer_row_length, region_index, + ), + Self::BufferImageHeightTooSmall { + resource, + region_index, + image_height, + min, + } => write!( + f, + "the {} buffer image height ({}) of region {} is smaller than the {} image extent \ + height ({})", + resource, image_height, region_index, resource, min, + ), + Self::BufferRowLengthNotAligned { + resource, + region_index, + row_length, + required_alignment, + } => write!( + f, + "the {} buffer row length ({}) of region {} is not a multiple of the required {} \ + buffer alignment ({})", + resource, row_length, region_index, resource, required_alignment, + ), + Self::BufferRowLengthTooSmall { + resource, + region_index, + row_length, + min, + } => write!( + f, + "the {} buffer row length length ({}) of region {} is smaller than the {} image \ + extent width ({})", + resource, row_length, region_index, resource, min, + ), + Self::DepthStencilNotSupportedByQueueFamily => write!( + f, + "depth/stencil images are not supported by the queue family of this command \ + buffer; a graphics queue family is required", + ), + Self::ExtentNotAlignedForImage { + resource, + region_index, + extent, + required_alignment, + } => write!( + f, + "the {} image extent ({:?}) of region {} is not a multiple of the required {} \ + image alignment ({:?})", + resource, extent, region_index, resource, required_alignment, + ), + Self::FilterNotSupportedForImageType => write!( + f, + "the chosen filter is not supported for the source image type", + ), + Self::FilterNotSupportedByFormat => write!( + f, + "the chosen filter is not supported by the format of the source image", + ), + Self::FormatNotSupported { resource, format } => write!( + f, + "the format of the {} image ({:?}) is not supported for this operation", + resource, format, + ), + Self::FormatsMismatch { + src_format, + dst_format, + } => write!( + f, + "the format of the source image ({:?}) does not match the format of the \ + destination image ({:?})", + src_format, dst_format, + ), + Self::FormatsNotCompatible { + src_format, + dst_format, + } => write!( + f, + "the format of the source image subresource ({:?}) is not compatible with the \ + format of the destination image subresource ({:?})", + src_format, dst_format, + ), + Self::ImageLayoutInvalid { + resource, + image_layout, + } => write!( + f, + "the specified {} image layout {:?} is not valid for this operation", + resource, image_layout, + ), + Self::MipLevelsOutOfRange { + resource, + region_index, + mip_levels_range_end, + image_mip_levels, + } => write!( + f, + "the end of the range of accessed mip levels ({}) of the {} subresource range of \ + region {} is not less than the number of mip levels in the {} image ({})", + mip_levels_range_end, resource, region_index, resource, image_mip_levels, + ), + Self::MissingFormatFeature { + resource, + format_feature, + } => write!( + f, + "the {} image does not have the required format feature {}", + resource, format_feature, + ), + Self::MissingUsage { resource, usage } => write!( + f, + "the {} resource did not have the required usage {} enabled", + resource, usage, + ), + Self::MultipleAspectsNotAllowed { + resource, + region_index, + aspects, + } => write!( + f, + "the {} subresource range of region {} specifies multiple aspects ({:?}), but only \ + one aspect can be selected for the {} image", + resource, region_index, aspects, resource, + ), + Self::OffsetNotAlignedForBuffer { + resource, + region_index, + offset, + required_alignment, + } => write!( + f, + "the {} buffer offset ({}) of region {} is not a multiple of the required {} \ + buffer alignment ({})", + resource, offset, region_index, resource, required_alignment, + ), + Self::OffsetNotAlignedForImage { + resource, + region_index, + offset, + required_alignment, + } => write!( + f, + "the {} image offset ({:?}) of region {} is not a multiple of the required {} \ + image alignment ({:?})", + resource, offset, region_index, resource, required_alignment, + ), + Self::OffsetsInvalidForImageType { + resource, + region_index, + offsets, + } => write!( + f, + "the {} image offsets ({:?}) of region {} are not the values required for that \ + axis ([0, 1]) for the type of the {} image", + resource, offsets, region_index, resource, + ), + Self::OverlappingRegions { + src_region_index, + dst_region_index, + } => write!( + f, + "the source bounds of region {} overlap with the destination bounds of region {}", + src_region_index, dst_region_index, + ), + Self::OverlappingSubresourcesLayoutMismatch { + src_region_index, + dst_region_index, + src_image_layout, + dst_image_layout, + } => write!( + f, + "the source subresources of region {} overlap with the destination subresources of \ + region {}, but the source image layout ({:?}) does not equal the destination image \ + layout ({:?})", + src_region_index, dst_region_index, src_image_layout, dst_image_layout, + ), + Self::RegionOutOfBufferBounds { + resource, + region_index, + offset_range_end, + buffer_size, + } => write!( + f, + "the end of the range of accessed {} byte offsets ({}) of region {} is greater \ + than the size of the {} buffer ({})", + resource, offset_range_end, region_index, resource, buffer_size, + ), + Self::RegionOutOfImageBounds { + resource, + region_index, + offset_range_end, + subresource_extent, + } => write!( + f, + "the end of the range of accessed {} texel offsets ({:?}) of region {} is greater \ + than the extent of the selected subresource of the {} image ({:?})", + resource, offset_range_end, region_index, resource, subresource_extent, + ), + Self::SampleCountInvalid { + resource, + sample_count, + allowed_sample_counts, + } => write!( + f, + "the {} image has a sample count ({:?}) that is not valid for this operation \ + ({:?})", + resource, sample_count, allowed_sample_counts, + ), + Self::SampleCountMismatch { + src_sample_count, + dst_sample_count, + } => write!( + f, + "the source image has a different sample count ({:?}) than the destination image \ + ({:?})", + src_sample_count, dst_sample_count, + ), + } + } +} + +impl From<SyncCommandBufferBuilderError> for CopyError { + fn from(err: SyncCommandBufferBuilderError) -> Self { + Self::SyncCommandBufferBuilderError(err) + } +} + +impl From<RequirementNotMet> for CopyError { + fn from(err: RequirementNotMet) -> Self { + Self::RequirementNotMet { + required_for: err.required_for, + requires_one_of: err.requires_one_of, + } + } +} + +/// Indicates which resource a `CopyError` applies to. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CopyErrorResource { + Source, + Destination, +} + +impl Display for CopyErrorResource { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + match self { + Self::Source => write!(f, "source"), + Self::Destination => write!(f, "destination"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::format::Format; + + /// Computes the minimum required len in elements for buffer with image data in specified + /// format of specified size. + fn required_size_for_format(format: Format, extent: [u32; 3], layer_count: u32) -> DeviceSize { + let num_blocks = extent + .into_iter() + .zip(format.block_extent()) + .map(|(extent, block_extent)| { + let extent = extent as DeviceSize; + let block_extent = block_extent as DeviceSize; + (extent + block_extent - 1) / block_extent + }) + .product::<DeviceSize>() + * layer_count as DeviceSize; + let block_size = format + .block_size() + .expect("this format cannot accept pixels"); + num_blocks * block_size + } + + #[test] + fn test_required_len_for_format() { + // issue #1292 + assert_eq!( + required_size_for_format(Format::BC1_RGB_UNORM_BLOCK, [2048, 2048, 1], 1), + 2097152 + ); + // other test cases + assert_eq!( + required_size_for_format(Format::R8G8B8A8_UNORM, [2048, 2048, 1], 1), + 16777216 + ); + assert_eq!( + required_size_for_format(Format::R4G4_UNORM_PACK8, [512, 512, 1], 1), + 262144 + ); + assert_eq!( + required_size_for_format(Format::R8G8B8_USCALED, [512, 512, 1], 1), + 786432 + ); + assert_eq!( + required_size_for_format(Format::R32G32_UINT, [512, 512, 1], 1), + 2097152 + ); + assert_eq!( + required_size_for_format(Format::R32G32_UINT, [512, 512, 1], 1), + 2097152 + ); + assert_eq!( + required_size_for_format(Format::ASTC_8x8_UNORM_BLOCK, [512, 512, 1], 1), + 65536 + ); + assert_eq!( + required_size_for_format(Format::ASTC_12x12_SRGB_BLOCK, [512, 512, 1], 1), + 29584 + ); + } +} |