pub use self::{aspect::*, layout::*, sys::ImageCreateInfo, usage::*};
use self::{sys::RawImage, view::ImageViewType};
use crate::{
device::{physical::PhysicalDevice, Device, DeviceOwned},
format::{Format, FormatFeatures},
macros::{vulkan_bitflags, vulkan_bitflags_enum, vulkan_enum},
memory::{
allocator::{AllocationCreateInfo, MemoryAllocator, MemoryAllocatorError},
DedicatedAllocation, ExternalMemoryHandleType, ExternalMemoryHandleTypes,
ExternalMemoryProperties, MemoryRequirements, ResourceMemory,
},
range_map::RangeMap,
swapchain::Swapchain,
sync::{future::AccessError, AccessConflict, CurrentAccess, Sharing},
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version,
VulkanError, VulkanObject,
};
use parking_lot::{Mutex, MutexGuard};
use smallvec::SmallVec;
use std::{
cmp::max,
error::Error,
fmt::{Display, Formatter},
hash::{Hash, Hasher},
iter::{FusedIterator, Peekable},
ops::Range,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
mod aspect;
mod layout;
pub mod sampler;
pub mod sys;
mod usage;
pub mod view;
#[derive(Debug)]
pub struct Image {
inner: RawImage,
memory: ImageMemory,
aspect_list: SmallVec<[ImageAspect; 4]>,
aspect_size: DeviceSize,
mip_level_size: DeviceSize,
range_size: DeviceSize,
state: Mutex<ImageState>,
layout: ImageLayout,
is_layout_initialized: AtomicBool,
}
#[derive(Debug)]
pub enum ImageMemory {
Normal(SmallVec<[ResourceMemory; 4]>),
Sparse(Vec<SparseImageMemoryRequirements>),
Swapchain {
swapchain: Arc<Swapchain>,
image_index: u32,
},
}
impl Image {
pub fn new(
allocator: Arc<dyn MemoryAllocator>,
create_info: ImageCreateInfo,
allocation_info: AllocationCreateInfo,
) -> Result<Arc<Self>, Validated<AllocateImageError>> {
assert!(!create_info.flags.intersects(ImageCreateFlags::DISJOINT));
let allocation_type = create_info.tiling.into();
let raw_image =
RawImage::new(allocator.device().clone(), create_info).map_err(|err| match err {
Validated::Error(err) => Validated::Error(AllocateImageError::CreateImage(err)),
Validated::ValidationError(err) => err.into(),
})?;
let requirements = raw_image.memory_requirements()[0];
let allocation = allocator
.allocate(
requirements,
allocation_type,
allocation_info,
Some(DedicatedAllocation::Image(&raw_image)),
)
.map_err(AllocateImageError::AllocateMemory)?;
let allocation = unsafe { ResourceMemory::from_allocation(allocator, allocation) };
let image = raw_image.bind_memory([allocation]).map_err(|(err, _, _)| {
err.map(AllocateImageError::BindMemory)
.map_validation(|err| err.add_context("RawImage::bind_memory"))
})?;
Ok(Arc::new(image))
}
fn from_raw(inner: RawImage, memory: ImageMemory, layout: ImageLayout) -> Self {
let aspects = inner.format().aspects();
let aspect_list: SmallVec<[ImageAspect; 4]> = aspects.into_iter().collect();
let mip_level_size = inner.array_layers() as DeviceSize;
let aspect_size = mip_level_size * inner.mip_levels() as DeviceSize;
let range_size = aspect_list.len() as DeviceSize * aspect_size;
let state = Mutex::new(ImageState::new(range_size, inner.initial_layout()));
Image {
inner,
memory,
aspect_list,
aspect_size,
mip_level_size,
range_size,
state,
is_layout_initialized: AtomicBool::new(false),
layout,
}
}
pub(crate) unsafe fn from_swapchain(
handle: ash::vk::Image,
swapchain: Arc<Swapchain>,
image_index: u32,
) -> Result<Self, VulkanError> {
let create_info = ImageCreateInfo {
flags: swapchain.flags().into(),
image_type: ImageType::Dim2d,
format: swapchain.image_format(),
view_formats: swapchain.image_view_formats().to_vec(),
extent: [swapchain.image_extent()[0], swapchain.image_extent()[1], 1],
array_layers: swapchain.image_array_layers(),
mip_levels: 1,
samples: SampleCount::Sample1,
tiling: ImageTiling::Optimal,
usage: swapchain.image_usage(),
stencil_usage: None,
sharing: swapchain.image_sharing().clone(),
initial_layout: ImageLayout::Undefined,
drm_format_modifiers: Vec::new(),
drm_format_modifier_plane_layouts: Vec::new(),
external_memory_handle_types: ExternalMemoryHandleTypes::empty(),
_ne: crate::NonExhaustive(()),
};
Ok(Self::from_raw(
RawImage::from_handle_with_destruction(
swapchain.device().clone(),
handle,
create_info,
false,
)?,
ImageMemory::Swapchain {
swapchain,
image_index,
},
ImageLayout::PresentSrc,
))
}
#[inline]
pub fn memory(&self) -> &ImageMemory {
&self.memory
}
#[inline]
pub fn memory_requirements(&self) -> &[MemoryRequirements] {
self.inner.memory_requirements()
}
#[inline]
pub fn flags(&self) -> ImageCreateFlags {
self.inner.flags()
}
#[inline]
pub fn image_type(&self) -> ImageType {
self.inner.image_type()
}
#[inline]
pub fn format(&self) -> Format {
self.inner.format()
}
#[inline]
pub fn format_features(&self) -> FormatFeatures {
self.inner.format_features()
}
#[inline]
pub fn view_formats(&self) -> &[Format] {
self.inner.view_formats()
}
#[inline]
pub fn extent(&self) -> [u32; 3] {
self.inner.extent()
}
#[inline]
pub fn array_layers(&self) -> u32 {
self.inner.array_layers()
}
#[inline]
pub fn mip_levels(&self) -> u32 {
self.inner.mip_levels()
}
#[inline]
pub fn initial_layout(&self) -> ImageLayout {
self.inner.initial_layout()
}
#[inline]
pub fn samples(&self) -> SampleCount {
self.inner.samples()
}
#[inline]
pub fn tiling(&self) -> ImageTiling {
self.inner.tiling()
}
#[inline]
pub fn usage(&self) -> ImageUsage {
self.inner.usage()
}
#[inline]
pub fn stencil_usage(&self) -> Option<ImageUsage> {
self.inner.stencil_usage()
}
#[inline]
pub fn sharing(&self) -> &Sharing<SmallVec<[u32; 4]>> {
self.inner.sharing()
}
#[inline]
pub fn drm_format_modifier(&self) -> Option<(u64, u32)> {
self.inner.drm_format_modifier()
}
#[inline]
pub fn external_memory_handle_types(&self) -> ExternalMemoryHandleTypes {
self.inner.external_memory_handle_types()
}
#[inline]
pub fn subresource_layers(&self) -> ImageSubresourceLayers {
self.inner.subresource_layers()
}
#[inline]
pub fn subresource_range(&self) -> ImageSubresourceRange {
self.inner.subresource_range()
}
#[inline]
pub fn subresource_layout(
&self,
aspect: ImageAspect,
mip_level: u32,
array_layer: u32,
) -> Result<SubresourceLayout, Box<ValidationError>> {
self.inner
.subresource_layout(aspect, mip_level, array_layer)
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub unsafe fn subresource_layout_unchecked(
&self,
aspect: ImageAspect,
mip_level: u32,
array_layer: u32,
) -> SubresourceLayout {
self.inner
.subresource_layout_unchecked(aspect, mip_level, array_layer)
}
pub(crate) fn range_size(&self) -> DeviceSize {
self.range_size
}
pub(crate) fn iter_ranges(
&self,
subresource_range: ImageSubresourceRange,
) -> SubresourceRangeIterator {
assert!(self.format().aspects().contains(subresource_range.aspects));
assert!(subresource_range.mip_levels.end <= self.mip_levels());
assert!(subresource_range.array_layers.end <= self.array_layers());
SubresourceRangeIterator::new(
subresource_range,
&self.aspect_list,
self.aspect_size,
self.mip_levels(),
self.mip_level_size,
self.array_layers(),
)
}
pub(crate) fn range_to_subresources(
&self,
mut range: Range<DeviceSize>,
) -> ImageSubresourceRange {
debug_assert!(!range.is_empty());
debug_assert!(range.end <= self.range_size);
if range.end - range.start > self.aspect_size {
debug_assert!(range.start % self.aspect_size == 0);
debug_assert!(range.end % self.aspect_size == 0);
let start_aspect_num = (range.start / self.aspect_size) as usize;
let end_aspect_num = (range.end / self.aspect_size) as usize;
ImageSubresourceRange {
aspects: self.aspect_list[start_aspect_num..end_aspect_num]
.iter()
.copied()
.collect(),
mip_levels: 0..self.mip_levels(),
array_layers: 0..self.array_layers(),
}
} else {
let aspect_num = (range.start / self.aspect_size) as usize;
range.start %= self.aspect_size;
range.end %= self.aspect_size;
if range.end == 0 {
range.end = self.aspect_size;
}
if range.end - range.start > self.mip_level_size {
debug_assert!(range.start % self.mip_level_size == 0);
debug_assert!(range.end % self.mip_level_size == 0);
let start_mip_level = (range.start / self.mip_level_size) as u32;
let end_mip_level = (range.end / self.mip_level_size) as u32;
ImageSubresourceRange {
aspects: self.aspect_list[aspect_num].into(),
mip_levels: start_mip_level..end_mip_level,
array_layers: 0..self.array_layers(),
}
} else {
let mip_level = (range.start / self.mip_level_size) as u32;
range.start %= self.mip_level_size;
range.end %= self.mip_level_size;
if range.end == 0 {
range.end = self.mip_level_size;
}
let start_array_layer = range.start as u32;
let end_array_layer = range.end as u32;
ImageSubresourceRange {
aspects: self.aspect_list[aspect_num].into(),
mip_levels: mip_level..mip_level + 1,
array_layers: start_array_layer..end_array_layer,
}
}
}
}
pub(crate) fn state(&self) -> MutexGuard<'_, ImageState> {
self.state.lock()
}
pub(crate) fn initial_layout_requirement(&self) -> ImageLayout {
self.layout
}
pub(crate) fn final_layout_requirement(&self) -> ImageLayout {
self.layout
}
pub(crate) unsafe fn layout_initialized(&self) {
match &self.memory {
ImageMemory::Normal(..) | ImageMemory::Sparse(..) => {
self.is_layout_initialized.store(true, Ordering::Release);
}
ImageMemory::Swapchain {
swapchain,
image_index,
} => {
swapchain.image_layout_initialized(*image_index);
}
}
}
pub(crate) fn is_layout_initialized(&self) -> bool {
match &self.memory {
ImageMemory::Normal(..) | ImageMemory::Sparse(..) => {
self.is_layout_initialized.load(Ordering::Acquire)
}
ImageMemory::Swapchain {
swapchain,
image_index,
} => swapchain.is_image_layout_initialized(*image_index),
}
}
}
unsafe impl VulkanObject for Image {
type Handle = ash::vk::Image;
#[inline]
fn handle(&self) -> Self::Handle {
self.inner.handle()
}
}
unsafe impl DeviceOwned for Image {
#[inline]
fn device(&self) -> &Arc<Device> {
self.inner.device()
}
}
impl PartialEq for Image {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl Eq for Image {}
impl Hash for Image {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}
#[derive(Clone, Debug)]
pub enum AllocateImageError {
CreateImage(VulkanError),
AllocateMemory(MemoryAllocatorError),
BindMemory(VulkanError),
}
impl Error for AllocateImageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::CreateImage(err) => Some(err),
Self::AllocateMemory(err) => Some(err),
Self::BindMemory(err) => Some(err),
}
}
}
impl Display for AllocateImageError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::CreateImage(_) => write!(f, "creating the image failed"),
Self::AllocateMemory(_) => write!(f, "allocating memory for the image failed"),
Self::BindMemory(_) => write!(f, "binding memory to the image failed"),
}
}
}
impl From<AllocateImageError> for Validated<AllocateImageError> {
fn from(err: AllocateImageError) -> Self {
Self::Error(err)
}
}
#[derive(Debug)]
pub(crate) struct ImageState {
ranges: RangeMap<DeviceSize, ImageRangeState>,
}
impl ImageState {
fn new(size: DeviceSize, initial_layout: ImageLayout) -> Self {
ImageState {
ranges: [(
0..size,
ImageRangeState {
current_access: CurrentAccess::Shared {
cpu_reads: 0,
gpu_reads: 0,
},
layout: initial_layout,
},
)]
.into_iter()
.collect(),
}
}
#[allow(dead_code)]
pub(crate) fn check_cpu_read(&self, range: Range<DeviceSize>) -> Result<(), AccessConflict> {
for (_range, state) in self.ranges.range(&range) {
match &state.current_access {
CurrentAccess::CpuExclusive { .. } => return Err(AccessConflict::HostWrite),
CurrentAccess::GpuExclusive { .. } => return Err(AccessConflict::DeviceWrite),
CurrentAccess::Shared { .. } => (),
}
}
Ok(())
}
#[allow(dead_code)]
pub(crate) unsafe fn cpu_read_lock(&mut self, range: Range<DeviceSize>) {
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
match &mut state.current_access {
CurrentAccess::Shared { cpu_reads, .. } => {
*cpu_reads += 1;
}
_ => unreachable!("Image is being written by the CPU or GPU"),
}
}
}
#[allow(dead_code)]
pub(crate) unsafe fn cpu_read_unlock(&mut self, range: Range<DeviceSize>) {
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
match &mut state.current_access {
CurrentAccess::Shared { cpu_reads, .. } => *cpu_reads -= 1,
_ => unreachable!("Image was not locked for CPU read"),
}
}
}
#[allow(dead_code)]
pub(crate) fn check_cpu_write(&self, range: Range<DeviceSize>) -> Result<(), AccessConflict> {
for (_range, state) in self.ranges.range(&range) {
match &state.current_access {
CurrentAccess::CpuExclusive => return Err(AccessConflict::HostWrite),
CurrentAccess::GpuExclusive { .. } => return Err(AccessConflict::DeviceWrite),
CurrentAccess::Shared {
cpu_reads: 0,
gpu_reads: 0,
} => (),
CurrentAccess::Shared { cpu_reads, .. } if *cpu_reads > 0 => {
return Err(AccessConflict::HostRead)
}
CurrentAccess::Shared { .. } => return Err(AccessConflict::DeviceRead),
}
}
Ok(())
}
#[allow(dead_code)]
pub(crate) unsafe fn cpu_write_lock(&mut self, range: Range<DeviceSize>) {
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
state.current_access = CurrentAccess::CpuExclusive;
}
}
#[allow(dead_code)]
pub(crate) unsafe fn cpu_write_unlock(&mut self, range: Range<DeviceSize>) {
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
match &mut state.current_access {
CurrentAccess::CpuExclusive => {
state.current_access = CurrentAccess::Shared {
cpu_reads: 0,
gpu_reads: 0,
}
}
_ => unreachable!("Image was not locked for CPU write"),
}
}
}
pub(crate) fn check_gpu_read(
&self,
range: Range<DeviceSize>,
expected_layout: ImageLayout,
) -> Result<(), AccessError> {
for (_range, state) in self.ranges.range(&range) {
match &state.current_access {
CurrentAccess::Shared { .. } => (),
_ => return Err(AccessError::AlreadyInUse),
}
if expected_layout != ImageLayout::Undefined && state.layout != expected_layout {
return Err(AccessError::UnexpectedImageLayout {
allowed: state.layout,
requested: expected_layout,
});
}
}
Ok(())
}
pub(crate) unsafe fn gpu_read_lock(&mut self, range: Range<DeviceSize>) {
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
match &mut state.current_access {
CurrentAccess::GpuExclusive { gpu_reads, .. }
| CurrentAccess::Shared { gpu_reads, .. } => *gpu_reads += 1,
_ => unreachable!("Image is being written by the CPU"),
}
}
}
pub(crate) unsafe fn gpu_read_unlock(&mut self, range: Range<DeviceSize>) {
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
match &mut state.current_access {
CurrentAccess::GpuExclusive { gpu_reads, .. } => *gpu_reads -= 1,
CurrentAccess::Shared { gpu_reads, .. } => *gpu_reads -= 1,
_ => unreachable!("Buffer was not locked for GPU read"),
}
}
}
pub(crate) fn check_gpu_write(
&self,
range: Range<DeviceSize>,
expected_layout: ImageLayout,
) -> Result<(), AccessError> {
for (_range, state) in self.ranges.range(&range) {
match &state.current_access {
CurrentAccess::Shared {
cpu_reads: 0,
gpu_reads: 0,
} => (),
_ => return Err(AccessError::AlreadyInUse),
}
if expected_layout != ImageLayout::Undefined && state.layout != expected_layout {
return Err(AccessError::UnexpectedImageLayout {
allowed: state.layout,
requested: expected_layout,
});
}
}
Ok(())
}
pub(crate) unsafe fn gpu_write_lock(
&mut self,
range: Range<DeviceSize>,
destination_layout: ImageLayout,
) {
debug_assert!(!matches!(
destination_layout,
ImageLayout::Undefined | ImageLayout::Preinitialized
));
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
match &mut state.current_access {
CurrentAccess::GpuExclusive { gpu_writes, .. } => *gpu_writes += 1,
&mut CurrentAccess::Shared {
cpu_reads: 0,
gpu_reads,
} => {
state.current_access = CurrentAccess::GpuExclusive {
gpu_reads,
gpu_writes: 1,
}
}
_ => unreachable!("Image is being accessed by the CPU"),
}
state.layout = destination_layout;
}
}
pub(crate) unsafe fn gpu_write_unlock(&mut self, range: Range<DeviceSize>) {
self.ranges.split_at(&range.start);
self.ranges.split_at(&range.end);
for (_range, state) in self.ranges.range_mut(&range) {
match &mut state.current_access {
&mut CurrentAccess::GpuExclusive {
gpu_reads,
gpu_writes: 1,
} => {
state.current_access = CurrentAccess::Shared {
cpu_reads: 0,
gpu_reads,
}
}
CurrentAccess::GpuExclusive { gpu_writes, .. } => *gpu_writes -= 1,
_ => unreachable!("Image was not locked for GPU write"),
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ImageRangeState {
current_access: CurrentAccess,
layout: ImageLayout,
}
#[derive(Clone)]
pub(crate) struct SubresourceRangeIterator {
next_fn: fn(&mut Self) -> Option<Range<DeviceSize>>,
image_aspect_size: DeviceSize,
image_mip_level_size: DeviceSize,
mip_levels: Range<u32>,
array_layers: Range<u32>,
aspect_nums: Peekable<smallvec::IntoIter<[usize; 4]>>,
current_aspect_num: Option<usize>,
current_mip_level: u32,
}
impl SubresourceRangeIterator {
fn new(
subresource_range: ImageSubresourceRange,
image_aspect_list: &[ImageAspect],
image_aspect_size: DeviceSize,
image_mip_levels: u32,
image_mip_level_size: DeviceSize,
image_array_layers: u32,
) -> Self {
assert!(!subresource_range.mip_levels.is_empty());
assert!(!subresource_range.array_layers.is_empty());
let next_fn = if subresource_range.array_layers.start != 0
|| subresource_range.array_layers.end != image_array_layers
{
Self::next_some_layers
} else if subresource_range.mip_levels.start != 0
|| subresource_range.mip_levels.end != image_mip_levels
{
Self::next_some_levels_all_layers
} else {
Self::next_all_levels_all_layers
};
let mut aspect_nums = subresource_range
.aspects
.into_iter()
.map(|aspect| image_aspect_list.iter().position(|&a| a == aspect).unwrap())
.collect::<SmallVec<[usize; 4]>>()
.into_iter()
.peekable();
assert!(aspect_nums.len() != 0);
let current_aspect_num = aspect_nums.next();
let current_mip_level = subresource_range.mip_levels.start;
Self {
next_fn,
image_aspect_size,
image_mip_level_size,
mip_levels: subresource_range.mip_levels,
array_layers: subresource_range.array_layers,
aspect_nums,
current_aspect_num,
current_mip_level,
}
}
fn next_some_layers(&mut self) -> Option<Range<DeviceSize>> {
self.current_aspect_num.map(|aspect_num| {
let mip_level_offset = aspect_num as DeviceSize * self.image_aspect_size
+ self.current_mip_level as DeviceSize * self.image_mip_level_size;
self.current_mip_level += 1;
if self.current_mip_level >= self.mip_levels.end {
self.current_mip_level = self.mip_levels.start;
self.current_aspect_num = self.aspect_nums.next();
}
let start = mip_level_offset + self.array_layers.start as DeviceSize;
let end = mip_level_offset + self.array_layers.end as DeviceSize;
start..end
})
}
fn next_some_levels_all_layers(&mut self) -> Option<Range<DeviceSize>> {
self.current_aspect_num.map(|aspect_num| {
let aspect_offset = aspect_num as DeviceSize * self.image_aspect_size;
self.current_aspect_num = self.aspect_nums.next();
let start =
aspect_offset + self.mip_levels.start as DeviceSize * self.image_mip_level_size;
let end = aspect_offset + self.mip_levels.end as DeviceSize * self.image_mip_level_size;
start..end
})
}
fn next_all_levels_all_layers(&mut self) -> Option<Range<DeviceSize>> {
self.current_aspect_num.map(|aspect_num_start| {
self.current_aspect_num = self.aspect_nums.next();
let mut aspect_num_end = aspect_num_start + 1;
while self.current_aspect_num == Some(aspect_num_end) {
self.current_aspect_num = self.aspect_nums.next();
aspect_num_end += 1;
}
let start = aspect_num_start as DeviceSize * self.image_aspect_size;
let end = aspect_num_end as DeviceSize * self.image_aspect_size;
start..end
})
}
}
impl Iterator for SubresourceRangeIterator {
type Item = Range<DeviceSize>;
fn next(&mut self) -> Option<Self::Item> {
(self.next_fn)(self)
}
}
impl FusedIterator for SubresourceRangeIterator {}
vulkan_bitflags! {
#[non_exhaustive]
ImageCreateFlags = ImageCreateFlags(u32);
MUTABLE_FORMAT = MUTABLE_FORMAT,
CUBE_COMPATIBLE = CUBE_COMPATIBLE,
DIM2D_ARRAY_COMPATIBLE = TYPE_2D_ARRAY_COMPATIBLE
RequiresOneOf([
RequiresAllOf([APIVersion(V1_1)]),
RequiresAllOf([DeviceExtension(khr_maintenance1)]),
]),
BLOCK_TEXEL_VIEW_COMPATIBLE = BLOCK_TEXEL_VIEW_COMPATIBLE
RequiresOneOf([
RequiresAllOf([APIVersion(V1_1)]),
RequiresAllOf([DeviceExtension(khr_maintenance2)]),
]),
EXTENDED_USAGE = EXTENDED_USAGE
RequiresOneOf([
RequiresAllOf([APIVersion(V1_1)]),
RequiresAllOf([DeviceExtension(khr_maintenance2)]),
]),
DISJOINT = DISJOINT
RequiresOneOf([
RequiresAllOf([APIVersion(V1_1)]),
RequiresAllOf([DeviceExtension(khr_sampler_ycbcr_conversion)]),
]),
DIM2D_VIEW_COMPATIBLE = TYPE_2D_VIEW_COMPATIBLE_EXT
RequiresOneOf([
RequiresAllOf([DeviceExtension(ext_image_2d_view_of_3d)]),
]),
}
vulkan_bitflags_enum! {
#[non_exhaustive]
SampleCounts,
SampleCount,
= SampleCountFlags(u32);
SAMPLE_1, Sample1 = TYPE_1,
SAMPLE_2, Sample2 = TYPE_2,
SAMPLE_4, Sample4 = TYPE_4,
SAMPLE_8, Sample8 = TYPE_8,
SAMPLE_16, Sample16 = TYPE_16,
SAMPLE_32, Sample32 = TYPE_32,
SAMPLE_64, Sample64 = TYPE_64,
}
impl SampleCounts {
#[inline]
pub const fn max_count(self) -> SampleCount {
if self.intersects(SampleCounts::SAMPLE_64) {
SampleCount::Sample64
} else if self.intersects(SampleCounts::SAMPLE_32) {
SampleCount::Sample32
} else if self.intersects(SampleCounts::SAMPLE_16) {
SampleCount::Sample16
} else if self.intersects(SampleCounts::SAMPLE_8) {
SampleCount::Sample8
} else if self.intersects(SampleCounts::SAMPLE_4) {
SampleCount::Sample4
} else if self.intersects(SampleCounts::SAMPLE_2) {
SampleCount::Sample2
} else {
SampleCount::Sample1
}
}
}
impl From<SampleCount> for u32 {
#[inline]
fn from(value: SampleCount) -> Self {
value as u32
}
}
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(()),
}
}
}
vulkan_enum! {
#[non_exhaustive]
ImageType = ImageType(i32);
Dim1d = TYPE_1D,
Dim2d = TYPE_2D,
Dim3d = TYPE_3D,
}
vulkan_enum! {
#[non_exhaustive]
ImageTiling = ImageTiling(i32);
Optimal = OPTIMAL,
Linear = LINEAR,
DrmFormatModifier = DRM_FORMAT_MODIFIER_EXT
RequiresOneOf([
RequiresAllOf([DeviceExtension(ext_image_drm_format_modifier)]),
]),
}
#[inline]
pub fn max_mip_levels(extent: [u32; 3]) -> u32 {
32 - (extent[0] | extent[1] | extent[2]).leading_zeros()
}
#[inline]
pub fn mip_level_extent(extent: [u32; 3], level: u32) -> Option<[u32; 3]> {
if level == 0 {
return Some(extent);
}
if level >= max_mip_levels(extent) {
return None;
}
Some(extent.map(|x| {
debug_assert!(x != 0);
max(1, x >> level)
}))
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImageSubresourceLayers {
pub aspects: ImageAspects,
pub mip_level: u32,
pub array_layers: Range<u32>,
}
impl ImageSubresourceLayers {
#[inline]
pub fn from_parameters(format: Format, array_layers: u32) -> Self {
Self {
aspects: {
let aspects = format.aspects();
if aspects.intersects(ImageAspects::PLANE_0) {
ImageAspects::PLANE_0
} else {
aspects
}
},
mip_level: 0,
array_layers: 0..array_layers,
}
}
}
impl ImageSubresourceLayers {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
aspects,
mip_level: _,
ref array_layers,
} = self;
aspects.validate_device(device).map_err(|err| {
err.add_context("aspects")
.set_vuids(&["VUID-VkImageSubresourceLayers-aspectMask-parameter"])
})?;
if aspects.is_empty() {
return Err(Box::new(ValidationError {
context: "aspects".into(),
problem: "is empty".into(),
vuids: &["VUID-VkImageSubresourceLayers-aspectMask-requiredbitmask"],
..Default::default()
}));
}
if aspects.intersects(ImageAspects::COLOR)
&& aspects.intersects(ImageAspects::DEPTH | ImageAspects::STENCIL)
{
return Err(Box::new(ValidationError {
context: "aspects".into(),
problem: "contains both `ImageAspects::COLOR`, and either `ImageAspects::DEPTH` \
or `ImageAspects::STENCIL`"
.into(),
vuids: &["VUID-VkImageSubresourceLayers-aspectMask-00167"],
..Default::default()
}));
}
if aspects.intersects(ImageAspects::METADATA) {
return Err(Box::new(ValidationError {
context: "aspects".into(),
problem: "contains `ImageAspects::METADATA`".into(),
vuids: &["VUID-VkImageSubresourceLayers-aspectMask-00168"],
..Default::default()
}));
}
if aspects.intersects(
ImageAspects::MEMORY_PLANE_0
| ImageAspects::MEMORY_PLANE_1
| ImageAspects::MEMORY_PLANE_2
| ImageAspects::MEMORY_PLANE_3,
) {
return Err(Box::new(ValidationError {
context: "aspects".into(),
problem: "contains `ImageAspects::MEMORY_PLANE_0`, \
`ImageAspects::MEMORY_PLANE_1`, `ImageAspects::MEMORY_PLANE_2` or \
`ImageAspects::MEMORY_PLANE_3`"
.into(),
vuids: &["VUID-VkImageSubresourceLayers-aspectMask-02247"],
..Default::default()
}));
}
if array_layers.is_empty() {
return Err(Box::new(ValidationError {
context: "array_layers".into(),
problem: "is empty".into(),
vuids: &["VUID-VkImageSubresourceLayers-layerCount-01700"],
..Default::default()
}));
}
Ok(())
}
}
impl From<ImageSubresourceLayers> for ash::vk::ImageSubresourceLayers {
#[inline]
fn from(val: ImageSubresourceLayers) -> Self {
Self {
aspect_mask: val.aspects.into(),
mip_level: val.mip_level,
base_array_layer: val.array_layers.start,
layer_count: val.array_layers.end - val.array_layers.start,
}
}
}
impl From<&ImageSubresourceLayers> for ash::vk::ImageSubresourceLayers {
#[inline]
fn from(val: &ImageSubresourceLayers) -> Self {
Self {
aspect_mask: val.aspects.into(),
mip_level: val.mip_level,
base_array_layer: val.array_layers.start,
layer_count: val.array_layers.end - val.array_layers.start,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImageSubresourceRange {
pub aspects: ImageAspects,
pub mip_levels: Range<u32>,
pub array_layers: Range<u32>,
}
impl ImageSubresourceRange {
#[inline]
pub fn from_parameters(format: Format, mip_levels: u32, array_layers: u32) -> Self {
Self {
aspects: format.aspects()
- (ImageAspects::PLANE_0 | ImageAspects::PLANE_1 | ImageAspects::PLANE_2),
mip_levels: 0..mip_levels,
array_layers: 0..array_layers,
}
}
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
aspects,
ref mip_levels,
ref array_layers,
} = self;
aspects.validate_device(device).map_err(|err| {
err.add_context("aspects")
.set_vuids(&["VUID-VkImageSubresourceRange-aspectMask-parameter"])
})?;
if aspects.is_empty() {
return Err(Box::new(ValidationError {
context: "aspects".into(),
problem: "is empty".into(),
vuids: &["VUID-VkImageSubresourceRange-aspectMask-requiredbitmask"],
..Default::default()
}));
}
if mip_levels.is_empty() {
return Err(Box::new(ValidationError {
context: "mip_levels".into(),
problem: "is empty".into(),
vuids: &["VUID-VkImageSubresourceRange-levelCount-01720"],
..Default::default()
}));
}
if array_layers.is_empty() {
return Err(Box::new(ValidationError {
context: "array_layers".into(),
problem: "is empty".into(),
vuids: &["VUID-VkImageSubresourceRange-layerCount-01721"],
..Default::default()
}));
}
if aspects.intersects(ImageAspects::COLOR)
&& aspects
.intersects(ImageAspects::PLANE_0 | ImageAspects::PLANE_1 | ImageAspects::PLANE_2)
{
return Err(Box::new(ValidationError {
context: "aspects".into(),
problem: "contains both `ImageAspects::COLOR`, and one of \
`ImageAspects::PLANE_0`, `ImageAspects::PLANE_1` or `ImageAspects::PLANE_2`"
.into(),
vuids: &["VUID-VkImageSubresourceRange-aspectMask-01670"],
..Default::default()
}));
}
if aspects.intersects(
ImageAspects::MEMORY_PLANE_0
| ImageAspects::MEMORY_PLANE_1
| ImageAspects::MEMORY_PLANE_2
| ImageAspects::MEMORY_PLANE_3,
) {
return Err(Box::new(ValidationError {
context: "aspects".into(),
problem: "contains `ImageAspects::MEMORY_PLANE_0`, \
`ImageAspects::MEMORY_PLANE_1`, `ImageAspects::MEMORY_PLANE_2` or \
`ImageAspects::MEMORY_PLANE_3`"
.into(),
vuids: &["VUID-VkImageSubresourceLayers-aspectMask-02247"],
..Default::default()
}));
}
Ok(())
}
}
impl From<ImageSubresourceRange> for ash::vk::ImageSubresourceRange {
#[inline]
fn from(val: ImageSubresourceRange) -> Self {
Self {
aspect_mask: val.aspects.into(),
base_mip_level: val.mip_levels.start,
level_count: val.mip_levels.end - val.mip_levels.start,
base_array_layer: val.array_layers.start,
layer_count: val.array_layers.end - val.array_layers.start,
}
}
}
impl From<ImageSubresourceLayers> for ImageSubresourceRange {
#[inline]
fn from(val: ImageSubresourceLayers) -> Self {
Self {
aspects: val.aspects,
mip_levels: val.mip_level..val.mip_level + 1,
array_layers: val.array_layers,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubresourceLayout {
pub offset: DeviceSize,
pub size: DeviceSize,
pub row_pitch: DeviceSize,
pub array_pitch: Option<DeviceSize>,
pub depth_pitch: Option<DeviceSize>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImageFormatInfo {
pub flags: ImageCreateFlags,
pub format: Format,
pub view_formats: Vec<Format>,
pub image_type: ImageType,
pub tiling: ImageTiling,
pub usage: ImageUsage,
pub stencil_usage: Option<ImageUsage>,
pub drm_format_modifier_info: Option<ImageDrmFormatModifierInfo>,
pub external_memory_handle_type: Option<ExternalMemoryHandleType>,
pub image_view_type: Option<ImageViewType>,
pub _ne: crate::NonExhaustive,
}
impl Default for ImageFormatInfo {
#[inline]
fn default() -> Self {
Self {
flags: ImageCreateFlags::empty(),
format: Format::UNDEFINED,
view_formats: Vec::new(),
image_type: ImageType::Dim2d,
tiling: ImageTiling::Optimal,
usage: ImageUsage::empty(),
stencil_usage: None,
drm_format_modifier_info: None,
external_memory_handle_type: None,
image_view_type: None,
_ne: crate::NonExhaustive(()),
}
}
}
impl ImageFormatInfo {
pub(crate) fn validate(
&self,
physical_device: &PhysicalDevice,
) -> Result<(), Box<ValidationError>> {
let &Self {
flags,
format,
ref view_formats,
image_type,
tiling,
usage,
stencil_usage,
ref drm_format_modifier_info,
external_memory_handle_type,
image_view_type,
_ne: _,
} = self;
flags
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("flags")
.set_vuids(&["VUID-VkPhysicalDeviceImageFormatInfo2-flags-parameter"])
})?;
format
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("format")
.set_vuids(&["VUID-VkPhysicalDeviceImageFormatInfo2-format-parameter"])
})?;
image_type
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("image_type")
.set_vuids(&["VUID-VkPhysicalDeviceImageFormatInfo2-imageType-parameter"])
})?;
tiling
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("tiling")
.set_vuids(&["VUID-VkPhysicalDeviceImageFormatInfo2-tiling-parameter"])
})?;
usage
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("usage")
.set_vuids(&["VUID-VkPhysicalDeviceImageFormatInfo2-usage-parameter"])
})?;
if usage.is_empty() {
return Err(Box::new(ValidationError {
context: "usage".into(),
problem: "is empty".into(),
vuids: &["VUID-VkPhysicalDeviceImageFormatInfo2-usage-requiredbitmask"],
..Default::default()
}));
}
if let Some(stencil_usage) = stencil_usage {
if !(physical_device.api_version() >= Version::V1_2
|| physical_device
.supported_extensions()
.ext_separate_stencil_usage)
{
return Err(Box::new(ValidationError {
context: "stencil_usage".into(),
problem: "is `Some`".into(),
requires_one_of: RequiresOneOf(&[
RequiresAllOf(&[Requires::APIVersion(Version::V1_2)]),
RequiresAllOf(&[Requires::DeviceExtension("ext_separate_stencil_usage")]),
]),
..Default::default()
}));
}
stencil_usage
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("stencil_usage")
.set_vuids(&["VUID-VkImageStencilUsageCreateInfo-stencilUsage-parameter"])
})?;
if stencil_usage.is_empty() {
return Err(Box::new(ValidationError {
context: "stencil_usage".into(),
problem: "is empty".into(),
vuids: &["VUID-VkImageStencilUsageCreateInfo-usage-requiredbitmask"],
..Default::default()
}));
}
if stencil_usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT)
&& !(stencil_usage
- (ImageUsage::TRANSIENT_ATTACHMENT
| ImageUsage::DEPTH_STENCIL_ATTACHMENT
| ImageUsage::INPUT_ATTACHMENT))
.is_empty()
{
return Err(Box::new(ValidationError {
context: "stencil_usage".into(),
problem: "contains `ImageUsage::TRANSIENT_ATTACHMENT`, but also contains \
usages other than `ImageUsage::DEPTH_STENCIL_ATTACHMENT` or \
`ImageUsage::INPUT_ATTACHMENT`"
.into(),
vuids: &["VUID-VkImageStencilUsageCreateInfo-stencilUsage-02539"],
..Default::default()
}));
}
}
if !view_formats.is_empty() {
if !(physical_device.api_version() >= Version::V1_2
|| physical_device.supported_extensions().khr_image_format_list)
{
return Err(Box::new(ValidationError {
context: "view_formats".into(),
problem: "is not empty".into(),
requires_one_of: RequiresOneOf(&[
RequiresAllOf(&[Requires::APIVersion(Version::V1_2)]),
RequiresAllOf(&[Requires::DeviceExtension("khr_image_format_list")]),
]),
..Default::default()
}));
}
for (index, view_format) in view_formats.iter().enumerate() {
view_format
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context(format!("view_formats[{}]", index))
.set_vuids(&["VUID-VkImageFormatListCreateInfo-pViewFormats-parameter"])
})?;
}
}
if let Some(drm_format_modifier_info) = drm_format_modifier_info {
if !physical_device
.supported_extensions()
.ext_image_drm_format_modifier
{
return Err(Box::new(ValidationError {
context: "drm_format_modifier_info".into(),
problem: "is `Some`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension(
"ext_image_drm_format_modifier",
)])]),
..Default::default()
}));
}
drm_format_modifier_info
.validate(physical_device)
.map_err(|err| err.add_context("drm_format_modifier_info"))?;
if tiling != ImageTiling::DrmFormatModifier {
return Err(Box::new(ValidationError {
problem: "`drm_format_modifier_info` is `Some` but \
`tiling` is not `ImageTiling::DrmFormatModifier`"
.into(),
vuids: &[" VUID-VkPhysicalDeviceImageFormatInfo2-tiling-02249"],
..Default::default()
}));
}
if flags.intersects(ImageCreateFlags::MUTABLE_FORMAT) && view_formats.is_empty() {
return Err(Box::new(ValidationError {
problem: "`tiling` is `ImageTiling::DrmFormatModifier`, and \
`flags` contains `ImageCreateFlags::MUTABLE_FORMAT`, but \
`view_formats` is empty"
.into(),
vuids: &["VUID-VkPhysicalDeviceImageFormatInfo2-tiling-02313"],
..Default::default()
}));
}
} else if tiling == ImageTiling::DrmFormatModifier {
return Err(Box::new(ValidationError {
problem: "`tiling` is `ImageTiling::DrmFormatModifier`, but \
`drm_format_modifier_info` is `None`"
.into(),
vuids: &[" VUID-VkPhysicalDeviceImageFormatInfo2-tiling-02249"],
..Default::default()
}));
}
if let Some(handle_type) = external_memory_handle_type {
if !(physical_device.api_version() >= Version::V1_1
|| physical_device
.instance()
.enabled_extensions()
.khr_external_memory_capabilities)
{
return Err(Box::new(ValidationError {
problem: "`external_memory_handle_type` is `Some`".into(),
requires_one_of: RequiresOneOf(&[
RequiresAllOf(&[Requires::APIVersion(Version::V1_1)]),
RequiresAllOf(&[Requires::InstanceExtension(
"khr_external_memory_capabilities",
)]),
]),
..Default::default()
}));
}
handle_type
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("handle_type").set_vuids(&[
"VUID-VkPhysicalDeviceExternalImageFormatInfo-handleType-parameter",
])
})?;
}
if let Some(image_view_type) = image_view_type {
if !physical_device.supported_extensions().ext_filter_cubic {
return Err(Box::new(ValidationError {
problem: "`image_view_type` is `Some`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension(
"ext_filter_cubic",
)])]),
..Default::default()
}));
}
image_view_type
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("image_view_type").set_vuids(&[
"VUID-VkPhysicalDeviceImageViewImageFormatInfoEXT-imageViewType-parameter",
])
})?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImageDrmFormatModifierInfo {
pub drm_format_modifier: u64,
pub sharing: Sharing<SmallVec<[u32; 4]>>,
pub _ne: crate::NonExhaustive,
}
impl Default for ImageDrmFormatModifierInfo {
#[inline]
fn default() -> Self {
Self {
drm_format_modifier: 0,
sharing: Sharing::Exclusive,
_ne: crate::NonExhaustive(()),
}
}
}
impl ImageDrmFormatModifierInfo {
pub(crate) fn validate(
&self,
physical_device: &PhysicalDevice,
) -> Result<(), Box<ValidationError>> {
let &Self {
drm_format_modifier: _,
ref sharing,
_ne,
} = self;
match sharing {
Sharing::Exclusive => (),
Sharing::Concurrent(queue_family_indices) => {
if queue_family_indices.len() < 2 {
return Err(Box::new(ValidationError {
context: "sharing".into(),
problem: "is `Sharing::Concurrent`, but contains less than 2 elements"
.into(),
vuids: &[
"VUID-VkPhysicalDeviceImageDrmFormatModifierInfoEXT-sharingMode-02315",
],
..Default::default()
}));
}
let queue_family_count = physical_device.queue_family_properties().len() as u32;
for (index, &queue_family_index) in queue_family_indices.iter().enumerate() {
if queue_family_indices[..index].contains(&queue_family_index) {
return Err(Box::new(ValidationError {
context: "queue_family_indices".into(),
problem: format!(
"the queue family index in the list at index {} is contained in \
the list more than once",
index,
)
.into(),
vuids: &[" VUID-VkPhysicalDeviceImageDrmFormatModifierInfoEXT-sharingMode-02316"],
..Default::default()
}));
}
if queue_family_index >= queue_family_count {
return Err(Box::new(ValidationError {
context: format!("sharing[{}]", index).into(),
problem: "is not less than the number of queue families in the device"
.into(),
vuids: &[" VUID-VkPhysicalDeviceImageDrmFormatModifierInfoEXT-sharingMode-02316"],
..Default::default()
}));
}
}
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ImageFormatProperties {
pub max_extent: [u32; 3],
pub max_mip_levels: u32,
pub max_array_layers: u32,
pub sample_counts: SampleCounts,
pub max_resource_size: DeviceSize,
pub external_memory_properties: ExternalMemoryProperties,
pub filter_cubic: bool,
pub filter_cubic_minmax: bool,
}
impl From<ash::vk::ImageFormatProperties> for ImageFormatProperties {
#[inline]
fn from(props: ash::vk::ImageFormatProperties) -> Self {
Self {
max_extent: [
props.max_extent.width,
props.max_extent.height,
props.max_extent.depth,
],
max_mip_levels: props.max_mip_levels,
max_array_layers: props.max_array_layers,
sample_counts: props.sample_counts.into(),
max_resource_size: props.max_resource_size,
external_memory_properties: Default::default(),
filter_cubic: false,
filter_cubic_minmax: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SparseImageFormatInfo {
pub format: Format,
pub image_type: ImageType,
pub samples: SampleCount,
pub usage: ImageUsage,
pub tiling: ImageTiling,
pub _ne: crate::NonExhaustive,
}
impl Default for SparseImageFormatInfo {
#[inline]
fn default() -> Self {
Self {
format: Format::UNDEFINED,
image_type: ImageType::Dim2d,
samples: SampleCount::Sample1,
usage: ImageUsage::empty(),
tiling: ImageTiling::Optimal,
_ne: crate::NonExhaustive(()),
}
}
}
impl SparseImageFormatInfo {
pub(crate) fn validate(
&self,
physical_device: &PhysicalDevice,
) -> Result<(), Box<ValidationError>> {
let &Self {
format,
image_type,
samples,
usage,
tiling,
_ne: _,
} = self;
format
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("format")
.set_vuids(&["VUID-VkPhysicalDeviceSparseImageFormatInfo2-format-parameter"])
})?;
image_type
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("image_type")
.set_vuids(&["VUID-VkPhysicalDeviceSparseImageFormatInfo2-type-parameter"])
})?;
samples
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("samples")
.set_vuids(&["VUID-VkPhysicalDeviceSparseImageFormatInfo2-samples-parameter"])
})?;
usage
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("usage")
.set_vuids(&["VUID-VkPhysicalDeviceSparseImageFormatInfo2-usage-parameter"])
})?;
if usage.is_empty() {
return Err(Box::new(ValidationError {
context: "usage".into(),
problem: "is empty".into(),
vuids: &["VUID-VkPhysicalDeviceSparseImageFormatInfo2-usage-requiredbitmask"],
..Default::default()
}));
}
tiling
.validate_physical_device(physical_device)
.map_err(|err| {
err.add_context("tiling")
.set_vuids(&["VUID-VkPhysicalDeviceSparseImageFormatInfo2-tiling-parameter"])
})?;
Ok(())
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct SparseImageFormatProperties {
pub aspects: ImageAspects,
pub image_granularity: [u32; 3],
pub flags: SparseImageFormatFlags,
}
vulkan_bitflags! {
#[non_exhaustive]
SparseImageFormatFlags = SparseImageFormatFlags(u32);
SINGLE_MIPTAIL = SINGLE_MIPTAIL,
ALIGNED_MIP_SIZE = ALIGNED_MIP_SIZE,
NONSTANDARD_BLOCK_SIZE = NONSTANDARD_BLOCK_SIZE,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct SparseImageMemoryRequirements {
pub format_properties: SparseImageFormatProperties,
pub image_mip_tail_first_lod: u32,
pub image_mip_tail_size: DeviceSize,
pub image_mip_tail_offset: DeviceSize,
pub image_mip_tail_stride: Option<DeviceSize>,
}
#[cfg(test)]
mod tests {
#[test]
fn max_mip_levels() {
assert_eq!(super::max_mip_levels([2, 1, 1]), 2);
assert_eq!(super::max_mip_levels([2, 3, 1]), 2);
assert_eq!(super::max_mip_levels([512, 512, 1]), 10);
}
#[test]
fn mip_level_size() {
let extent = [283, 175, 1];
assert_eq!(super::mip_level_extent(extent, 0), Some(extent));
assert_eq!(super::mip_level_extent(extent, 1), Some([141, 87, 1]));
assert_eq!(super::mip_level_extent(extent, 2), Some([70, 43, 1]));
assert_eq!(super::mip_level_extent(extent, 3), Some([35, 21, 1]));
assert_eq!(super::mip_level_extent(extent, 4), Some([17, 10, 1]));
assert_eq!(super::mip_level_extent(extent, 5), Some([8, 5, 1]));
assert_eq!(super::mip_level_extent(extent, 6), Some([4, 2, 1]));
assert_eq!(super::mip_level_extent(extent, 7), Some([2, 1, 1]));
assert_eq!(super::mip_level_extent(extent, 8), Some([1, 1, 1]));
assert_eq!(super::mip_level_extent(extent, 9), None);
}
}