// Copyright (c) 2016 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. //! Low-level implementation of images. //! //! This module contains low-level wrappers around the Vulkan image types. All //! other image types of this library, and all custom image types //! that you create must wrap around the types in this module. use super::{ ImageAspect, ImageAspects, ImageCreateFlags, ImageDimensions, ImageLayout, ImageSubresourceLayers, ImageSubresourceRange, ImageTiling, ImageUsage, SampleCount, SampleCounts, SparseImageMemoryRequirements, }; use crate::{ buffer::subbuffer::{ReadLockError, WriteLockError}, cache::OnceCache, device::{Device, DeviceOwned}, format::{ChromaSampling, Format, FormatFeatures, NumericType}, image::{ view::ImageViewCreationError, ImageFormatInfo, ImageFormatProperties, ImageType, SparseImageFormatProperties, }, macros::impl_id_counter, memory::{ allocator::{AllocationCreationError, AllocationType, DeviceLayout, MemoryAlloc}, is_aligned, DedicatedTo, DeviceAlignment, ExternalMemoryHandleType, ExternalMemoryHandleTypes, MemoryPropertyFlags, MemoryRequirements, }, range_map::RangeMap, swapchain::Swapchain, sync::{future::AccessError, CurrentAccess, Sharing}, DeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject, }; use ash::vk::ImageDrmFormatModifierExplicitCreateInfoEXT; use parking_lot::{Mutex, MutexGuard}; use smallvec::{smallvec, SmallVec}; use std::{ error::Error, fmt::{Display, Error as FmtError, Formatter}, hash::{Hash, Hasher}, iter::{FusedIterator, Peekable}, mem::{size_of_val, MaybeUninit}, num::NonZeroU64, ops::Range, ptr, sync::Arc, }; /// A raw image, with no memory backing it. /// /// This is the basic image type, a direct translation of a `VkImage` object, but it is mostly /// useless in this form. After creating a raw image, you must call `bind_memory` to make a /// complete image object. #[derive(Debug)] pub struct RawImage { handle: ash::vk::Image, device: Arc, id: NonZeroU64, flags: ImageCreateFlags, dimensions: ImageDimensions, format: Option, format_features: FormatFeatures, initial_layout: ImageLayout, mip_levels: u32, samples: SampleCount, tiling: ImageTiling, usage: ImageUsage, sharing: Sharing>, stencil_usage: ImageUsage, external_memory_handle_types: ExternalMemoryHandleTypes, memory_requirements: SmallVec<[MemoryRequirements; 3]>, needs_destruction: bool, // `vkDestroyImage` is called only if true. subresource_layout: OnceCache<(ImageAspect, u32, u32), SubresourceLayout>, } impl RawImage { /// Creates a new `RawImage`. /// /// # Panics /// /// - Panics if one of the values in `create_info.dimensions` is zero. /// - Panics if `create_info.format` is `None`. /// - Panics if `create_info.block_texel_view_compatible` is set but not /// `create_info.mutable_format`. /// - Panics if `create_info.mip_levels` is `0`. /// - Panics if `create_info.sharing` is [`Sharing::Concurrent`] with less than 2 items. /// - Panics if `create_info.initial_layout` is something other than /// [`ImageLayout::Undefined`] or [`ImageLayout::Preinitialized`]. /// - Panics if `create_info.usage` is empty. /// - Panics if `create_info.usage` contains `transient_attachment`, but does not also contain /// at least one of `color_attachment`, `depth_stencil_attachment`, `input_attachment`, or /// if it contains values other than these. #[inline] pub fn new( device: Arc, mut create_info: ImageCreateInfo, ) -> Result { match &mut create_info.sharing { Sharing::Exclusive => (), Sharing::Concurrent(queue_family_indices) => { // VUID-VkImageCreateInfo-sharingMode-01420 queue_family_indices.sort_unstable(); queue_family_indices.dedup(); } } Self::validate_new(&device, &create_info)?; unsafe { Ok(RawImage::new_unchecked(device, create_info)?) } } fn validate_new( device: &Device, create_info: &ImageCreateInfo, ) -> Result { let &ImageCreateInfo { flags, dimensions, format, mip_levels, samples, tiling, usage, mut stencil_usage, ref sharing, initial_layout, external_memory_handle_types, _ne: _, image_drm_format_modifier_create_info, } = create_info; let physical_device = device.physical_device(); let device_properties = physical_device.properties(); let format = format.unwrap(); // Can be None for "external formats" but Vulkano doesn't support that yet let aspects = format.aspects(); let has_separate_stencil_usage = if stencil_usage.is_empty() || !aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL) { stencil_usage = usage; false } else { stencil_usage == usage }; // VUID-VkImageCreateInfo-flags-parameter flags.validate_device(device)?; // VUID-VkImageCreateInfo-format-parameter format.validate_device(device)?; // VUID-VkImageCreateInfo-samples-parameter samples.validate_device(device)?; // VUID-VkImageCreateInfo-tiling-parameter tiling.validate_device(device)?; // VUID-VkImageCreateInfo-usage-parameter usage.validate_device(device)?; // VUID-VkImageCreateInfo-usage-requiredbitmask assert!(!usage.is_empty()); if has_separate_stencil_usage { if !(device.api_version() >= Version::V1_2 || device.enabled_extensions().ext_separate_stencil_usage) { return Err(ImageError::RequirementNotMet { required_for: "`create_info.stencil_usage` is `Some` and `create_info.format` \ has both a depth and a stencil aspect", requires_one_of: RequiresOneOf { api_version: Some(Version::V1_2), device_extensions: &["ext_separate_stencil_usage"], ..Default::default() }, }); } // VUID-VkImageStencilUsageCreateInfo-stencilUsage-parameter stencil_usage.validate_device(device)?; // VUID-VkImageStencilUsageCreateInfo-usage-requiredbitmask assert!(!stencil_usage.is_empty()); } // VUID-VkImageCreateInfo-initialLayout-parameter initial_layout.validate_device(device)?; // VUID-VkImageCreateInfo-initialLayout-00993 assert!(matches!( initial_layout, ImageLayout::Undefined | ImageLayout::Preinitialized )); // VUID-VkImageCreateInfo-flags-01573 assert!( !flags.intersects(ImageCreateFlags::BLOCK_TEXEL_VIEW_COMPATIBLE) || flags.intersects(ImageCreateFlags::MUTABLE_FORMAT) ); // VUID-VkImageCreateInfo-tiling-02261 // VUID-VkImageCreateInfo-pNext-02262 if (tiling == ImageTiling::DrmFormatModifier) != image_drm_format_modifier_create_info.is_some() { return Err(ImageError::DrmFormatModifierRequiresCreateInfo); } // Get format features let format_features = { // Use unchecked, because all validation has been done above. let format_properties = unsafe { physical_device.format_properties_unchecked(format) }; match tiling { ImageTiling::Linear => format_properties.linear_tiling_features, ImageTiling::Optimal => format_properties.optimal_tiling_features, ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features, // TODO: Improve } }; // TODO: VUID-VkImageCreateInfo-tiling-02353 // Vulkano currently has no high-level way to add or check for VkImageFormatListCreateInfo. // Format isn't supported at all? if format_features.is_empty() { return Err(ImageError::FormatNotSupported); } // Decode the dimensions let (image_type, extent, array_layers) = match dimensions { ImageDimensions::Dim1d { width, array_layers, } => (ImageType::Dim1d, [width, 1, 1], array_layers), ImageDimensions::Dim2d { width, height, array_layers, } => (ImageType::Dim2d, [width, height, 1], array_layers), ImageDimensions::Dim3d { width, height, depth, } => (ImageType::Dim3d, [width, height, depth], 1), }; // VUID-VkImageCreateInfo-extent-00944 assert!(extent[0] != 0); // VUID-VkImageCreateInfo-extent-00945 assert!(extent[1] != 0); // VUID-VkImageCreateInfo-extent-00946 assert!(extent[2] != 0); // VUID-VkImageCreateInfo-arrayLayers-00948 assert!(array_layers != 0); // VUID-VkImageCreateInfo-mipLevels-00947 assert!(mip_levels != 0); // Check mip levels let max_mip_levels = dimensions.max_mip_levels(); debug_assert!(max_mip_levels >= 1); // VUID-VkImageCreateInfo-mipLevels-00958 if mip_levels > max_mip_levels { return Err(ImageError::MaxMipLevelsExceeded { mip_levels, max: max_mip_levels, }); } // VUID-VkImageCreateInfo-samples-02257 if samples != SampleCount::Sample1 { if image_type != ImageType::Dim2d { return Err(ImageError::MultisampleNot2d); } if flags.intersects(ImageCreateFlags::CUBE_COMPATIBLE) { return Err(ImageError::MultisampleCubeCompatible); } if mip_levels != 1 { return Err(ImageError::MultisampleMultipleMipLevels); } if tiling == ImageTiling::Linear { return Err(ImageError::MultisampleLinearTiling); } // VUID-VkImageCreateInfo-multisampleArrayImage-04460 if device.enabled_extensions().khr_portability_subset && !device.enabled_features().multisample_array_image && array_layers != 1 { return Err(ImageError::RequirementNotMet { required_for: "this device is a portability subset device, \ `create_info.samples` is not `SampleCount::Sample1` and \ `create_info.dimensions.array_layers()` is greater than `1`", requires_one_of: RequiresOneOf { features: &["multisample_array_image"], ..Default::default() }, }); } } // Check limits for YCbCr formats if let Some(chroma_sampling) = format.ycbcr_chroma_sampling() { // VUID-VkImageCreateInfo-format-06410 if mip_levels != 1 { return Err(ImageError::YcbcrFormatMultipleMipLevels); } // VUID-VkImageCreateInfo-format-06411 if samples != SampleCount::Sample1 { return Err(ImageError::YcbcrFormatMultisampling); } // VUID-VkImageCreateInfo-format-06412 if image_type != ImageType::Dim2d { return Err(ImageError::YcbcrFormatNot2d); } // VUID-VkImageCreateInfo-format-06413 if array_layers > 1 && !device.enabled_features().ycbcr_image_arrays { return Err(ImageError::RequirementNotMet { required_for: "`create_info.format.ycbcr_chroma_sampling()` is `Some` and \ `create_info.dimensions.array_layers()` is greater than `1`", requires_one_of: RequiresOneOf { features: &["ycbcr_image_arrays"], ..Default::default() }, }); } match chroma_sampling { ChromaSampling::Mode444 => (), ChromaSampling::Mode422 => { // VUID-VkImageCreateInfo-format-04712 if extent[0] % 2 != 0 { return Err(ImageError::YcbcrFormatInvalidDimensions); } } ChromaSampling::Mode420 => { // VUID-VkImageCreateInfo-format-04712 // VUID-VkImageCreateInfo-format-04713 if !(extent[0] % 2 == 0 && extent[1] % 2 == 0) { return Err(ImageError::YcbcrFormatInvalidDimensions); } } } } /* Check usage requirements */ let combined_usage = usage | stencil_usage; if combined_usage.intersects(ImageUsage::SAMPLED) && !format_features.intersects(FormatFeatures::SAMPLED_IMAGE) { return Err(ImageError::FormatUsageNotSupported { usage: "sampled" }); } if combined_usage.intersects(ImageUsage::COLOR_ATTACHMENT) && !format_features.intersects(FormatFeatures::COLOR_ATTACHMENT) { return Err(ImageError::FormatUsageNotSupported { usage: "color_attachment", }); } if combined_usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT) && !format_features.intersects(FormatFeatures::DEPTH_STENCIL_ATTACHMENT) { return Err(ImageError::FormatUsageNotSupported { usage: "depth_stencil_attachment", }); } if combined_usage.intersects(ImageUsage::INPUT_ATTACHMENT) && !format_features.intersects( FormatFeatures::COLOR_ATTACHMENT | FormatFeatures::DEPTH_STENCIL_ATTACHMENT, ) { return Err(ImageError::FormatUsageNotSupported { usage: "input_attachment", }); } if combined_usage.intersects( ImageUsage::COLOR_ATTACHMENT | ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT, ) { // VUID-VkImageCreateInfo-usage-00964 // VUID-VkImageCreateInfo-usage-00965 // VUID-VkImageCreateInfo-Format-02536 // VUID-VkImageCreateInfo-format-02537 if extent[0] > device_properties.max_framebuffer_width || extent[1] > device_properties.max_framebuffer_height { return Err(ImageError::MaxFramebufferDimensionsExceeded { extent: [extent[0], extent[1]], max: [ device_properties.max_framebuffer_width, device_properties.max_framebuffer_height, ], }); } } if combined_usage.intersects(ImageUsage::STORAGE) { if !format_features.intersects(FormatFeatures::STORAGE_IMAGE) { return Err(ImageError::FormatUsageNotSupported { usage: "storage" }); } // VUID-VkImageCreateInfo-usage-00968 // VUID-VkImageCreateInfo-format-02538 if !device.enabled_features().shader_storage_image_multisample && samples != SampleCount::Sample1 { return Err(ImageError::RequirementNotMet { required_for: "`create_info.usage` or `create_info.stencil_usage` contains \ `ImageUsage::STORAGE`, and `create_info.samples` is not \ `SampleCount::Sample1`", requires_one_of: RequiresOneOf { features: &["shader_storage_image_multisample"], ..Default::default() }, }); } } // These flags only exist in later versions, ignore them otherwise if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_maintenance1 { if combined_usage.intersects(ImageUsage::TRANSFER_SRC) && !format_features.intersects(FormatFeatures::TRANSFER_SRC) { return Err(ImageError::FormatUsageNotSupported { usage: "transfer_src", }); } if combined_usage.intersects(ImageUsage::TRANSFER_DST) && !format_features.intersects(FormatFeatures::TRANSFER_DST) { return Err(ImageError::FormatUsageNotSupported { usage: "transfer_dst", }); } } if usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT) { // VUID-VkImageCreateInfo-usage-00966 assert!(usage.intersects( ImageUsage::COLOR_ATTACHMENT | ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT )); // VUID-VkImageCreateInfo-usage-00963 assert!((usage - (ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::COLOR_ATTACHMENT | ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT)) .is_empty()) } if has_separate_stencil_usage { // VUID-VkImageCreateInfo-format-02795 // VUID-VkImageCreateInfo-format-02796 if usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT) != stencil_usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT) { return Err(ImageError::StencilUsageMismatch { usage, stencil_usage, }); } // VUID-VkImageCreateInfo-format-02797 // VUID-VkImageCreateInfo-format-02798 if usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT) != stencil_usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT) { return Err(ImageError::StencilUsageMismatch { usage, stencil_usage, }); } if stencil_usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT) { // VUID-VkImageStencilUsageCreateInfo-stencilUsage-02539 assert!((stencil_usage - (ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT)) .is_empty()) } } /* Check flags requirements */ if flags.intersects(ImageCreateFlags::CUBE_COMPATIBLE) { // VUID-VkImageCreateInfo-flags-00949 if image_type != ImageType::Dim2d { return Err(ImageError::CubeCompatibleNot2d); } // VUID-VkImageCreateInfo-imageType-00954 if extent[0] != extent[1] { return Err(ImageError::CubeCompatibleNotSquare); } // VUID-VkImageCreateInfo-imageType-00954 if array_layers < 6 { return Err(ImageError::CubeCompatibleNotEnoughArrayLayers); } } if flags.intersects(ImageCreateFlags::ARRAY_2D_COMPATIBLE) { // VUID-VkImageCreateInfo-flags-00950 if image_type != ImageType::Dim3d { return Err(ImageError::Array2dCompatibleNot3d); } // VUID-VkImageCreateInfo-imageView2DOn3DImage-04459 if device.enabled_extensions().khr_portability_subset && !device.enabled_features().image_view2_d_on3_d_image { return Err(ImageError::RequirementNotMet { required_for: "this device is a portability subset device, and \ `create_info.flags` contains `ImageCreateFlags::ARRAY_2D_COMPATIBLE`", requires_one_of: RequiresOneOf { features: &["image_view2_d_on3_d_image"], ..Default::default() }, }); } } if flags.intersects(ImageCreateFlags::BLOCK_TEXEL_VIEW_COMPATIBLE) { // VUID-VkImageCreateInfo-flags-01572 if format.compression().is_none() { return Err(ImageError::BlockTexelViewCompatibleNotCompressed); } } if flags.intersects(ImageCreateFlags::DISJOINT) { // VUID-VkImageCreateInfo-format-01577 if format.planes().len() < 2 { return Err(ImageError::DisjointFormatNotSupported); } // VUID-VkImageCreateInfo-imageCreateFormatFeatures-02260 if !format_features.intersects(FormatFeatures::DISJOINT) { return Err(ImageError::DisjointFormatNotSupported); } } /* Check sharing mode and queue families */ match sharing { Sharing::Exclusive => (), Sharing::Concurrent(queue_family_indices) => { // VUID-VkImageCreateInfo-sharingMode-00942 assert!(queue_family_indices.len() >= 2); for &queue_family_index in queue_family_indices { // VUID-VkImageCreateInfo-sharingMode-01420 if queue_family_index >= device.physical_device().queue_family_properties().len() as u32 { return Err(ImageError::SharingQueueFamilyIndexOutOfRange { queue_family_index, queue_family_count: device .physical_device() .queue_family_properties() .len() as u32, }); } } } } /* External memory handles */ if !external_memory_handle_types.is_empty() { if !(device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_external_memory) { return Err(ImageError::RequirementNotMet { required_for: "`create_info.external_memory_handle_types` is not empty", requires_one_of: RequiresOneOf { api_version: Some(Version::V1_1), device_extensions: &["khr_external_memory"], ..Default::default() }, }); } // VUID-VkExternalMemoryImageCreateInfo-handleTypes-parameter external_memory_handle_types.validate_device(device)?; // VUID-VkImageCreateInfo-pNext-01443 if initial_layout != ImageLayout::Undefined { return Err(ImageError::ExternalMemoryInvalidInitialLayout); } } /* Some device limits can be exceeded, but only for particular image configurations, which must be queried with `image_format_properties`. See: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap44.html#capabilities-image First, we check if this is the case, then query the device if so. */ // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap44.html#features-extentperimagetype let extent_must_query = || match image_type { ImageType::Dim1d => { let limit = device.physical_device().properties().max_image_dimension1_d; extent[0] > limit } ImageType::Dim2d if flags.intersects(ImageCreateFlags::CUBE_COMPATIBLE) => { let limit = device .physical_device() .properties() .max_image_dimension_cube; extent[0] > limit } ImageType::Dim2d => { let limit = device.physical_device().properties().max_image_dimension2_d; extent[0] > limit || extent[1] > limit } ImageType::Dim3d => { let limit = device.physical_device().properties().max_image_dimension3_d; extent[0] > limit || extent[1] > limit || extent[2] > limit } }; // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageFormatProperties.html let mip_levels_must_query = || { if mip_levels > 1 { // TODO: for external memory, the spec says: // "handle type included in the handleTypes member for which mipmap image support is // not required". But which handle types are those? !external_memory_handle_types.is_empty() } else { false } }; // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageFormatProperties.html let array_layers_must_query = || { if array_layers > device.physical_device().properties().max_image_array_layers { true } else if array_layers > 1 { image_type == ImageType::Dim3d } else { false } }; // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap44.html#features-supported-sample-counts let samples_must_query = || { if samples == SampleCount::Sample1 { return false; } if combined_usage.intersects(ImageUsage::COLOR_ATTACHMENT) && !device_properties .framebuffer_color_sample_counts .contains_enum(samples) { // TODO: how to handle framebuffer_integer_color_sample_counts limit, which only // exists >= Vulkan 1.2 return true; } if combined_usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT) { if aspects.intersects(ImageAspects::DEPTH) && !device_properties .framebuffer_depth_sample_counts .contains_enum(samples) { return true; } if aspects.intersects(ImageAspects::STENCIL) && !device_properties .framebuffer_stencil_sample_counts .contains_enum(samples) { return true; } } if combined_usage.intersects(ImageUsage::SAMPLED) { if let Some(numeric_type) = format.type_color() { match numeric_type { NumericType::UINT | NumericType::SINT => { if !device_properties .sampled_image_integer_sample_counts .contains_enum(samples) { return true; } } NumericType::SFLOAT | NumericType::UFLOAT | NumericType::SNORM | NumericType::UNORM | NumericType::SSCALED | NumericType::USCALED | NumericType::SRGB => { if !device_properties .sampled_image_color_sample_counts .contains_enum(samples) { return true; } } } } else { if aspects.intersects(ImageAspects::DEPTH) && !device_properties .sampled_image_depth_sample_counts .contains_enum(samples) { return true; } if aspects.intersects(ImageAspects::STENCIL) && device_properties .sampled_image_stencil_sample_counts .contains_enum(samples) { return true; } } } if combined_usage.intersects(ImageUsage::STORAGE) && !device_properties .storage_image_sample_counts .contains_enum(samples) { return true; } false }; // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCreateInfo.html#_description let linear_must_query = || { if tiling == ImageTiling::Linear { !(image_type == ImageType::Dim2d && format.type_color().is_some() && mip_levels == 1 && array_layers == 1 // VUID-VkImageCreateInfo-samples-02257 already states that multisampling+linear // is invalid so no need to check for that here. && (usage - (ImageUsage::TRANSFER_SRC | ImageUsage::TRANSFER_DST)).is_empty()) } else { false } }; let must_query_device = extent_must_query() || mip_levels_must_query() || array_layers_must_query() || samples_must_query() || linear_must_query(); // We determined that we must query the device in order to be sure that the image // configuration is supported. if must_query_device { let external_memory_handle_types: SmallVec<[Option; 4]> = if !external_memory_handle_types.is_empty() { // If external memory handles are used, the properties need to be queried // individually for each handle type. external_memory_handle_types.into_iter().map(Some).collect() } else { smallvec![None] }; for external_memory_handle_type in external_memory_handle_types { // Use unchecked, because all validation has been done above. let image_format_properties = unsafe { device .physical_device() .image_format_properties_unchecked(ImageFormatInfo { flags, format: Some(format), image_type, tiling, usage, external_memory_handle_type, ..Default::default() })? }; let ImageFormatProperties { max_extent, max_mip_levels, max_array_layers, sample_counts, max_resource_size: _, .. } = match image_format_properties { Some(x) => x, None => return Err(ImageError::ImageFormatPropertiesNotSupported), }; // VUID-VkImageCreateInfo-extent-02252 // VUID-VkImageCreateInfo-extent-02253 // VUID-VkImageCreateInfo-extent-02254 if extent[0] > max_extent[0] || extent[1] > max_extent[1] || extent[2] > max_extent[2] { return Err(ImageError::MaxDimensionsExceeded { extent, max: max_extent, }); } // VUID-VkImageCreateInfo-mipLevels-02255 if mip_levels > max_mip_levels { return Err(ImageError::MaxMipLevelsExceeded { mip_levels, max: max_mip_levels, }); } // VUID-VkImageCreateInfo-arrayLayers-02256 if array_layers > max_array_layers { return Err(ImageError::MaxArrayLayersExceeded { array_layers, max: max_array_layers, }); } // VUID-VkImageCreateInfo-samples-02258 if !sample_counts.contains_enum(samples) { return Err(ImageError::SampleCountNotSupported { samples, supported: sample_counts, }); } // TODO: check resource size? } } Ok(format_features) } #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] #[inline] pub unsafe fn new_unchecked( device: Arc, create_info: ImageCreateInfo, ) -> Result { let &ImageCreateInfo { flags, dimensions, format, mip_levels, samples, tiling, usage, mut stencil_usage, ref sharing, initial_layout, external_memory_handle_types, _ne: _, mut image_drm_format_modifier_create_info, } = &create_info; let aspects = format.map_or_else(Default::default, |format| format.aspects()); let has_separate_stencil_usage = if stencil_usage.is_empty() || !aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL) { stencil_usage = usage; false } else { stencil_usage == usage }; let (image_type, extent, array_layers) = match dimensions { ImageDimensions::Dim1d { width, array_layers, } => (ImageType::Dim1d, [width, 1, 1], array_layers), ImageDimensions::Dim2d { width, height, array_layers, } => (ImageType::Dim2d, [width, height, 1], array_layers), ImageDimensions::Dim3d { width, height, depth, } => (ImageType::Dim3d, [width, height, depth], 1), }; let (sharing_mode, queue_family_index_count, p_queue_family_indices) = match sharing { Sharing::Exclusive => (ash::vk::SharingMode::EXCLUSIVE, 0, &[] as _), Sharing::Concurrent(queue_family_indices) => ( ash::vk::SharingMode::CONCURRENT, queue_family_indices.len() as u32, queue_family_indices.as_ptr(), ), }; let mut info_vk = ash::vk::ImageCreateInfo { flags: flags.into(), image_type: image_type.into(), format: format.map(Into::into).unwrap_or_default(), extent: ash::vk::Extent3D { width: extent[0], height: extent[1], depth: extent[2], }, mip_levels, array_layers, samples: samples.into(), tiling: tiling.into(), usage: usage.into(), sharing_mode, queue_family_index_count, p_queue_family_indices, initial_layout: initial_layout.into(), ..Default::default() }; let mut external_memory_info_vk = None; let mut stencil_usage_info_vk = None; if !external_memory_handle_types.is_empty() { let next = external_memory_info_vk.insert(ash::vk::ExternalMemoryImageCreateInfo { handle_types: external_memory_handle_types.into(), ..Default::default() }); next.p_next = info_vk.p_next; info_vk.p_next = next as *const _ as *const _; } if has_separate_stencil_usage { let next = stencil_usage_info_vk.insert(ash::vk::ImageStencilUsageCreateInfo { stencil_usage: stencil_usage.into(), ..Default::default() }); next.p_next = info_vk.p_next; info_vk.p_next = next as *const _ as *const _; } if external_memory_handle_types.intersects(ExternalMemoryHandleTypes::DMA_BUF) { let next = image_drm_format_modifier_create_info.as_mut().unwrap(); next.p_next = info_vk.p_next; info_vk.p_next = next as *const _ as *const _; } let handle = { let fns = device.fns(); let mut output = MaybeUninit::uninit(); (fns.v1_0.create_image)(device.handle(), &info_vk, ptr::null(), output.as_mut_ptr()) .result() .map_err(VulkanError::from)?; output.assume_init() }; Ok(Self::from_handle(device, handle, create_info)) } /// Creates a new `RawImage` from a raw object handle. /// /// # Safety /// /// - `handle` must be a valid Vulkan object handle created from `device`. /// - `handle` must refer to an image that has not yet had memory bound to it. /// - `create_info` must match the info used to create the object. #[inline] pub unsafe fn from_handle( device: Arc, handle: ash::vk::Image, create_info: ImageCreateInfo, ) -> Self { Self::from_handle_with_destruction(device, handle, create_info, true) } unsafe fn from_handle_with_destruction( device: Arc, handle: ash::vk::Image, create_info: ImageCreateInfo, needs_destruction: bool, ) -> Self { let ImageCreateInfo { flags, dimensions, format, mip_levels, samples, tiling, usage, mut stencil_usage, sharing, initial_layout, external_memory_handle_types, _ne: _, image_drm_format_modifier_create_info: _, } = create_info; let aspects = format.map_or_else(Default::default, |format| format.aspects()); if stencil_usage.is_empty() || !aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL) { stencil_usage = usage; } // Get format features let format_features = { // Use unchecked, because `create_info` is assumed to match the info of the handle, and // therefore already valid. let format_properties = device .physical_device() .format_properties_unchecked(format.unwrap()); match tiling { ImageTiling::Linear => format_properties.linear_tiling_features, ImageTiling::Optimal => format_properties.optimal_tiling_features, ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features, // TODO: improve } }; let memory_requirements = if needs_destruction { if flags.intersects(ImageCreateFlags::DISJOINT) { (0..format.unwrap().planes().len()) .map(|plane| Self::get_memory_requirements(&device, handle, Some(plane))) .collect() } else { smallvec![Self::get_memory_requirements(&device, handle, None)] } } else { smallvec![] }; RawImage { handle, device, id: Self::next_id(), flags, dimensions, format, format_features, mip_levels, initial_layout, samples, tiling, usage, stencil_usage, sharing, external_memory_handle_types, memory_requirements, needs_destruction, subresource_layout: OnceCache::new(), } } fn get_memory_requirements( device: &Device, handle: ash::vk::Image, plane: Option, ) -> MemoryRequirements { let mut info_vk = ash::vk::ImageMemoryRequirementsInfo2 { image: handle, ..Default::default() }; let mut plane_info_vk = None; if let Some(plane) = plane { debug_assert!( device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_get_memory_requirements2 && device.enabled_extensions().khr_sampler_ycbcr_conversion ); let next = plane_info_vk.insert(ash::vk::ImagePlaneMemoryRequirementsInfo { plane_aspect: match plane { 0 => ash::vk::ImageAspectFlags::PLANE_0, 1 => ash::vk::ImageAspectFlags::PLANE_1, 2 => ash::vk::ImageAspectFlags::PLANE_2, _ => unreachable!(), }, ..Default::default() }); next.p_next = info_vk.p_next; info_vk.p_next = next as *mut _ as *mut _; } let mut memory_requirements2_vk = ash::vk::MemoryRequirements2::default(); let mut memory_dedicated_requirements_vk = None; if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_dedicated_allocation { debug_assert!( device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_get_memory_requirements2 ); let next = memory_dedicated_requirements_vk .insert(ash::vk::MemoryDedicatedRequirements::default()); next.p_next = memory_requirements2_vk.p_next; memory_requirements2_vk.p_next = next as *mut _ as *mut _; } unsafe { let fns = device.fns(); if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_get_memory_requirements2 { if device.api_version() >= Version::V1_1 { (fns.v1_1.get_image_memory_requirements2)( device.handle(), &info_vk, &mut memory_requirements2_vk, ); } else { (fns.khr_get_memory_requirements2 .get_image_memory_requirements2_khr)( device.handle(), &info_vk, &mut memory_requirements2_vk, ); } } else { (fns.v1_0.get_image_memory_requirements)( device.handle(), handle, &mut memory_requirements2_vk.memory_requirements, ); } } MemoryRequirements { layout: DeviceLayout::from_size_alignment( memory_requirements2_vk.memory_requirements.size, memory_requirements2_vk.memory_requirements.alignment, ) .unwrap(), memory_type_bits: memory_requirements2_vk.memory_requirements.memory_type_bits, prefers_dedicated_allocation: memory_dedicated_requirements_vk .map_or(false, |dreqs| dreqs.prefers_dedicated_allocation != 0), requires_dedicated_allocation: memory_dedicated_requirements_vk .map_or(false, |dreqs| dreqs.requires_dedicated_allocation != 0), } } #[inline] #[allow(dead_code)] // Remove when sparse memory is implemented fn get_sparse_memory_requirements(&self) -> Vec { let device = &self.device; unsafe { let fns = self.device.fns(); if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_get_memory_requirements2 { let info2 = ash::vk::ImageSparseMemoryRequirementsInfo2 { image: self.handle, ..Default::default() }; let mut count = 0; if device.api_version() >= Version::V1_1 { (fns.v1_1.get_image_sparse_memory_requirements2)( device.handle(), &info2, &mut count, ptr::null_mut(), ); } else { (fns.khr_get_memory_requirements2 .get_image_sparse_memory_requirements2_khr)( device.handle(), &info2, &mut count, ptr::null_mut(), ); } let mut sparse_image_memory_requirements2 = vec![ash::vk::SparseImageMemoryRequirements2::default(); count as usize]; if device.api_version() >= Version::V1_1 { (fns.v1_1.get_image_sparse_memory_requirements2)( self.device.handle(), &info2, &mut count, sparse_image_memory_requirements2.as_mut_ptr(), ); } else { (fns.khr_get_memory_requirements2 .get_image_sparse_memory_requirements2_khr)( self.device.handle(), &info2, &mut count, sparse_image_memory_requirements2.as_mut_ptr(), ); } sparse_image_memory_requirements2.set_len(count as usize); sparse_image_memory_requirements2 .into_iter() .map( |sparse_image_memory_requirements2| SparseImageMemoryRequirements { format_properties: SparseImageFormatProperties { aspects: sparse_image_memory_requirements2 .memory_requirements .format_properties .aspect_mask .into(), image_granularity: [ sparse_image_memory_requirements2 .memory_requirements .format_properties .image_granularity .width, sparse_image_memory_requirements2 .memory_requirements .format_properties .image_granularity .height, sparse_image_memory_requirements2 .memory_requirements .format_properties .image_granularity .depth, ], flags: sparse_image_memory_requirements2 .memory_requirements .format_properties .flags .into(), }, image_mip_tail_first_lod: sparse_image_memory_requirements2 .memory_requirements .image_mip_tail_first_lod, image_mip_tail_size: sparse_image_memory_requirements2 .memory_requirements .image_mip_tail_size, image_mip_tail_offset: sparse_image_memory_requirements2 .memory_requirements .image_mip_tail_offset, image_mip_tail_stride: (!sparse_image_memory_requirements2 .memory_requirements .format_properties .flags .intersects(ash::vk::SparseImageFormatFlags::SINGLE_MIPTAIL)) .then_some( sparse_image_memory_requirements2 .memory_requirements .image_mip_tail_stride, ), }, ) .collect() } else { let mut count = 0; (fns.v1_0.get_image_sparse_memory_requirements)( device.handle(), self.handle, &mut count, ptr::null_mut(), ); let mut sparse_image_memory_requirements = vec![ash::vk::SparseImageMemoryRequirements::default(); count as usize]; (fns.v1_0.get_image_sparse_memory_requirements)( device.handle(), self.handle, &mut count, sparse_image_memory_requirements.as_mut_ptr(), ); sparse_image_memory_requirements.set_len(count as usize); sparse_image_memory_requirements .into_iter() .map( |sparse_image_memory_requirements| SparseImageMemoryRequirements { format_properties: SparseImageFormatProperties { aspects: sparse_image_memory_requirements .format_properties .aspect_mask .into(), image_granularity: [ sparse_image_memory_requirements .format_properties .image_granularity .width, sparse_image_memory_requirements .format_properties .image_granularity .height, sparse_image_memory_requirements .format_properties .image_granularity .depth, ], flags: sparse_image_memory_requirements .format_properties .flags .into(), }, image_mip_tail_first_lod: sparse_image_memory_requirements .image_mip_tail_first_lod, image_mip_tail_size: sparse_image_memory_requirements .image_mip_tail_size, image_mip_tail_offset: sparse_image_memory_requirements .image_mip_tail_offset, image_mip_tail_stride: (!sparse_image_memory_requirements .format_properties .flags .intersects(ash::vk::SparseImageFormatFlags::SINGLE_MIPTAIL)) .then_some(sparse_image_memory_requirements.image_mip_tail_stride), }, ) .collect() } } } pub(crate) fn id(&self) -> NonZeroU64 { self.id } /// Binds device memory to this image. /// /// - If `self.flags().disjoint` is not set, then `allocations` must contain exactly one /// element. This element may be a dedicated allocation. /// - If `self.flags().disjoint` is set, then `allocations` must contain exactly /// `self.format().unwrap().planes().len()` elements. These elements must not be dedicated /// allocations. pub fn bind_memory( self, allocations: impl IntoIterator, ) -> Result< Image, ( ImageError, RawImage, impl ExactSizeIterator, ), > { let allocations: SmallVec<[_; 3]> = allocations.into_iter().collect(); if let Err(err) = self.validate_bind_memory(&allocations) { return Err((err, self, allocations.into_iter())); } unsafe { self.bind_memory_unchecked(allocations) }.map_err(|(err, image, allocations)| { ( err.into(), image, allocations .into_iter() .collect::>() .into_iter(), ) }) } fn validate_bind_memory(&self, allocations: &[MemoryAlloc]) -> Result<(), ImageError> { if self.flags.intersects(ImageCreateFlags::DISJOINT) { if allocations.len() != self.format.unwrap().planes().len() { return Err(ImageError::AllocationsWrongNumberOfElements { provided: allocations.len(), required: self.format.unwrap().planes().len(), }); } } else { if allocations.len() != 1 { return Err(ImageError::AllocationsWrongNumberOfElements { provided: allocations.len(), required: 1, }); } } for (allocations_index, (allocation, memory_requirements)) in (allocations.iter()) .zip(self.memory_requirements.iter()) .enumerate() { assert_ne!(allocation.allocation_type(), AllocationType::Linear); let memory = allocation.device_memory(); let memory_offset = allocation.offset(); let memory_type = &self .device .physical_device() .memory_properties() .memory_types[memory.memory_type_index() as usize]; // VUID-VkBindImageMemoryInfo-commonparent assert_eq!(self.device(), memory.device()); // VUID-VkBindImageMemoryInfo-image-07460 // Ensured by taking ownership of `RawImage`. // VUID-VkBindImageMemoryInfo-image-01045 // Currently ensured by not having sparse binding flags, but this needs to be checked // once those are enabled. // VUID-VkBindImageMemoryInfo-memoryOffset-01046 // Assume that `allocation` was created correctly. if let Some(dedicated_to) = memory.dedicated_to() { // VUID-VkBindImageMemoryInfo-memory-02628 match dedicated_to { DedicatedTo::Image(id) if id == self.id => {} _ => return Err(ImageError::DedicatedAllocationMismatch), } debug_assert!(memory_offset == 0); // This should be ensured by the allocator } else { // VUID-VkBindImageMemoryInfo-image-01445 if memory_requirements.requires_dedicated_allocation { return Err(ImageError::DedicatedAllocationRequired); } } // VUID-VkBindImageMemoryInfo-None-01901 if memory_type .property_flags .intersects(MemoryPropertyFlags::PROTECTED) { return Err(ImageError::MemoryProtectedMismatch { allocations_index, image_protected: false, memory_protected: true, }); } // VUID-VkBindImageMemoryInfo-memory-02728 if !memory.export_handle_types().is_empty() && !memory .export_handle_types() .intersects(self.external_memory_handle_types) { return Err(ImageError::MemoryExternalHandleTypesDisjoint { allocations_index, image_handle_types: self.external_memory_handle_types, memory_export_handle_types: memory.export_handle_types(), }); } if let Some(handle_type) = memory.imported_handle_type() { // VUID-VkBindImageMemoryInfo-memory-02989 if !ExternalMemoryHandleTypes::from(handle_type) .intersects(self.external_memory_handle_types) { return Err(ImageError::MemoryImportedHandleTypeNotEnabled { allocations_index, image_handle_types: self.external_memory_handle_types, memory_imported_handle_type: handle_type, }); } } // VUID-VkBindImageMemoryInfo-pNext-01615 // VUID-VkBindImageMemoryInfo-pNext-01619 if memory_requirements.memory_type_bits & (1 << memory.memory_type_index()) == 0 { return Err(ImageError::MemoryTypeNotAllowed { allocations_index, provided_memory_type_index: memory.memory_type_index(), allowed_memory_type_bits: memory_requirements.memory_type_bits, }); } // VUID-VkBindImageMemoryInfo-pNext-01616 // VUID-VkBindImageMemoryInfo-pNext-01620 if !is_aligned(memory_offset, memory_requirements.layout.alignment()) { return Err(ImageError::MemoryAllocationNotAligned { allocations_index, allocation_offset: memory_offset, required_alignment: memory_requirements.layout.alignment(), }); } // VUID-VkBindImageMemoryInfo-pNext-01617 // VUID-VkBindImageMemoryInfo-pNext-01621 if allocation.size() < memory_requirements.layout.size() { return Err(ImageError::MemoryAllocationTooSmall { allocations_index, allocation_size: allocation.size(), required_size: memory_requirements.layout.size(), }); } } Ok(()) } /// # Safety /// /// - If `self.flags().disjoint` is not set, then `allocations` must contain exactly one /// element. /// - If `self.flags().disjoint` is set, then `allocations` must contain exactly /// `self.format().unwrap().planes().len()` elements. #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn bind_memory_unchecked( self, allocations: impl IntoIterator, ) -> Result< Image, ( VulkanError, RawImage, impl ExactSizeIterator, ), > { let allocations: SmallVec<[_; 3]> = allocations.into_iter().collect(); let fns = self.device.fns(); let result = if self.device.api_version() >= Version::V1_1 || self.device.enabled_extensions().khr_bind_memory2 { let mut infos_vk: SmallVec<[_; 3]> = SmallVec::with_capacity(3); let mut plane_infos_vk: SmallVec<[_; 3]> = SmallVec::with_capacity(3); if self.flags.intersects(ImageCreateFlags::DISJOINT) { debug_assert_eq!(allocations.len(), self.format.unwrap().planes().len()); for (plane, allocation) in allocations.iter().enumerate() { let memory = allocation.device_memory(); let memory_offset = allocation.offset(); infos_vk.push(ash::vk::BindImageMemoryInfo { image: self.handle, memory: memory.handle(), memory_offset, ..Default::default() }); // VUID-VkBindImageMemoryInfo-pNext-01618 plane_infos_vk.push(ash::vk::BindImagePlaneMemoryInfo { plane_aspect: match plane { 0 => ash::vk::ImageAspectFlags::PLANE_0, 1 => ash::vk::ImageAspectFlags::PLANE_1, 2 => ash::vk::ImageAspectFlags::PLANE_2, _ => unreachable!(), }, ..Default::default() }); } } else { debug_assert_eq!(allocations.len(), 1); let allocation = &allocations[0]; let memory = allocation.device_memory(); let memory_offset = allocation.offset(); infos_vk.push(ash::vk::BindImageMemoryInfo { image: self.handle, memory: memory.handle(), memory_offset, ..Default::default() }); }; for (info_vk, plane_info_vk) in (infos_vk.iter_mut()).zip(plane_infos_vk.iter_mut()) { info_vk.p_next = plane_info_vk as *mut _ as *mut _; } if self.device.api_version() >= Version::V1_1 { (fns.v1_1.bind_image_memory2)( self.device.handle(), infos_vk.len() as u32, infos_vk.as_ptr(), ) } else { (fns.khr_bind_memory2.bind_image_memory2_khr)( self.device.handle(), infos_vk.len() as u32, infos_vk.as_ptr(), ) } } else { debug_assert_eq!(allocations.len(), 1); let allocation = &allocations[0]; let memory = allocation.device_memory(); let memory_offset = allocation.offset(); (fns.v1_0.bind_image_memory)( self.device.handle(), self.handle, memory.handle(), memory_offset, ) } .result(); if let Err(err) = result { return Err((VulkanError::from(err), self, allocations.into_iter())); } Ok(Image::from_raw(self, ImageMemory::Normal(allocations))) } /// Returns the memory requirements for this image. /// /// - If the image is a swapchain image, this returns a slice with a length of 0. /// - If `self.flags().disjoint` is not set, this returns a slice with a length of 1. /// - If `self.flags().disjoint` is set, this returns a slice with a length equal to /// `self.format().unwrap().planes().len()`. #[inline] pub fn memory_requirements(&self) -> &[MemoryRequirements] { &self.memory_requirements } /// Returns the flags the image was created with. #[inline] pub fn flags(&self) -> ImageCreateFlags { self.flags } /// Returns the dimensions of the image. #[inline] pub fn dimensions(&self) -> ImageDimensions { self.dimensions } /// Returns the image's format. #[inline] pub fn format(&self) -> Option { self.format } /// Returns the features supported by the image's format. #[inline] pub fn format_features(&self) -> FormatFeatures { self.format_features } /// Returns the number of mipmap levels in the image. #[inline] pub fn mip_levels(&self) -> u32 { self.mip_levels } /// Returns the initial layout of the image. #[inline] pub fn initial_layout(&self) -> ImageLayout { self.initial_layout } /// Returns the number of samples for the image. #[inline] pub fn samples(&self) -> SampleCount { self.samples } /// Returns the tiling of the image. #[inline] pub fn tiling(&self) -> ImageTiling { self.tiling } /// Returns the usage the image was created with. #[inline] pub fn usage(&self) -> ImageUsage { self.usage } /// Returns the stencil usage the image was created with. #[inline] pub fn stencil_usage(&self) -> ImageUsage { self.stencil_usage } /// Returns the sharing the image was created with. #[inline] pub fn sharing(&self) -> &Sharing> { &self.sharing } /// Returns the external memory handle types that are supported with this image. #[inline] pub fn external_memory_handle_types(&self) -> ExternalMemoryHandleTypes { self.external_memory_handle_types } /// Returns an `ImageSubresourceLayers` covering the first mip level of the image. All aspects /// of the image are selected, or `plane0` if the image is multi-planar. #[inline] pub fn subresource_layers(&self) -> ImageSubresourceLayers { ImageSubresourceLayers { aspects: { let aspects = self.format.unwrap().aspects(); if aspects.intersects(ImageAspects::PLANE_0) { ImageAspects::PLANE_0 } else { aspects } }, mip_level: 0, array_layers: 0..self.dimensions.array_layers(), } } /// Returns an `ImageSubresourceRange` covering the whole image. If the image is multi-planar, /// only the `color` aspect is selected. #[inline] pub fn subresource_range(&self) -> ImageSubresourceRange { ImageSubresourceRange { aspects: self.format.unwrap().aspects() - (ImageAspects::PLANE_0 | ImageAspects::PLANE_1 | ImageAspects::PLANE_2), mip_levels: 0..self.mip_levels, array_layers: 0..self.dimensions.array_layers(), } } /// Queries the memory layout of a single subresource of the image. /// /// Only images with linear tiling are supported, if they do not have a format with both a /// depth and a stencil format. Images with optimal tiling have an opaque image layout that is /// not suitable for direct memory accesses, and likewise for combined depth/stencil formats. /// Multi-planar formats are supported, but you must specify one of the planes as the `aspect`, /// not [`ImageAspect::Color`]. /// /// The results of this function are cached, so that future calls with the same arguments /// do not need to make a call to the Vulkan API again. pub fn subresource_layout( &self, aspect: ImageAspect, mip_level: u32, array_layer: u32, ) -> Result { self.validate_subresource_layout(aspect, mip_level, array_layer)?; unsafe { Ok(self.subresource_layout_unchecked(aspect, mip_level, array_layer)) } } fn validate_subresource_layout( &self, aspect: ImageAspect, mip_level: u32, array_layer: u32, ) -> Result<(), ImageError> { // VUID-VkImageSubresource-aspectMask-parameter aspect.validate_device(&self.device)?; // VUID-VkImageSubresource-aspectMask-requiredbitmask // VUID-vkGetImageSubresourceLayout-aspectMask-00997 // Ensured by use of enum `ImageAspect`. // VUID-vkGetImageSubresourceLayout-image-02270 if !matches!( self.tiling, ImageTiling::DrmFormatModifier | ImageTiling::Linear ) { return Err(ImageError::OptimalTilingNotSupported); } // VUID-vkGetImageSubresourceLayout-mipLevel-01716 if mip_level >= self.mip_levels { return Err(ImageError::MipLevelOutOfRange { provided_mip_level: mip_level, image_mip_levels: self.mip_levels, }); } // VUID-vkGetImageSubresourceLayout-arrayLayer-01717 if array_layer >= self.dimensions.array_layers() { return Err(ImageError::ArrayLayerOutOfRange { provided_array_layer: array_layer, image_array_layers: self.dimensions.array_layers(), }); } let mut allowed_aspects = self.format.unwrap().aspects(); // Follows from the combination of these three VUIDs. See: // https://github.com/KhronosGroup/Vulkan-Docs/issues/1942 // VUID-vkGetImageSubresourceLayout-aspectMask-00997 // VUID-vkGetImageSubresourceLayout-format-04462 // VUID-vkGetImageSubresourceLayout-format-04463 if allowed_aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL) { return Err(ImageError::DepthStencilFormatsNotSupported); } if allowed_aspects .intersects(ImageAspects::PLANE_0 | ImageAspects::PLANE_1 | ImageAspects::PLANE_2) { allowed_aspects -= ImageAspects::COLOR; } // TODO: VUID-vkGetImageSubresourceLayout-tiling-02271 //if self.tiling == ImageTiling::DrmFormatModifier { // Only one-plane image importing is possible for now. //} // VUID-vkGetImageSubresourceLayout-format-04461 // VUID-vkGetImageSubresourceLayout-format-04462 // VUID-vkGetImageSubresourceLayout-format-04463 // VUID-vkGetImageSubresourceLayout-format-04464 // VUID-vkGetImageSubresourceLayout-format-01581 // VUID-vkGetImageSubresourceLayout-format-01582 if !allowed_aspects.contains(aspect.into()) { return Err(ImageError::AspectNotAllowed { provided_aspect: aspect, allowed_aspects, }); } Ok(()) } #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn subresource_layout_unchecked( &self, aspect: ImageAspect, mip_level: u32, array_layer: u32, ) -> SubresourceLayout { self.subresource_layout.get_or_insert( (aspect, mip_level, array_layer), |&(aspect, mip_level, array_layer)| { let fns = self.device.fns(); let subresource = ash::vk::ImageSubresource { aspect_mask: aspect.into(), mip_level, array_layer, }; let mut output = MaybeUninit::uninit(); (fns.v1_0.get_image_subresource_layout)( self.device.handle(), self.handle, &subresource, output.as_mut_ptr(), ); let output = output.assume_init(); SubresourceLayout { offset: output.offset, size: output.size, row_pitch: output.row_pitch, array_pitch: (self.dimensions.array_layers() > 1).then_some(output.array_pitch), depth_pitch: matches!(self.dimensions, ImageDimensions::Dim3d { .. }) .then_some(output.depth_pitch), } }, ) } } impl Drop for RawImage { #[inline] fn drop(&mut self) { if !self.needs_destruction { return; } unsafe { let fns = self.device.fns(); (fns.v1_0.destroy_image)(self.device.handle(), self.handle, ptr::null()); } } } unsafe impl VulkanObject for RawImage { type Handle = ash::vk::Image; #[inline] fn handle(&self) -> Self::Handle { self.handle } } unsafe impl DeviceOwned for RawImage { #[inline] fn device(&self) -> &Arc { &self.device } } impl_id_counter!(RawImage); /// Parameters to create a new `Image`. #[derive(Clone, Debug)] pub struct ImageCreateInfo { /// Flags to enable. /// /// The default value is [`ImageCreateFlags::empty()`]. pub flags: ImageCreateFlags, /// The type, extent and number of array layers to create the image with. /// /// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag) /// devices, if `samples` is not [`SampleCount::Sample1`] and `dimensions.array_layers()` is /// not 1, the [`multisample_array_image`](crate::device::Features::multisample_array_image) /// feature must be enabled on the device. /// /// The default value is `ImageDimensions::Dim2d { width: 0, height: 0, array_layers: 1 }`, /// which must be overridden. pub dimensions: ImageDimensions, /// The format used to store the image data. /// /// The default value is `None`, which must be overridden. pub format: Option, /// The number of mip levels to create the image with. /// /// The default value is `1`. pub mip_levels: u32, /// The number of samples per texel that the image should use. /// /// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag) /// devices, if `samples` is not [`SampleCount::Sample1`] and `dimensions.array_layers()` is /// not 1, the [`multisample_array_image`](crate::device::Features::multisample_array_image) /// feature must be enabled on the device. /// /// The default value is [`SampleCount::Sample1`]. pub samples: SampleCount, /// The memory arrangement of the texel blocks. /// /// The default value is [`ImageTiling::Optimal`]. pub tiling: ImageTiling, /// How the image is going to be used. /// /// The default value is [`ImageUsage::empty()`], which must be overridden. pub usage: ImageUsage, /// How the stencil aspect of the image is going to be used, if any. /// /// If `stencil_usage` is empty or if `format` does not have both a depth and a stencil aspect, /// then it is automatically set to equal `usage`. /// /// If after this, `stencil_usage` does not equal `usage`, /// then the device API version must be at least 1.2, or the /// [`ext_separate_stencil_usage`](crate::device::DeviceExtensions::ext_separate_stencil_usage) /// extension must be enabled on the device. /// /// The default value is [`ImageUsage::empty()`]. pub stencil_usage: ImageUsage, /// Whether the image can be shared across multiple queues, or is limited to a single queue. /// /// The default value is [`Sharing::Exclusive`]. pub sharing: Sharing>, /// The image layout that the image will have when it is created. /// /// The default value is [`ImageLayout::Undefined`]. pub initial_layout: ImageLayout, /// The external memory handle types that are going to be used with the image. /// /// If any of the fields in this value are set, the device must either support API version 1.1 /// or the [`khr_external_memory`](crate::device::DeviceExtensions::khr_external_memory) /// extension must be enabled, and `initial_layout` must be set to /// [`ImageLayout::Undefined`]. /// /// The default value is [`ExternalMemoryHandleTypes::empty()`]. pub external_memory_handle_types: ExternalMemoryHandleTypes, /// Specify that an image be created with the provided DRM format modifier and explicit memory layout pub image_drm_format_modifier_create_info: Option, pub _ne: crate::NonExhaustive, } impl Default for ImageCreateInfo { #[inline] fn default() -> Self { Self { flags: ImageCreateFlags::empty(), dimensions: ImageDimensions::Dim2d { width: 0, height: 0, array_layers: 1, }, format: None, mip_levels: 1, samples: SampleCount::Sample1, tiling: ImageTiling::Optimal, usage: ImageUsage::empty(), stencil_usage: ImageUsage::empty(), sharing: Sharing::Exclusive, initial_layout: ImageLayout::Undefined, external_memory_handle_types: ExternalMemoryHandleTypes::empty(), image_drm_format_modifier_create_info: None, _ne: crate::NonExhaustive(()), } } } /// A multi-dimensioned storage for texel data. /// /// Unlike [`RawImage`], an `Image` has memory backing it, and can be used normally. #[derive(Debug)] pub struct Image { inner: RawImage, memory: ImageMemory, aspect_list: SmallVec<[ImageAspect; 4]>, aspect_size: DeviceSize, mip_level_size: DeviceSize, range_size: DeviceSize, state: Mutex, } /// The type of backing memory that an image can have. #[derive(Debug)] pub enum ImageMemory { /// The image is backed by normal memory, bound with [`bind_memory`]. /// /// [`bind_memory`]: RawImage::bind_memory Normal(SmallVec<[MemoryAlloc; 3]>), /// The image is backed by sparse memory, bound with [`bind_sparse`]. /// /// [`bind_sparse`]: crate::device::QueueGuard::bind_sparse Sparse(Vec), /// The image is backed by memory owned by a [`Swapchain`]. Swapchain { swapchain: Arc, image_index: u32, }, } impl Image { fn from_raw(inner: RawImage, memory: ImageMemory) -> Self { let aspects = inner.format.unwrap().aspects(); let aspect_list: SmallVec<[ImageAspect; 4]> = aspects.into_iter().collect(); let mip_level_size = inner.dimensions.array_layers() as DeviceSize; let aspect_size = mip_level_size * inner.mip_levels as DeviceSize; let range_size = aspect_list.len() as DeviceSize * aspect_size; let state = Mutex::new(ImageState::new(range_size, inner.initial_layout)); Image { inner, memory, aspect_list, aspect_size, mip_level_size, range_size, state, } } pub(crate) unsafe fn from_swapchain( handle: ash::vk::Image, swapchain: Arc, image_index: u32, ) -> Self { let create_info = ImageCreateInfo { flags: ImageCreateFlags::empty(), dimensions: ImageDimensions::Dim2d { width: swapchain.image_extent()[0], height: swapchain.image_extent()[1], array_layers: swapchain.image_array_layers(), }, format: Some(swapchain.image_format()), initial_layout: ImageLayout::Undefined, mip_levels: 1, samples: SampleCount::Sample1, tiling: ImageTiling::Optimal, usage: swapchain.image_usage(), stencil_usage: swapchain.image_usage(), sharing: swapchain.image_sharing().clone(), ..Default::default() }; Self::from_raw( RawImage::from_handle_with_destruction( swapchain.device().clone(), handle, create_info, false, ), ImageMemory::Swapchain { swapchain, image_index, }, ) } /// Returns the type of memory that is backing this image. #[inline] pub fn memory(&self) -> &ImageMemory { &self.memory } /// Returns the memory requirements for this image. /// /// - If the image is a swapchain image, this returns a slice with a length of 0. /// - If `self.flags().disjoint` is not set, this returns a slice with a length of 1. /// - If `self.flags().disjoint` is set, this returns a slice with a length equal to /// `self.format().unwrap().planes().len()`. #[inline] pub fn memory_requirements(&self) -> &[MemoryRequirements] { &self.inner.memory_requirements } /// Returns the flags the image was created with. #[inline] pub fn flags(&self) -> ImageCreateFlags { self.inner.flags } /// Returns the dimensions of the image. #[inline] pub fn dimensions(&self) -> ImageDimensions { self.inner.dimensions } /// Returns the image's format. #[inline] pub fn format(&self) -> Option { self.inner.format } /// Returns the features supported by the image's format. #[inline] pub fn format_features(&self) -> FormatFeatures { self.inner.format_features } /// Returns the number of mipmap levels in the image. #[inline] pub fn mip_levels(&self) -> u32 { self.inner.mip_levels } /// Returns the initial layout of the image. #[inline] pub fn initial_layout(&self) -> ImageLayout { self.inner.initial_layout } /// Returns the number of samples for the image. #[inline] pub fn samples(&self) -> SampleCount { self.inner.samples } /// Returns the tiling of the image. #[inline] pub fn tiling(&self) -> ImageTiling { self.inner.tiling } /// Returns the usage the image was created with. #[inline] pub fn usage(&self) -> ImageUsage { self.inner.usage } /// Returns the stencil usage the image was created with. #[inline] pub fn stencil_usage(&self) -> ImageUsage { self.inner.stencil_usage } /// Returns the sharing the image was created with. #[inline] pub fn sharing(&self) -> &Sharing> { &self.inner.sharing } /// Returns the external memory handle types that are supported with this image. #[inline] pub fn external_memory_handle_types(&self) -> ExternalMemoryHandleTypes { self.inner.external_memory_handle_types } /// Returns an `ImageSubresourceLayers` covering the first mip level of the image. All aspects /// of the image are selected, or `plane0` if the image is multi-planar. #[inline] pub fn subresource_layers(&self) -> ImageSubresourceLayers { self.inner.subresource_layers() } /// Returns an `ImageSubresourceRange` covering the whole image. If the image is multi-planar, /// only the `color` aspect is selected. #[inline] pub fn subresource_range(&self) -> ImageSubresourceRange { self.inner.subresource_range() } /// Queries the memory layout of a single subresource of the image. /// /// Only images with linear tiling are supported, if they do not have a format with both a /// depth and a stencil format. Images with optimal tiling have an opaque image layout that is /// not suitable for direct memory accesses, and likewise for combined depth/stencil formats. /// Multi-planar formats are supported, but you must specify one of the planes as the `aspect`, /// not [`ImageAspect::Color`]. /// /// The layout is invariant for each image. However it is not cached, as this would waste /// memory in the case of non-linear-tiling images. You are encouraged to store the layout /// somewhere in order to avoid calling this semi-expensive function at every single memory /// access. #[inline] pub fn subresource_layout( &self, aspect: ImageAspect, mip_level: u32, array_layer: u32, ) -> Result { self.inner .subresource_layout(aspect, mip_level, array_layer) } #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] #[inline] pub unsafe fn subresource_layout_unchecked( &self, aspect: ImageAspect, mip_level: u32, array_layer: u32, ) -> SubresourceLayout { self.inner .subresource_layout_unchecked(aspect, mip_level, array_layer) } pub(crate) fn range_size(&self) -> DeviceSize { self.range_size } /// Returns an iterator over subresource ranges. /// /// In ranges, the subresources are "flattened" to `DeviceSize`, where each index in the range /// is a single array layer. The layers are arranged hierarchically: aspects at the top level, /// with the mip levels in that aspect, and the array layers in that mip level. pub(crate) fn iter_ranges( &self, subresource_range: ImageSubresourceRange, ) -> SubresourceRangeIterator { assert!(self .format() .unwrap() .aspects() .contains(subresource_range.aspects)); assert!(subresource_range.mip_levels.end <= self.inner.mip_levels); assert!(subresource_range.array_layers.end <= self.inner.dimensions.array_layers()); SubresourceRangeIterator::new( subresource_range, &self.aspect_list, self.aspect_size, self.inner.mip_levels, self.mip_level_size, self.inner.dimensions.array_layers(), ) } pub(crate) fn range_to_subresources( &self, mut range: Range, ) -> ImageSubresourceRange { debug_assert!(!range.is_empty()); debug_assert!(range.end <= self.range_size); if range.end - range.start > self.aspect_size { debug_assert!(range.start % self.aspect_size == 0); debug_assert!(range.end % self.aspect_size == 0); let start_aspect_num = (range.start / self.aspect_size) as usize; let end_aspect_num = (range.end / self.aspect_size) as usize; ImageSubresourceRange { aspects: self.aspect_list[start_aspect_num..end_aspect_num] .iter() .copied() .collect(), mip_levels: 0..self.inner.mip_levels, array_layers: 0..self.inner.dimensions.array_layers(), } } else { let aspect_num = (range.start / self.aspect_size) as usize; range.start %= self.aspect_size; range.end %= self.aspect_size; // Wraparound if range.end == 0 { range.end = self.aspect_size; } if range.end - range.start > self.mip_level_size { debug_assert!(range.start % self.mip_level_size == 0); debug_assert!(range.end % self.mip_level_size == 0); let start_mip_level = (range.start / self.mip_level_size) as u32; let end_mip_level = (range.end / self.mip_level_size) as u32; ImageSubresourceRange { aspects: self.aspect_list[aspect_num].into(), mip_levels: start_mip_level..end_mip_level, array_layers: 0..self.inner.dimensions.array_layers(), } } else { let mip_level = (range.start / self.mip_level_size) as u32; range.start %= self.mip_level_size; range.end %= self.mip_level_size; // Wraparound if range.end == 0 { range.end = self.mip_level_size; } let start_array_layer = range.start as u32; let end_array_layer = range.end as u32; ImageSubresourceRange { aspects: self.aspect_list[aspect_num].into(), mip_levels: mip_level..mip_level + 1, array_layers: start_array_layer..end_array_layer, } } } } pub(crate) fn state(&self) -> MutexGuard<'_, ImageState> { self.state.lock() } } unsafe impl VulkanObject for Image { type Handle = ash::vk::Image; #[inline] fn handle(&self) -> Self::Handle { self.inner.handle } } unsafe impl DeviceOwned for Image { #[inline] fn device(&self) -> &Arc { &self.inner.device } } impl PartialEq for Image { #[inline] fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } impl Eq for Image {} impl Hash for Image { fn hash(&self, state: &mut H) { self.inner.hash(state); } } /// The current state of an image. #[derive(Debug)] pub(crate) struct ImageState { ranges: RangeMap, } impl ImageState { fn new(size: DeviceSize, initial_layout: ImageLayout) -> Self { ImageState { ranges: [( 0..size, ImageRangeState { current_access: CurrentAccess::Shared { cpu_reads: 0, gpu_reads: 0, }, layout: initial_layout, }, )] .into_iter() .collect(), } } #[allow(dead_code)] pub(crate) fn check_cpu_read(&self, range: Range) -> Result<(), ReadLockError> { for (_range, state) in self.ranges.range(&range) { match &state.current_access { CurrentAccess::CpuExclusive { .. } => return Err(ReadLockError::CpuWriteLocked), CurrentAccess::GpuExclusive { .. } => return Err(ReadLockError::GpuWriteLocked), CurrentAccess::Shared { .. } => (), } } Ok(()) } #[allow(dead_code)] pub(crate) unsafe fn cpu_read_lock(&mut self, range: Range) { self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { match &mut state.current_access { CurrentAccess::Shared { cpu_reads, .. } => { *cpu_reads += 1; } _ => unreachable!("Image is being written by the CPU or GPU"), } } } #[allow(dead_code)] pub(crate) unsafe fn cpu_read_unlock(&mut self, range: Range) { self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { match &mut state.current_access { CurrentAccess::Shared { cpu_reads, .. } => *cpu_reads -= 1, _ => unreachable!("Image was not locked for CPU read"), } } } #[allow(dead_code)] pub(crate) fn check_cpu_write(&self, range: Range) -> Result<(), WriteLockError> { for (_range, state) in self.ranges.range(&range) { match &state.current_access { CurrentAccess::CpuExclusive => return Err(WriteLockError::CpuLocked), CurrentAccess::GpuExclusive { .. } => return Err(WriteLockError::GpuLocked), CurrentAccess::Shared { cpu_reads: 0, gpu_reads: 0, } => (), CurrentAccess::Shared { cpu_reads, .. } if *cpu_reads > 0 => { return Err(WriteLockError::CpuLocked) } CurrentAccess::Shared { .. } => return Err(WriteLockError::GpuLocked), } } Ok(()) } #[allow(dead_code)] pub(crate) unsafe fn cpu_write_lock(&mut self, range: Range) { self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { state.current_access = CurrentAccess::CpuExclusive; } } #[allow(dead_code)] pub(crate) unsafe fn cpu_write_unlock(&mut self, range: Range) { self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { match &mut state.current_access { CurrentAccess::CpuExclusive => { state.current_access = CurrentAccess::Shared { cpu_reads: 0, gpu_reads: 0, } } _ => unreachable!("Image was not locked for CPU write"), } } } pub(crate) fn check_gpu_read( &self, range: Range, expected_layout: ImageLayout, ) -> Result<(), AccessError> { for (_range, state) in self.ranges.range(&range) { match &state.current_access { CurrentAccess::Shared { .. } => (), _ => return Err(AccessError::AlreadyInUse), } if expected_layout != ImageLayout::Undefined && state.layout != expected_layout { return Err(AccessError::UnexpectedImageLayout { allowed: state.layout, requested: expected_layout, }); } } Ok(()) } pub(crate) unsafe fn gpu_read_lock(&mut self, range: Range) { self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { match &mut state.current_access { CurrentAccess::GpuExclusive { gpu_reads, .. } | CurrentAccess::Shared { gpu_reads, .. } => *gpu_reads += 1, _ => unreachable!("Image is being written by the CPU"), } } } pub(crate) unsafe fn gpu_read_unlock(&mut self, range: Range) { self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { match &mut state.current_access { CurrentAccess::GpuExclusive { gpu_reads, .. } => *gpu_reads -= 1, CurrentAccess::Shared { gpu_reads, .. } => *gpu_reads -= 1, _ => unreachable!("Buffer was not locked for GPU read"), } } } pub(crate) fn check_gpu_write( &self, range: Range, expected_layout: ImageLayout, ) -> Result<(), AccessError> { for (_range, state) in self.ranges.range(&range) { match &state.current_access { CurrentAccess::Shared { cpu_reads: 0, gpu_reads: 0, } => (), _ => return Err(AccessError::AlreadyInUse), } if expected_layout != ImageLayout::Undefined && state.layout != expected_layout { return Err(AccessError::UnexpectedImageLayout { allowed: state.layout, requested: expected_layout, }); } } Ok(()) } pub(crate) unsafe fn gpu_write_lock( &mut self, range: Range, destination_layout: ImageLayout, ) { debug_assert!(!matches!( destination_layout, ImageLayout::Undefined | ImageLayout::Preinitialized )); self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { match &mut state.current_access { CurrentAccess::GpuExclusive { gpu_writes, .. } => *gpu_writes += 1, &mut CurrentAccess::Shared { cpu_reads: 0, gpu_reads, } => { state.current_access = CurrentAccess::GpuExclusive { gpu_reads, gpu_writes: 1, } } _ => unreachable!("Image is being accessed by the CPU"), } state.layout = destination_layout; } } pub(crate) unsafe fn gpu_write_unlock(&mut self, range: Range) { self.ranges.split_at(&range.start); self.ranges.split_at(&range.end); for (_range, state) in self.ranges.range_mut(&range) { match &mut state.current_access { &mut CurrentAccess::GpuExclusive { gpu_reads, gpu_writes: 1, } => { state.current_access = CurrentAccess::Shared { cpu_reads: 0, gpu_reads, } } CurrentAccess::GpuExclusive { gpu_writes, .. } => *gpu_writes -= 1, _ => unreachable!("Image was not locked for GPU write"), } } } } /// The current state of a specific subresource range in an image. #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct ImageRangeState { current_access: CurrentAccess, layout: ImageLayout, } #[derive(Clone)] pub(crate) struct SubresourceRangeIterator { next_fn: fn(&mut Self) -> Option>, image_aspect_size: DeviceSize, image_mip_level_size: DeviceSize, mip_levels: Range, array_layers: Range, aspect_nums: Peekable>, current_aspect_num: Option, current_mip_level: u32, } impl SubresourceRangeIterator { fn new( subresource_range: ImageSubresourceRange, image_aspect_list: &[ImageAspect], image_aspect_size: DeviceSize, image_mip_levels: u32, image_mip_level_size: DeviceSize, image_array_layers: u32, ) -> Self { assert!(!subresource_range.mip_levels.is_empty()); assert!(!subresource_range.array_layers.is_empty()); let next_fn = if subresource_range.array_layers.start != 0 || subresource_range.array_layers.end != image_array_layers { Self::next_some_layers } else if subresource_range.mip_levels.start != 0 || subresource_range.mip_levels.end != image_mip_levels { Self::next_some_levels_all_layers } else { Self::next_all_levels_all_layers }; let mut aspect_nums = subresource_range .aspects .into_iter() .map(|aspect| image_aspect_list.iter().position(|&a| a == aspect).unwrap()) .collect::>() .into_iter() .peekable(); assert!(aspect_nums.len() != 0); let current_aspect_num = aspect_nums.next(); let current_mip_level = subresource_range.mip_levels.start; Self { next_fn, image_aspect_size, image_mip_level_size, mip_levels: subresource_range.mip_levels, array_layers: subresource_range.array_layers, aspect_nums, current_aspect_num, current_mip_level, } } /// Used when the requested range contains only a subset of the array layers in the image. /// The iterator returns one range for each mip level and aspect, each covering the range of /// array layers of that mip level and aspect. fn next_some_layers(&mut self) -> Option> { self.current_aspect_num.map(|aspect_num| { let mip_level_offset = aspect_num as DeviceSize * self.image_aspect_size + self.current_mip_level as DeviceSize * self.image_mip_level_size; self.current_mip_level += 1; if self.current_mip_level >= self.mip_levels.end { self.current_mip_level = self.mip_levels.start; self.current_aspect_num = self.aspect_nums.next(); } let start = mip_level_offset + self.array_layers.start as DeviceSize; let end = mip_level_offset + self.array_layers.end as DeviceSize; start..end }) } /// Used when the requested range contains all array layers in the image, but not all mip /// levels. The iterator returns one range for each aspect, each covering all layers of the /// range of mip levels of that aspect. fn next_some_levels_all_layers(&mut self) -> Option> { self.current_aspect_num.map(|aspect_num| { let aspect_offset = aspect_num as DeviceSize * self.image_aspect_size; self.current_aspect_num = self.aspect_nums.next(); let start = aspect_offset + self.mip_levels.start as DeviceSize * self.image_mip_level_size; let end = aspect_offset + self.mip_levels.end as DeviceSize * self.image_mip_level_size; start..end }) } /// Used when the requested range contains all array layers and mip levels in the image. /// The iterator returns one range for each series of adjacent aspect numbers, each covering /// all mip levels and all layers of those aspects. If the range contains the whole image, then /// exactly one range is returned since all aspect numbers will be adjacent. fn next_all_levels_all_layers(&mut self) -> Option> { self.current_aspect_num.map(|aspect_num_start| { self.current_aspect_num = self.aspect_nums.next(); let mut aspect_num_end = aspect_num_start + 1; while self.current_aspect_num == Some(aspect_num_end) { self.current_aspect_num = self.aspect_nums.next(); aspect_num_end += 1; } let start = aspect_num_start as DeviceSize * self.image_aspect_size; let end = aspect_num_end as DeviceSize * self.image_aspect_size; start..end }) } } impl Iterator for SubresourceRangeIterator { type Item = Range; fn next(&mut self) -> Option { (self.next_fn)(self) } } impl FusedIterator for SubresourceRangeIterator {} /// Describes the memory layout of a single subresource of an image. /// /// The address of a texel at `(x, y, z, layer)` is `layer * array_pitch + z * depth_pitch + /// y * row_pitch + x * size_of_each_texel + offset`. `size_of_each_texel` must be determined /// depending on the format. The same formula applies for compressed formats, except that the /// coordinates must be in number of blocks. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SubresourceLayout { /// The number of bytes from the start of the memory to the start of the queried subresource. pub offset: DeviceSize, /// The total number of bytes for the queried subresource. pub size: DeviceSize, /// The number of bytes between two texels or two blocks in adjacent rows. pub row_pitch: DeviceSize, /// For images with more than one array layer, the number of bytes between two texels or two /// blocks in adjacent array layers. pub array_pitch: Option, /// For 3D images, the number of bytes between two texels or two blocks in adjacent depth /// layers. pub depth_pitch: Option, } /// Error that can happen in image functions. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ImageError { VulkanError(VulkanError), /// Allocating memory failed. AllocError(AllocationCreationError), RequirementNotMet { required_for: &'static str, requires_one_of: RequiresOneOf, }, /// The provided number of elements in `allocations` is not what is required for `image`. AllocationsWrongNumberOfElements { provided: usize, required: usize, }, /// The `array_2d_compatible` flag was enabled, but the image type was not 3D. Array2dCompatibleNot3d, /// The provided array layer is not less than the number of array layers in the image. ArrayLayerOutOfRange { provided_array_layer: u32, image_array_layers: u32, }, /// The provided aspect is not present in the image, or is not allowed. AspectNotAllowed { provided_aspect: ImageAspect, allowed_aspects: ImageAspects, }, /// The `block_texel_view_compatible` flag was enabled, but the given format was not compressed. BlockTexelViewCompatibleNotCompressed, /// The `cube_compatible` flag was enabled, but the image type was not 2D. CubeCompatibleNot2d, /// The `cube_compatible` flag was enabled, but the number of array layers was less than 6. CubeCompatibleNotEnoughArrayLayers, /// The `cube_compatible` flag was enabled, but the image dimensions were not square. CubeCompatibleNotSquare, /// The `cube_compatible` flag was enabled together with multisampling. CubeCompatibleMultisampling, /// The memory was created dedicated to a resource, but not to this image. DedicatedAllocationMismatch, /// A dedicated allocation is required for this image, but one was not provided. DedicatedAllocationRequired, /// The image has a format with both a depth and a stencil aspect, which is not supported for /// this operation. DepthStencilFormatsNotSupported, /// The `disjoint` flag was enabled, but the given format is either not multi-planar, or does /// not support disjoint images. DisjointFormatNotSupported, /// One or more external memory handle types were provided, but the initial layout was not /// `Undefined`. ExternalMemoryInvalidInitialLayout, /// The given format was not supported by the device. FormatNotSupported, /// A requested usage flag was not supported by the given format. FormatUsageNotSupported { usage: &'static str, }, /// The image configuration as queried through the `image_format_properties` function was not /// supported by the device. ImageFormatPropertiesNotSupported, /// The number of array layers exceeds the maximum supported by the device for this image /// configuration. MaxArrayLayersExceeded { array_layers: u32, max: u32, }, /// The specified dimensions exceed the maximum supported by the device for this image /// configuration. MaxDimensionsExceeded { extent: [u32; 3], max: [u32; 3], }, /// The usage included one of the attachment types, and the specified width and height exceeded /// the `max_framebuffer_width` or `max_framebuffer_height` limits. MaxFramebufferDimensionsExceeded { extent: [u32; 2], max: [u32; 2], }, /// The maximum number of mip levels for the given dimensions has been exceeded. MaxMipLevelsExceeded { mip_levels: u32, max: u32, }, /// In an `allocations` element, the offset of the allocation does not have the required /// alignment. MemoryAllocationNotAligned { allocations_index: usize, allocation_offset: DeviceSize, required_alignment: DeviceAlignment, }, /// In an `allocations` element, the size of the allocation is smaller than what is required. MemoryAllocationTooSmall { allocations_index: usize, allocation_size: DeviceSize, required_size: DeviceSize, }, /// In an `allocations` element, the memory was created with export handle types, but none of /// these handle types were enabled on the image. MemoryExternalHandleTypesDisjoint { allocations_index: usize, image_handle_types: ExternalMemoryHandleTypes, memory_export_handle_types: ExternalMemoryHandleTypes, }, /// In an `allocations` element, the memory was created with an import, but the import's handle /// type was not enabled on the image. MemoryImportedHandleTypeNotEnabled { allocations_index: usize, image_handle_types: ExternalMemoryHandleTypes, memory_imported_handle_type: ExternalMemoryHandleType, }, /// In an `allocations` element, the protection of image and memory are not equal. MemoryProtectedMismatch { allocations_index: usize, image_protected: bool, memory_protected: bool, }, /// In an `allocations` element, the provided memory type is not one of the allowed memory /// types that can be bound to this image or image plane. MemoryTypeNotAllowed { allocations_index: usize, provided_memory_type_index: u32, allowed_memory_type_bits: u32, }, /// The provided mip level is not less than the number of mip levels in the image. MipLevelOutOfRange { provided_mip_level: u32, image_mip_levels: u32, }, /// Multisampling was enabled, and the `cube_compatible` flag was set. MultisampleCubeCompatible, /// Multisampling was enabled, and tiling was `Linear`. MultisampleLinearTiling, /// Multisampling was enabled, and multiple mip levels were specified. MultisampleMultipleMipLevels, /// Multisampling was enabled, but the image type was not 2D. MultisampleNot2d, /// The image has optimal tiling, which is not supported for this operation. OptimalTilingNotSupported, /// The sample count is not supported by the device for this image configuration. SampleCountNotSupported { samples: SampleCount, supported: SampleCounts, }, /// The sharing mode was set to `Concurrent`, but one of the specified queue family indices was /// out of range. SharingQueueFamilyIndexOutOfRange { queue_family_index: u32, queue_family_count: u32, }, /// The provided `usage` and `stencil_usage` have different values for /// `depth_stencil_attachment` or `transient_attachment`. StencilUsageMismatch { usage: ImageUsage, stencil_usage: ImageUsage, }, /// A YCbCr format was given, but the specified width and/or height was not a multiple of 2 /// as required by the format's chroma subsampling. YcbcrFormatInvalidDimensions, /// A YCbCr format was given, and multiple mip levels were specified. YcbcrFormatMultipleMipLevels, /// A YCbCr format was given, and multisampling was enabled. YcbcrFormatMultisampling, /// A YCbCr format was given, but the image type was not 2D. YcbcrFormatNot2d, DirectImageViewCreationFailed(ImageViewCreationError), /// If and only if tiling is `DRMFormatModifier`, then `image_drm_format_modifier_create_info` must not be `None`. DrmFormatModifierRequiresCreateInfo, } impl Error for ImageError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { ImageError::AllocError(err) => Some(err), _ => None, } } } impl Display for ImageError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { match self { Self::VulkanError(_) => write!(f, "a runtime error occurred"), Self::AllocError(_) => write!(f, "allocating memory failed"), Self::RequirementNotMet { required_for, requires_one_of, } => write!( f, "a requirement was not met for: {}; requires one of: {}", required_for, requires_one_of, ), Self::AllocationsWrongNumberOfElements { provided, required } => write!( f, "the provided number of elements in `allocations` ({}) is not what is required for \ `image` ({})", provided, required, ), Self::Array2dCompatibleNot3d => write!( f, "the `array_2d_compatible` flag was enabled, but the image type was not 3D", ), Self::ArrayLayerOutOfRange { provided_array_layer, image_array_layers, } => write!( f, "the provided array layer ({}) is not less than the number of array layers in the image ({})", provided_array_layer, image_array_layers, ), Self::AspectNotAllowed { provided_aspect, allowed_aspects, } => write!( f, "the provided aspect ({:?}) is not present in the image, or is not allowed ({:?})", provided_aspect, allowed_aspects, ), Self::BlockTexelViewCompatibleNotCompressed => write!( f, "the `block_texel_view_compatible` flag was enabled, but the given format was not \ compressed", ), Self::CubeCompatibleNot2d => write!( f, "the `cube_compatible` flag was enabled, but the image type was not 2D", ), Self::CubeCompatibleNotEnoughArrayLayers => write!( f, "the `cube_compatible` flag was enabled, but the number of array layers was less \ than 6", ), Self::CubeCompatibleNotSquare => write!( f, "the `cube_compatible` flag was enabled, but the image dimensions were not square", ), Self::CubeCompatibleMultisampling => write!( f, "the `cube_compatible` flag was enabled together with multisampling", ), Self::DedicatedAllocationMismatch => write!( f, "the memory was created dedicated to a resource, but not to this image", ), Self::DedicatedAllocationRequired => write!( f, "a dedicated allocation is required for this image, but one was not provided" ), Self::DepthStencilFormatsNotSupported => write!( f, "the image has a format with both a depth and a stencil aspect, which is not \ supported for this operation", ), Self::DisjointFormatNotSupported => write!( f, "the `disjoint` flag was enabled, but the given format is either not multi-planar, \ or does not support disjoint images", ), Self::ExternalMemoryInvalidInitialLayout => write!( f, "one or more external memory handle types were provided, but the initial layout \ was not `Undefined`", ), Self::FormatNotSupported => { write!(f, "the given format was not supported by the device") } Self::FormatUsageNotSupported { .. } => write!( f, "a requested usage flag was not supported by the given format", ), Self::ImageFormatPropertiesNotSupported => write!( f, "the image configuration as queried through the `image_format_properties` function \ was not supported by the device", ), Self::MaxArrayLayersExceeded { .. } => write!( f, "the number of array layers exceeds the maximum supported by the device for this \ image configuration", ), Self::MaxDimensionsExceeded { .. } => write!( f, "the specified dimensions exceed the maximum supported by the device for this \ image configuration", ), Self::MaxFramebufferDimensionsExceeded { .. } => write!( f, "the usage included one of the attachment types, and the specified width and \ height exceeded the `max_framebuffer_width` or `max_framebuffer_height` limits", ), Self::MaxMipLevelsExceeded { .. } => write!( f, "the maximum number of mip levels for the given dimensions has been exceeded", ), Self::MemoryAllocationNotAligned { allocations_index, allocation_offset, required_alignment, } => write!( f, "in `allocations` element {}, the offset of the allocation ({}) does not have the \ required alignment ({:?})", allocations_index, allocation_offset, required_alignment, ), Self::MemoryAllocationTooSmall { allocations_index, allocation_size, required_size, } => write!( f, "in `allocations` element {}, the size of the allocation ({}) is smaller than what \ is required ({})", allocations_index, allocation_size, required_size, ), Self::MemoryExternalHandleTypesDisjoint { allocations_index, .. } => write!( f, "in `allocations` element {}, the memory was created with export handle types, but \ none of these handle types were enabled on the image", allocations_index, ), Self::MemoryImportedHandleTypeNotEnabled { allocations_index, .. } => write!( f, "in `allocations` element {}, the memory was created with an import, but the \ import's handle type was not enabled on the image", allocations_index, ), Self::MemoryProtectedMismatch { allocations_index, image_protected, memory_protected, } => write!( f, "in `allocations` element {}, the protection of image ({}) and memory ({}) are not \ equal", allocations_index, image_protected, memory_protected, ), Self::MemoryTypeNotAllowed { allocations_index, provided_memory_type_index, allowed_memory_type_bits, } => write!( f, "in `allocations` element {}, the provided memory type ({}) is not one of the \ allowed memory types (", allocations_index, provided_memory_type_index, ) .and_then(|_| { let mut first = true; for i in (0..size_of_val(allowed_memory_type_bits)) .filter(|i| allowed_memory_type_bits & (1 << i) != 0) { if first { write!(f, "{}", i)?; first = false; } else { write!(f, ", {}", i)?; } } Ok(()) }) .and_then(|_| write!(f, ") that can be bound to this buffer")), Self::MipLevelOutOfRange { provided_mip_level, image_mip_levels, } => write!( f, "the provided mip level ({}) is not less than the number of mip levels in the image ({})", provided_mip_level, image_mip_levels, ), Self::MultisampleCubeCompatible => write!( f, "multisampling was enabled, and the `cube_compatible` flag was set", ), Self::MultisampleLinearTiling => { write!(f, "multisampling was enabled, and tiling was `Linear`") } Self::MultisampleMultipleMipLevels => write!( f, "multisampling was enabled, and multiple mip levels were specified", ), Self::MultisampleNot2d => write!( f, "multisampling was enabled, but the image type was not 2D", ), Self::OptimalTilingNotSupported => write!( f, "the image has optimal tiling, which is not supported for this operation", ), Self::SampleCountNotSupported { .. } => write!( f, "the sample count is not supported by the device for this image configuration", ), Self::SharingQueueFamilyIndexOutOfRange { .. } => write!( f, "the sharing mode was set to `Concurrent`, but one of the specified queue family \ indices was out of range", ), Self::StencilUsageMismatch { usage: _, stencil_usage: _, } => write!( f, "the provided `usage` and `stencil_usage` have different values for \ `depth_stencil_attachment` or `transient_attachment`", ), Self::YcbcrFormatInvalidDimensions => write!( f, "a YCbCr format was given, but the specified width and/or height was not a \ multiple of 2 as required by the format's chroma subsampling", ), Self::YcbcrFormatMultipleMipLevels => write!( f, "a YCbCr format was given, and multiple mip levels were specified", ), Self::YcbcrFormatMultisampling => { write!(f, "a YCbCr format was given, and multisampling was enabled") } Self::YcbcrFormatNot2d => { write!(f, "a YCbCr format was given, but the image type was not 2D") } Self::DirectImageViewCreationFailed(e) => write!(f, "Image view creation failed {}", e), Self::DrmFormatModifierRequiresCreateInfo => write!(f, "If and only if tiling is `DRMFormatModifier`, then `image_drm_format_modifier_create_info` must be `Some`"), } } } impl From for ImageError { fn from(err: VulkanError) -> Self { Self::VulkanError(err) } } impl From for ImageError { fn from(err: AllocationCreationError) -> Self { Self::AllocError(err) } } impl From for ImageError { fn from(err: RequirementNotMet) -> Self { Self::RequirementNotMet { required_for: err.required_for, requires_one_of: err.requires_one_of, } } } #[cfg(test)] mod tests { use super::{ImageCreateInfo, ImageError, ImageUsage, RawImage}; use crate::{ format::Format, image::{ sys::SubresourceRangeIterator, ImageAspect, ImageAspects, ImageCreateFlags, ImageDimensions, ImageSubresourceRange, SampleCount, }, DeviceSize, RequiresOneOf, }; use smallvec::SmallVec; #[test] fn create_sampled() { let (device, _) = gfx_dev_and_queue!(); let _ = RawImage::new( device, ImageCreateInfo { dimensions: ImageDimensions::Dim2d { width: 32, height: 32, array_layers: 1, }, format: Some(Format::R8G8B8A8_UNORM), usage: ImageUsage::SAMPLED, ..Default::default() }, ) .unwrap(); } #[test] fn create_transient() { let (device, _) = gfx_dev_and_queue!(); let _ = RawImage::new( device, ImageCreateInfo { dimensions: ImageDimensions::Dim2d { width: 32, height: 32, array_layers: 1, }, format: Some(Format::R8G8B8A8_UNORM), usage: ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::COLOR_ATTACHMENT, ..Default::default() }, ) .unwrap(); } #[test] fn zero_mipmap() { let (device, _) = gfx_dev_and_queue!(); assert_should_panic!({ let _ = RawImage::new( device, ImageCreateInfo { dimensions: ImageDimensions::Dim2d { width: 32, height: 32, array_layers: 1, }, format: Some(Format::R8G8B8A8_UNORM), mip_levels: 0, usage: ImageUsage::SAMPLED, ..Default::default() }, ); }); } #[test] fn mipmaps_too_high() { let (device, _) = gfx_dev_and_queue!(); let res = RawImage::new( device, ImageCreateInfo { dimensions: ImageDimensions::Dim2d { width: 32, height: 32, array_layers: 1, }, format: Some(Format::R8G8B8A8_UNORM), mip_levels: u32::MAX, usage: ImageUsage::SAMPLED, ..Default::default() }, ); match res { Err(ImageError::MaxMipLevelsExceeded { .. }) => (), _ => panic!(), }; } #[test] fn shader_storage_image_multisample() { let (device, _) = gfx_dev_and_queue!(); let res = RawImage::new( device, ImageCreateInfo { dimensions: ImageDimensions::Dim2d { width: 32, height: 32, array_layers: 1, }, format: Some(Format::R8G8B8A8_UNORM), samples: SampleCount::Sample2, usage: ImageUsage::STORAGE, ..Default::default() }, ); match res { Err(ImageError::RequirementNotMet { requires_one_of: RequiresOneOf { features, .. }, .. }) if features.contains(&"shader_storage_image_multisample") => (), Err(ImageError::SampleCountNotSupported { .. }) => (), // unlikely but possible _ => panic!(), }; } #[test] fn compressed_not_color_attachment() { let (device, _) = gfx_dev_and_queue!(); let res = RawImage::new( device, ImageCreateInfo { dimensions: ImageDimensions::Dim2d { width: 32, height: 32, array_layers: 1, }, format: Some(Format::ASTC_5x4_UNORM_BLOCK), usage: ImageUsage::COLOR_ATTACHMENT, ..Default::default() }, ); match res { Err(ImageError::FormatNotSupported) => (), Err(ImageError::FormatUsageNotSupported { usage: "color_attachment", }) => (), _ => panic!(), }; } #[test] fn transient_forbidden_with_some_usages() { let (device, _) = gfx_dev_and_queue!(); assert_should_panic!({ let _ = RawImage::new( device, ImageCreateInfo { dimensions: ImageDimensions::Dim2d { width: 32, height: 32, array_layers: 1, }, format: Some(Format::R8G8B8A8_UNORM), usage: ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::SAMPLED, ..Default::default() }, ); }) } #[test] fn cubecompatible_dims_mismatch() { let (device, _) = gfx_dev_and_queue!(); let res = RawImage::new( device, ImageCreateInfo { flags: ImageCreateFlags::CUBE_COMPATIBLE, dimensions: ImageDimensions::Dim2d { width: 32, height: 64, array_layers: 1, }, format: Some(Format::R8G8B8A8_UNORM), usage: ImageUsage::SAMPLED, ..Default::default() }, ); match res { Err(ImageError::CubeCompatibleNotEnoughArrayLayers) => (), Err(ImageError::CubeCompatibleNotSquare) => (), _ => panic!(), }; } #[test] #[allow(clippy::erasing_op, clippy::identity_op)] fn subresource_range_iterator() { // A fictitious set of aspects that no real image would actually ever have. let image_aspect_list: SmallVec<[ImageAspect; 4]> = (ImageAspects::COLOR | ImageAspects::DEPTH | ImageAspects::STENCIL | ImageAspects::PLANE_0) .into_iter() .collect(); let image_mip_levels = 6; let image_array_layers = 8; let mip = image_array_layers as DeviceSize; let asp = mip * image_mip_levels as DeviceSize; // Whole image let mut iter = SubresourceRangeIterator::new( ImageSubresourceRange { aspects: ImageAspects::COLOR | ImageAspects::DEPTH | ImageAspects::STENCIL | ImageAspects::PLANE_0, mip_levels: 0..6, array_layers: 0..8, }, &image_aspect_list, asp, image_mip_levels, mip, image_array_layers, ); assert_eq!(iter.next(), Some(0 * asp..4 * asp)); assert_eq!(iter.next(), None); // Only some aspects let mut iter = SubresourceRangeIterator::new( ImageSubresourceRange { aspects: ImageAspects::COLOR | ImageAspects::DEPTH | ImageAspects::PLANE_0, mip_levels: 0..6, array_layers: 0..8, }, &image_aspect_list, asp, image_mip_levels, mip, image_array_layers, ); assert_eq!(iter.next(), Some(0 * asp..2 * asp)); assert_eq!(iter.next(), Some(3 * asp..4 * asp)); assert_eq!(iter.next(), None); // Two aspects, and only some of the mip levels let mut iter = SubresourceRangeIterator::new( ImageSubresourceRange { aspects: ImageAspects::DEPTH | ImageAspects::STENCIL, mip_levels: 2..4, array_layers: 0..8, }, &image_aspect_list, asp, image_mip_levels, mip, image_array_layers, ); assert_eq!(iter.next(), Some(1 * asp + 2 * mip..1 * asp + 4 * mip)); assert_eq!(iter.next(), Some(2 * asp + 2 * mip..2 * asp + 4 * mip)); assert_eq!(iter.next(), None); // One aspect, one mip level, only some of the array layers let mut iter = SubresourceRangeIterator::new( ImageSubresourceRange { aspects: ImageAspects::COLOR, mip_levels: 0..1, array_layers: 2..4, }, &image_aspect_list, asp, image_mip_levels, mip, image_array_layers, ); assert_eq!( iter.next(), Some(0 * asp + 0 * mip + 2..0 * asp + 0 * mip + 4) ); assert_eq!(iter.next(), None); // Two aspects, two mip levels, only some of the array layers let mut iter = SubresourceRangeIterator::new( ImageSubresourceRange { aspects: ImageAspects::DEPTH | ImageAspects::STENCIL, mip_levels: 2..4, array_layers: 6..8, }, &image_aspect_list, asp, image_mip_levels, mip, image_array_layers, ); assert_eq!( iter.next(), Some(1 * asp + 2 * mip + 6..1 * asp + 2 * mip + 8) ); assert_eq!( iter.next(), Some(1 * asp + 3 * mip + 6..1 * asp + 3 * mip + 8) ); assert_eq!( iter.next(), Some(2 * asp + 2 * mip + 6..2 * asp + 2 * mip + 8) ); assert_eq!( iter.next(), Some(2 * asp + 3 * mip + 6..2 * asp + 3 * mip + 8) ); assert_eq!(iter.next(), None); } }