diff options
Diffstat (limited to 'src/image/mod.rs')
-rw-r--r-- | src/image/mod.rs | 701 |
1 files changed, 701 insertions, 0 deletions
diff --git a/src/image/mod.rs b/src/image/mod.rs new file mode 100644 index 0000000..4e7f4af --- /dev/null +++ b/src/image/mod.rs @@ -0,0 +1,701 @@ +// 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. + +//! Image storage (1D, 2D, 3D, arrays, etc.) and image views. +//! +//! An *image* is a region of memory whose purpose is to store multi-dimensional data. Its +//! most common use is to store a 2D array of color pixels (in other words an *image* in +//! everyday language), but it can also be used to store arbitrary data. +//! +//! The advantage of using an image compared to a buffer is that the memory layout is optimized +//! for locality. When reading a specific pixel of an image, reading the nearby pixels is really +//! fast. Most implementations have hardware dedicated to reading from images if you access them +//! through a sampler. +//! +//! # Properties of an image +//! +//! # Images and image views +//! +//! There is a distinction between *images* and *image views*. As its name suggests, an image +//! view describes how the GPU must interpret the image. +//! +//! Transfer and memory operations operate on images themselves, while reading/writing an image +//! operates on image views. You can create multiple image views from the same image. +//! +//! # High-level wrappers +//! +//! In the vulkano library, an image is any object that implements the [`ImageAccess`] trait. You +//! can create a view by wrapping them in an [`ImageView`](crate::image::view::ImageView). +//! +//! Since the `ImageAccess` trait is low-level, you are encouraged to not implement it yourself but +//! instead use one of the provided implementations that are specialized depending on the way you +//! are going to use the image: +//! +//! - An `AttachmentImage` can be used when you want to draw to an image. +//! - An `ImmutableImage` stores data which never need be changed after the initial upload, +//! like a texture. +//! +//! # Low-level information +//! +//! To be written. +//! + +pub use self::aspect::ImageAspect; +pub use self::aspect::ImageAspects; +pub use self::attachment::AttachmentImage; +pub use self::immutable::ImmutableImage; +pub use self::layout::ImageDescriptorLayouts; +pub use self::layout::ImageLayout; +pub use self::storage::StorageImage; +pub use self::swapchain::SwapchainImage; +pub use self::sys::ImageCreationError; +pub use self::traits::ImageAccess; +pub use self::traits::ImageInner; +pub use self::usage::ImageUsage; +pub use self::view::ImageViewAbstract; +use std::cmp; +use std::convert::TryFrom; + +mod aspect; +pub mod attachment; // TODO: make private +pub mod immutable; // TODO: make private +mod layout; +mod storage; +pub mod swapchain; // TODO: make private +pub mod sys; +pub mod traits; +mod usage; +pub mod view; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum SampleCount { + Sample1 = ash::vk::SampleCountFlags::TYPE_1.as_raw(), + Sample2 = ash::vk::SampleCountFlags::TYPE_2.as_raw(), + Sample4 = ash::vk::SampleCountFlags::TYPE_4.as_raw(), + Sample8 = ash::vk::SampleCountFlags::TYPE_8.as_raw(), + Sample16 = ash::vk::SampleCountFlags::TYPE_16.as_raw(), + Sample32 = ash::vk::SampleCountFlags::TYPE_32.as_raw(), + Sample64 = ash::vk::SampleCountFlags::TYPE_64.as_raw(), +} + +impl From<SampleCount> for ash::vk::SampleCountFlags { + #[inline] + fn from(val: SampleCount) -> Self { + Self::from_raw(val as u32) + } +} + +impl TryFrom<ash::vk::SampleCountFlags> for SampleCount { + type Error = (); + + #[inline] + fn try_from(val: ash::vk::SampleCountFlags) -> Result<Self, Self::Error> { + match val { + ash::vk::SampleCountFlags::TYPE_1 => Ok(Self::Sample1), + ash::vk::SampleCountFlags::TYPE_2 => Ok(Self::Sample2), + ash::vk::SampleCountFlags::TYPE_4 => Ok(Self::Sample4), + ash::vk::SampleCountFlags::TYPE_8 => Ok(Self::Sample8), + ash::vk::SampleCountFlags::TYPE_16 => Ok(Self::Sample16), + ash::vk::SampleCountFlags::TYPE_32 => Ok(Self::Sample32), + ash::vk::SampleCountFlags::TYPE_64 => Ok(Self::Sample64), + _ => Err(()), + } + } +} + +impl TryFrom<u32> for SampleCount { + type Error = (); + + #[inline] + fn try_from(val: u32) -> Result<Self, Self::Error> { + match val { + 1 => Ok(Self::Sample1), + 2 => Ok(Self::Sample2), + 4 => Ok(Self::Sample4), + 8 => Ok(Self::Sample8), + 16 => Ok(Self::Sample16), + 32 => Ok(Self::Sample32), + 64 => Ok(Self::Sample64), + _ => Err(()), + } + } +} + +/// Specifies how many sample counts supported for an image used for storage operations. +#[derive(Debug, Copy, Clone, Default)] +pub struct SampleCounts { + // specify an image with one sample per pixel + pub sample1: bool, + // specify an image with 2 samples per pixel + pub sample2: bool, + // specify an image with 4 samples per pixel + pub sample4: bool, + // specify an image with 8 samples per pixel + pub sample8: bool, + // specify an image with 16 samples per pixel + pub sample16: bool, + // specify an image with 32 samples per pixel + pub sample32: bool, + // specify an image with 64 samples per pixel + pub sample64: bool, +} + +impl From<ash::vk::SampleCountFlags> for SampleCounts { + fn from(sample_counts: ash::vk::SampleCountFlags) -> SampleCounts { + SampleCounts { + sample1: !(sample_counts & ash::vk::SampleCountFlags::TYPE_1).is_empty(), + sample2: !(sample_counts & ash::vk::SampleCountFlags::TYPE_2).is_empty(), + sample4: !(sample_counts & ash::vk::SampleCountFlags::TYPE_4).is_empty(), + sample8: !(sample_counts & ash::vk::SampleCountFlags::TYPE_8).is_empty(), + sample16: !(sample_counts & ash::vk::SampleCountFlags::TYPE_16).is_empty(), + sample32: !(sample_counts & ash::vk::SampleCountFlags::TYPE_32).is_empty(), + sample64: !(sample_counts & ash::vk::SampleCountFlags::TYPE_64).is_empty(), + } + } +} + +impl From<SampleCounts> for ash::vk::SampleCountFlags { + fn from(val: SampleCounts) -> ash::vk::SampleCountFlags { + let mut sample_counts = ash::vk::SampleCountFlags::default(); + + if val.sample1 { + sample_counts |= ash::vk::SampleCountFlags::TYPE_1; + } + if val.sample2 { + sample_counts |= ash::vk::SampleCountFlags::TYPE_2; + } + if val.sample4 { + sample_counts |= ash::vk::SampleCountFlags::TYPE_4; + } + if val.sample8 { + sample_counts |= ash::vk::SampleCountFlags::TYPE_8; + } + if val.sample16 { + sample_counts |= ash::vk::SampleCountFlags::TYPE_16; + } + if val.sample32 { + sample_counts |= ash::vk::SampleCountFlags::TYPE_32; + } + if val.sample64 { + sample_counts |= ash::vk::SampleCountFlags::TYPE_64; + } + + sample_counts + } +} + +/// Specifies how many mipmaps must be allocated. +/// +/// Note that at least one mipmap must be allocated, to store the main level of the image. +#[derive(Debug, Copy, Clone)] +pub enum MipmapsCount { + /// Allocates the number of mipmaps required to store all the mipmaps of the image where each + /// mipmap is half the dimensions of the previous level. Guaranteed to be always supported. + /// + /// Note that this is not necessarily the maximum number of mipmaps, as the Vulkan + /// implementation may report that it supports a greater value. + Log2, + + /// Allocate one mipmap (ie. just the main level). Always supported. + One, + + /// Allocate the given number of mipmaps. May result in an error if the value is out of range + /// of what the implementation supports. + Specific(u32), +} + +impl From<u32> for MipmapsCount { + #[inline] + fn from(num: u32) -> MipmapsCount { + MipmapsCount::Specific(num) + } +} + +/// Helper type for creating extents +#[derive(Debug, Copy, Clone)] +pub enum Extent { + E1D([u32; 1]), + E2D([u32; 2]), + E3D([u32; 3]), +} + +impl From<ash::vk::Extent2D> for Extent { + fn from(extent: ash::vk::Extent2D) -> Self { + Extent::E2D([extent.width, extent.height]) + } +} + +impl From<ash::vk::Extent3D> for Extent { + fn from(extent: ash::vk::Extent3D) -> Self { + Extent::E3D([extent.width, extent.height, extent.depth]) + } +} +impl TryFrom<Extent> for ash::vk::Extent2D { + type Error = (); + + fn try_from(extent: Extent) -> Result<Self, Self::Error> { + match extent { + Extent::E2D(a) => Ok(ash::vk::Extent2D { + width: a[0], + height: a[1], + }), + _ => Err(()), + } + } +} + +impl TryFrom<Extent> for ash::vk::Extent3D { + type Error = (); + + fn try_from(extent: Extent) -> Result<Self, Self::Error> { + match extent { + Extent::E3D(a) => Ok(ash::vk::Extent3D { + width: a[0], + height: a[1], + depth: a[2], + }), + _ => Err(()), + } + } +} + +/// Helper type returned from Device's `fn image_format_properties()` +pub struct ImageFormatProperties { + pub max_extent: Extent, + pub max_mip_levels: MipmapsCount, + pub max_array_layers: u32, + pub sample_counts: SampleCounts, + pub max_resource_size: usize, +} + +impl From<ash::vk::ImageFormatProperties> for ImageFormatProperties { + fn from(props: ash::vk::ImageFormatProperties) -> Self { + Self { + max_extent: props.max_extent.into(), + max_mip_levels: props.max_mip_levels.into(), + max_array_layers: props.max_array_layers, + sample_counts: props.sample_counts.into(), + max_resource_size: props.max_resource_size as usize, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +pub struct ImageCreateFlags { + pub sparse_binding: bool, + pub sparse_residency: bool, + pub sparse_aliased: bool, + pub mutable_format: bool, + pub cube_compatible: bool, + pub array_2d_compatible: bool, +} + +impl ImageCreateFlags { + pub fn all() -> Self { + Self { + sparse_binding: true, + sparse_residency: true, + sparse_aliased: true, + mutable_format: true, + cube_compatible: true, + array_2d_compatible: true, + } + } + + pub fn none() -> Self { + Self::default() + } +} + +impl From<ImageCreateFlags> for ash::vk::ImageCreateFlags { + fn from(flags: ImageCreateFlags) -> Self { + let mut vk_flags = Self::default(); + if flags.sparse_binding { + vk_flags |= ash::vk::ImageCreateFlags::SPARSE_BINDING + }; + if flags.sparse_residency { + vk_flags |= ash::vk::ImageCreateFlags::SPARSE_RESIDENCY + }; + if flags.sparse_aliased { + vk_flags |= ash::vk::ImageCreateFlags::SPARSE_ALIASED + }; + if flags.mutable_format { + vk_flags |= ash::vk::ImageCreateFlags::MUTABLE_FORMAT + }; + if flags.cube_compatible { + vk_flags |= ash::vk::ImageCreateFlags::CUBE_COMPATIBLE + }; + if flags.array_2d_compatible { + vk_flags |= ash::vk::ImageCreateFlags::TYPE_2D_ARRAY_COMPATIBLE_KHR + }; + vk_flags + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(i32)] +pub enum ImageType { + Dim1d = ash::vk::ImageType::TYPE_1D.as_raw(), + Dim2d = ash::vk::ImageType::TYPE_2D.as_raw(), + Dim3d = ash::vk::ImageType::TYPE_3D.as_raw(), +} +impl From<ImageType> for ash::vk::ImageType { + fn from(val: ImageType) -> Self { + ash::vk::ImageType::from_raw(val as i32) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(i32)] +pub enum ImageTiling { + Optimal = ash::vk::ImageTiling::OPTIMAL.as_raw(), + Linear = ash::vk::ImageTiling::LINEAR.as_raw(), +} + +impl From<ImageTiling> for ash::vk::ImageTiling { + fn from(val: ImageTiling) -> Self { + ash::vk::ImageTiling::from_raw(val as i32) + } +} + +/// The dimensions of an image. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ImageDimensions { + Dim1d { + width: u32, + array_layers: u32, + }, + Dim2d { + width: u32, + height: u32, + array_layers: u32, + }, + Dim3d { + width: u32, + height: u32, + depth: u32, + }, +} + +impl ImageDimensions { + #[inline] + pub fn width(&self) -> u32 { + match *self { + ImageDimensions::Dim1d { width, .. } => width, + ImageDimensions::Dim2d { width, .. } => width, + ImageDimensions::Dim3d { width, .. } => width, + } + } + + #[inline] + pub fn height(&self) -> u32 { + match *self { + ImageDimensions::Dim1d { .. } => 1, + ImageDimensions::Dim2d { height, .. } => height, + ImageDimensions::Dim3d { height, .. } => height, + } + } + + #[inline] + pub fn width_height(&self) -> [u32; 2] { + [self.width(), self.height()] + } + + #[inline] + pub fn depth(&self) -> u32 { + match *self { + ImageDimensions::Dim1d { .. } => 1, + ImageDimensions::Dim2d { .. } => 1, + ImageDimensions::Dim3d { depth, .. } => depth, + } + } + + #[inline] + pub fn width_height_depth(&self) -> [u32; 3] { + [self.width(), self.height(), self.depth()] + } + + #[inline] + pub fn array_layers(&self) -> u32 { + match *self { + ImageDimensions::Dim1d { array_layers, .. } => array_layers, + ImageDimensions::Dim2d { array_layers, .. } => array_layers, + ImageDimensions::Dim3d { .. } => 1, + } + } + + /// Returns the total number of texels for an image of these dimensions. + #[inline] + pub fn num_texels(&self) -> u32 { + self.width() * self.height() * self.depth() * self.array_layers() + } + + /// Returns the maximum number of mipmaps for these image dimensions. + /// + /// The returned value is always at least superior or equal to 1. + /// + /// # Example + /// + /// ``` + /// use vulkano::image::ImageDimensions; + /// + /// let dims = ImageDimensions::Dim2d { + /// width: 32, + /// height: 50, + /// array_layers: 1, + /// }; + /// + /// assert_eq!(dims.max_mipmaps(), 6); + /// ``` + /// + pub fn max_mipmaps(&self) -> u32 { + 32 - (self.width() | self.height() | self.depth()).leading_zeros() + } + + /// Returns the dimensions of the `level`th mipmap level. If `level` is 0, then the dimensions + /// are left unchanged. + /// + /// Returns `None` if `level` is superior or equal to `max_mipmaps()`. + /// + /// # Example + /// + /// ``` + /// use vulkano::image::ImageDimensions; + /// + /// let dims = ImageDimensions::Dim2d { + /// width: 963, + /// height: 256, + /// array_layers: 1, + /// }; + /// + /// assert_eq!(dims.mipmap_dimensions(0), Some(dims)); + /// assert_eq!(dims.mipmap_dimensions(1), Some(ImageDimensions::Dim2d { + /// width: 481, + /// height: 128, + /// array_layers: 1, + /// })); + /// assert_eq!(dims.mipmap_dimensions(6), Some(ImageDimensions::Dim2d { + /// width: 15, + /// height: 4, + /// array_layers: 1, + /// })); + /// assert_eq!(dims.mipmap_dimensions(9), Some(ImageDimensions::Dim2d { + /// width: 1, + /// height: 1, + /// array_layers: 1, + /// })); + /// assert_eq!(dims.mipmap_dimensions(11), None); + /// ``` + /// + /// # Panic + /// + /// In debug mode, Panics if `width`, `height` or `depth` is equal to 0. In release, returns + /// an unspecified value. + /// + pub fn mipmap_dimensions(&self, level: u32) -> Option<ImageDimensions> { + if level == 0 { + return Some(*self); + } + + if level >= self.max_mipmaps() { + return None; + } + + Some(match *self { + ImageDimensions::Dim1d { + width, + array_layers, + } => { + debug_assert_ne!(width, 0); + ImageDimensions::Dim1d { + array_layers, + width: cmp::max(1, width >> level), + } + } + + ImageDimensions::Dim2d { + width, + height, + array_layers, + } => { + debug_assert_ne!(width, 0); + debug_assert_ne!(height, 0); + ImageDimensions::Dim2d { + width: cmp::max(1, width >> level), + height: cmp::max(1, height >> level), + array_layers, + } + } + + ImageDimensions::Dim3d { + width, + height, + depth, + } => { + debug_assert_ne!(width, 0); + debug_assert_ne!(height, 0); + ImageDimensions::Dim3d { + width: cmp::max(1, width >> level), + height: cmp::max(1, height >> level), + depth: cmp::max(1, depth >> level), + } + } + }) + } +} + +#[cfg(test)] +mod tests { + use crate::format::Format; + use crate::image::ImageDimensions; + use crate::image::ImmutableImage; + use crate::image::MipmapsCount; + + #[test] + fn max_mipmaps() { + let dims = ImageDimensions::Dim2d { + width: 2, + height: 1, + array_layers: 1, + }; + assert_eq!(dims.max_mipmaps(), 2); + + let dims = ImageDimensions::Dim2d { + width: 2, + height: 3, + array_layers: 1, + }; + assert_eq!(dims.max_mipmaps(), 2); + + let dims = ImageDimensions::Dim2d { + width: 512, + height: 512, + array_layers: 1, + }; + assert_eq!(dims.max_mipmaps(), 10); + } + + #[test] + fn mipmap_dimensions() { + let dims = ImageDimensions::Dim2d { + width: 283, + height: 175, + array_layers: 1, + }; + assert_eq!(dims.mipmap_dimensions(0), Some(dims)); + assert_eq!( + dims.mipmap_dimensions(1), + Some(ImageDimensions::Dim2d { + width: 141, + height: 87, + array_layers: 1, + }) + ); + assert_eq!( + dims.mipmap_dimensions(2), + Some(ImageDimensions::Dim2d { + width: 70, + height: 43, + array_layers: 1, + }) + ); + assert_eq!( + dims.mipmap_dimensions(3), + Some(ImageDimensions::Dim2d { + width: 35, + height: 21, + array_layers: 1, + }) + ); + + assert_eq!( + dims.mipmap_dimensions(4), + Some(ImageDimensions::Dim2d { + width: 17, + height: 10, + array_layers: 1, + }) + ); + assert_eq!( + dims.mipmap_dimensions(5), + Some(ImageDimensions::Dim2d { + width: 8, + height: 5, + array_layers: 1, + }) + ); + assert_eq!( + dims.mipmap_dimensions(6), + Some(ImageDimensions::Dim2d { + width: 4, + height: 2, + array_layers: 1, + }) + ); + assert_eq!( + dims.mipmap_dimensions(7), + Some(ImageDimensions::Dim2d { + width: 2, + height: 1, + array_layers: 1, + }) + ); + assert_eq!( + dims.mipmap_dimensions(8), + Some(ImageDimensions::Dim2d { + width: 1, + height: 1, + array_layers: 1, + }) + ); + assert_eq!(dims.mipmap_dimensions(9), None); + } + + #[test] + fn mipmap_working_immutable_image() { + let (device, queue) = gfx_dev_and_queue!(); + + let dimensions = ImageDimensions::Dim2d { + width: 512, + height: 512, + array_layers: 1, + }; + { + let mut vec = Vec::new(); + + vec.resize(512 * 512, 0u8); + + let (image, _) = ImmutableImage::from_iter( + vec.into_iter(), + dimensions, + MipmapsCount::One, + Format::R8Unorm, + queue.clone(), + ) + .unwrap(); + assert_eq!(image.mipmap_levels(), 1); + } + { + let mut vec = Vec::new(); + + vec.resize(512 * 512, 0u8); + + let (image, _) = ImmutableImage::from_iter( + vec.into_iter(), + dimensions, + MipmapsCount::Log2, + Format::R8Unorm, + queue.clone(), + ) + .unwrap(); + assert_eq!(image.mipmap_levels(), 10); + } + } +} |