blob: 1a4faaff59f82b3fcb6f852ba73c9ea8f349c640 [file] [log] [blame] [edit]
// 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.
use super::{
sys::{Image, ImageMemory, RawImage},
traits::ImageContent,
ImageAccess, ImageAspects, ImageDescriptorLayouts, ImageError, ImageInner, ImageLayout,
ImageUsage, SampleCount,
};
use crate::{
device::{Device, DeviceOwned},
format::Format,
image::{sys::ImageCreateInfo, ImageCreateFlags, ImageDimensions, ImageFormatInfo},
memory::{
allocator::{
AllocationCreateInfo, AllocationType, MemoryAllocatePreference, MemoryAllocator,
MemoryUsage,
},
is_aligned, DedicatedAllocation, DeviceMemoryError, ExternalMemoryHandleType,
ExternalMemoryHandleTypes,
},
DeviceSize,
};
use std::{
fs::File,
hash::{Hash, Hasher},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
/// ImageAccess whose purpose is to be used as a framebuffer attachment.
///
/// The image is always two-dimensional and has only one mipmap, but it can have any kind of
/// format. Trying to use a format that the backend doesn't support for rendering will result in
/// an error being returned when creating the image. Once you have an `AttachmentImage`, you are
/// guaranteed that you will be able to draw on it.
///
/// The template parameter of `AttachmentImage` is a type that describes the format of the image.
///
/// # Regular vs transient
///
/// Calling `AttachmentImage::new` will create a regular image, while calling
/// `AttachmentImage::transient` will create a *transient* image. Transient image are only
/// relevant for images that serve as attachments, so `AttachmentImage` is the only type of
/// image in vulkano that provides a shortcut for this.
///
/// A transient image is a special kind of image whose content is undefined outside of render
/// passes. Once you finish drawing, reading from it will returned undefined data (which can be
/// either valid or garbage, depending on the implementation).
///
/// This gives a hint to the Vulkan implementation that it is possible for the image's content to
/// live exclusively in some cache memory, and that no real memory has to be allocated for it.
///
/// In other words, if you are going to read from the image after drawing to it, use a regular
/// image. If you don't need to read from it (for example if it's some kind of intermediary color,
/// or a depth buffer that is only used once) then use a transient image as it may improve
/// performance.
///
// TODO: forbid reading transient images outside render passes?
#[derive(Debug)]
pub struct AttachmentImage {
inner: Arc<Image>,
// Layout to use when the image is used as a framebuffer attachment.
// Must be either "depth-stencil optimal" or "color optimal".
attachment_layout: ImageLayout,
// If true, then the image is in the layout of `attachment_layout` (above). If false, then it
// is still `Undefined`.
layout_initialized: AtomicBool,
}
impl AttachmentImage {
/// Creates a new image with the given dimensions and format.
///
/// Returns an error if the dimensions are too large or if the backend doesn't support this
/// format as a framebuffer attachment.
#[inline]
pub fn new(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
ImageUsage::empty(),
SampleCount::Sample1,
)
}
/// Same as `new`, but creates an image that can be used as an input attachment.
///
/// > **Note**: This function is just a convenient shortcut for `with_usage`.
#[inline]
pub fn input_attachment(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::INPUT_ATTACHMENT;
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
base_usage,
SampleCount::Sample1,
)
}
/// Same as `new`, but creates a multisampled image.
///
/// > **Note**: You can also use this function and pass `1` for the number of samples if you
/// > want a regular image.
#[inline]
pub fn multisampled(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
samples: SampleCount,
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
ImageUsage::empty(),
samples,
)
}
/// Same as `multisampled`, but creates an image that can be used as an input attachment.
///
/// > **Note**: This function is just a convenient shortcut for `multisampled_with_usage`.
#[inline]
pub fn multisampled_input_attachment(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
samples: SampleCount,
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::INPUT_ATTACHMENT;
AttachmentImage::new_impl(allocator, dimensions, 1, format, base_usage, samples)
}
/// Same as `new`, but lets you specify additional usages.
///
/// The `color_attachment` or `depth_stencil_attachment` usages are automatically added based
/// on the format of the usage. Therefore the `usage` parameter allows you specify usages in
/// addition to these two.
#[inline]
pub fn with_usage(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
format: Format,
usage: ImageUsage,
) -> Result<Arc<AttachmentImage>, ImageError> {
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
usage,
SampleCount::Sample1,
)
}
/// Same as `with_usage`, but creates a multisampled image.
///
/// > **Note**: You can also use this function and pass `1` for the number of samples if you
/// > want a regular image.
#[inline]
pub fn multisampled_with_usage(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
samples: SampleCount,
format: Format,
usage: ImageUsage,
) -> Result<Arc<AttachmentImage>, ImageError> {
AttachmentImage::new_impl(allocator, dimensions, 1, format, usage, samples)
}
/// Same as `multisampled_with_usage`, but creates an image with multiple layers.
///
/// > **Note**: You can also use this function and pass `1` for the number of layers if you
/// > want a regular image.
#[inline]
pub fn multisampled_with_usage_with_layers(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
array_layers: u32,
samples: SampleCount,
format: Format,
usage: ImageUsage,
) -> Result<Arc<AttachmentImage>, ImageError> {
AttachmentImage::new_impl(allocator, dimensions, array_layers, format, usage, samples)
}
/// Same as `new`, except that the image can later be sampled.
///
/// > **Note**: This function is just a convenient shortcut for `with_usage`.
#[inline]
pub fn sampled(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::SAMPLED;
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
base_usage,
SampleCount::Sample1,
)
}
/// Same as `sampled`, except that the image can be used as an input attachment.
///
/// > **Note**: This function is just a convenient shortcut for `with_usage`.
#[inline]
pub fn sampled_input_attachment(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::SAMPLED | ImageUsage::INPUT_ATTACHMENT;
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
base_usage,
SampleCount::Sample1,
)
}
/// Same as `sampled`, but creates a multisampled image.
///
/// > **Note**: You can also use this function and pass `1` for the number of samples if you
/// > want a regular image.
///
/// > **Note**: This function is just a convenient shortcut for `multisampled_with_usage`.
#[inline]
pub fn sampled_multisampled(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
samples: SampleCount,
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::SAMPLED;
AttachmentImage::new_impl(allocator, dimensions, 1, format, base_usage, samples)
}
/// Same as `sampled_multisampled`, but creates an image that can be used as an input
/// attachment.
///
/// > **Note**: This function is just a convenient shortcut for `multisampled_with_usage`.
#[inline]
pub fn sampled_multisampled_input_attachment(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
samples: SampleCount,
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::SAMPLED | ImageUsage::INPUT_ATTACHMENT;
AttachmentImage::new_impl(allocator, dimensions, 1, format, base_usage, samples)
}
/// Same as `new`, except that the image will be transient.
///
/// A transient image is special because its content is undefined outside of a render pass.
/// This means that the implementation has the possibility to not allocate any memory for it.
///
/// > **Note**: This function is just a convenient shortcut for `with_usage`.
#[inline]
pub fn transient(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::TRANSIENT_ATTACHMENT;
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
base_usage,
SampleCount::Sample1,
)
}
/// Same as `transient`, except that the image can be used as an input attachment.
///
/// > **Note**: This function is just a convenient shortcut for `with_usage`.
#[inline]
pub fn transient_input_attachment(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT;
AttachmentImage::new_impl(
allocator,
dimensions,
1,
format,
base_usage,
SampleCount::Sample1,
)
}
/// Same as `transient`, but creates a multisampled image.
///
/// > **Note**: You can also use this function and pass `1` for the number of samples if you
/// > want a regular image.
///
/// > **Note**: This function is just a convenient shortcut for `multisampled_with_usage`.
#[inline]
pub fn transient_multisampled(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
samples: SampleCount,
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::TRANSIENT_ATTACHMENT;
AttachmentImage::new_impl(allocator, dimensions, 1, format, base_usage, samples)
}
/// Same as `transient_multisampled`, but creates an image that can be used as an input
/// attachment.
///
/// > **Note**: This function is just a convenient shortcut for `multisampled_with_usage`.
#[inline]
pub fn transient_multisampled_input_attachment(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
samples: SampleCount,
format: Format,
) -> Result<Arc<AttachmentImage>, ImageError> {
let base_usage = ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT;
AttachmentImage::new_impl(allocator, dimensions, 1, format, base_usage, samples)
}
// All constructors dispatch to this one.
fn new_impl(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
array_layers: u32,
format: Format,
mut usage: ImageUsage,
samples: SampleCount,
) -> Result<Arc<AttachmentImage>, ImageError> {
let physical_device = allocator.device().physical_device();
let device_properties = physical_device.properties();
if dimensions[0] > device_properties.max_framebuffer_height {
panic!("AttachmentImage height exceeds physical device's max_framebuffer_height");
}
if dimensions[1] > device_properties.max_framebuffer_width {
panic!("AttachmentImage width exceeds physical device's max_framebuffer_width");
}
if array_layers > device_properties.max_framebuffer_layers {
panic!("AttachmentImage layer count exceeds physical device's max_framebuffer_layers");
}
let aspects = format.aspects();
let is_depth_stencil = aspects.intersects(ImageAspects::DEPTH | ImageAspects::STENCIL);
if is_depth_stencil {
usage -= ImageUsage::COLOR_ATTACHMENT;
usage |= ImageUsage::DEPTH_STENCIL_ATTACHMENT;
} else {
usage |= ImageUsage::COLOR_ATTACHMENT;
usage -= ImageUsage::DEPTH_STENCIL_ATTACHMENT;
}
if format.compression().is_some() {
panic!() // TODO: message?
}
let raw_image = RawImage::new(
allocator.device().clone(),
ImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: dimensions[0],
height: dimensions[1],
array_layers,
},
format: Some(format),
samples,
usage,
..Default::default()
},
)?;
let requirements = raw_image.memory_requirements()[0];
let res = unsafe {
allocator.allocate_unchecked(
requirements,
AllocationType::NonLinear,
AllocationCreateInfo {
usage: MemoryUsage::DeviceOnly,
allocate_preference: MemoryAllocatePreference::Unknown,
_ne: crate::NonExhaustive(()),
},
Some(DedicatedAllocation::Image(&raw_image)),
)
};
match res {
Ok(alloc) => {
debug_assert!(is_aligned(alloc.offset(), requirements.layout.alignment()));
debug_assert!(alloc.size() == requirements.layout.size());
let inner = Arc::new(
unsafe { raw_image.bind_memory_unchecked([alloc]) }
.map_err(|(err, _, _)| err)?,
);
Ok(Arc::new(AttachmentImage {
inner,
attachment_layout: if is_depth_stencil {
ImageLayout::DepthStencilAttachmentOptimal
} else {
ImageLayout::ColorAttachmentOptimal
},
layout_initialized: AtomicBool::new(false),
}))
}
Err(err) => Err(err.into()),
}
}
pub fn new_with_exportable_fd(
allocator: &(impl MemoryAllocator + ?Sized),
dimensions: [u32; 2],
array_layers: u32,
format: Format,
mut usage: ImageUsage,
samples: SampleCount,
) -> Result<Arc<AttachmentImage>, ImageError> {
let physical_device = allocator.device().physical_device();
let device_properties = physical_device.properties();
if dimensions[0] > device_properties.max_framebuffer_height {
panic!("AttachmentImage height exceeds physical device's max_framebuffer_height");
}
if dimensions[1] > device_properties.max_framebuffer_width {
panic!("AttachmentImage width exceeds physical device's max_framebuffer_width");
}
if array_layers > device_properties.max_framebuffer_layers {
panic!("AttachmentImage layer count exceeds physical device's max_framebuffer_layers");
}
let aspects = format.aspects();
let is_depth_stencil = aspects.intersects(ImageAspects::DEPTH | ImageAspects::STENCIL);
if is_depth_stencil {
usage -= ImageUsage::COLOR_ATTACHMENT;
usage |= ImageUsage::DEPTH_STENCIL_ATTACHMENT;
} else {
usage |= ImageUsage::COLOR_ATTACHMENT;
usage -= ImageUsage::DEPTH_STENCIL_ATTACHMENT;
}
let external_memory_properties = allocator
.device()
.physical_device()
.image_format_properties(ImageFormatInfo {
flags: ImageCreateFlags::MUTABLE_FORMAT,
format: Some(format),
usage,
external_memory_handle_type: Some(ExternalMemoryHandleType::OpaqueFd),
..Default::default()
})
.unwrap()
.unwrap()
.external_memory_properties;
// VUID-VkExportMemoryAllocateInfo-handleTypes-00656
assert!(external_memory_properties.exportable);
// VUID-VkMemoryAllocateInfo-pNext-00639
// Guaranteed because we always create a dedicated allocation
let external_memory_handle_types = ExternalMemoryHandleTypes::OPAQUE_FD;
let raw_image = RawImage::new(
allocator.device().clone(),
ImageCreateInfo {
flags: ImageCreateFlags::MUTABLE_FORMAT,
dimensions: ImageDimensions::Dim2d {
width: dimensions[0],
height: dimensions[1],
array_layers,
},
format: Some(format),
samples,
usage,
external_memory_handle_types,
..Default::default()
},
)?;
let requirements = raw_image.memory_requirements()[0];
let memory_type_index = allocator
.find_memory_type_index(
requirements.memory_type_bits,
MemoryUsage::DeviceOnly.into(),
)
.expect("failed to find a suitable memory type");
match unsafe {
allocator.allocate_dedicated_unchecked(
memory_type_index,
requirements.layout.size(),
Some(DedicatedAllocation::Image(&raw_image)),
external_memory_handle_types,
)
} {
Ok(alloc) => {
debug_assert!(is_aligned(alloc.offset(), requirements.layout.alignment()));
debug_assert!(alloc.size() == requirements.layout.size());
let inner = Arc::new(unsafe {
raw_image
.bind_memory_unchecked([alloc])
.map_err(|(err, _, _)| err)?
});
Ok(Arc::new(AttachmentImage {
inner,
attachment_layout: if is_depth_stencil {
ImageLayout::DepthStencilAttachmentOptimal
} else {
ImageLayout::ColorAttachmentOptimal
},
layout_initialized: AtomicBool::new(false),
}))
}
Err(err) => Err(err.into()),
}
}
/// Exports posix file descriptor for the allocated memory.
/// Requires `khr_external_memory_fd` and `khr_external_memory` extensions to be loaded.
#[inline]
pub fn export_posix_fd(&self) -> Result<File, DeviceMemoryError> {
let allocation = match self.inner.memory() {
ImageMemory::Normal(a) => &a[0],
_ => unreachable!(),
};
allocation
.device_memory()
.export_fd(ExternalMemoryHandleType::OpaqueFd)
}
/// Return the size of the allocated memory (used e.g. with cuda).
#[inline]
pub fn mem_size(&self) -> DeviceSize {
let allocation = match self.inner.memory() {
ImageMemory::Normal(a) => &a[0],
_ => unreachable!(),
};
allocation.device_memory().allocation_size()
}
}
unsafe impl ImageAccess for AttachmentImage {
#[inline]
fn inner(&self) -> ImageInner<'_> {
ImageInner {
image: &self.inner,
first_layer: 0,
num_layers: self.inner.dimensions().array_layers(),
first_mipmap_level: 0,
num_mipmap_levels: 1,
}
}
#[inline]
fn initial_layout_requirement(&self) -> ImageLayout {
self.attachment_layout
}
#[inline]
fn final_layout_requirement(&self) -> ImageLayout {
self.attachment_layout
}
#[inline]
fn descriptor_layouts(&self) -> Option<ImageDescriptorLayouts> {
Some(ImageDescriptorLayouts {
storage_image: ImageLayout::General,
combined_image_sampler: ImageLayout::ShaderReadOnlyOptimal,
sampled_image: ImageLayout::ShaderReadOnlyOptimal,
input_attachment: ImageLayout::ShaderReadOnlyOptimal,
})
}
#[inline]
unsafe fn layout_initialized(&self) {
self.layout_initialized.store(true, Ordering::SeqCst);
}
#[inline]
fn is_layout_initialized(&self) -> bool {
self.layout_initialized.load(Ordering::SeqCst)
}
}
unsafe impl DeviceOwned for AttachmentImage {
#[inline]
fn device(&self) -> &Arc<Device> {
self.inner.device()
}
}
unsafe impl<P> ImageContent<P> for AttachmentImage {
fn matches_format(&self) -> bool {
true // FIXME:
}
}
impl PartialEq for AttachmentImage {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner() == other.inner()
}
}
impl Eq for AttachmentImage {}
impl Hash for AttachmentImage {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner().hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::allocator::StandardMemoryAllocator;
#[test]
fn create_regular() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = StandardMemoryAllocator::new_default(device);
let _img =
AttachmentImage::new(&memory_allocator, [32, 32], Format::R8G8B8A8_UNORM).unwrap();
}
#[test]
fn create_transient() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = StandardMemoryAllocator::new_default(device);
let _img = AttachmentImage::transient(&memory_allocator, [32, 32], Format::R8G8B8A8_UNORM)
.unwrap();
}
#[test]
fn d16_unorm_always_supported() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = StandardMemoryAllocator::new_default(device);
let _img = AttachmentImage::new(&memory_allocator, [32, 32], Format::D16_UNORM).unwrap();
}
}