aboutsummaryrefslogtreecommitdiff
path: root/src/image/sys.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/image/sys.rs')
-rw-r--r--src/image/sys.rs1349
1 files changed, 1349 insertions, 0 deletions
diff --git a/src/image/sys.rs b/src/image/sys.rs
new file mode 100644
index 0000000..c810478
--- /dev/null
+++ b/src/image/sys.rs
@@ -0,0 +1,1349 @@
+// 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!(),
+ };
+ }
+}