// Copyright (c) 2022 The vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // 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 AutoCommandBufferBuilder 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, ) -> 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, dst_buffer: Subbuffer) -> 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 { /// 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 CopyBufferInfoTyped { /// 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 From> for CopyBufferInfo { fn from(typed: CopyBufferInfoTyped) -> Self { let CopyBufferInfoTyped { src_buffer, dst_buffer, mut regions, _ne: _, } = typed; for region in &mut regions { region.src_offset *= size_of::() as DeviceSize; region.dst_offset *= size_of::() as DeviceSize; region.size *= size_of::() 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, /// 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, /// 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, dst_image: Arc) -> 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, /// 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, dst_image: Arc, ) -> 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, /// 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, dst_buffer: Subbuffer, ) -> 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, /// 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, /// 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, dst_image: Arc) -> 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, /// 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, /// 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, dst_image: Arc) -> 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 for CopyError { fn from(err: SyncCommandBufferBuilderError) -> Self { Self::SyncCommandBufferBuilderError(err) } } impl From 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::() * 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 ); } }