| // Copyright (c) 2016 The vulkano developers |
| // Licensed under the Apache License, Version 2.0 |
| // <LICENSE-APACHE or |
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT |
| // license <LICENSE-MIT or https://opensource.org/licenses/MIT>, |
| // at your option. All files in the project carrying such |
| // notice may not be copied, modified, or distributed except |
| // according to those terms. |
| |
| //! 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 crate::check_errors; |
| use crate::device::Device; |
| use crate::format::Format; |
| use crate::format::FormatFeatures; |
| use crate::format::FormatTy; |
| use crate::image::ImageAspect; |
| use crate::image::ImageCreateFlags; |
| use crate::image::ImageDimensions; |
| use crate::image::ImageUsage; |
| use crate::image::MipmapsCount; |
| use crate::image::SampleCount; |
| use crate::memory::DeviceMemory; |
| use crate::memory::DeviceMemoryAllocError; |
| use crate::memory::MemoryRequirements; |
| use crate::sync::Sharing; |
| use crate::DeviceSize; |
| use crate::Error; |
| use crate::OomError; |
| use crate::Version; |
| use crate::VulkanObject; |
| use ash::vk::Handle; |
| use smallvec::SmallVec; |
| use std::error; |
| use std::fmt; |
| use std::hash::Hash; |
| use std::hash::Hasher; |
| use std::mem::MaybeUninit; |
| use std::ops::Range; |
| use std::ptr; |
| use std::sync::Arc; |
| |
| /// A storage for pixels or arbitrary data. |
| /// |
| /// # Safety |
| /// |
| /// This type is not just unsafe but very unsafe. Don't use it directly. |
| /// |
| /// - You must manually bind memory to the image with `bind_memory`. The memory must respect the |
| /// requirements returned by `new`. |
| /// - The memory that you bind to the image must be manually kept alive. |
| /// - The queue family ownership must be manually enforced. |
| /// - The usage must be manually enforced. |
| /// - The image layout must be manually enforced and transitioned. |
| /// |
| pub struct UnsafeImage { |
| image: ash::vk::Image, |
| device: Arc<Device>, |
| usage: ImageUsage, |
| format: Format, |
| flags: ImageCreateFlags, |
| |
| dimensions: ImageDimensions, |
| samples: SampleCount, |
| mipmaps: u32, |
| |
| // Features that are supported for this particular format. |
| format_features: FormatFeatures, |
| |
| // `vkDestroyImage` is called only if `needs_destruction` is true. |
| needs_destruction: bool, |
| preinitialized_layout: bool, |
| } |
| |
| impl UnsafeImage { |
| /// Creates a new image and allocates memory for it. |
| /// |
| /// # Panic |
| /// |
| /// - Panics if one of the dimensions is 0. |
| /// - Panics if the number of mipmaps is 0. |
| /// - Panics if the number of samples is 0. |
| /// |
| #[inline] |
| pub unsafe fn new<'a, Mi, I>( |
| device: Arc<Device>, |
| usage: ImageUsage, |
| format: Format, |
| flags: ImageCreateFlags, |
| dimensions: ImageDimensions, |
| num_samples: SampleCount, |
| mipmaps: Mi, |
| sharing: Sharing<I>, |
| linear_tiling: bool, |
| preinitialized_layout: bool, |
| ) -> Result<(UnsafeImage, MemoryRequirements), ImageCreationError> |
| where |
| Mi: Into<MipmapsCount>, |
| I: Iterator<Item = u32>, |
| { |
| let sharing = match sharing { |
| Sharing::Exclusive => (ash::vk::SharingMode::EXCLUSIVE, SmallVec::<[u32; 8]>::new()), |
| Sharing::Concurrent(ids) => (ash::vk::SharingMode::CONCURRENT, ids.collect()), |
| }; |
| |
| UnsafeImage::new_impl( |
| device, |
| usage, |
| format, |
| flags, |
| dimensions, |
| num_samples, |
| mipmaps.into(), |
| sharing, |
| linear_tiling, |
| preinitialized_layout, |
| ) |
| } |
| |
| // Non-templated version to avoid inlining and improve compile times. |
| unsafe fn new_impl( |
| device: Arc<Device>, |
| usage: ImageUsage, |
| format: Format, |
| flags: ImageCreateFlags, |
| dimensions: ImageDimensions, |
| num_samples: SampleCount, |
| mipmaps: MipmapsCount, |
| (sh_mode, sh_indices): (ash::vk::SharingMode, SmallVec<[u32; 8]>), |
| linear_tiling: bool, |
| preinitialized_layout: bool, |
| ) -> Result<(UnsafeImage, MemoryRequirements), ImageCreationError> { |
| // TODO: doesn't check that the proper features are enabled |
| |
| if flags.sparse_binding |
| || flags.sparse_residency |
| || flags.sparse_aliased |
| || flags.mutable_format |
| { |
| unimplemented!(); |
| } |
| |
| let fns = device.fns(); |
| let fns_i = device.instance().fns(); |
| |
| // Checking if image usage conforms to what is supported. |
| let format_features = { |
| let format_properties = format.properties(device.physical_device()); |
| |
| let features = if linear_tiling { |
| format_properties.linear_tiling_features |
| } else { |
| format_properties.optimal_tiling_features |
| }; |
| |
| if features == FormatFeatures::default() { |
| return Err(ImageCreationError::FormatNotSupported); |
| } |
| |
| if usage.sampled && !features.sampled_image { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| if usage.storage && !features.storage_image { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| if usage.color_attachment && !features.color_attachment { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| if usage.depth_stencil_attachment && !features.depth_stencil_attachment { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| if usage.input_attachment |
| && !(features.color_attachment || features.depth_stencil_attachment) |
| { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_maintenance1 |
| { |
| if usage.transfer_source && !features.transfer_src { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| if usage.transfer_destination && !features.transfer_dst { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| } |
| |
| features |
| }; |
| |
| // VUID-VkImageCreateInfo-usage-requiredbitmask: usage must not be 0 |
| if usage == ImageUsage::none() { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| |
| // If `transient_attachment` is true, then only `color_attachment`, |
| // `depth_stencil_attachment` and `input_attachment` can be true as well. |
| if usage.transient_attachment { |
| let u = ImageUsage { |
| transient_attachment: false, |
| color_attachment: false, |
| depth_stencil_attachment: false, |
| input_attachment: false, |
| ..usage.clone() |
| }; |
| |
| if u != ImageUsage::none() { |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| } |
| |
| // This function is going to perform various checks and write to `capabilities_error` in |
| // case of error. |
| // |
| // If `capabilities_error` is not `None` after the checks are finished, the function will |
| // check for additional image capabilities (section 31.4 of the specs). |
| let mut capabilities_error = None; |
| |
| // Compute the number of mipmaps. |
| let mipmaps = match mipmaps.into() { |
| MipmapsCount::Specific(num) => { |
| let max_mipmaps = dimensions.max_mipmaps(); |
| debug_assert!(max_mipmaps >= 1); |
| if num < 1 { |
| return Err(ImageCreationError::InvalidMipmapsCount { |
| obtained: num, |
| valid_range: 1..max_mipmaps + 1, |
| }); |
| } else if num > max_mipmaps { |
| capabilities_error = Some(ImageCreationError::InvalidMipmapsCount { |
| obtained: num, |
| valid_range: 1..max_mipmaps + 1, |
| }); |
| } |
| |
| num |
| } |
| MipmapsCount::Log2 => dimensions.max_mipmaps(), |
| MipmapsCount::One => 1, |
| }; |
| |
| // Checking whether the number of samples is supported. |
| let mut supported_samples = ash::vk::SampleCountFlags::from_raw(0x7f); // all bits up to VK_SAMPLE_COUNT_64_BIT |
| |
| if usage.sampled { |
| match format.ty() { |
| FormatTy::Float | FormatTy::Compressed => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .sampled_image_color_sample_counts |
| .into(); |
| } |
| FormatTy::Uint | FormatTy::Sint => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .sampled_image_integer_sample_counts |
| .into(); |
| } |
| FormatTy::Depth => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .sampled_image_depth_sample_counts |
| .into(); |
| } |
| FormatTy::Stencil => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .sampled_image_stencil_sample_counts |
| .into(); |
| } |
| FormatTy::DepthStencil => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .sampled_image_depth_sample_counts |
| .into(); |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .sampled_image_stencil_sample_counts |
| .into(); |
| } |
| FormatTy::Ycbcr => { |
| /* |
| * VUID-VkImageCreateInfo-format-02562: If the image format is one of |
| * those formats requiring sampler ycbcr conversion, samples *must* be |
| * VK_SAMPLE_COUNT_1_BIT |
| */ |
| supported_samples &= ash::vk::SampleCountFlags::TYPE_1; |
| } |
| } |
| |
| if usage.storage { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .storage_image_sample_counts |
| .into(); |
| } |
| |
| if usage.color_attachment |
| || usage.depth_stencil_attachment |
| || usage.input_attachment |
| || usage.transient_attachment |
| { |
| match format.ty() { |
| FormatTy::Float | FormatTy::Compressed | FormatTy::Uint | FormatTy::Sint => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .framebuffer_color_sample_counts |
| .into(); |
| } |
| FormatTy::Depth => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .framebuffer_depth_sample_counts |
| .into(); |
| } |
| FormatTy::Stencil => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .framebuffer_stencil_sample_counts |
| .into(); |
| } |
| FormatTy::DepthStencil => { |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .framebuffer_depth_sample_counts |
| .into(); |
| supported_samples &= device |
| .physical_device() |
| .properties() |
| .framebuffer_stencil_sample_counts |
| .into(); |
| } |
| FormatTy::Ycbcr => { |
| /* |
| * It's generally not possible to use a Ycbcr image as a framebuffer color |
| * attachment. |
| */ |
| return Err(ImageCreationError::UnsupportedUsage); |
| } |
| } |
| } |
| |
| if (ash::vk::SampleCountFlags::from(num_samples) & supported_samples).is_empty() { |
| let err = ImageCreationError::UnsupportedSamplesCount { |
| obtained: num_samples, |
| }; |
| capabilities_error = Some(err); |
| } |
| } |
| |
| // If the `shaderStorageImageMultisample` feature is not enabled and we have |
| // `usage_storage` set to true, then the number of samples must be 1. |
| if usage.storage && num_samples as u32 > 1 { |
| if !device.enabled_features().shader_storage_image_multisample { |
| return Err(ImageCreationError::ShaderStorageImageMultisampleFeatureNotEnabled); |
| } |
| } |
| |
| // Decoding the dimensions. |
| let (ty, extent, array_layers) = match dimensions { |
| ImageDimensions::Dim1d { |
| width, |
| array_layers, |
| } => { |
| if width == 0 || array_layers == 0 { |
| return Err(ImageCreationError::UnsupportedDimensions { dimensions }); |
| } |
| let extent = ash::vk::Extent3D { |
| width, |
| height: 1, |
| depth: 1, |
| }; |
| (ash::vk::ImageType::TYPE_1D, extent, array_layers) |
| } |
| ImageDimensions::Dim2d { |
| width, |
| height, |
| array_layers, |
| } => { |
| if width == 0 || height == 0 || array_layers == 0 { |
| return Err(ImageCreationError::UnsupportedDimensions { dimensions }); |
| } |
| let extent = ash::vk::Extent3D { |
| width, |
| height, |
| depth: 1, |
| }; |
| (ash::vk::ImageType::TYPE_2D, extent, array_layers) |
| } |
| ImageDimensions::Dim3d { |
| width, |
| height, |
| depth, |
| } => { |
| if width == 0 || height == 0 || depth == 0 { |
| return Err(ImageCreationError::UnsupportedDimensions { dimensions }); |
| } |
| let extent = ash::vk::Extent3D { |
| width, |
| height, |
| depth, |
| }; |
| (ash::vk::ImageType::TYPE_3D, extent, 1) |
| } |
| }; |
| |
| // Checking flags requirements. |
| if flags.cube_compatible { |
| if !(ty == ash::vk::ImageType::TYPE_2D |
| && extent.width == extent.height |
| && array_layers >= 6) |
| { |
| return Err(ImageCreationError::CreationFlagRequirementsNotMet); |
| } |
| } |
| |
| if flags.array_2d_compatible { |
| if !(ty == ash::vk::ImageType::TYPE_3D) { |
| return Err(ImageCreationError::CreationFlagRequirementsNotMet); |
| } |
| } |
| |
| // Checking the dimensions against the limits. |
| if array_layers |
| > device |
| .physical_device() |
| .properties() |
| .max_image_array_layers |
| { |
| let err = ImageCreationError::UnsupportedDimensions { dimensions }; |
| capabilities_error = Some(err); |
| } |
| match ty { |
| ash::vk::ImageType::TYPE_1D => { |
| if extent.width |
| > device |
| .physical_device() |
| .properties() |
| .max_image_dimension1_d |
| { |
| let err = ImageCreationError::UnsupportedDimensions { dimensions }; |
| capabilities_error = Some(err); |
| } |
| } |
| ash::vk::ImageType::TYPE_2D => { |
| let limit = device |
| .physical_device() |
| .properties() |
| .max_image_dimension2_d; |
| if extent.width > limit || extent.height > limit { |
| let err = ImageCreationError::UnsupportedDimensions { dimensions }; |
| capabilities_error = Some(err); |
| } |
| |
| if flags.cube_compatible { |
| let limit = device |
| .physical_device() |
| .properties() |
| .max_image_dimension_cube; |
| if extent.width > limit { |
| let err = ImageCreationError::UnsupportedDimensions { dimensions }; |
| capabilities_error = Some(err); |
| } |
| } |
| } |
| ash::vk::ImageType::TYPE_3D => { |
| let limit = device |
| .physical_device() |
| .properties() |
| .max_image_dimension3_d; |
| if extent.width > limit || extent.height > limit || extent.depth > limit { |
| let err = ImageCreationError::UnsupportedDimensions { dimensions }; |
| capabilities_error = Some(err); |
| } |
| } |
| _ => unreachable!(), |
| }; |
| |
| let usage_bits = usage.into(); |
| |
| // Now that all checks have been performed, if any of the check failed we query the Vulkan |
| // implementation for additional image capabilities. |
| if let Some(capabilities_error) = capabilities_error { |
| let tiling = if linear_tiling { |
| ash::vk::ImageTiling::LINEAR |
| } else { |
| ash::vk::ImageTiling::OPTIMAL |
| }; |
| |
| let mut output = MaybeUninit::uninit(); |
| let physical_device = device.physical_device().internal_object(); |
| let r = fns_i.v1_0.get_physical_device_image_format_properties( |
| physical_device, |
| format.into(), |
| ty, |
| tiling, |
| usage_bits, |
| ash::vk::ImageCreateFlags::empty(), /* TODO */ |
| output.as_mut_ptr(), |
| ); |
| |
| match check_errors(r) { |
| Ok(_) => (), |
| Err(Error::FormatNotSupported) => { |
| return Err(ImageCreationError::FormatNotSupported) |
| } |
| Err(err) => return Err(err.into()), |
| } |
| |
| let output = output.assume_init(); |
| |
| if extent.width > output.max_extent.width |
| || extent.height > output.max_extent.height |
| || extent.depth > output.max_extent.depth |
| || mipmaps > output.max_mip_levels |
| || array_layers > output.max_array_layers |
| || (ash::vk::SampleCountFlags::from(num_samples) & output.sample_counts).is_empty() |
| { |
| return Err(capabilities_error); |
| } |
| } |
| |
| // Everything now ok. Creating the image. |
| let image = { |
| let infos = ash::vk::ImageCreateInfo { |
| flags: flags.into(), |
| image_type: ty, |
| format: format.into(), |
| extent, |
| mip_levels: mipmaps, |
| array_layers: array_layers, |
| samples: num_samples.into(), |
| tiling: if linear_tiling { |
| ash::vk::ImageTiling::LINEAR |
| } else { |
| ash::vk::ImageTiling::OPTIMAL |
| }, |
| usage: usage_bits, |
| sharing_mode: sh_mode, |
| queue_family_index_count: sh_indices.len() as u32, |
| p_queue_family_indices: sh_indices.as_ptr(), |
| initial_layout: if preinitialized_layout { |
| ash::vk::ImageLayout::PREINITIALIZED |
| } else { |
| ash::vk::ImageLayout::UNDEFINED |
| }, |
| ..Default::default() |
| }; |
| |
| let mut output = MaybeUninit::uninit(); |
| check_errors(fns.v1_0.create_image( |
| device.internal_object(), |
| &infos, |
| ptr::null(), |
| output.as_mut_ptr(), |
| ))?; |
| output.assume_init() |
| }; |
| |
| let mem_reqs = if device.api_version() >= Version::V1_1 |
| || device.enabled_extensions().khr_get_memory_requirements2 |
| { |
| let infos = ash::vk::ImageMemoryRequirementsInfo2 { |
| image, |
| ..Default::default() |
| }; |
| |
| let mut output2 = if device.api_version() >= Version::V1_1 |
| || device.enabled_extensions().khr_dedicated_allocation |
| { |
| Some(ash::vk::MemoryDedicatedRequirements::default()) |
| } else { |
| None |
| }; |
| |
| let mut output = ash::vk::MemoryRequirements2 { |
| p_next: output2 |
| .as_mut() |
| .map(|o| o as *mut _) |
| .unwrap_or(ptr::null_mut()) as *mut _, |
| ..Default::default() |
| }; |
| |
| if device.api_version() >= Version::V1_1 { |
| fns.v1_1.get_image_memory_requirements2( |
| device.internal_object(), |
| &infos, |
| &mut output, |
| ); |
| } else { |
| fns.khr_get_memory_requirements2 |
| .get_image_memory_requirements2_khr( |
| device.internal_object(), |
| &infos, |
| &mut output, |
| ); |
| } |
| |
| debug_assert!(output.memory_requirements.memory_type_bits != 0); |
| |
| let mut out = MemoryRequirements::from(output.memory_requirements); |
| if let Some(output2) = output2 { |
| debug_assert_eq!(output2.requires_dedicated_allocation, 0); |
| out.prefer_dedicated = output2.prefers_dedicated_allocation != 0; |
| } |
| out |
| } else { |
| let mut output: MaybeUninit<ash::vk::MemoryRequirements> = MaybeUninit::uninit(); |
| fns.v1_0.get_image_memory_requirements( |
| device.internal_object(), |
| image, |
| output.as_mut_ptr(), |
| ); |
| let output = output.assume_init(); |
| debug_assert!(output.memory_type_bits != 0); |
| MemoryRequirements::from(output) |
| }; |
| |
| let image = UnsafeImage { |
| device: device.clone(), |
| image, |
| usage, |
| format, |
| flags, |
| dimensions, |
| samples: num_samples, |
| mipmaps, |
| format_features, |
| needs_destruction: true, |
| preinitialized_layout, |
| }; |
| |
| Ok((image, mem_reqs)) |
| } |
| |
| /// Creates an image from a raw handle. The image won't be destroyed. |
| /// |
| /// This function is for example used at the swapchain's initialization. |
| pub unsafe fn from_raw( |
| device: Arc<Device>, |
| handle: ash::vk::Image, |
| usage: ImageUsage, |
| format: Format, |
| flags: ImageCreateFlags, |
| dimensions: ImageDimensions, |
| samples: SampleCount, |
| mipmaps: u32, |
| ) -> UnsafeImage { |
| let format_properties = format.properties(device.physical_device()); |
| |
| // TODO: check that usage is correct in regard to `output`? |
| |
| UnsafeImage { |
| device: device.clone(), |
| image: handle, |
| usage, |
| format, |
| flags, |
| dimensions, |
| samples, |
| mipmaps, |
| format_features: format_properties.optimal_tiling_features, |
| needs_destruction: false, // TODO: pass as parameter |
| preinitialized_layout: false, // TODO: Maybe this should be passed in? |
| } |
| } |
| |
| pub unsafe fn bind_memory( |
| &self, |
| memory: &DeviceMemory, |
| offset: DeviceSize, |
| ) -> Result<(), OomError> { |
| let fns = self.device.fns(); |
| |
| // We check for correctness in debug mode. |
| debug_assert!({ |
| let mut mem_reqs = MaybeUninit::uninit(); |
| fns.v1_0.get_image_memory_requirements( |
| self.device.internal_object(), |
| self.image, |
| mem_reqs.as_mut_ptr(), |
| ); |
| |
| let mem_reqs = mem_reqs.assume_init(); |
| mem_reqs.size <= memory.size() - offset |
| && offset % mem_reqs.alignment == 0 |
| && mem_reqs.memory_type_bits & (1 << memory.memory_type().id()) != 0 |
| }); |
| |
| check_errors(fns.v1_0.bind_image_memory( |
| self.device.internal_object(), |
| self.image, |
| memory.internal_object(), |
| offset, |
| ))?; |
| Ok(()) |
| } |
| |
| #[inline] |
| pub fn device(&self) -> &Arc<Device> { |
| &self.device |
| } |
| |
| #[inline] |
| pub fn format(&self) -> Format { |
| self.format |
| } |
| |
| pub fn create_flags(&self) -> ImageCreateFlags { |
| self.flags |
| } |
| |
| #[inline] |
| pub fn mipmap_levels(&self) -> u32 { |
| self.mipmaps |
| } |
| |
| #[inline] |
| pub fn dimensions(&self) -> ImageDimensions { |
| self.dimensions |
| } |
| |
| #[inline] |
| pub fn samples(&self) -> SampleCount { |
| self.samples |
| } |
| |
| /// Returns a key unique to each `UnsafeImage`. Can be used for the `conflicts_key` method. |
| #[inline] |
| pub fn key(&self) -> u64 { |
| self.image.as_raw() |
| } |
| |
| /// Queries the layout of an image in memory. Only valid for images with linear tiling. |
| /// |
| /// This function is only valid for images with a color format. See the other similar functions |
| /// for the other aspects. |
| /// |
| /// 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. |
| /// |
| /// Note that while Vulkan allows querying the array layers other than 0, it is redundant as |
| /// you can easily calculate the position of any layer. |
| /// |
| /// # Panic |
| /// |
| /// - Panics if the mipmap level is out of range. |
| /// |
| /// # Safety |
| /// |
| /// - The image must *not* have a depth, stencil or depth-stencil format. |
| /// - The image must have been created with linear tiling. |
| /// |
| #[inline] |
| pub unsafe fn color_linear_layout(&self, mip_level: u32) -> LinearLayout { |
| self.linear_layout_impl(mip_level, ImageAspect::Color) |
| } |
| |
| /// Same as `color_linear_layout`, except that it retrieves the depth component of the image. |
| /// |
| /// # Panic |
| /// |
| /// - Panics if the mipmap level is out of range. |
| /// |
| /// # Safety |
| /// |
| /// - The image must have a depth or depth-stencil format. |
| /// - The image must have been created with linear tiling. |
| /// |
| #[inline] |
| pub unsafe fn depth_linear_layout(&self, mip_level: u32) -> LinearLayout { |
| self.linear_layout_impl(mip_level, ImageAspect::Depth) |
| } |
| |
| /// Same as `color_linear_layout`, except that it retrieves the stencil component of the image. |
| /// |
| /// # Panic |
| /// |
| /// - Panics if the mipmap level is out of range. |
| /// |
| /// # Safety |
| /// |
| /// - The image must have a stencil or depth-stencil format. |
| /// - The image must have been created with linear tiling. |
| /// |
| #[inline] |
| pub unsafe fn stencil_linear_layout(&self, mip_level: u32) -> LinearLayout { |
| self.linear_layout_impl(mip_level, ImageAspect::Stencil) |
| } |
| |
| /// Same as `color_linear_layout`, except that it retrieves layout for the requested ycbcr |
| /// component too if the format is a YcbCr format. |
| /// |
| /// # Panic |
| /// |
| /// - Panics if plane aspect is out of range. |
| /// - Panics if the aspect is not a color or planar aspect. |
| /// - Panics if the number of mipmaps is not 1. |
| #[inline] |
| pub unsafe fn multiplane_color_layout(&self, aspect: ImageAspect) -> LinearLayout { |
| // This function only supports color and planar aspects currently. |
| assert!(matches!( |
| aspect, |
| ImageAspect::Color | ImageAspect::Plane0 | ImageAspect::Plane1 | ImageAspect::Plane2 |
| )); |
| assert!(self.mipmaps == 1); |
| |
| if matches!( |
| aspect, |
| ImageAspect::Plane0 | ImageAspect::Plane1 | ImageAspect::Plane2 |
| ) { |
| assert_eq!(self.format.ty(), FormatTy::Ycbcr); |
| if aspect == ImageAspect::Plane2 { |
| // Vulkano only supports NV12 and YV12 currently. If that changes, this will too. |
| assert!(self.format == Format::G8B8R8_3PLANE420Unorm); |
| } |
| } |
| |
| self.linear_layout_impl(0, aspect) |
| } |
| |
| // Implementation of the `*_layout` functions. |
| unsafe fn linear_layout_impl(&self, mip_level: u32, aspect: ImageAspect) -> LinearLayout { |
| let fns = self.device.fns(); |
| |
| assert!(mip_level < self.mipmaps); |
| |
| let subresource = ash::vk::ImageSubresource { |
| aspect_mask: ash::vk::ImageAspectFlags::from(aspect), |
| mip_level: mip_level, |
| array_layer: 0, |
| }; |
| |
| let mut out = MaybeUninit::uninit(); |
| fns.v1_0.get_image_subresource_layout( |
| self.device.internal_object(), |
| self.image, |
| &subresource, |
| out.as_mut_ptr(), |
| ); |
| |
| let out = out.assume_init(); |
| LinearLayout { |
| offset: out.offset, |
| size: out.size, |
| row_pitch: out.row_pitch, |
| array_pitch: out.array_pitch, |
| depth_pitch: out.depth_pitch, |
| } |
| } |
| |
| /// Returns the flags the image was created with. |
| #[inline] |
| pub fn flags(&self) -> ImageCreateFlags { |
| self.flags |
| } |
| |
| /// Returns the features supported by the image's format. |
| #[inline] |
| pub fn format_features(&self) -> FormatFeatures { |
| self.format_features |
| } |
| |
| /// Returns the usage the image was created with. |
| #[inline] |
| pub fn usage(&self) -> ImageUsage { |
| self.usage |
| } |
| |
| #[inline] |
| pub fn preinitialized_layout(&self) -> bool { |
| self.preinitialized_layout |
| } |
| } |
| |
| unsafe impl VulkanObject for UnsafeImage { |
| type Object = ash::vk::Image; |
| |
| #[inline] |
| fn internal_object(&self) -> ash::vk::Image { |
| self.image |
| } |
| } |
| |
| impl fmt::Debug for UnsafeImage { |
| #[inline] |
| fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
| write!(fmt, "<Vulkan image {:?}>", self.image) |
| } |
| } |
| |
| impl Drop for UnsafeImage { |
| #[inline] |
| fn drop(&mut self) { |
| if !self.needs_destruction { |
| return; |
| } |
| |
| unsafe { |
| let fns = self.device.fns(); |
| fns.v1_0 |
| .destroy_image(self.device.internal_object(), self.image, ptr::null()); |
| } |
| } |
| } |
| |
| impl PartialEq for UnsafeImage { |
| #[inline] |
| fn eq(&self, other: &Self) -> bool { |
| self.image == other.image && self.device == other.device |
| } |
| } |
| |
| impl Eq for UnsafeImage {} |
| |
| impl Hash for UnsafeImage { |
| #[inline] |
| fn hash<H: Hasher>(&self, state: &mut H) { |
| self.image.hash(state); |
| self.device.hash(state); |
| } |
| } |
| |
| /// Error that can happen when creating an instance. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum ImageCreationError { |
| /// Allocating memory failed. |
| AllocError(DeviceMemoryAllocError), |
| /// The specified creation flags have requirements (e.g. specific dimension) that were not met. |
| CreationFlagRequirementsNotMet, |
| /// A wrong number of mipmaps was provided. |
| FormatNotSupported, |
| /// The format is supported, but at least one of the requested usages is not supported. |
| InvalidMipmapsCount { |
| obtained: u32, |
| valid_range: Range<u32>, |
| }, |
| /// The requested number of samples is not supported, or is 0. |
| UnsupportedSamplesCount { obtained: SampleCount }, |
| /// The dimensions are too large, or one of the dimensions is 0. |
| UnsupportedDimensions { dimensions: ImageDimensions }, |
| /// The requested format is not supported by the Vulkan implementation. |
| UnsupportedUsage, |
| /// The `shader_storage_image_multisample` feature must be enabled to create such an image. |
| ShaderStorageImageMultisampleFeatureNotEnabled, |
| } |
| |
| impl error::Error for ImageCreationError { |
| #[inline] |
| fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
| match *self { |
| ImageCreationError::AllocError(ref err) => Some(err), |
| _ => None, |
| } |
| } |
| } |
| |
| impl fmt::Display for ImageCreationError { |
| #[inline] |
| fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
| write!( |
| fmt, |
| "{}", |
| match *self { |
| ImageCreationError::AllocError(_) => "allocating memory failed", |
| ImageCreationError::CreationFlagRequirementsNotMet => { |
| "the requested creation flags have additional requirements that were not met" |
| } |
| ImageCreationError::FormatNotSupported => { |
| "the requested format is not supported by the Vulkan implementation" |
| } |
| ImageCreationError::InvalidMipmapsCount { .. } => { |
| "a wrong number of mipmaps was provided" |
| } |
| ImageCreationError::UnsupportedSamplesCount { .. } => { |
| "the requested number of samples is not supported, or is 0" |
| } |
| ImageCreationError::UnsupportedDimensions { .. } => { |
| "the dimensions are too large, or one of the dimensions is 0" |
| } |
| ImageCreationError::UnsupportedUsage => { |
| "the format is supported, but at least one of the requested usages is not \ |
| supported" |
| } |
| ImageCreationError::ShaderStorageImageMultisampleFeatureNotEnabled => { |
| "the `shader_storage_image_multisample` feature must be enabled to create such \ |
| an image" |
| } |
| } |
| ) |
| } |
| } |
| |
| impl From<OomError> for ImageCreationError { |
| #[inline] |
| fn from(err: OomError) -> ImageCreationError { |
| ImageCreationError::AllocError(DeviceMemoryAllocError::OomError(err)) |
| } |
| } |
| |
| impl From<DeviceMemoryAllocError> for ImageCreationError { |
| #[inline] |
| fn from(err: DeviceMemoryAllocError) -> ImageCreationError { |
| ImageCreationError::AllocError(err) |
| } |
| } |
| |
| impl From<Error> for ImageCreationError { |
| #[inline] |
| fn from(err: Error) -> ImageCreationError { |
| match err { |
| err @ Error::OutOfHostMemory => ImageCreationError::AllocError(err.into()), |
| err @ Error::OutOfDeviceMemory => ImageCreationError::AllocError(err.into()), |
| _ => panic!("unexpected error: {:?}", err), |
| } |
| } |
| } |
| |
| /// Describes the memory layout of an image with linear tiling. |
| /// |
| /// Obtained by calling `*_linear_layout` on the 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(Debug, Copy, Clone, PartialEq, Eq)] |
| pub struct LinearLayout { |
| /// Number of bytes from the start of the memory and the start of the queried subresource. |
| pub offset: DeviceSize, |
| /// Total number of bytes for the queried subresource. Can be used for a safety check. |
| pub size: DeviceSize, |
| /// Number of bytes between two texels or two blocks in adjacent rows. |
| pub row_pitch: DeviceSize, |
| /// Number of bytes between two texels or two blocks in adjacent array layers. This value is |
| /// undefined for images with only one array layer. |
| pub array_pitch: DeviceSize, |
| /// Number of bytes between two texels or two blocks in adjacent depth layers. This value is |
| /// undefined for images that are not three-dimensional. |
| pub depth_pitch: DeviceSize, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::ImageCreateFlags; |
| use super::ImageCreationError; |
| use super::ImageUsage; |
| use super::UnsafeImage; |
| use crate::format::Format; |
| use crate::image::ImageDimensions; |
| use crate::image::SampleCount; |
| use crate::sync::Sharing; |
| use std::iter::Empty; |
| use std::u32; |
| |
| #[test] |
| fn create_sampled() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| sampled: true, |
| ..ImageUsage::none() |
| }; |
| |
| let (_img, _) = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::R8G8B8A8Unorm, |
| ImageCreateFlags::none(), |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 32, |
| array_layers: 1, |
| }, |
| SampleCount::Sample1, |
| 1, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| } |
| .unwrap(); |
| } |
| |
| #[test] |
| fn create_transient() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| transient_attachment: true, |
| color_attachment: true, |
| ..ImageUsage::none() |
| }; |
| |
| let (_img, _) = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::R8G8B8A8Unorm, |
| ImageCreateFlags::none(), |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 32, |
| array_layers: 1, |
| }, |
| SampleCount::Sample1, |
| 1, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| } |
| .unwrap(); |
| } |
| |
| #[test] |
| fn zero_mipmap() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| sampled: true, |
| ..ImageUsage::none() |
| }; |
| |
| let res = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::R8G8B8A8Unorm, |
| ImageCreateFlags::none(), |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 32, |
| array_layers: 1, |
| }, |
| SampleCount::Sample1, |
| 0, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| }; |
| |
| match res { |
| Err(ImageCreationError::InvalidMipmapsCount { .. }) => (), |
| _ => panic!(), |
| }; |
| } |
| |
| #[test] |
| #[ignore] // TODO: AMD card seems to support a u32::MAX number of mipmaps |
| fn mipmaps_too_high() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| sampled: true, |
| ..ImageUsage::none() |
| }; |
| |
| let res = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::R8G8B8A8Unorm, |
| ImageCreateFlags::none(), |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 32, |
| array_layers: 1, |
| }, |
| SampleCount::Sample1, |
| u32::MAX, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| }; |
| |
| match res { |
| Err(ImageCreationError::InvalidMipmapsCount { |
| obtained, |
| valid_range, |
| }) => { |
| assert_eq!(obtained, u32::MAX); |
| assert_eq!(valid_range.start, 1); |
| } |
| _ => panic!(), |
| }; |
| } |
| |
| #[test] |
| fn shader_storage_image_multisample() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| storage: true, |
| ..ImageUsage::none() |
| }; |
| |
| let res = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::R8G8B8A8Unorm, |
| ImageCreateFlags::none(), |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 32, |
| array_layers: 1, |
| }, |
| SampleCount::Sample2, |
| 1, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| }; |
| |
| match res { |
| Err(ImageCreationError::ShaderStorageImageMultisampleFeatureNotEnabled) => (), |
| Err(ImageCreationError::UnsupportedSamplesCount { .. }) => (), // unlikely but possible |
| _ => panic!(), |
| }; |
| } |
| |
| #[test] |
| fn compressed_not_color_attachment() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| color_attachment: true, |
| ..ImageUsage::none() |
| }; |
| |
| let res = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::ASTC_5x4UnormBlock, |
| ImageCreateFlags::none(), |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 32, |
| array_layers: 1, |
| }, |
| SampleCount::Sample1, |
| u32::MAX, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| }; |
| |
| match res { |
| Err(ImageCreationError::FormatNotSupported) => (), |
| Err(ImageCreationError::UnsupportedUsage) => (), |
| _ => panic!(), |
| }; |
| } |
| |
| #[test] |
| fn transient_forbidden_with_some_usages() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| transient_attachment: true, |
| sampled: true, |
| ..ImageUsage::none() |
| }; |
| |
| let res = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::R8G8B8A8Unorm, |
| ImageCreateFlags::none(), |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 32, |
| array_layers: 1, |
| }, |
| SampleCount::Sample1, |
| 1, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| }; |
| |
| match res { |
| Err(ImageCreationError::UnsupportedUsage) => (), |
| _ => panic!(), |
| }; |
| } |
| |
| #[test] |
| fn cubecompatible_dims_mismatch() { |
| let (device, _) = gfx_dev_and_queue!(); |
| |
| let usage = ImageUsage { |
| sampled: true, |
| ..ImageUsage::none() |
| }; |
| |
| let res = unsafe { |
| UnsafeImage::new( |
| device, |
| usage, |
| Format::R8G8B8A8Unorm, |
| ImageCreateFlags { |
| cube_compatible: true, |
| ..ImageCreateFlags::none() |
| }, |
| ImageDimensions::Dim2d { |
| width: 32, |
| height: 64, |
| array_layers: 1, |
| }, |
| SampleCount::Sample1, |
| 1, |
| Sharing::Exclusive::<Empty<_>>, |
| false, |
| false, |
| ) |
| }; |
| |
| match res { |
| Err(ImageCreationError::CreationFlagRequirementsNotMet) => (), |
| _ => panic!(), |
| }; |
| } |
| } |