// 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 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, 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, usage: ImageUsage, format: Format, flags: ImageCreateFlags, dimensions: ImageDimensions, num_samples: SampleCount, mipmaps: Mi, sharing: Sharing, linear_tiling: bool, preinitialized_layout: bool, ) -> Result<(UnsafeImage, MemoryRequirements), ImageCreationError> where Mi: Into, I: Iterator, { 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, 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 = 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, 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 { &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, "", 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(&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, }, /// 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 for ImageCreationError { #[inline] fn from(err: OomError) -> ImageCreationError { ImageCreationError::AllocError(DeviceMemoryAllocError::OomError(err)) } } impl From for ImageCreationError { #[inline] fn from(err: DeviceMemoryAllocError) -> ImageCreationError { ImageCreationError::AllocError(err) } } impl From 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::>, 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::>, 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::>, 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::>, 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::>, 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::>, 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::>, 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::>, false, false, ) }; match res { Err(ImageCreationError::CreationFlagRequirementsNotMet) => (), _ => panic!(), }; } }