blob: 1933ffeeaa1400d20d8fa3ecc411273cab640ed3 [file] [log] [blame]
// 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 super::{
ImageAspect, ImageAspects, ImageCreateFlags, ImageDimensions, ImageLayout,
ImageSubresourceLayers, ImageSubresourceRange, ImageTiling, ImageUsage, SampleCount,
SampleCounts, SparseImageMemoryRequirements,
};
use crate::{
buffer::subbuffer::{ReadLockError, WriteLockError},
cache::OnceCache,
device::{Device, DeviceOwned},
format::{ChromaSampling, Format, FormatFeatures, NumericType},
image::{
view::ImageViewCreationError, ImageFormatInfo, ImageFormatProperties, ImageType,
SparseImageFormatProperties,
},
macros::impl_id_counter,
memory::{
allocator::{AllocationCreationError, AllocationType, DeviceLayout, MemoryAlloc},
is_aligned, DedicatedTo, DeviceAlignment, ExternalMemoryHandleType,
ExternalMemoryHandleTypes, MemoryPropertyFlags, MemoryRequirements,
},
range_map::RangeMap,
swapchain::Swapchain,
sync::{future::AccessError, CurrentAccess, Sharing},
DeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
};
use ash::vk::ImageDrmFormatModifierExplicitCreateInfoEXT;
use parking_lot::{Mutex, MutexGuard};
use smallvec::{smallvec, SmallVec};
use std::{
error::Error,
fmt::{Display, Error as FmtError, Formatter},
hash::{Hash, Hasher},
iter::{FusedIterator, Peekable},
mem::{size_of_val, MaybeUninit},
num::NonZeroU64,
ops::Range,
ptr,
sync::Arc,
};
/// A raw image, with no memory backing it.
///
/// This is the basic image type, a direct translation of a `VkImage` object, but it is mostly
/// useless in this form. After creating a raw image, you must call `bind_memory` to make a
/// complete image object.
#[derive(Debug)]
pub struct RawImage {
handle: ash::vk::Image,
device: Arc<Device>,
id: NonZeroU64,
flags: ImageCreateFlags,
dimensions: ImageDimensions,
format: Option<Format>,
format_features: FormatFeatures,
initial_layout: ImageLayout,
mip_levels: u32,
samples: SampleCount,
tiling: ImageTiling,
usage: ImageUsage,
sharing: Sharing<SmallVec<[u32; 4]>>,
stencil_usage: ImageUsage,
external_memory_handle_types: ExternalMemoryHandleTypes,
memory_requirements: SmallVec<[MemoryRequirements; 3]>,
needs_destruction: bool, // `vkDestroyImage` is called only if true.
subresource_layout: OnceCache<(ImageAspect, u32, u32), SubresourceLayout>,
}
impl RawImage {
/// Creates a new `RawImage`.
///
/// # Panics
///
/// - Panics if one of the values in `create_info.dimensions` is zero.
/// - Panics if `create_info.format` is `None`.
/// - Panics if `create_info.block_texel_view_compatible` is set but not
/// `create_info.mutable_format`.
/// - Panics if `create_info.mip_levels` is `0`.
/// - Panics if `create_info.sharing` is [`Sharing::Concurrent`] with less than 2 items.
/// - Panics if `create_info.initial_layout` is something other than
/// [`ImageLayout::Undefined`] or [`ImageLayout::Preinitialized`].
/// - Panics if `create_info.usage` is empty.
/// - Panics if `create_info.usage` contains `transient_attachment`, but does not also contain
/// at least one of `color_attachment`, `depth_stencil_attachment`, `input_attachment`, or
/// if it contains values other than these.
#[inline]
pub fn new(
device: Arc<Device>,
mut create_info: ImageCreateInfo,
) -> Result<RawImage, ImageError> {
match &mut create_info.sharing {
Sharing::Exclusive => (),
Sharing::Concurrent(queue_family_indices) => {
// VUID-VkImageCreateInfo-sharingMode-01420
queue_family_indices.sort_unstable();
queue_family_indices.dedup();
}
}
Self::validate_new(&device, &create_info)?;
unsafe { Ok(RawImage::new_unchecked(device, create_info)?) }
}
fn validate_new(
device: &Device,
create_info: &ImageCreateInfo,
) -> Result<FormatFeatures, ImageError> {
let &ImageCreateInfo {
flags,
dimensions,
format,
mip_levels,
samples,
tiling,
usage,
mut stencil_usage,
ref sharing,
initial_layout,
external_memory_handle_types,
_ne: _,
image_drm_format_modifier_create_info,
} = create_info;
let physical_device = device.physical_device();
let device_properties = physical_device.properties();
let format = format.unwrap(); // Can be None for "external formats" but Vulkano doesn't support that yet
let aspects = format.aspects();
let has_separate_stencil_usage = if stencil_usage.is_empty()
|| !aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL)
{
stencil_usage = usage;
false
} else {
stencil_usage == usage
};
// VUID-VkImageCreateInfo-flags-parameter
flags.validate_device(device)?;
// VUID-VkImageCreateInfo-format-parameter
format.validate_device(device)?;
// VUID-VkImageCreateInfo-samples-parameter
samples.validate_device(device)?;
// VUID-VkImageCreateInfo-tiling-parameter
tiling.validate_device(device)?;
// VUID-VkImageCreateInfo-usage-parameter
usage.validate_device(device)?;
// VUID-VkImageCreateInfo-usage-requiredbitmask
assert!(!usage.is_empty());
if has_separate_stencil_usage {
if !(device.api_version() >= Version::V1_2
|| device.enabled_extensions().ext_separate_stencil_usage)
{
return Err(ImageError::RequirementNotMet {
required_for: "`create_info.stencil_usage` is `Some` and `create_info.format` \
has both a depth and a stencil aspect",
requires_one_of: RequiresOneOf {
api_version: Some(Version::V1_2),
device_extensions: &["ext_separate_stencil_usage"],
..Default::default()
},
});
}
// VUID-VkImageStencilUsageCreateInfo-stencilUsage-parameter
stencil_usage.validate_device(device)?;
// VUID-VkImageStencilUsageCreateInfo-usage-requiredbitmask
assert!(!stencil_usage.is_empty());
}
// VUID-VkImageCreateInfo-initialLayout-parameter
initial_layout.validate_device(device)?;
// VUID-VkImageCreateInfo-initialLayout-00993
assert!(matches!(
initial_layout,
ImageLayout::Undefined | ImageLayout::Preinitialized
));
// VUID-VkImageCreateInfo-flags-01573
assert!(
!flags.intersects(ImageCreateFlags::BLOCK_TEXEL_VIEW_COMPATIBLE)
|| flags.intersects(ImageCreateFlags::MUTABLE_FORMAT)
);
// VUID-VkImageCreateInfo-tiling-02261
// VUID-VkImageCreateInfo-pNext-02262
if (tiling == ImageTiling::DrmFormatModifier)
!= image_drm_format_modifier_create_info.is_some()
{
return Err(ImageError::DrmFormatModifierRequiresCreateInfo);
}
// Get format features
let format_features = {
// Use unchecked, because all validation has been done above.
let format_properties = unsafe { physical_device.format_properties_unchecked(format) };
match tiling {
ImageTiling::Linear => format_properties.linear_tiling_features,
ImageTiling::Optimal => format_properties.optimal_tiling_features,
ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features, // TODO: Improve
}
};
// TODO: VUID-VkImageCreateInfo-tiling-02353
// Vulkano currently has no high-level way to add or check for VkImageFormatListCreateInfo.
// Format isn't supported at all?
if format_features.is_empty() {
return Err(ImageError::FormatNotSupported);
}
// Decode the dimensions
let (image_type, extent, array_layers) = match dimensions {
ImageDimensions::Dim1d {
width,
array_layers,
} => (ImageType::Dim1d, [width, 1, 1], array_layers),
ImageDimensions::Dim2d {
width,
height,
array_layers,
} => (ImageType::Dim2d, [width, height, 1], array_layers),
ImageDimensions::Dim3d {
width,
height,
depth,
} => (ImageType::Dim3d, [width, height, depth], 1),
};
// VUID-VkImageCreateInfo-extent-00944
assert!(extent[0] != 0);
// VUID-VkImageCreateInfo-extent-00945
assert!(extent[1] != 0);
// VUID-VkImageCreateInfo-extent-00946
assert!(extent[2] != 0);
// VUID-VkImageCreateInfo-arrayLayers-00948
assert!(array_layers != 0);
// VUID-VkImageCreateInfo-mipLevels-00947
assert!(mip_levels != 0);
// Check mip levels
let max_mip_levels = dimensions.max_mip_levels();
debug_assert!(max_mip_levels >= 1);
// VUID-VkImageCreateInfo-mipLevels-00958
if mip_levels > max_mip_levels {
return Err(ImageError::MaxMipLevelsExceeded {
mip_levels,
max: max_mip_levels,
});
}
// VUID-VkImageCreateInfo-samples-02257
if samples != SampleCount::Sample1 {
if image_type != ImageType::Dim2d {
return Err(ImageError::MultisampleNot2d);
}
if flags.intersects(ImageCreateFlags::CUBE_COMPATIBLE) {
return Err(ImageError::MultisampleCubeCompatible);
}
if mip_levels != 1 {
return Err(ImageError::MultisampleMultipleMipLevels);
}
if tiling == ImageTiling::Linear {
return Err(ImageError::MultisampleLinearTiling);
}
// VUID-VkImageCreateInfo-multisampleArrayImage-04460
if device.enabled_extensions().khr_portability_subset
&& !device.enabled_features().multisample_array_image
&& array_layers != 1
{
return Err(ImageError::RequirementNotMet {
required_for: "this device is a portability subset device, \
`create_info.samples` is not `SampleCount::Sample1` and \
`create_info.dimensions.array_layers()` is greater than `1`",
requires_one_of: RequiresOneOf {
features: &["multisample_array_image"],
..Default::default()
},
});
}
}
// Check limits for YCbCr formats
if let Some(chroma_sampling) = format.ycbcr_chroma_sampling() {
// VUID-VkImageCreateInfo-format-06410
if mip_levels != 1 {
return Err(ImageError::YcbcrFormatMultipleMipLevels);
}
// VUID-VkImageCreateInfo-format-06411
if samples != SampleCount::Sample1 {
return Err(ImageError::YcbcrFormatMultisampling);
}
// VUID-VkImageCreateInfo-format-06412
if image_type != ImageType::Dim2d {
return Err(ImageError::YcbcrFormatNot2d);
}
// VUID-VkImageCreateInfo-format-06413
if array_layers > 1 && !device.enabled_features().ycbcr_image_arrays {
return Err(ImageError::RequirementNotMet {
required_for: "`create_info.format.ycbcr_chroma_sampling()` is `Some` and \
`create_info.dimensions.array_layers()` is greater than `1`",
requires_one_of: RequiresOneOf {
features: &["ycbcr_image_arrays"],
..Default::default()
},
});
}
match chroma_sampling {
ChromaSampling::Mode444 => (),
ChromaSampling::Mode422 => {
// VUID-VkImageCreateInfo-format-04712
if extent[0] % 2 != 0 {
return Err(ImageError::YcbcrFormatInvalidDimensions);
}
}
ChromaSampling::Mode420 => {
// VUID-VkImageCreateInfo-format-04712
// VUID-VkImageCreateInfo-format-04713
if !(extent[0] % 2 == 0 && extent[1] % 2 == 0) {
return Err(ImageError::YcbcrFormatInvalidDimensions);
}
}
}
}
/* Check usage requirements */
let combined_usage = usage | stencil_usage;
if combined_usage.intersects(ImageUsage::SAMPLED)
&& !format_features.intersects(FormatFeatures::SAMPLED_IMAGE)
{
return Err(ImageError::FormatUsageNotSupported { usage: "sampled" });
}
if combined_usage.intersects(ImageUsage::COLOR_ATTACHMENT)
&& !format_features.intersects(FormatFeatures::COLOR_ATTACHMENT)
{
return Err(ImageError::FormatUsageNotSupported {
usage: "color_attachment",
});
}
if combined_usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT)
&& !format_features.intersects(FormatFeatures::DEPTH_STENCIL_ATTACHMENT)
{
return Err(ImageError::FormatUsageNotSupported {
usage: "depth_stencil_attachment",
});
}
if combined_usage.intersects(ImageUsage::INPUT_ATTACHMENT)
&& !format_features.intersects(
FormatFeatures::COLOR_ATTACHMENT | FormatFeatures::DEPTH_STENCIL_ATTACHMENT,
)
{
return Err(ImageError::FormatUsageNotSupported {
usage: "input_attachment",
});
}
if combined_usage.intersects(
ImageUsage::COLOR_ATTACHMENT
| ImageUsage::DEPTH_STENCIL_ATTACHMENT
| ImageUsage::INPUT_ATTACHMENT
| ImageUsage::TRANSIENT_ATTACHMENT,
) {
// VUID-VkImageCreateInfo-usage-00964
// VUID-VkImageCreateInfo-usage-00965
// VUID-VkImageCreateInfo-Format-02536
// VUID-VkImageCreateInfo-format-02537
if extent[0] > device_properties.max_framebuffer_width
|| extent[1] > device_properties.max_framebuffer_height
{
return Err(ImageError::MaxFramebufferDimensionsExceeded {
extent: [extent[0], extent[1]],
max: [
device_properties.max_framebuffer_width,
device_properties.max_framebuffer_height,
],
});
}
}
if combined_usage.intersects(ImageUsage::STORAGE) {
if !format_features.intersects(FormatFeatures::STORAGE_IMAGE) {
return Err(ImageError::FormatUsageNotSupported { usage: "storage" });
}
// VUID-VkImageCreateInfo-usage-00968
// VUID-VkImageCreateInfo-format-02538
if !device.enabled_features().shader_storage_image_multisample
&& samples != SampleCount::Sample1
{
return Err(ImageError::RequirementNotMet {
required_for: "`create_info.usage` or `create_info.stencil_usage` contains \
`ImageUsage::STORAGE`, and `create_info.samples` is not \
`SampleCount::Sample1`",
requires_one_of: RequiresOneOf {
features: &["shader_storage_image_multisample"],
..Default::default()
},
});
}
}
// These flags only exist in later versions, ignore them otherwise
if device.api_version() >= Version::V1_1 || device.enabled_extensions().khr_maintenance1 {
if combined_usage.intersects(ImageUsage::TRANSFER_SRC)
&& !format_features.intersects(FormatFeatures::TRANSFER_SRC)
{
return Err(ImageError::FormatUsageNotSupported {
usage: "transfer_src",
});
}
if combined_usage.intersects(ImageUsage::TRANSFER_DST)
&& !format_features.intersects(FormatFeatures::TRANSFER_DST)
{
return Err(ImageError::FormatUsageNotSupported {
usage: "transfer_dst",
});
}
}
if usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT) {
// VUID-VkImageCreateInfo-usage-00966
assert!(usage.intersects(
ImageUsage::COLOR_ATTACHMENT
| ImageUsage::DEPTH_STENCIL_ATTACHMENT
| ImageUsage::INPUT_ATTACHMENT
));
// VUID-VkImageCreateInfo-usage-00963
assert!((usage
- (ImageUsage::TRANSIENT_ATTACHMENT
| ImageUsage::COLOR_ATTACHMENT
| ImageUsage::DEPTH_STENCIL_ATTACHMENT
| ImageUsage::INPUT_ATTACHMENT))
.is_empty())
}
if has_separate_stencil_usage {
// VUID-VkImageCreateInfo-format-02795
// VUID-VkImageCreateInfo-format-02796
if usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT)
!= stencil_usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT)
{
return Err(ImageError::StencilUsageMismatch {
usage,
stencil_usage,
});
}
// VUID-VkImageCreateInfo-format-02797
// VUID-VkImageCreateInfo-format-02798
if usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT)
!= stencil_usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT)
{
return Err(ImageError::StencilUsageMismatch {
usage,
stencil_usage,
});
}
if stencil_usage.intersects(ImageUsage::TRANSIENT_ATTACHMENT) {
// VUID-VkImageStencilUsageCreateInfo-stencilUsage-02539
assert!((stencil_usage
- (ImageUsage::TRANSIENT_ATTACHMENT
| ImageUsage::DEPTH_STENCIL_ATTACHMENT
| ImageUsage::INPUT_ATTACHMENT))
.is_empty())
}
}
/* Check flags requirements */
if flags.intersects(ImageCreateFlags::CUBE_COMPATIBLE) {
// VUID-VkImageCreateInfo-flags-00949
if image_type != ImageType::Dim2d {
return Err(ImageError::CubeCompatibleNot2d);
}
// VUID-VkImageCreateInfo-imageType-00954
if extent[0] != extent[1] {
return Err(ImageError::CubeCompatibleNotSquare);
}
// VUID-VkImageCreateInfo-imageType-00954
if array_layers < 6 {
return Err(ImageError::CubeCompatibleNotEnoughArrayLayers);
}
}
if flags.intersects(ImageCreateFlags::ARRAY_2D_COMPATIBLE) {
// VUID-VkImageCreateInfo-flags-00950
if image_type != ImageType::Dim3d {
return Err(ImageError::Array2dCompatibleNot3d);
}
// VUID-VkImageCreateInfo-imageView2DOn3DImage-04459
if device.enabled_extensions().khr_portability_subset
&& !device.enabled_features().image_view2_d_on3_d_image
{
return Err(ImageError::RequirementNotMet {
required_for: "this device is a portability subset device, and \
`create_info.flags` contains `ImageCreateFlags::ARRAY_2D_COMPATIBLE`",
requires_one_of: RequiresOneOf {
features: &["image_view2_d_on3_d_image"],
..Default::default()
},
});
}
}
if flags.intersects(ImageCreateFlags::BLOCK_TEXEL_VIEW_COMPATIBLE) {
// VUID-VkImageCreateInfo-flags-01572
if format.compression().is_none() {
return Err(ImageError::BlockTexelViewCompatibleNotCompressed);
}
}
if flags.intersects(ImageCreateFlags::DISJOINT) {
// VUID-VkImageCreateInfo-format-01577
if format.planes().len() < 2 {
return Err(ImageError::DisjointFormatNotSupported);
}
// VUID-VkImageCreateInfo-imageCreateFormatFeatures-02260
if !format_features.intersects(FormatFeatures::DISJOINT) {
return Err(ImageError::DisjointFormatNotSupported);
}
}
/* Check sharing mode and queue families */
match sharing {
Sharing::Exclusive => (),
Sharing::Concurrent(queue_family_indices) => {
// VUID-VkImageCreateInfo-sharingMode-00942
assert!(queue_family_indices.len() >= 2);
for &queue_family_index in queue_family_indices {
// VUID-VkImageCreateInfo-sharingMode-01420
if queue_family_index
>= device.physical_device().queue_family_properties().len() as u32
{
return Err(ImageError::SharingQueueFamilyIndexOutOfRange {
queue_family_index,
queue_family_count: device
.physical_device()
.queue_family_properties()
.len() as u32,
});
}
}
}
}
/* External memory handles */
if !external_memory_handle_types.is_empty() {
if !(device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_external_memory)
{
return Err(ImageError::RequirementNotMet {
required_for: "`create_info.external_memory_handle_types` is not empty",
requires_one_of: RequiresOneOf {
api_version: Some(Version::V1_1),
device_extensions: &["khr_external_memory"],
..Default::default()
},
});
}
// VUID-VkExternalMemoryImageCreateInfo-handleTypes-parameter
external_memory_handle_types.validate_device(device)?;
// VUID-VkImageCreateInfo-pNext-01443
if initial_layout != ImageLayout::Undefined {
return Err(ImageError::ExternalMemoryInvalidInitialLayout);
}
}
/*
Some device limits can be exceeded, but only for particular image configurations, which
must be queried with `image_format_properties`. See:
https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap44.html#capabilities-image
First, we check if this is the case, then query the device if so.
*/
// https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap44.html#features-extentperimagetype
let extent_must_query = || match image_type {
ImageType::Dim1d => {
let limit = device.physical_device().properties().max_image_dimension1_d;
extent[0] > limit
}
ImageType::Dim2d if flags.intersects(ImageCreateFlags::CUBE_COMPATIBLE) => {
let limit = device
.physical_device()
.properties()
.max_image_dimension_cube;
extent[0] > limit
}
ImageType::Dim2d => {
let limit = device.physical_device().properties().max_image_dimension2_d;
extent[0] > limit || extent[1] > limit
}
ImageType::Dim3d => {
let limit = device.physical_device().properties().max_image_dimension3_d;
extent[0] > limit || extent[1] > limit || extent[2] > limit
}
};
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageFormatProperties.html
let mip_levels_must_query = || {
if mip_levels > 1 {
// TODO: for external memory, the spec says:
// "handle type included in the handleTypes member for which mipmap image support is
// not required". But which handle types are those?
!external_memory_handle_types.is_empty()
} else {
false
}
};
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageFormatProperties.html
let array_layers_must_query = || {
if array_layers > device.physical_device().properties().max_image_array_layers {
true
} else if array_layers > 1 {
image_type == ImageType::Dim3d
} else {
false
}
};
// https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap44.html#features-supported-sample-counts
let samples_must_query = || {
if samples == SampleCount::Sample1 {
return false;
}
if combined_usage.intersects(ImageUsage::COLOR_ATTACHMENT)
&& !device_properties
.framebuffer_color_sample_counts
.contains_enum(samples)
{
// TODO: how to handle framebuffer_integer_color_sample_counts limit, which only
// exists >= Vulkan 1.2
return true;
}
if combined_usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT) {
if aspects.intersects(ImageAspects::DEPTH)
&& !device_properties
.framebuffer_depth_sample_counts
.contains_enum(samples)
{
return true;
}
if aspects.intersects(ImageAspects::STENCIL)
&& !device_properties
.framebuffer_stencil_sample_counts
.contains_enum(samples)
{
return true;
}
}
if combined_usage.intersects(ImageUsage::SAMPLED) {
if let Some(numeric_type) = format.type_color() {
match numeric_type {
NumericType::UINT | NumericType::SINT => {
if !device_properties
.sampled_image_integer_sample_counts
.contains_enum(samples)
{
return true;
}
}
NumericType::SFLOAT
| NumericType::UFLOAT
| NumericType::SNORM
| NumericType::UNORM
| NumericType::SSCALED
| NumericType::USCALED
| NumericType::SRGB => {
if !device_properties
.sampled_image_color_sample_counts
.contains_enum(samples)
{
return true;
}
}
}
} else {
if aspects.intersects(ImageAspects::DEPTH)
&& !device_properties
.sampled_image_depth_sample_counts
.contains_enum(samples)
{
return true;
}
if aspects.intersects(ImageAspects::STENCIL)
&& device_properties
.sampled_image_stencil_sample_counts
.contains_enum(samples)
{
return true;
}
}
}
if combined_usage.intersects(ImageUsage::STORAGE)
&& !device_properties
.storage_image_sample_counts
.contains_enum(samples)
{
return true;
}
false
};
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCreateInfo.html#_description
let linear_must_query = || {
if tiling == ImageTiling::Linear {
!(image_type == ImageType::Dim2d
&& format.type_color().is_some()
&& mip_levels == 1
&& array_layers == 1
// VUID-VkImageCreateInfo-samples-02257 already states that multisampling+linear
// is invalid so no need to check for that here.
&& (usage - (ImageUsage::TRANSFER_SRC | ImageUsage::TRANSFER_DST)).is_empty())
} else {
false
}
};
let must_query_device = extent_must_query()
|| mip_levels_must_query()
|| array_layers_must_query()
|| samples_must_query()
|| linear_must_query();
// We determined that we must query the device in order to be sure that the image
// configuration is supported.
if must_query_device {
let external_memory_handle_types: SmallVec<[Option<ExternalMemoryHandleType>; 4]> =
if !external_memory_handle_types.is_empty() {
// If external memory handles are used, the properties need to be queried
// individually for each handle type.
external_memory_handle_types.into_iter().map(Some).collect()
} else {
smallvec![None]
};
for external_memory_handle_type in external_memory_handle_types {
// Use unchecked, because all validation has been done above.
let image_format_properties = unsafe {
device
.physical_device()
.image_format_properties_unchecked(ImageFormatInfo {
flags,
format: Some(format),
image_type,
tiling,
usage,
external_memory_handle_type,
..Default::default()
})?
};
let ImageFormatProperties {
max_extent,
max_mip_levels,
max_array_layers,
sample_counts,
max_resource_size: _,
..
} = match image_format_properties {
Some(x) => x,
None => return Err(ImageError::ImageFormatPropertiesNotSupported),
};
// VUID-VkImageCreateInfo-extent-02252
// VUID-VkImageCreateInfo-extent-02253
// VUID-VkImageCreateInfo-extent-02254
if extent[0] > max_extent[0]
|| extent[1] > max_extent[1]
|| extent[2] > max_extent[2]
{
return Err(ImageError::MaxDimensionsExceeded {
extent,
max: max_extent,
});
}
// VUID-VkImageCreateInfo-mipLevels-02255
if mip_levels > max_mip_levels {
return Err(ImageError::MaxMipLevelsExceeded {
mip_levels,
max: max_mip_levels,
});
}
// VUID-VkImageCreateInfo-arrayLayers-02256
if array_layers > max_array_layers {
return Err(ImageError::MaxArrayLayersExceeded {
array_layers,
max: max_array_layers,
});
}
// VUID-VkImageCreateInfo-samples-02258
if !sample_counts.contains_enum(samples) {
return Err(ImageError::SampleCountNotSupported {
samples,
supported: sample_counts,
});
}
// TODO: check resource size?
}
}
Ok(format_features)
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub unsafe fn new_unchecked(
device: Arc<Device>,
create_info: ImageCreateInfo,
) -> Result<Self, VulkanError> {
let &ImageCreateInfo {
flags,
dimensions,
format,
mip_levels,
samples,
tiling,
usage,
mut stencil_usage,
ref sharing,
initial_layout,
external_memory_handle_types,
_ne: _,
mut image_drm_format_modifier_create_info,
} = &create_info;
let aspects = format.map_or_else(Default::default, |format| format.aspects());
let has_separate_stencil_usage = if stencil_usage.is_empty()
|| !aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL)
{
stencil_usage = usage;
false
} else {
stencil_usage == usage
};
let (image_type, extent, array_layers) = match dimensions {
ImageDimensions::Dim1d {
width,
array_layers,
} => (ImageType::Dim1d, [width, 1, 1], array_layers),
ImageDimensions::Dim2d {
width,
height,
array_layers,
} => (ImageType::Dim2d, [width, height, 1], array_layers),
ImageDimensions::Dim3d {
width,
height,
depth,
} => (ImageType::Dim3d, [width, height, depth], 1),
};
let (sharing_mode, queue_family_index_count, p_queue_family_indices) = match sharing {
Sharing::Exclusive => (ash::vk::SharingMode::EXCLUSIVE, 0, &[] as _),
Sharing::Concurrent(queue_family_indices) => (
ash::vk::SharingMode::CONCURRENT,
queue_family_indices.len() as u32,
queue_family_indices.as_ptr(),
),
};
let mut info_vk = ash::vk::ImageCreateInfo {
flags: flags.into(),
image_type: image_type.into(),
format: format.map(Into::into).unwrap_or_default(),
extent: ash::vk::Extent3D {
width: extent[0],
height: extent[1],
depth: extent[2],
},
mip_levels,
array_layers,
samples: samples.into(),
tiling: tiling.into(),
usage: usage.into(),
sharing_mode,
queue_family_index_count,
p_queue_family_indices,
initial_layout: initial_layout.into(),
..Default::default()
};
let mut external_memory_info_vk = None;
let mut stencil_usage_info_vk = None;
if !external_memory_handle_types.is_empty() {
let next = external_memory_info_vk.insert(ash::vk::ExternalMemoryImageCreateInfo {
handle_types: external_memory_handle_types.into(),
..Default::default()
});
next.p_next = info_vk.p_next;
info_vk.p_next = next as *const _ as *const _;
}
if has_separate_stencil_usage {
let next = stencil_usage_info_vk.insert(ash::vk::ImageStencilUsageCreateInfo {
stencil_usage: stencil_usage.into(),
..Default::default()
});
next.p_next = info_vk.p_next;
info_vk.p_next = next as *const _ as *const _;
}
if external_memory_handle_types.intersects(ExternalMemoryHandleTypes::DMA_BUF) {
let next = image_drm_format_modifier_create_info.as_mut().unwrap();
next.p_next = info_vk.p_next;
info_vk.p_next = next as *const _ as *const _;
}
let handle = {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.create_image)(device.handle(), &info_vk, ptr::null(), output.as_mut_ptr())
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(Self::from_handle(device, handle, create_info))
}
/// Creates a new `RawImage` from a raw object handle.
///
/// # Safety
///
/// - `handle` must be a valid Vulkan object handle created from `device`.
/// - `handle` must refer to an image that has not yet had memory bound to it.
/// - `create_info` must match the info used to create the object.
#[inline]
pub unsafe fn from_handle(
device: Arc<Device>,
handle: ash::vk::Image,
create_info: ImageCreateInfo,
) -> Self {
Self::from_handle_with_destruction(device, handle, create_info, true)
}
unsafe fn from_handle_with_destruction(
device: Arc<Device>,
handle: ash::vk::Image,
create_info: ImageCreateInfo,
needs_destruction: bool,
) -> Self {
let ImageCreateInfo {
flags,
dimensions,
format,
mip_levels,
samples,
tiling,
usage,
mut stencil_usage,
sharing,
initial_layout,
external_memory_handle_types,
_ne: _,
image_drm_format_modifier_create_info: _,
} = create_info;
let aspects = format.map_or_else(Default::default, |format| format.aspects());
if stencil_usage.is_empty()
|| !aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL)
{
stencil_usage = usage;
}
// Get format features
let format_features = {
// Use unchecked, because `create_info` is assumed to match the info of the handle, and
// therefore already valid.
let format_properties = device
.physical_device()
.format_properties_unchecked(format.unwrap());
match tiling {
ImageTiling::Linear => format_properties.linear_tiling_features,
ImageTiling::Optimal => format_properties.optimal_tiling_features,
ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features, // TODO: improve
}
};
let memory_requirements = if needs_destruction {
if flags.intersects(ImageCreateFlags::DISJOINT) {
(0..format.unwrap().planes().len())
.map(|plane| Self::get_memory_requirements(&device, handle, Some(plane)))
.collect()
} else {
smallvec![Self::get_memory_requirements(&device, handle, None)]
}
} else {
smallvec![]
};
RawImage {
handle,
device,
id: Self::next_id(),
flags,
dimensions,
format,
format_features,
mip_levels,
initial_layout,
samples,
tiling,
usage,
stencil_usage,
sharing,
external_memory_handle_types,
memory_requirements,
needs_destruction,
subresource_layout: OnceCache::new(),
}
}
fn get_memory_requirements(
device: &Device,
handle: ash::vk::Image,
plane: Option<usize>,
) -> MemoryRequirements {
let mut info_vk = ash::vk::ImageMemoryRequirementsInfo2 {
image: handle,
..Default::default()
};
let mut plane_info_vk = None;
if let Some(plane) = plane {
debug_assert!(
device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_get_memory_requirements2
&& device.enabled_extensions().khr_sampler_ycbcr_conversion
);
let next = plane_info_vk.insert(ash::vk::ImagePlaneMemoryRequirementsInfo {
plane_aspect: match plane {
0 => ash::vk::ImageAspectFlags::PLANE_0,
1 => ash::vk::ImageAspectFlags::PLANE_1,
2 => ash::vk::ImageAspectFlags::PLANE_2,
_ => unreachable!(),
},
..Default::default()
});
next.p_next = info_vk.p_next;
info_vk.p_next = next as *mut _ as *mut _;
}
let mut memory_requirements2_vk = ash::vk::MemoryRequirements2::default();
let mut memory_dedicated_requirements_vk = None;
if device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_dedicated_allocation
{
debug_assert!(
device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_get_memory_requirements2
);
let next = memory_dedicated_requirements_vk
.insert(ash::vk::MemoryDedicatedRequirements::default());
next.p_next = memory_requirements2_vk.p_next;
memory_requirements2_vk.p_next = next as *mut _ as *mut _;
}
unsafe {
let fns = device.fns();
if device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_get_memory_requirements2
{
if device.api_version() >= Version::V1_1 {
(fns.v1_1.get_image_memory_requirements2)(
device.handle(),
&info_vk,
&mut memory_requirements2_vk,
);
} else {
(fns.khr_get_memory_requirements2
.get_image_memory_requirements2_khr)(
device.handle(),
&info_vk,
&mut memory_requirements2_vk,
);
}
} else {
(fns.v1_0.get_image_memory_requirements)(
device.handle(),
handle,
&mut memory_requirements2_vk.memory_requirements,
);
}
}
MemoryRequirements {
layout: DeviceLayout::from_size_alignment(
memory_requirements2_vk.memory_requirements.size,
memory_requirements2_vk.memory_requirements.alignment,
)
.unwrap(),
memory_type_bits: memory_requirements2_vk.memory_requirements.memory_type_bits,
prefers_dedicated_allocation: memory_dedicated_requirements_vk
.map_or(false, |dreqs| dreqs.prefers_dedicated_allocation != 0),
requires_dedicated_allocation: memory_dedicated_requirements_vk
.map_or(false, |dreqs| dreqs.requires_dedicated_allocation != 0),
}
}
#[inline]
#[allow(dead_code)] // Remove when sparse memory is implemented
fn get_sparse_memory_requirements(&self) -> Vec<SparseImageMemoryRequirements> {
let device = &self.device;
unsafe {
let fns = self.device.fns();
if device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_get_memory_requirements2
{
let info2 = ash::vk::ImageSparseMemoryRequirementsInfo2 {
image: self.handle,
..Default::default()
};
let mut count = 0;
if device.api_version() >= Version::V1_1 {
(fns.v1_1.get_image_sparse_memory_requirements2)(
device.handle(),
&info2,
&mut count,
ptr::null_mut(),
);
} else {
(fns.khr_get_memory_requirements2
.get_image_sparse_memory_requirements2_khr)(
device.handle(),
&info2,
&mut count,
ptr::null_mut(),
);
}
let mut sparse_image_memory_requirements2 =
vec![ash::vk::SparseImageMemoryRequirements2::default(); count as usize];
if device.api_version() >= Version::V1_1 {
(fns.v1_1.get_image_sparse_memory_requirements2)(
self.device.handle(),
&info2,
&mut count,
sparse_image_memory_requirements2.as_mut_ptr(),
);
} else {
(fns.khr_get_memory_requirements2
.get_image_sparse_memory_requirements2_khr)(
self.device.handle(),
&info2,
&mut count,
sparse_image_memory_requirements2.as_mut_ptr(),
);
}
sparse_image_memory_requirements2.set_len(count as usize);
sparse_image_memory_requirements2
.into_iter()
.map(
|sparse_image_memory_requirements2| SparseImageMemoryRequirements {
format_properties: SparseImageFormatProperties {
aspects: sparse_image_memory_requirements2
.memory_requirements
.format_properties
.aspect_mask
.into(),
image_granularity: [
sparse_image_memory_requirements2
.memory_requirements
.format_properties
.image_granularity
.width,
sparse_image_memory_requirements2
.memory_requirements
.format_properties
.image_granularity
.height,
sparse_image_memory_requirements2
.memory_requirements
.format_properties
.image_granularity
.depth,
],
flags: sparse_image_memory_requirements2
.memory_requirements
.format_properties
.flags
.into(),
},
image_mip_tail_first_lod: sparse_image_memory_requirements2
.memory_requirements
.image_mip_tail_first_lod,
image_mip_tail_size: sparse_image_memory_requirements2
.memory_requirements
.image_mip_tail_size,
image_mip_tail_offset: sparse_image_memory_requirements2
.memory_requirements
.image_mip_tail_offset,
image_mip_tail_stride: (!sparse_image_memory_requirements2
.memory_requirements
.format_properties
.flags
.intersects(ash::vk::SparseImageFormatFlags::SINGLE_MIPTAIL))
.then_some(
sparse_image_memory_requirements2
.memory_requirements
.image_mip_tail_stride,
),
},
)
.collect()
} else {
let mut count = 0;
(fns.v1_0.get_image_sparse_memory_requirements)(
device.handle(),
self.handle,
&mut count,
ptr::null_mut(),
);
let mut sparse_image_memory_requirements =
vec![ash::vk::SparseImageMemoryRequirements::default(); count as usize];
(fns.v1_0.get_image_sparse_memory_requirements)(
device.handle(),
self.handle,
&mut count,
sparse_image_memory_requirements.as_mut_ptr(),
);
sparse_image_memory_requirements.set_len(count as usize);
sparse_image_memory_requirements
.into_iter()
.map(
|sparse_image_memory_requirements| SparseImageMemoryRequirements {
format_properties: SparseImageFormatProperties {
aspects: sparse_image_memory_requirements
.format_properties
.aspect_mask
.into(),
image_granularity: [
sparse_image_memory_requirements
.format_properties
.image_granularity
.width,
sparse_image_memory_requirements
.format_properties
.image_granularity
.height,
sparse_image_memory_requirements
.format_properties
.image_granularity
.depth,
],
flags: sparse_image_memory_requirements
.format_properties
.flags
.into(),
},
image_mip_tail_first_lod: sparse_image_memory_requirements
.image_mip_tail_first_lod,
image_mip_tail_size: sparse_image_memory_requirements
.image_mip_tail_size,
image_mip_tail_offset: sparse_image_memory_requirements
.image_mip_tail_offset,
image_mip_tail_stride: (!sparse_image_memory_requirements
.format_properties
.flags
.intersects(ash::vk::SparseImageFormatFlags::SINGLE_MIPTAIL))
.then_some(sparse_image_memory_requirements.image_mip_tail_stride),
},
)
.collect()
}
}
}
pub(crate) fn id(&self) -> NonZeroU64 {
self.id
}
/// Binds device memory to this image.
///
/// - If `self.flags().disjoint` is not set, then `allocations` must contain exactly one
/// element. This element may be a dedicated allocation.
/// - If `self.flags().disjoint` is set, then `allocations` must contain exactly
/// `self.format().unwrap().planes().len()` elements. These elements must not be dedicated
/// allocations.
pub fn bind_memory(
self,
allocations: impl IntoIterator<Item = MemoryAlloc>,
) -> Result<
Image,
(
ImageError,
RawImage,
impl ExactSizeIterator<Item = MemoryAlloc>,
),
> {
let allocations: SmallVec<[_; 3]> = allocations.into_iter().collect();
if let Err(err) = self.validate_bind_memory(&allocations) {
return Err((err, self, allocations.into_iter()));
}
unsafe { self.bind_memory_unchecked(allocations) }.map_err(|(err, image, allocations)| {
(
err.into(),
image,
allocations
.into_iter()
.collect::<SmallVec<[_; 3]>>()
.into_iter(),
)
})
}
fn validate_bind_memory(&self, allocations: &[MemoryAlloc]) -> Result<(), ImageError> {
if self.flags.intersects(ImageCreateFlags::DISJOINT) {
if allocations.len() != self.format.unwrap().planes().len() {
return Err(ImageError::AllocationsWrongNumberOfElements {
provided: allocations.len(),
required: self.format.unwrap().planes().len(),
});
}
} else {
if allocations.len() != 1 {
return Err(ImageError::AllocationsWrongNumberOfElements {
provided: allocations.len(),
required: 1,
});
}
}
for (allocations_index, (allocation, memory_requirements)) in (allocations.iter())
.zip(self.memory_requirements.iter())
.enumerate()
{
assert_ne!(allocation.allocation_type(), AllocationType::Linear);
let memory = allocation.device_memory();
let memory_offset = allocation.offset();
let memory_type = &self
.device
.physical_device()
.memory_properties()
.memory_types[memory.memory_type_index() as usize];
// VUID-VkBindImageMemoryInfo-commonparent
assert_eq!(self.device(), memory.device());
// VUID-VkBindImageMemoryInfo-image-07460
// Ensured by taking ownership of `RawImage`.
// VUID-VkBindImageMemoryInfo-image-01045
// Currently ensured by not having sparse binding flags, but this needs to be checked
// once those are enabled.
// VUID-VkBindImageMemoryInfo-memoryOffset-01046
// Assume that `allocation` was created correctly.
if let Some(dedicated_to) = memory.dedicated_to() {
// VUID-VkBindImageMemoryInfo-memory-02628
match dedicated_to {
DedicatedTo::Image(id) if id == self.id => {}
_ => return Err(ImageError::DedicatedAllocationMismatch),
}
debug_assert!(memory_offset == 0); // This should be ensured by the allocator
} else {
// VUID-VkBindImageMemoryInfo-image-01445
if memory_requirements.requires_dedicated_allocation {
return Err(ImageError::DedicatedAllocationRequired);
}
}
// VUID-VkBindImageMemoryInfo-None-01901
if memory_type
.property_flags
.intersects(MemoryPropertyFlags::PROTECTED)
{
return Err(ImageError::MemoryProtectedMismatch {
allocations_index,
image_protected: false,
memory_protected: true,
});
}
// VUID-VkBindImageMemoryInfo-memory-02728
if !memory.export_handle_types().is_empty()
&& !memory
.export_handle_types()
.intersects(self.external_memory_handle_types)
{
return Err(ImageError::MemoryExternalHandleTypesDisjoint {
allocations_index,
image_handle_types: self.external_memory_handle_types,
memory_export_handle_types: memory.export_handle_types(),
});
}
if let Some(handle_type) = memory.imported_handle_type() {
// VUID-VkBindImageMemoryInfo-memory-02989
if !ExternalMemoryHandleTypes::from(handle_type)
.intersects(self.external_memory_handle_types)
{
return Err(ImageError::MemoryImportedHandleTypeNotEnabled {
allocations_index,
image_handle_types: self.external_memory_handle_types,
memory_imported_handle_type: handle_type,
});
}
}
// VUID-VkBindImageMemoryInfo-pNext-01615
// VUID-VkBindImageMemoryInfo-pNext-01619
if memory_requirements.memory_type_bits & (1 << memory.memory_type_index()) == 0 {
return Err(ImageError::MemoryTypeNotAllowed {
allocations_index,
provided_memory_type_index: memory.memory_type_index(),
allowed_memory_type_bits: memory_requirements.memory_type_bits,
});
}
// VUID-VkBindImageMemoryInfo-pNext-01616
// VUID-VkBindImageMemoryInfo-pNext-01620
if !is_aligned(memory_offset, memory_requirements.layout.alignment()) {
return Err(ImageError::MemoryAllocationNotAligned {
allocations_index,
allocation_offset: memory_offset,
required_alignment: memory_requirements.layout.alignment(),
});
}
// VUID-VkBindImageMemoryInfo-pNext-01617
// VUID-VkBindImageMemoryInfo-pNext-01621
if allocation.size() < memory_requirements.layout.size() {
return Err(ImageError::MemoryAllocationTooSmall {
allocations_index,
allocation_size: allocation.size(),
required_size: memory_requirements.layout.size(),
});
}
}
Ok(())
}
/// # Safety
///
/// - If `self.flags().disjoint` is not set, then `allocations` must contain exactly one
/// element.
/// - If `self.flags().disjoint` is set, then `allocations` must contain exactly
/// `self.format().unwrap().planes().len()` elements.
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn bind_memory_unchecked(
self,
allocations: impl IntoIterator<Item = MemoryAlloc>,
) -> Result<
Image,
(
VulkanError,
RawImage,
impl ExactSizeIterator<Item = MemoryAlloc>,
),
> {
let allocations: SmallVec<[_; 3]> = allocations.into_iter().collect();
let fns = self.device.fns();
let result = if self.device.api_version() >= Version::V1_1
|| self.device.enabled_extensions().khr_bind_memory2
{
let mut infos_vk: SmallVec<[_; 3]> = SmallVec::with_capacity(3);
let mut plane_infos_vk: SmallVec<[_; 3]> = SmallVec::with_capacity(3);
if self.flags.intersects(ImageCreateFlags::DISJOINT) {
debug_assert_eq!(allocations.len(), self.format.unwrap().planes().len());
for (plane, allocation) in allocations.iter().enumerate() {
let memory = allocation.device_memory();
let memory_offset = allocation.offset();
infos_vk.push(ash::vk::BindImageMemoryInfo {
image: self.handle,
memory: memory.handle(),
memory_offset,
..Default::default()
});
// VUID-VkBindImageMemoryInfo-pNext-01618
plane_infos_vk.push(ash::vk::BindImagePlaneMemoryInfo {
plane_aspect: match plane {
0 => ash::vk::ImageAspectFlags::PLANE_0,
1 => ash::vk::ImageAspectFlags::PLANE_1,
2 => ash::vk::ImageAspectFlags::PLANE_2,
_ => unreachable!(),
},
..Default::default()
});
}
} else {
debug_assert_eq!(allocations.len(), 1);
let allocation = &allocations[0];
let memory = allocation.device_memory();
let memory_offset = allocation.offset();
infos_vk.push(ash::vk::BindImageMemoryInfo {
image: self.handle,
memory: memory.handle(),
memory_offset,
..Default::default()
});
};
for (info_vk, plane_info_vk) in (infos_vk.iter_mut()).zip(plane_infos_vk.iter_mut()) {
info_vk.p_next = plane_info_vk as *mut _ as *mut _;
}
if self.device.api_version() >= Version::V1_1 {
(fns.v1_1.bind_image_memory2)(
self.device.handle(),
infos_vk.len() as u32,
infos_vk.as_ptr(),
)
} else {
(fns.khr_bind_memory2.bind_image_memory2_khr)(
self.device.handle(),
infos_vk.len() as u32,
infos_vk.as_ptr(),
)
}
} else {
debug_assert_eq!(allocations.len(), 1);
let allocation = &allocations[0];
let memory = allocation.device_memory();
let memory_offset = allocation.offset();
(fns.v1_0.bind_image_memory)(
self.device.handle(),
self.handle,
memory.handle(),
memory_offset,
)
}
.result();
if let Err(err) = result {
return Err((VulkanError::from(err), self, allocations.into_iter()));
}
Ok(Image::from_raw(self, ImageMemory::Normal(allocations)))
}
/// Returns the memory requirements for this image.
///
/// - If the image is a swapchain image, this returns a slice with a length of 0.
/// - If `self.flags().disjoint` is not set, this returns a slice with a length of 1.
/// - If `self.flags().disjoint` is set, this returns a slice with a length equal to
/// `self.format().unwrap().planes().len()`.
#[inline]
pub fn memory_requirements(&self) -> &[MemoryRequirements] {
&self.memory_requirements
}
/// Returns the flags the image was created with.
#[inline]
pub fn flags(&self) -> ImageCreateFlags {
self.flags
}
/// Returns the dimensions of the image.
#[inline]
pub fn dimensions(&self) -> ImageDimensions {
self.dimensions
}
/// Returns the image's format.
#[inline]
pub fn format(&self) -> Option<Format> {
self.format
}
/// Returns the features supported by the image's format.
#[inline]
pub fn format_features(&self) -> FormatFeatures {
self.format_features
}
/// Returns the number of mipmap levels in the image.
#[inline]
pub fn mip_levels(&self) -> u32 {
self.mip_levels
}
/// Returns the initial layout of the image.
#[inline]
pub fn initial_layout(&self) -> ImageLayout {
self.initial_layout
}
/// Returns the number of samples for the image.
#[inline]
pub fn samples(&self) -> SampleCount {
self.samples
}
/// Returns the tiling of the image.
#[inline]
pub fn tiling(&self) -> ImageTiling {
self.tiling
}
/// Returns the usage the image was created with.
#[inline]
pub fn usage(&self) -> ImageUsage {
self.usage
}
/// Returns the stencil usage the image was created with.
#[inline]
pub fn stencil_usage(&self) -> ImageUsage {
self.stencil_usage
}
/// Returns the sharing the image was created with.
#[inline]
pub fn sharing(&self) -> &Sharing<SmallVec<[u32; 4]>> {
&self.sharing
}
/// Returns the external memory handle types that are supported with this image.
#[inline]
pub fn external_memory_handle_types(&self) -> ExternalMemoryHandleTypes {
self.external_memory_handle_types
}
/// Returns an `ImageSubresourceLayers` covering the first mip level of the image. All aspects
/// of the image are selected, or `plane0` if the image is multi-planar.
#[inline]
pub fn subresource_layers(&self) -> ImageSubresourceLayers {
ImageSubresourceLayers {
aspects: {
let aspects = self.format.unwrap().aspects();
if aspects.intersects(ImageAspects::PLANE_0) {
ImageAspects::PLANE_0
} else {
aspects
}
},
mip_level: 0,
array_layers: 0..self.dimensions.array_layers(),
}
}
/// Returns an `ImageSubresourceRange` covering the whole image. If the image is multi-planar,
/// only the `color` aspect is selected.
#[inline]
pub fn subresource_range(&self) -> ImageSubresourceRange {
ImageSubresourceRange {
aspects: self.format.unwrap().aspects()
- (ImageAspects::PLANE_0 | ImageAspects::PLANE_1 | ImageAspects::PLANE_2),
mip_levels: 0..self.mip_levels,
array_layers: 0..self.dimensions.array_layers(),
}
}
/// Queries the memory layout of a single subresource of the image.
///
/// Only images with linear tiling are supported, if they do not have a format with both a
/// depth and a stencil format. Images with optimal tiling have an opaque image layout that is
/// not suitable for direct memory accesses, and likewise for combined depth/stencil formats.
/// Multi-planar formats are supported, but you must specify one of the planes as the `aspect`,
/// not [`ImageAspect::Color`].
///
/// The results of this function are cached, so that future calls with the same arguments
/// do not need to make a call to the Vulkan API again.
pub fn subresource_layout(
&self,
aspect: ImageAspect,
mip_level: u32,
array_layer: u32,
) -> Result<SubresourceLayout, ImageError> {
self.validate_subresource_layout(aspect, mip_level, array_layer)?;
unsafe { Ok(self.subresource_layout_unchecked(aspect, mip_level, array_layer)) }
}
fn validate_subresource_layout(
&self,
aspect: ImageAspect,
mip_level: u32,
array_layer: u32,
) -> Result<(), ImageError> {
// VUID-VkImageSubresource-aspectMask-parameter
aspect.validate_device(&self.device)?;
// VUID-VkImageSubresource-aspectMask-requiredbitmask
// VUID-vkGetImageSubresourceLayout-aspectMask-00997
// Ensured by use of enum `ImageAspect`.
// VUID-vkGetImageSubresourceLayout-image-02270
if !matches!(
self.tiling,
ImageTiling::DrmFormatModifier | ImageTiling::Linear
) {
return Err(ImageError::OptimalTilingNotSupported);
}
// VUID-vkGetImageSubresourceLayout-mipLevel-01716
if mip_level >= self.mip_levels {
return Err(ImageError::MipLevelOutOfRange {
provided_mip_level: mip_level,
image_mip_levels: self.mip_levels,
});
}
// VUID-vkGetImageSubresourceLayout-arrayLayer-01717
if array_layer >= self.dimensions.array_layers() {
return Err(ImageError::ArrayLayerOutOfRange {
provided_array_layer: array_layer,
image_array_layers: self.dimensions.array_layers(),
});
}
let mut allowed_aspects = self.format.unwrap().aspects();
// Follows from the combination of these three VUIDs. See:
// https://github.com/KhronosGroup/Vulkan-Docs/issues/1942
// VUID-vkGetImageSubresourceLayout-aspectMask-00997
// VUID-vkGetImageSubresourceLayout-format-04462
// VUID-vkGetImageSubresourceLayout-format-04463
if allowed_aspects.contains(ImageAspects::DEPTH | ImageAspects::STENCIL) {
return Err(ImageError::DepthStencilFormatsNotSupported);
}
if allowed_aspects
.intersects(ImageAspects::PLANE_0 | ImageAspects::PLANE_1 | ImageAspects::PLANE_2)
{
allowed_aspects -= ImageAspects::COLOR;
}
// TODO: VUID-vkGetImageSubresourceLayout-tiling-02271
//if self.tiling == ImageTiling::DrmFormatModifier {
// Only one-plane image importing is possible for now.
//}
// VUID-vkGetImageSubresourceLayout-format-04461
// VUID-vkGetImageSubresourceLayout-format-04462
// VUID-vkGetImageSubresourceLayout-format-04463
// VUID-vkGetImageSubresourceLayout-format-04464
// VUID-vkGetImageSubresourceLayout-format-01581
// VUID-vkGetImageSubresourceLayout-format-01582
if !allowed_aspects.contains(aspect.into()) {
return Err(ImageError::AspectNotAllowed {
provided_aspect: aspect,
allowed_aspects,
});
}
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn subresource_layout_unchecked(
&self,
aspect: ImageAspect,
mip_level: u32,
array_layer: u32,
) -> SubresourceLayout {
self.subresource_layout.get_or_insert(
(aspect, mip_level, array_layer),
|&(aspect, mip_level, array_layer)| {
let fns = self.device.fns();
let subresource = ash::vk::ImageSubresource {
aspect_mask: aspect.into(),
mip_level,
array_layer,
};
let mut output = MaybeUninit::uninit();
(fns.v1_0.get_image_subresource_layout)(
self.device.handle(),
self.handle,
&subresource,
output.as_mut_ptr(),
);
let output = output.assume_init();
SubresourceLayout {
offset: output.offset,
size: output.size,
row_pitch: output.row_pitch,
array_pitch: (self.dimensions.array_layers() > 1).then_some(output.array_pitch),
depth_pitch: matches!(self.dimensions, ImageDimensions::Dim3d { .. })
.then_some(output.depth_pitch),
}
},
)
}
}
impl Drop for RawImage {
#[inline]
fn drop(&mut self) {
if !self.needs_destruction {
return;
}
unsafe {
let fns = self.device.fns();
(fns.v1_0.destroy_image)(self.device.handle(), self.handle, ptr::null());
}
}
}
unsafe impl VulkanObject for RawImage {
type Handle = ash::vk::Image;
#[inline]
fn handle(&self) -> Self::Handle {
self.handle
}
}
unsafe impl DeviceOwned for RawImage {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl_id_counter!(RawImage);
/// Parameters to create a new `Image`.
#[derive(Clone, Debug)]
pub struct ImageCreateInfo {
/// Flags to enable.
///
/// The default value is [`ImageCreateFlags::empty()`].
pub flags: ImageCreateFlags,
/// The type, extent and number of array layers to create the image with.
///
/// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag)
/// devices, if `samples` is not [`SampleCount::Sample1`] and `dimensions.array_layers()` is
/// not 1, the [`multisample_array_image`](crate::device::Features::multisample_array_image)
/// feature must be enabled on the device.
///
/// The default value is `ImageDimensions::Dim2d { width: 0, height: 0, array_layers: 1 }`,
/// which must be overridden.
pub dimensions: ImageDimensions,
/// The format used to store the image data.
///
/// The default value is `None`, which must be overridden.
pub format: Option<Format>,
/// The number of mip levels to create the image with.
///
/// The default value is `1`.
pub mip_levels: u32,
/// The number of samples per texel that the image should use.
///
/// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag)
/// devices, if `samples` is not [`SampleCount::Sample1`] and `dimensions.array_layers()` is
/// not 1, the [`multisample_array_image`](crate::device::Features::multisample_array_image)
/// feature must be enabled on the device.
///
/// The default value is [`SampleCount::Sample1`].
pub samples: SampleCount,
/// The memory arrangement of the texel blocks.
///
/// The default value is [`ImageTiling::Optimal`].
pub tiling: ImageTiling,
/// How the image is going to be used.
///
/// The default value is [`ImageUsage::empty()`], which must be overridden.
pub usage: ImageUsage,
/// How the stencil aspect of the image is going to be used, if any.
///
/// If `stencil_usage` is empty or if `format` does not have both a depth and a stencil aspect,
/// then it is automatically set to equal `usage`.
///
/// If after this, `stencil_usage` does not equal `usage`,
/// then the device API version must be at least 1.2, or the
/// [`ext_separate_stencil_usage`](crate::device::DeviceExtensions::ext_separate_stencil_usage)
/// extension must be enabled on the device.
///
/// The default value is [`ImageUsage::empty()`].
pub stencil_usage: ImageUsage,
/// Whether the image can be shared across multiple queues, or is limited to a single queue.
///
/// The default value is [`Sharing::Exclusive`].
pub sharing: Sharing<SmallVec<[u32; 4]>>,
/// The image layout that the image will have when it is created.
///
/// The default value is [`ImageLayout::Undefined`].
pub initial_layout: ImageLayout,
/// The external memory handle types that are going to be used with the image.
///
/// If any of the fields in this value are set, the device must either support API version 1.1
/// or the [`khr_external_memory`](crate::device::DeviceExtensions::khr_external_memory)
/// extension must be enabled, and `initial_layout` must be set to
/// [`ImageLayout::Undefined`].
///
/// The default value is [`ExternalMemoryHandleTypes::empty()`].
pub external_memory_handle_types: ExternalMemoryHandleTypes,
/// Specify that an image be created with the provided DRM format modifier and explicit memory layout
pub image_drm_format_modifier_create_info: Option<ImageDrmFormatModifierExplicitCreateInfoEXT>,
pub _ne: crate::NonExhaustive,
}
impl Default for ImageCreateInfo {
#[inline]
fn default() -> Self {
Self {
flags: ImageCreateFlags::empty(),
dimensions: ImageDimensions::Dim2d {
width: 0,
height: 0,
array_layers: 1,
},
format: None,
mip_levels: 1,
samples: SampleCount::Sample1,
tiling: ImageTiling::Optimal,
usage: ImageUsage::empty(),
stencil_usage: ImageUsage::empty(),
sharing: Sharing::Exclusive,
initial_layout: ImageLayout::Undefined,
external_memory_handle_types: ExternalMemoryHandleTypes::empty(),
image_drm_format_modifier_create_info: None,
_ne: crate::NonExhaustive(()),
}
}
}
/// A multi-dimensioned storage for texel data.
///
/// Unlike [`RawImage`], an `Image` has memory backing it, and can be used normally.
#[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>,
}
/// The type of backing memory that an image can have.
#[derive(Debug)]
pub enum ImageMemory {
/// The image is backed by normal memory, bound with [`bind_memory`].
///
/// [`bind_memory`]: RawImage::bind_memory
Normal(SmallVec<[MemoryAlloc; 3]>),
/// The image is backed by sparse memory, bound with [`bind_sparse`].
///
/// [`bind_sparse`]: crate::device::QueueGuard::bind_sparse
Sparse(Vec<SparseImageMemoryRequirements>),
/// The image is backed by memory owned by a [`Swapchain`].
Swapchain {
swapchain: Arc<Swapchain>,
image_index: u32,
},
}
impl Image {
fn from_raw(inner: RawImage, memory: ImageMemory) -> Self {
let aspects = inner.format.unwrap().aspects();
let aspect_list: SmallVec<[ImageAspect; 4]> = aspects.into_iter().collect();
let mip_level_size = inner.dimensions.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,
}
}
pub(crate) unsafe fn from_swapchain(
handle: ash::vk::Image,
swapchain: Arc<Swapchain>,
image_index: u32,
) -> Self {
let create_info = ImageCreateInfo {
flags: ImageCreateFlags::empty(),
dimensions: ImageDimensions::Dim2d {
width: swapchain.image_extent()[0],
height: swapchain.image_extent()[1],
array_layers: swapchain.image_array_layers(),
},
format: Some(swapchain.image_format()),
initial_layout: ImageLayout::Undefined,
mip_levels: 1,
samples: SampleCount::Sample1,
tiling: ImageTiling::Optimal,
usage: swapchain.image_usage(),
stencil_usage: swapchain.image_usage(),
sharing: swapchain.image_sharing().clone(),
..Default::default()
};
Self::from_raw(
RawImage::from_handle_with_destruction(
swapchain.device().clone(),
handle,
create_info,
false,
),
ImageMemory::Swapchain {
swapchain,
image_index,
},
)
}
/// Returns the type of memory that is backing this image.
#[inline]
pub fn memory(&self) -> &ImageMemory {
&self.memory
}
/// Returns the memory requirements for this image.
///
/// - If the image is a swapchain image, this returns a slice with a length of 0.
/// - If `self.flags().disjoint` is not set, this returns a slice with a length of 1.
/// - If `self.flags().disjoint` is set, this returns a slice with a length equal to
/// `self.format().unwrap().planes().len()`.
#[inline]
pub fn memory_requirements(&self) -> &[MemoryRequirements] {
&self.inner.memory_requirements
}
/// Returns the flags the image was created with.
#[inline]
pub fn flags(&self) -> ImageCreateFlags {
self.inner.flags
}
/// Returns the dimensions of the image.
#[inline]
pub fn dimensions(&self) -> ImageDimensions {
self.inner.dimensions
}
/// Returns the image's format.
#[inline]
pub fn format(&self) -> Option<Format> {
self.inner.format
}
/// Returns the features supported by the image's format.
#[inline]
pub fn format_features(&self) -> FormatFeatures {
self.inner.format_features
}
/// Returns the number of mipmap levels in the image.
#[inline]
pub fn mip_levels(&self) -> u32 {
self.inner.mip_levels
}
/// Returns the initial layout of the image.
#[inline]
pub fn initial_layout(&self) -> ImageLayout {
self.inner.initial_layout
}
/// Returns the number of samples for the image.
#[inline]
pub fn samples(&self) -> SampleCount {
self.inner.samples
}
/// Returns the tiling of the image.
#[inline]
pub fn tiling(&self) -> ImageTiling {
self.inner.tiling
}
/// Returns the usage the image was created with.
#[inline]
pub fn usage(&self) -> ImageUsage {
self.inner.usage
}
/// Returns the stencil usage the image was created with.
#[inline]
pub fn stencil_usage(&self) -> ImageUsage {
self.inner.stencil_usage
}
/// Returns the sharing the image was created with.
#[inline]
pub fn sharing(&self) -> &Sharing<SmallVec<[u32; 4]>> {
&self.inner.sharing
}
/// Returns the external memory handle types that are supported with this image.
#[inline]
pub fn external_memory_handle_types(&self) -> ExternalMemoryHandleTypes {
self.inner.external_memory_handle_types
}
/// Returns an `ImageSubresourceLayers` covering the first mip level of the image. All aspects
/// of the image are selected, or `plane0` if the image is multi-planar.
#[inline]
pub fn subresource_layers(&self) -> ImageSubresourceLayers {
self.inner.subresource_layers()
}
/// Returns an `ImageSubresourceRange` covering the whole image. If the image is multi-planar,
/// only the `color` aspect is selected.
#[inline]
pub fn subresource_range(&self) -> ImageSubresourceRange {
self.inner.subresource_range()
}
/// Queries the memory layout of a single subresource of the image.
///
/// Only images with linear tiling are supported, if they do not have a format with both a
/// depth and a stencil format. Images with optimal tiling have an opaque image layout that is
/// not suitable for direct memory accesses, and likewise for combined depth/stencil formats.
/// Multi-planar formats are supported, but you must specify one of the planes as the `aspect`,
/// not [`ImageAspect::Color`].
///
/// 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.
#[inline]
pub fn subresource_layout(
&self,
aspect: ImageAspect,
mip_level: u32,
array_layer: u32,
) -> Result<SubresourceLayout, ImageError> {
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
}
/// Returns an iterator over subresource ranges.
///
/// In ranges, the subresources are "flattened" to `DeviceSize`, where each index in the range
/// is a single array layer. The layers are arranged hierarchically: aspects at the top level,
/// with the mip levels in that aspect, and the array layers in that mip level.
pub(crate) fn iter_ranges(
&self,
subresource_range: ImageSubresourceRange,
) -> SubresourceRangeIterator {
assert!(self
.format()
.unwrap()
.aspects()
.contains(subresource_range.aspects));
assert!(subresource_range.mip_levels.end <= self.inner.mip_levels);
assert!(subresource_range.array_layers.end <= self.inner.dimensions.array_layers());
SubresourceRangeIterator::new(
subresource_range,
&self.aspect_list,
self.aspect_size,
self.inner.mip_levels,
self.mip_level_size,
self.inner.dimensions.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.inner.mip_levels,
array_layers: 0..self.inner.dimensions.array_layers(),
}
} else {
let aspect_num = (range.start / self.aspect_size) as usize;
range.start %= self.aspect_size;
range.end %= self.aspect_size;
// Wraparound
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.inner.dimensions.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;
// Wraparound
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()
}
}
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);
}
}
/// The current state of an image.
#[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<(), ReadLockError> {
for (_range, state) in self.ranges.range(&range) {
match &state.current_access {
CurrentAccess::CpuExclusive { .. } => return Err(ReadLockError::CpuWriteLocked),
CurrentAccess::GpuExclusive { .. } => return Err(ReadLockError::GpuWriteLocked),
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<(), WriteLockError> {
for (_range, state) in self.ranges.range(&range) {
match &state.current_access {
CurrentAccess::CpuExclusive => return Err(WriteLockError::CpuLocked),
CurrentAccess::GpuExclusive { .. } => return Err(WriteLockError::GpuLocked),
CurrentAccess::Shared {
cpu_reads: 0,
gpu_reads: 0,
} => (),
CurrentAccess::Shared { cpu_reads, .. } if *cpu_reads > 0 => {
return Err(WriteLockError::CpuLocked)
}
CurrentAccess::Shared { .. } => return Err(WriteLockError::GpuLocked),
}
}
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"),
}
}
}
}
/// The current state of a specific subresource range in an image.
#[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,
}
}
/// Used when the requested range contains only a subset of the array layers in the image.
/// The iterator returns one range for each mip level and aspect, each covering the range of
/// array layers of that mip level and aspect.
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
})
}
/// Used when the requested range contains all array layers in the image, but not all mip
/// levels. The iterator returns one range for each aspect, each covering all layers of the
/// range of mip levels of that aspect.
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
})
}
/// Used when the requested range contains all array layers and mip levels in the image.
/// The iterator returns one range for each series of adjacent aspect numbers, each covering
/// all mip levels and all layers of those aspects. If the range contains the whole image, then
/// exactly one range is returned since all aspect numbers will be adjacent.
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 {}
/// Describes the memory layout of a single subresource of an 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(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubresourceLayout {
/// The number of bytes from the start of the memory to the start of the queried subresource.
pub offset: DeviceSize,
/// The total number of bytes for the queried subresource.
pub size: DeviceSize,
/// The number of bytes between two texels or two blocks in adjacent rows.
pub row_pitch: DeviceSize,
/// For images with more than one array layer, the number of bytes between two texels or two
/// blocks in adjacent array layers.
pub array_pitch: Option<DeviceSize>,
/// For 3D images, the number of bytes between two texels or two blocks in adjacent depth
/// layers.
pub depth_pitch: Option<DeviceSize>,
}
/// Error that can happen in image functions.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ImageError {
VulkanError(VulkanError),
/// Allocating memory failed.
AllocError(AllocationCreationError),
RequirementNotMet {
required_for: &'static str,
requires_one_of: RequiresOneOf,
},
/// The provided number of elements in `allocations` is not what is required for `image`.
AllocationsWrongNumberOfElements {
provided: usize,
required: usize,
},
/// The `array_2d_compatible` flag was enabled, but the image type was not 3D.
Array2dCompatibleNot3d,
/// The provided array layer is not less than the number of array layers in the image.
ArrayLayerOutOfRange {
provided_array_layer: u32,
image_array_layers: u32,
},
/// The provided aspect is not present in the image, or is not allowed.
AspectNotAllowed {
provided_aspect: ImageAspect,
allowed_aspects: ImageAspects,
},
/// The `block_texel_view_compatible` flag was enabled, but the given format was not compressed.
BlockTexelViewCompatibleNotCompressed,
/// The `cube_compatible` flag was enabled, but the image type was not 2D.
CubeCompatibleNot2d,
/// The `cube_compatible` flag was enabled, but the number of array layers was less than 6.
CubeCompatibleNotEnoughArrayLayers,
/// The `cube_compatible` flag was enabled, but the image dimensions were not square.
CubeCompatibleNotSquare,
/// The `cube_compatible` flag was enabled together with multisampling.
CubeCompatibleMultisampling,
/// The memory was created dedicated to a resource, but not to this image.
DedicatedAllocationMismatch,
/// A dedicated allocation is required for this image, but one was not provided.
DedicatedAllocationRequired,
/// The image has a format with both a depth and a stencil aspect, which is not supported for
/// this operation.
DepthStencilFormatsNotSupported,
/// The `disjoint` flag was enabled, but the given format is either not multi-planar, or does
/// not support disjoint images.
DisjointFormatNotSupported,
/// One or more external memory handle types were provided, but the initial layout was not
/// `Undefined`.
ExternalMemoryInvalidInitialLayout,
/// The given format was not supported by the device.
FormatNotSupported,
/// A requested usage flag was not supported by the given format.
FormatUsageNotSupported {
usage: &'static str,
},
/// The image configuration as queried through the `image_format_properties` function was not
/// supported by the device.
ImageFormatPropertiesNotSupported,
/// The number of array layers exceeds the maximum supported by the device for this image
/// configuration.
MaxArrayLayersExceeded {
array_layers: u32,
max: u32,
},
/// The specified dimensions exceed the maximum supported by the device for this image
/// configuration.
MaxDimensionsExceeded {
extent: [u32; 3],
max: [u32; 3],
},
/// The usage included one of the attachment types, and the specified width and height exceeded
/// the `max_framebuffer_width` or `max_framebuffer_height` limits.
MaxFramebufferDimensionsExceeded {
extent: [u32; 2],
max: [u32; 2],
},
/// The maximum number of mip levels for the given dimensions has been exceeded.
MaxMipLevelsExceeded {
mip_levels: u32,
max: u32,
},
/// In an `allocations` element, the offset of the allocation does not have the required
/// alignment.
MemoryAllocationNotAligned {
allocations_index: usize,
allocation_offset: DeviceSize,
required_alignment: DeviceAlignment,
},
/// In an `allocations` element, the size of the allocation is smaller than what is required.
MemoryAllocationTooSmall {
allocations_index: usize,
allocation_size: DeviceSize,
required_size: DeviceSize,
},
/// In an `allocations` element, the memory was created with export handle types, but none of
/// these handle types were enabled on the image.
MemoryExternalHandleTypesDisjoint {
allocations_index: usize,
image_handle_types: ExternalMemoryHandleTypes,
memory_export_handle_types: ExternalMemoryHandleTypes,
},
/// In an `allocations` element, the memory was created with an import, but the import's handle
/// type was not enabled on the image.
MemoryImportedHandleTypeNotEnabled {
allocations_index: usize,
image_handle_types: ExternalMemoryHandleTypes,
memory_imported_handle_type: ExternalMemoryHandleType,
},
/// In an `allocations` element, the protection of image and memory are not equal.
MemoryProtectedMismatch {
allocations_index: usize,
image_protected: bool,
memory_protected: bool,
},
/// In an `allocations` element, the provided memory type is not one of the allowed memory
/// types that can be bound to this image or image plane.
MemoryTypeNotAllowed {
allocations_index: usize,
provided_memory_type_index: u32,
allowed_memory_type_bits: u32,
},
/// The provided mip level is not less than the number of mip levels in the image.
MipLevelOutOfRange {
provided_mip_level: u32,
image_mip_levels: u32,
},
/// Multisampling was enabled, and the `cube_compatible` flag was set.
MultisampleCubeCompatible,
/// Multisampling was enabled, and tiling was `Linear`.
MultisampleLinearTiling,
/// Multisampling was enabled, and multiple mip levels were specified.
MultisampleMultipleMipLevels,
/// Multisampling was enabled, but the image type was not 2D.
MultisampleNot2d,
/// The image has optimal tiling, which is not supported for this operation.
OptimalTilingNotSupported,
/// The sample count is not supported by the device for this image configuration.
SampleCountNotSupported {
samples: SampleCount,
supported: SampleCounts,
},
/// The sharing mode was set to `Concurrent`, but one of the specified queue family indices was
/// out of range.
SharingQueueFamilyIndexOutOfRange {
queue_family_index: u32,
queue_family_count: u32,
},
/// The provided `usage` and `stencil_usage` have different values for
/// `depth_stencil_attachment` or `transient_attachment`.
StencilUsageMismatch {
usage: ImageUsage,
stencil_usage: ImageUsage,
},
/// A YCbCr format was given, but the specified width and/or height was not a multiple of 2
/// as required by the format's chroma subsampling.
YcbcrFormatInvalidDimensions,
/// A YCbCr format was given, and multiple mip levels were specified.
YcbcrFormatMultipleMipLevels,
/// A YCbCr format was given, and multisampling was enabled.
YcbcrFormatMultisampling,
/// A YCbCr format was given, but the image type was not 2D.
YcbcrFormatNot2d,
DirectImageViewCreationFailed(ImageViewCreationError),
/// If and only if tiling is `DRMFormatModifier`, then `image_drm_format_modifier_create_info` must not be `None`.
DrmFormatModifierRequiresCreateInfo,
}
impl Error for ImageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ImageError::AllocError(err) => Some(err),
_ => None,
}
}
}
impl Display for ImageError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::VulkanError(_) => write!(f, "a runtime error occurred"),
Self::AllocError(_) => write!(f, "allocating memory failed"),
Self::RequirementNotMet {
required_for,
requires_one_of,
} => write!(
f,
"a requirement was not met for: {}; requires one of: {}",
required_for, requires_one_of,
),
Self::AllocationsWrongNumberOfElements { provided, required } => write!(
f,
"the provided number of elements in `allocations` ({}) is not what is required for \
`image` ({})",
provided, required,
),
Self::Array2dCompatibleNot3d => write!(
f,
"the `array_2d_compatible` flag was enabled, but the image type was not 3D",
),
Self::ArrayLayerOutOfRange {
provided_array_layer,
image_array_layers,
} => write!(
f,
"the provided array layer ({}) is not less than the number of array layers in the image ({})",
provided_array_layer, image_array_layers,
),
Self::AspectNotAllowed {
provided_aspect,
allowed_aspects,
} => write!(
f,
"the provided aspect ({:?}) is not present in the image, or is not allowed ({:?})",
provided_aspect, allowed_aspects,
),
Self::BlockTexelViewCompatibleNotCompressed => write!(
f,
"the `block_texel_view_compatible` flag was enabled, but the given format was not \
compressed",
),
Self::CubeCompatibleNot2d => write!(
f,
"the `cube_compatible` flag was enabled, but the image type was not 2D",
),
Self::CubeCompatibleNotEnoughArrayLayers => write!(
f,
"the `cube_compatible` flag was enabled, but the number of array layers was less \
than 6",
),
Self::CubeCompatibleNotSquare => write!(
f,
"the `cube_compatible` flag was enabled, but the image dimensions were not square",
),
Self::CubeCompatibleMultisampling => write!(
f,
"the `cube_compatible` flag was enabled together with multisampling",
),
Self::DedicatedAllocationMismatch => write!(
f,
"the memory was created dedicated to a resource, but not to this image",
),
Self::DedicatedAllocationRequired => write!(
f,
"a dedicated allocation is required for this image, but one was not provided"
),
Self::DepthStencilFormatsNotSupported => write!(
f,
"the image has a format with both a depth and a stencil aspect, which is not \
supported for this operation",
),
Self::DisjointFormatNotSupported => write!(
f,
"the `disjoint` flag was enabled, but the given format is either not multi-planar, \
or does not support disjoint images",
),
Self::ExternalMemoryInvalidInitialLayout => write!(
f,
"one or more external memory handle types were provided, but the initial layout \
was not `Undefined`",
),
Self::FormatNotSupported => {
write!(f, "the given format was not supported by the device")
}
Self::FormatUsageNotSupported { .. } => write!(
f,
"a requested usage flag was not supported by the given format",
),
Self::ImageFormatPropertiesNotSupported => write!(
f,
"the image configuration as queried through the `image_format_properties` function \
was not supported by the device",
),
Self::MaxArrayLayersExceeded { .. } => write!(
f,
"the number of array layers exceeds the maximum supported by the device for this \
image configuration",
),
Self::MaxDimensionsExceeded { .. } => write!(
f,
"the specified dimensions exceed the maximum supported by the device for this \
image configuration",
),
Self::MaxFramebufferDimensionsExceeded { .. } => write!(
f,
"the usage included one of the attachment types, and the specified width and \
height exceeded the `max_framebuffer_width` or `max_framebuffer_height` limits",
),
Self::MaxMipLevelsExceeded { .. } => write!(
f,
"the maximum number of mip levels for the given dimensions has been exceeded",
),
Self::MemoryAllocationNotAligned {
allocations_index,
allocation_offset,
required_alignment,
} => write!(
f,
"in `allocations` element {}, the offset of the allocation ({}) does not have the \
required alignment ({:?})",
allocations_index, allocation_offset, required_alignment,
),
Self::MemoryAllocationTooSmall {
allocations_index,
allocation_size,
required_size,
} => write!(
f,
"in `allocations` element {}, the size of the allocation ({}) is smaller than what \
is required ({})",
allocations_index, allocation_size, required_size,
),
Self::MemoryExternalHandleTypesDisjoint {
allocations_index, ..
} => write!(
f,
"in `allocations` element {}, the memory was created with export handle types, but \
none of these handle types were enabled on the image",
allocations_index,
),
Self::MemoryImportedHandleTypeNotEnabled {
allocations_index, ..
} => write!(
f,
"in `allocations` element {}, the memory was created with an import, but the \
import's handle type was not enabled on the image",
allocations_index,
),
Self::MemoryProtectedMismatch {
allocations_index,
image_protected,
memory_protected,
} => write!(
f,
"in `allocations` element {}, the protection of image ({}) and memory ({}) are not \
equal",
allocations_index, image_protected, memory_protected,
),
Self::MemoryTypeNotAllowed {
allocations_index,
provided_memory_type_index,
allowed_memory_type_bits,
} => write!(
f,
"in `allocations` element {}, the provided memory type ({}) is not one of the \
allowed memory types (",
allocations_index, provided_memory_type_index,
)
.and_then(|_| {
let mut first = true;
for i in (0..size_of_val(allowed_memory_type_bits))
.filter(|i| allowed_memory_type_bits & (1 << i) != 0)
{
if first {
write!(f, "{}", i)?;
first = false;
} else {
write!(f, ", {}", i)?;
}
}
Ok(())
})
.and_then(|_| write!(f, ") that can be bound to this buffer")),
Self::MipLevelOutOfRange {
provided_mip_level,
image_mip_levels,
} => write!(
f,
"the provided mip level ({}) is not less than the number of mip levels in the image ({})",
provided_mip_level, image_mip_levels,
),
Self::MultisampleCubeCompatible => write!(
f,
"multisampling was enabled, and the `cube_compatible` flag was set",
),
Self::MultisampleLinearTiling => {
write!(f, "multisampling was enabled, and tiling was `Linear`")
}
Self::MultisampleMultipleMipLevels => write!(
f,
"multisampling was enabled, and multiple mip levels were specified",
),
Self::MultisampleNot2d => write!(
f,
"multisampling was enabled, but the image type was not 2D",
),
Self::OptimalTilingNotSupported => write!(
f,
"the image has optimal tiling, which is not supported for this operation",
),
Self::SampleCountNotSupported { .. } => write!(
f,
"the sample count is not supported by the device for this image configuration",
),
Self::SharingQueueFamilyIndexOutOfRange { .. } => write!(
f,
"the sharing mode was set to `Concurrent`, but one of the specified queue family \
indices was out of range",
),
Self::StencilUsageMismatch {
usage: _,
stencil_usage: _,
} => write!(
f,
"the provided `usage` and `stencil_usage` have different values for \
`depth_stencil_attachment` or `transient_attachment`",
),
Self::YcbcrFormatInvalidDimensions => write!(
f,
"a YCbCr format was given, but the specified width and/or height was not a \
multiple of 2 as required by the format's chroma subsampling",
),
Self::YcbcrFormatMultipleMipLevels => write!(
f,
"a YCbCr format was given, and multiple mip levels were specified",
),
Self::YcbcrFormatMultisampling => {
write!(f, "a YCbCr format was given, and multisampling was enabled")
}
Self::YcbcrFormatNot2d => {
write!(f, "a YCbCr format was given, but the image type was not 2D")
}
Self::DirectImageViewCreationFailed(e) => write!(f, "Image view creation failed {}", e),
Self::DrmFormatModifierRequiresCreateInfo => write!(f, "If and only if tiling is `DRMFormatModifier`, then `image_drm_format_modifier_create_info` must be `Some`"),
}
}
}
impl From<VulkanError> for ImageError {
fn from(err: VulkanError) -> Self {
Self::VulkanError(err)
}
}
impl From<AllocationCreationError> for ImageError {
fn from(err: AllocationCreationError) -> Self {
Self::AllocError(err)
}
}
impl From<RequirementNotMet> for ImageError {
fn from(err: RequirementNotMet) -> Self {
Self::RequirementNotMet {
required_for: err.required_for,
requires_one_of: err.requires_one_of,
}
}
}
#[cfg(test)]
mod tests {
use super::{ImageCreateInfo, ImageError, ImageUsage, RawImage};
use crate::{
format::Format,
image::{
sys::SubresourceRangeIterator, ImageAspect, ImageAspects, ImageCreateFlags,
ImageDimensions, ImageSubresourceRange, SampleCount,
},
DeviceSize, RequiresOneOf,
};
use smallvec::SmallVec;
#[test]
fn create_sampled() {
let (device, _) = gfx_dev_and_queue!();
let _ = RawImage::new(
device,
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 32,
array_layers: 1,
},
format: Some(Format::R8G8B8A8_UNORM),
usage: ImageUsage::SAMPLED,
..Default::default()
},
)
.unwrap();
}
#[test]
fn create_transient() {
let (device, _) = gfx_dev_and_queue!();
let _ = RawImage::new(
device,
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 32,
array_layers: 1,
},
format: Some(Format::R8G8B8A8_UNORM),
usage: ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
)
.unwrap();
}
#[test]
fn zero_mipmap() {
let (device, _) = gfx_dev_and_queue!();
assert_should_panic!({
let _ = RawImage::new(
device,
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 32,
array_layers: 1,
},
format: Some(Format::R8G8B8A8_UNORM),
mip_levels: 0,
usage: ImageUsage::SAMPLED,
..Default::default()
},
);
});
}
#[test]
fn mipmaps_too_high() {
let (device, _) = gfx_dev_and_queue!();
let res = RawImage::new(
device,
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 32,
array_layers: 1,
},
format: Some(Format::R8G8B8A8_UNORM),
mip_levels: u32::MAX,
usage: ImageUsage::SAMPLED,
..Default::default()
},
);
match res {
Err(ImageError::MaxMipLevelsExceeded { .. }) => (),
_ => panic!(),
};
}
#[test]
fn shader_storage_image_multisample() {
let (device, _) = gfx_dev_and_queue!();
let res = RawImage::new(
device,
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 32,
array_layers: 1,
},
format: Some(Format::R8G8B8A8_UNORM),
samples: SampleCount::Sample2,
usage: ImageUsage::STORAGE,
..Default::default()
},
);
match res {
Err(ImageError::RequirementNotMet {
requires_one_of: RequiresOneOf { features, .. },
..
}) if features.contains(&"shader_storage_image_multisample") => (),
Err(ImageError::SampleCountNotSupported { .. }) => (), // unlikely but possible
_ => panic!(),
};
}
#[test]
fn compressed_not_color_attachment() {
let (device, _) = gfx_dev_and_queue!();
let res = RawImage::new(
device,
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 32,
array_layers: 1,
},
format: Some(Format::ASTC_5x4_UNORM_BLOCK),
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
);
match res {
Err(ImageError::FormatNotSupported) => (),
Err(ImageError::FormatUsageNotSupported {
usage: "color_attachment",
}) => (),
_ => panic!(),
};
}
#[test]
fn transient_forbidden_with_some_usages() {
let (device, _) = gfx_dev_and_queue!();
assert_should_panic!({
let _ = RawImage::new(
device,
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 32,
array_layers: 1,
},
format: Some(Format::R8G8B8A8_UNORM),
usage: ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::SAMPLED,
..Default::default()
},
);
})
}
#[test]
fn cubecompatible_dims_mismatch() {
let (device, _) = gfx_dev_and_queue!();
let res = RawImage::new(
device,
ImageCreateInfo {
flags: ImageCreateFlags::CUBE_COMPATIBLE,
dimensions: ImageDimensions::Dim2d {
width: 32,
height: 64,
array_layers: 1,
},
format: Some(Format::R8G8B8A8_UNORM),
usage: ImageUsage::SAMPLED,
..Default::default()
},
);
match res {
Err(ImageError::CubeCompatibleNotEnoughArrayLayers) => (),
Err(ImageError::CubeCompatibleNotSquare) => (),
_ => panic!(),
};
}
#[test]
#[allow(clippy::erasing_op, clippy::identity_op)]
fn subresource_range_iterator() {
// A fictitious set of aspects that no real image would actually ever have.
let image_aspect_list: SmallVec<[ImageAspect; 4]> = (ImageAspects::COLOR
| ImageAspects::DEPTH
| ImageAspects::STENCIL
| ImageAspects::PLANE_0)
.into_iter()
.collect();
let image_mip_levels = 6;
let image_array_layers = 8;
let mip = image_array_layers as DeviceSize;
let asp = mip * image_mip_levels as DeviceSize;
// Whole image
let mut iter = SubresourceRangeIterator::new(
ImageSubresourceRange {
aspects: ImageAspects::COLOR
| ImageAspects::DEPTH
| ImageAspects::STENCIL
| ImageAspects::PLANE_0,
mip_levels: 0..6,
array_layers: 0..8,
},
&image_aspect_list,
asp,
image_mip_levels,
mip,
image_array_layers,
);
assert_eq!(iter.next(), Some(0 * asp..4 * asp));
assert_eq!(iter.next(), None);
// Only some aspects
let mut iter = SubresourceRangeIterator::new(
ImageSubresourceRange {
aspects: ImageAspects::COLOR | ImageAspects::DEPTH | ImageAspects::PLANE_0,
mip_levels: 0..6,
array_layers: 0..8,
},
&image_aspect_list,
asp,
image_mip_levels,
mip,
image_array_layers,
);
assert_eq!(iter.next(), Some(0 * asp..2 * asp));
assert_eq!(iter.next(), Some(3 * asp..4 * asp));
assert_eq!(iter.next(), None);
// Two aspects, and only some of the mip levels
let mut iter = SubresourceRangeIterator::new(
ImageSubresourceRange {
aspects: ImageAspects::DEPTH | ImageAspects::STENCIL,
mip_levels: 2..4,
array_layers: 0..8,
},
&image_aspect_list,
asp,
image_mip_levels,
mip,
image_array_layers,
);
assert_eq!(iter.next(), Some(1 * asp + 2 * mip..1 * asp + 4 * mip));
assert_eq!(iter.next(), Some(2 * asp + 2 * mip..2 * asp + 4 * mip));
assert_eq!(iter.next(), None);
// One aspect, one mip level, only some of the array layers
let mut iter = SubresourceRangeIterator::new(
ImageSubresourceRange {
aspects: ImageAspects::COLOR,
mip_levels: 0..1,
array_layers: 2..4,
},
&image_aspect_list,
asp,
image_mip_levels,
mip,
image_array_layers,
);
assert_eq!(
iter.next(),
Some(0 * asp + 0 * mip + 2..0 * asp + 0 * mip + 4)
);
assert_eq!(iter.next(), None);
// Two aspects, two mip levels, only some of the array layers
let mut iter = SubresourceRangeIterator::new(
ImageSubresourceRange {
aspects: ImageAspects::DEPTH | ImageAspects::STENCIL,
mip_levels: 2..4,
array_layers: 6..8,
},
&image_aspect_list,
asp,
image_mip_levels,
mip,
image_array_layers,
);
assert_eq!(
iter.next(),
Some(1 * asp + 2 * mip + 6..1 * asp + 2 * mip + 8)
);
assert_eq!(
iter.next(),
Some(1 * asp + 3 * mip + 6..1 * asp + 3 * mip + 8)
);
assert_eq!(
iter.next(),
Some(2 * asp + 2 * mip + 6..2 * asp + 2 * mip + 8)
);
assert_eq!(
iter.next(),
Some(2 * asp + 3 * mip + 6..2 * asp + 3 * mip + 8)
);
assert_eq!(iter.next(), None);
}
}