use super::{DedicatedAllocation, DedicatedTo};
use crate::{
device::{Device, DeviceOwned},
macros::{vulkan_bitflags, vulkan_enum},
DeviceSize, OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
};
use std::{
error::Error,
ffi::c_void,
fmt::{Display, Error as FmtError, Formatter},
fs::File,
mem::MaybeUninit,
num::NonZeroU64,
ops::Range,
ptr, slice,
sync::{atomic::Ordering, Arc},
};
#[derive(Debug)]
pub struct DeviceMemory {
handle: ash::vk::DeviceMemory,
device: Arc<Device>,
id: NonZeroU64,
allocation_size: DeviceSize,
memory_type_index: u32,
dedicated_to: Option<DedicatedTo>,
export_handle_types: ExternalMemoryHandleTypes,
imported_handle_type: Option<ExternalMemoryHandleType>,
flags: MemoryAllocateFlags,
}
impl DeviceMemory {
#[inline]
pub fn allocate(
device: Arc<Device>,
mut allocate_info: MemoryAllocateInfo<'_>,
) -> Result<Self, DeviceMemoryError> {
Self::validate(&device, &mut allocate_info, None)?;
unsafe { Self::allocate_unchecked(device, allocate_info, None) }.map_err(Into::into)
}
#[inline]
pub unsafe fn from_handle(
device: Arc<Device>,
handle: ash::vk::DeviceMemory,
allocate_info: MemoryAllocateInfo<'_>,
) -> Self {
let MemoryAllocateInfo {
allocation_size,
memory_type_index,
dedicated_allocation,
export_handle_types,
flags,
_ne: _,
} = allocate_info;
DeviceMemory {
handle,
device,
id: Self::next_id(),
allocation_size,
memory_type_index,
dedicated_to: dedicated_allocation.map(Into::into),
export_handle_types,
imported_handle_type: None,
flags,
}
}
#[inline]
pub unsafe fn import(
device: Arc<Device>,
mut allocate_info: MemoryAllocateInfo<'_>,
import_info: MemoryImportInfo,
) -> Result<Self, DeviceMemoryError> {
Self::validate(&device, &mut allocate_info, Some(&import_info))?;
Self::allocate_unchecked(device, allocate_info, Some(import_info)).map_err(Into::into)
}
#[inline(never)]
fn validate(
device: &Device,
allocate_info: &mut MemoryAllocateInfo<'_>,
import_info: Option<&MemoryImportInfo>,
) -> Result<(), DeviceMemoryError> {
let &mut MemoryAllocateInfo {
allocation_size,
memory_type_index,
ref mut dedicated_allocation,
export_handle_types,
flags,
_ne: _,
} = allocate_info;
if !(device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_dedicated_allocation)
{
*dedicated_allocation = None;
}
let memory_properties = device.physical_device().memory_properties();
let memory_type = memory_properties
.memory_types
.get(memory_type_index as usize)
.ok_or(DeviceMemoryError::MemoryTypeIndexOutOfRange {
memory_type_index,
memory_type_count: memory_properties.memory_types.len() as u32,
})?;
if memory_type.property_flags.protected && !device.enabled_features().protected_memory {
return Err(DeviceMemoryError::RequirementNotMet {
required_for: "`allocate_info.memory_type_index` refers to a memory type where \
`property_flags.protected` is set",
requires_one_of: RequiresOneOf {
features: &["protected_memory"],
..Default::default()
},
});
}
assert!(allocation_size != 0);
let heap_size = memory_properties.memory_heaps[memory_type.heap_index as usize].size;
if heap_size != 0 && allocation_size > heap_size {
return Err(DeviceMemoryError::MemoryTypeHeapSizeExceeded {
allocation_size,
heap_size,
});
}
if memory_type.property_flags.device_coherent
&& !device.enabled_features().device_coherent_memory
{
return Err(DeviceMemoryError::RequirementNotMet {
required_for: "`allocate_info.memory_type_index` refers to a memory type where \
`property_flags.device_coherent` is set",
requires_one_of: RequiresOneOf {
features: &["device_coherent_memory"],
..Default::default()
},
});
}
if let Some(dedicated_allocation) = dedicated_allocation {
match dedicated_allocation {
DedicatedAllocation::Buffer(buffer) => {
assert_eq!(device, buffer.device().as_ref());
let required_size = buffer.memory_requirements().size;
if allocation_size != required_size {
return Err(DeviceMemoryError::DedicatedAllocationSizeMismatch {
allocation_size,
required_size,
});
}
}
DedicatedAllocation::Image(image) => {
assert_eq!(device, image.device().as_ref());
let required_size = image.memory_requirements()[0].size;
if allocation_size != required_size {
return Err(DeviceMemoryError::DedicatedAllocationSizeMismatch {
allocation_size,
required_size,
});
}
}
}
}
if !export_handle_types.is_empty() {
if !(device.api_version() >= Version::V1_1
|| device.enabled_extensions().khr_external_memory)
{
return Err(DeviceMemoryError::RequirementNotMet {
required_for: "`allocate_info.export_handle_types` is not empty",
requires_one_of: RequiresOneOf {
api_version: Some(Version::V1_1),
device_extensions: &["khr_external_memory"],
..Default::default()
},
});
}
export_handle_types.validate_device(device)?;
}
if let Some(import_info) = import_info {
match *import_info {
MemoryImportInfo::Fd {
#[cfg(unix)]
handle_type,
#[cfg(not(unix))]
handle_type: _,
file: _,
} => {
if !device.enabled_extensions().khr_external_memory_fd {
return Err(DeviceMemoryError::RequirementNotMet {
required_for:
"`allocate_info.import_info` is `Some(MemoryImportInfo::Fd)`",
requires_one_of: RequiresOneOf {
device_extensions: &["khr_external_memory_fd"],
..Default::default()
},
});
}
#[cfg(not(unix))]
unreachable!(
"`khr_external_memory_fd` was somehow enabled on a non-Unix system"
);
#[cfg(unix)]
{
handle_type.validate_device(device)?;
match handle_type {
ExternalMemoryHandleType::OpaqueFd => {
}
ExternalMemoryHandleType::DmaBuf => {}
_ => {
return Err(DeviceMemoryError::ImportFdHandleTypeNotSupported {
handle_type,
})
}
}
}
}
MemoryImportInfo::Win32 {
#[cfg(windows)]
handle_type,
#[cfg(not(windows))]
handle_type: _,
handle: _,
} => {
if !device.enabled_extensions().khr_external_memory_win32 {
return Err(DeviceMemoryError::RequirementNotMet {
required_for:
"`allocate_info.import_info` is `Some(MemoryImportInfo::Win32)`",
requires_one_of: RequiresOneOf {
device_extensions: &["khr_external_memory_win32"],
..Default::default()
},
});
}
#[cfg(not(windows))]
unreachable!(
"`khr_external_memory_win32` was somehow enabled on a non-Windows system"
);
#[cfg(windows)]
{
handle_type.validate_device(device)?;
match handle_type {
ExternalMemoryHandleType::OpaqueWin32
| ExternalMemoryHandleType::OpaqueWin32Kmt => {
}
_ => {
return Err(DeviceMemoryError::ImportWin32HandleTypeNotSupported {
handle_type,
})
}
}
}
}
}
}
if !flags.is_empty()
&& device.physical_device().api_version() < Version::V1_1
&& !device.enabled_extensions().khr_device_group
{
return Err(DeviceMemoryError::RequirementNotMet {
required_for: "`allocate_info.flags` is not empty",
requires_one_of: RequiresOneOf {
api_version: Some(Version::V1_1),
device_extensions: &["khr_device_group"],
..Default::default()
},
});
}
if flags.device_address {
if !device.enabled_features().buffer_device_address {
return Err(DeviceMemoryError::RequirementNotMet {
required_for: "`allocate_info.flags.device_address` is `true`",
requires_one_of: RequiresOneOf {
features: &["buffer_device_address"],
..Default::default()
},
});
}
if device.enabled_extensions().ext_buffer_device_address {
return Err(DeviceMemoryError::RequirementNotMet {
required_for: "`allocate_info.flags.device_address` is `true`",
requires_one_of: RequiresOneOf {
api_version: Some(Version::V1_2),
device_extensions: &["khr_buffer_device_address"],
..Default::default()
},
});
}
}
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline(never)]
pub unsafe fn allocate_unchecked(
device: Arc<Device>,
allocate_info: MemoryAllocateInfo<'_>,
import_info: Option<MemoryImportInfo>,
) -> Result<Self, VulkanError> {
let MemoryAllocateInfo {
allocation_size,
memory_type_index,
dedicated_allocation,
export_handle_types,
flags,
_ne: _,
} = allocate_info;
let mut allocate_info = ash::vk::MemoryAllocateInfo::builder()
.allocation_size(allocation_size)
.memory_type_index(memory_type_index);
let mut dedicated_allocate_info =
dedicated_allocation.map(|dedicated_allocation| match dedicated_allocation {
DedicatedAllocation::Buffer(buffer) => ash::vk::MemoryDedicatedAllocateInfo {
buffer: buffer.handle(),
..Default::default()
},
DedicatedAllocation::Image(image) => ash::vk::MemoryDedicatedAllocateInfo {
image: image.handle(),
..Default::default()
},
});
if let Some(info) = dedicated_allocate_info.as_mut() {
allocate_info = allocate_info.push_next(info);
}
let mut export_allocate_info = if !export_handle_types.is_empty() {
Some(ash::vk::ExportMemoryAllocateInfo {
handle_types: export_handle_types.into(),
..Default::default()
})
} else {
None
};
if let Some(info) = export_allocate_info.as_mut() {
allocate_info = allocate_info.push_next(info);
}
let imported_handle_type = import_info.as_ref().map(|import_info| match import_info {
MemoryImportInfo::Fd { handle_type, .. } => *handle_type,
MemoryImportInfo::Win32 { handle_type, .. } => *handle_type,
});
#[cfg(unix)]
let mut import_fd_info = match import_info {
Some(MemoryImportInfo::Fd { handle_type, file }) => {
use std::os::unix::io::IntoRawFd;
Some(ash::vk::ImportMemoryFdInfoKHR {
handle_type: handle_type.into(),
fd: file.into_raw_fd(),
..Default::default()
})
}
_ => None,
};
#[cfg(unix)]
if let Some(info) = import_fd_info.as_mut() {
allocate_info = allocate_info.push_next(info);
}
#[cfg(windows)]
let mut import_win32_handle_info = match import_info {
Some(MemoryImportInfo::Win32 {
handle_type,
handle,
}) => Some(ash::vk::ImportMemoryWin32HandleInfoKHR {
handle_type: handle_type.into(),
handle,
..Default::default()
}),
_ => None,
};
#[cfg(windows)]
if let Some(info) = import_win32_handle_info.as_mut() {
allocate_info = allocate_info.push_next(info);
}
let mut flags_info = ash::vk::MemoryAllocateFlagsInfo {
flags: flags.into(),
..Default::default()
};
if !flags.is_empty() {
allocate_info = allocate_info.push_next(&mut flags_info);
}
let max_allocations = device
.physical_device()
.properties()
.max_memory_allocation_count;
device
.allocation_count
.fetch_update(Ordering::Acquire, Ordering::Relaxed, move |count| {
(count < max_allocations).then_some(count + 1)
})
.map_err(|_| VulkanError::TooManyObjects)?;
let handle = {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.allocate_memory)(
device.handle(),
&allocate_info.build(),
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(|e| {
device.allocation_count.fetch_sub(1, Ordering::Release);
VulkanError::from(e)
})?;
output.assume_init()
};
Ok(DeviceMemory {
handle,
device,
id: Self::next_id(),
allocation_size,
memory_type_index,
dedicated_to: dedicated_allocation.map(Into::into),
export_handle_types,
imported_handle_type,
flags,
})
}
#[inline]
pub fn memory_type_index(&self) -> u32 {
self.memory_type_index
}
#[inline]
pub fn allocation_size(&self) -> DeviceSize {
self.allocation_size
}
#[inline]
pub fn is_dedicated(&self) -> bool {
self.dedicated_to.is_some()
}
pub(crate) fn dedicated_to(&self) -> Option<DedicatedTo> {
self.dedicated_to
}
#[inline]
pub fn export_handle_types(&self) -> ExternalMemoryHandleTypes {
self.export_handle_types
}
#[inline]
pub fn imported_handle_type(&self) -> Option<ExternalMemoryHandleType> {
self.imported_handle_type
}
#[inline]
pub fn flags(&self) -> MemoryAllocateFlags {
self.flags
}
#[inline]
pub fn commitment(&self) -> Result<DeviceSize, DeviceMemoryError> {
self.validate_commitment()?;
unsafe { Ok(self.commitment_unchecked()) }
}
fn validate_commitment(&self) -> Result<(), DeviceMemoryError> {
let memory_type = &self
.device
.physical_device()
.memory_properties()
.memory_types[self.memory_type_index as usize];
if !memory_type.property_flags.lazily_allocated {
return Err(DeviceMemoryError::NotLazilyAllocated);
}
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub unsafe fn commitment_unchecked(&self) -> DeviceSize {
let mut output: DeviceSize = 0;
let fns = self.device.fns();
(fns.v1_0.get_device_memory_commitment)(self.device.handle(), self.handle, &mut output);
output
}
#[inline]
pub fn export_fd(
&self,
handle_type: ExternalMemoryHandleType,
) -> Result<std::fs::File, DeviceMemoryError> {
handle_type.validate_device(&self.device)?;
if !matches!(
handle_type,
ExternalMemoryHandleType::OpaqueFd | ExternalMemoryHandleType::DmaBuf
) {
return Err(DeviceMemoryError::HandleTypeNotSupported { handle_type });
}
if !ash::vk::ExternalMemoryHandleTypeFlags::from(self.export_handle_types)
.intersects(ash::vk::ExternalMemoryHandleTypeFlags::from(handle_type))
{
return Err(DeviceMemoryError::HandleTypeNotSupported { handle_type });
}
debug_assert!(self.device().enabled_extensions().khr_external_memory_fd);
#[cfg(not(unix))]
unreachable!("`khr_external_memory_fd` was somehow enabled on a non-Unix system");
#[cfg(unix)]
{
use std::os::unix::io::FromRawFd;
let fd = unsafe {
let fns = self.device.fns();
let info = ash::vk::MemoryGetFdInfoKHR {
memory: self.handle,
handle_type: handle_type.into(),
..Default::default()
};
let mut output = MaybeUninit::uninit();
(fns.khr_external_memory_fd.get_memory_fd_khr)(
self.device.handle(),
&info,
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
let file = unsafe { std::fs::File::from_raw_fd(fd) };
Ok(file)
}
}
}
impl Drop for DeviceMemory {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
(fns.v1_0.free_memory)(self.device.handle(), self.handle, ptr::null());
self.device.allocation_count.fetch_sub(1, Ordering::Release);
}
}
}
unsafe impl VulkanObject for DeviceMemory {
type Handle = ash::vk::DeviceMemory;
#[inline]
fn handle(&self) -> Self::Handle {
self.handle
}
}
unsafe impl DeviceOwned for DeviceMemory {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
crate::impl_id_counter!(DeviceMemory);
#[derive(Clone, Debug)]
pub struct MemoryAllocateInfo<'d> {
pub allocation_size: DeviceSize,
pub memory_type_index: u32,
pub dedicated_allocation: Option<DedicatedAllocation<'d>>,
pub export_handle_types: ExternalMemoryHandleTypes,
pub flags: MemoryAllocateFlags,
pub _ne: crate::NonExhaustive,
}
impl Default for MemoryAllocateInfo<'static> {
#[inline]
fn default() -> Self {
Self {
allocation_size: 0,
memory_type_index: u32::MAX,
dedicated_allocation: None,
export_handle_types: ExternalMemoryHandleTypes::empty(),
flags: MemoryAllocateFlags::empty(),
_ne: crate::NonExhaustive(()),
}
}
}
impl<'d> MemoryAllocateInfo<'d> {
#[inline]
pub fn dedicated_allocation(dedicated_allocation: DedicatedAllocation<'d>) -> Self {
Self {
allocation_size: 0,
memory_type_index: u32::MAX,
dedicated_allocation: Some(dedicated_allocation),
export_handle_types: ExternalMemoryHandleTypes::empty(),
flags: MemoryAllocateFlags::empty(),
_ne: crate::NonExhaustive(()),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum MemoryImportInfo {
Fd {
handle_type: ExternalMemoryHandleType,
file: File,
},
Win32 {
handle_type: ExternalMemoryHandleType,
handle: ash::vk::HANDLE,
},
}
vulkan_enum! {
#[non_exhaustive]
ExternalMemoryHandleType = ExternalMemoryHandleTypeFlags(u32);
OpaqueFd = OPAQUE_FD,
OpaqueWin32 = OPAQUE_WIN32,
OpaqueWin32Kmt = OPAQUE_WIN32_KMT,
D3D11Texture = D3D11_TEXTURE,
D3D11TextureKmt = D3D11_TEXTURE_KMT,
D3D12Heap = D3D12_HEAP,
D3D12Resource = D3D12_RESOURCE,
DmaBuf = DMA_BUF_EXT {
device_extensions: [ext_external_memory_dma_buf],
},
AndroidHardwareBuffer = ANDROID_HARDWARE_BUFFER_ANDROID {
device_extensions: [android_external_memory_android_hardware_buffer],
},
HostAllocation = HOST_ALLOCATION_EXT {
device_extensions: [ext_external_memory_host],
},
HostMappedForeignMemory = HOST_MAPPED_FOREIGN_MEMORY_EXT {
device_extensions: [ext_external_memory_host],
},
ZirconVmo = ZIRCON_VMO_FUCHSIA {
device_extensions: [fuchsia_external_memory],
},
RdmaAddress = RDMA_ADDRESS_NV {
device_extensions: [nv_external_memory_rdma],
},
}
vulkan_bitflags! {
#[non_exhaustive]
ExternalMemoryHandleTypes = ExternalMemoryHandleTypeFlags(u32);
opaque_fd = OPAQUE_FD,
opaque_win32 = OPAQUE_WIN32,
opaque_win32_kmt = OPAQUE_WIN32_KMT,
d3d11_texture = D3D11_TEXTURE,
d3d11_texture_kmt = D3D11_TEXTURE_KMT,
d3d12_heap = D3D12_HEAP,
d3d12_resource = D3D12_RESOURCE,
dma_buf = DMA_BUF_EXT {
device_extensions: [ext_external_memory_dma_buf],
},
android_hardware_buffer = ANDROID_HARDWARE_BUFFER_ANDROID {
device_extensions: [android_external_memory_android_hardware_buffer],
},
host_allocation = HOST_ALLOCATION_EXT {
device_extensions: [ext_external_memory_host],
},
host_mapped_foreign_memory = HOST_MAPPED_FOREIGN_MEMORY_EXT {
device_extensions: [ext_external_memory_host],
},
zircon_vmo = ZIRCON_VMO_FUCHSIA {
device_extensions: [fuchsia_external_memory],
},
rdma_address = RDMA_ADDRESS_NV {
device_extensions: [nv_external_memory_rdma],
},
}
impl From<ExternalMemoryHandleType> for ExternalMemoryHandleTypes {
#[inline]
fn from(val: ExternalMemoryHandleType) -> Self {
let mut result = Self::empty();
match val {
ExternalMemoryHandleType::OpaqueFd => result.opaque_fd = true,
ExternalMemoryHandleType::OpaqueWin32 => result.opaque_win32 = true,
ExternalMemoryHandleType::OpaqueWin32Kmt => result.opaque_win32_kmt = true,
ExternalMemoryHandleType::D3D11Texture => result.d3d11_texture = true,
ExternalMemoryHandleType::D3D11TextureKmt => result.d3d11_texture_kmt = true,
ExternalMemoryHandleType::D3D12Heap => result.d3d12_heap = true,
ExternalMemoryHandleType::D3D12Resource => result.d3d12_resource = true,
ExternalMemoryHandleType::DmaBuf => result.dma_buf = true,
ExternalMemoryHandleType::AndroidHardwareBuffer => {
result.android_hardware_buffer = true
}
ExternalMemoryHandleType::HostAllocation => result.host_allocation = true,
ExternalMemoryHandleType::HostMappedForeignMemory => {
result.host_mapped_foreign_memory = true
}
ExternalMemoryHandleType::ZirconVmo => result.zircon_vmo = true,
ExternalMemoryHandleType::RdmaAddress => result.rdma_address = true,
}
result
}
}
impl ExternalMemoryHandleTypes {
#[inline]
pub fn iter(&self) -> impl Iterator<Item = ExternalMemoryHandleType> {
let ExternalMemoryHandleTypes {
opaque_fd,
opaque_win32,
opaque_win32_kmt,
d3d11_texture,
d3d11_texture_kmt,
d3d12_heap,
d3d12_resource,
dma_buf,
android_hardware_buffer,
host_allocation,
host_mapped_foreign_memory,
zircon_vmo,
rdma_address,
_ne: _,
} = *self;
[
opaque_fd.then_some(ExternalMemoryHandleType::OpaqueFd),
opaque_win32.then_some(ExternalMemoryHandleType::OpaqueWin32),
opaque_win32_kmt.then_some(ExternalMemoryHandleType::OpaqueWin32Kmt),
d3d11_texture.then_some(ExternalMemoryHandleType::D3D11Texture),
d3d11_texture_kmt.then_some(ExternalMemoryHandleType::D3D11TextureKmt),
d3d12_heap.then_some(ExternalMemoryHandleType::D3D12Heap),
d3d12_resource.then_some(ExternalMemoryHandleType::D3D12Resource),
dma_buf.then_some(ExternalMemoryHandleType::DmaBuf),
android_hardware_buffer.then_some(ExternalMemoryHandleType::AndroidHardwareBuffer),
host_allocation.then_some(ExternalMemoryHandleType::HostAllocation),
host_mapped_foreign_memory.then_some(ExternalMemoryHandleType::HostMappedForeignMemory),
zircon_vmo.then_some(ExternalMemoryHandleType::HostMappedForeignMemory),
rdma_address.then_some(ExternalMemoryHandleType::HostMappedForeignMemory),
]
.into_iter()
.flatten()
}
}
vulkan_bitflags! {
#[non_exhaustive]
MemoryAllocateFlags = MemoryAllocateFlags(u32);
device_address = DEVICE_ADDRESS,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeviceMemoryError {
OomError(OomError),
TooManyObjects,
MemoryMapError(MemoryMapError),
RequirementNotMet {
required_for: &'static str,
requires_one_of: RequiresOneOf,
},
DedicatedAllocationSizeMismatch {
allocation_size: DeviceSize,
required_size: DeviceSize,
},
HandleTypeNotSupported {
handle_type: ExternalMemoryHandleType,
},
ImportFdHandleTypeNotSupported {
handle_type: ExternalMemoryHandleType,
},
ImportWin32HandleTypeNotSupported {
handle_type: ExternalMemoryHandleType,
},
MemoryTypeHeapSizeExceeded {
allocation_size: DeviceSize,
heap_size: DeviceSize,
},
MemoryTypeIndexOutOfRange {
memory_type_index: u32,
memory_type_count: u32,
},
NotLazilyAllocated,
SpecViolation(u32),
ImplicitSpecViolation(&'static str),
}
impl Error for DeviceMemoryError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::OomError(err) => Some(err),
Self::MemoryMapError(err) => Some(err),
_ => None,
}
}
}
impl Display for DeviceMemoryError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::OomError(_) => write!(f, "not enough memory available"),
Self::TooManyObjects => {
write!(f, "the maximum number of allocations has been exceeded")
}
Self::MemoryMapError(_) => write!(f, "error occurred when mapping the memory"),
Self::RequirementNotMet {
required_for,
requires_one_of,
} => write!(
f,
"a requirement was not met for: {}; requires one of: {}",
required_for, requires_one_of,
),
Self::DedicatedAllocationSizeMismatch {
allocation_size,
required_size,
} => write!(
f,
"`dedicated_allocation` was `Some`, but the provided `allocation_size` ({}) was \
different from the required size of the buffer or image ({})",
allocation_size, required_size,
),
Self::HandleTypeNotSupported { handle_type } => write!(
f,
"the requested export handle type ({:?}) is not supported for this operation, or \
was not provided in `export_handle_types` when allocating the memory",
handle_type,
),
Self::ImportFdHandleTypeNotSupported { handle_type } => write!(
f,
"the provided `MemoryImportInfo::Fd::handle_type` ({:?}) is not supported for file \
descriptors",
handle_type,
),
Self::ImportWin32HandleTypeNotSupported { handle_type } => write!(
f,
"the provided `MemoryImportInfo::Win32::handle_type` ({:?}) is not supported",
handle_type,
),
Self::MemoryTypeHeapSizeExceeded {
allocation_size,
heap_size,
} => write!(
f,
"the provided `allocation_size` ({}) was greater than the memory type's heap size \
({})",
allocation_size, heap_size,
),
Self::MemoryTypeIndexOutOfRange {
memory_type_index,
memory_type_count,
} => write!(
f,
"the provided `memory_type_index` ({}) was not less than the number of memory \
types in the physical device ({})",
memory_type_index, memory_type_count,
),
Self::NotLazilyAllocated => write!(
f,
"the memory type from which this memory was allocated does not have the \
`lazily_allocated` flag set",
),
Self::SpecViolation(u) => {
write!(f, "valid usage ID check {} failed", u)
}
Self::ImplicitSpecViolation(e) => {
write!(f, "Implicit spec violation failed {}", e)
}
}
}
}
impl From<VulkanError> for DeviceMemoryError {
fn from(err: VulkanError) -> Self {
match err {
e @ VulkanError::OutOfHostMemory | e @ VulkanError::OutOfDeviceMemory => {
Self::OomError(e.into())
}
VulkanError::TooManyObjects => Self::TooManyObjects,
_ => panic!("unexpected error: {:?}", err),
}
}
}
impl From<OomError> for DeviceMemoryError {
fn from(err: OomError) -> Self {
Self::OomError(err)
}
}
impl From<MemoryMapError> for DeviceMemoryError {
fn from(err: MemoryMapError) -> Self {
Self::MemoryMapError(err)
}
}
impl From<RequirementNotMet> for DeviceMemoryError {
fn from(err: RequirementNotMet) -> Self {
Self::RequirementNotMet {
required_for: err.required_for,
requires_one_of: err.requires_one_of,
}
}
}
#[derive(Debug)]
pub struct MappedDeviceMemory {
memory: DeviceMemory,
pointer: *mut c_void, range: Range<DeviceSize>,
atom_size: DeviceSize,
coherent: bool,
}
impl MappedDeviceMemory {
pub fn new(memory: DeviceMemory, range: Range<DeviceSize>) -> Result<Self, MemoryMapError> {
assert!(!range.is_empty());
if range.end > memory.allocation_size {
return Err(MemoryMapError::OutOfRange {
provided_range: range,
allowed_range: 0..memory.allocation_size,
});
}
let device = memory.device();
let memory_type = &device.physical_device().memory_properties().memory_types
[memory.memory_type_index() as usize];
if !memory_type.property_flags.host_visible {
return Err(MemoryMapError::NotHostVisible);
}
let coherent = memory_type.property_flags.host_coherent;
let atom_size = device.physical_device().properties().non_coherent_atom_size;
if !coherent
&& (range.start % atom_size != 0
|| (range.end % atom_size != 0 && range.end != memory.allocation_size))
{
return Err(MemoryMapError::RangeNotAlignedToAtomSize { range, atom_size });
}
let pointer = unsafe {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.map_memory)(
device.handle(),
memory.handle,
range.start,
range.end - range.start,
ash::vk::MemoryMapFlags::empty(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(MappedDeviceMemory {
memory,
pointer,
range,
atom_size,
coherent,
})
}
#[inline]
pub fn unmap(self) -> DeviceMemory {
unsafe {
let device = self.memory.device();
let fns = device.fns();
(fns.v1_0.unmap_memory)(device.handle(), self.memory.handle);
}
self.memory
}
#[inline]
pub unsafe fn invalidate_range(&self, range: Range<DeviceSize>) -> Result<(), MemoryMapError> {
if self.coherent {
return Ok(());
}
self.check_range(range.clone())?;
let range = ash::vk::MappedMemoryRange {
memory: self.memory.handle(),
offset: range.start,
size: range.end - range.start,
..Default::default()
};
let fns = self.memory.device().fns();
(fns.v1_0.invalidate_mapped_memory_ranges)(self.memory.device().handle(), 1, &range)
.result()
.map_err(VulkanError::from)?;
Ok(())
}
#[inline]
pub unsafe fn flush_range(&self, range: Range<DeviceSize>) -> Result<(), MemoryMapError> {
self.check_range(range.clone())?;
if self.coherent {
return Ok(());
}
let range = ash::vk::MappedMemoryRange {
memory: self.memory.handle(),
offset: range.start,
size: range.end - range.start,
..Default::default()
};
let fns = self.device().fns();
(fns.v1_0.flush_mapped_memory_ranges)(self.memory.device().handle(), 1, &range)
.result()
.map_err(VulkanError::from)?;
Ok(())
}
#[inline]
pub unsafe fn read(&self, range: Range<DeviceSize>) -> Result<&[u8], MemoryMapError> {
self.check_range(range.clone())?;
let bytes = slice::from_raw_parts(
self.pointer.add((range.start - self.range.start) as usize) as *const u8,
(range.end - range.start) as usize,
);
Ok(bytes)
}
#[inline]
pub unsafe fn write(&self, range: Range<DeviceSize>) -> Result<&mut [u8], MemoryMapError> {
self.check_range(range.clone())?;
let bytes = slice::from_raw_parts_mut(
self.pointer.add((range.start - self.range.start) as usize) as *mut u8,
(range.end - range.start) as usize,
);
Ok(bytes)
}
#[inline]
fn check_range(&self, range: Range<DeviceSize>) -> Result<(), MemoryMapError> {
assert!(!range.is_empty());
if range.start < self.range.start || range.end > self.range.end {
return Err(MemoryMapError::OutOfRange {
provided_range: range,
allowed_range: self.range.clone(),
});
}
if !self.coherent {
if range.start % self.atom_size != 0
|| (range.end % self.atom_size != 0 && range.end != self.memory.allocation_size)
{
return Err(MemoryMapError::RangeNotAlignedToAtomSize {
range,
atom_size: self.atom_size,
});
}
}
Ok(())
}
}
impl AsRef<DeviceMemory> for MappedDeviceMemory {
#[inline]
fn as_ref(&self) -> &DeviceMemory {
&self.memory
}
}
impl AsMut<DeviceMemory> for MappedDeviceMemory {
#[inline]
fn as_mut(&mut self) -> &mut DeviceMemory {
&mut self.memory
}
}
unsafe impl DeviceOwned for MappedDeviceMemory {
#[inline]
fn device(&self) -> &Arc<Device> {
self.memory.device()
}
}
unsafe impl Send for MappedDeviceMemory {}
unsafe impl Sync for MappedDeviceMemory {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MemoryMapError {
OomError(OomError),
MemoryMapFailed,
NotHostVisible,
OutOfRange {
provided_range: Range<DeviceSize>,
allowed_range: Range<DeviceSize>,
},
RangeNotAlignedToAtomSize {
range: Range<DeviceSize>,
atom_size: DeviceSize,
},
}
impl Error for MemoryMapError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::OomError(err) => Some(err),
_ => None,
}
}
}
impl Display for MemoryMapError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::OomError(_) => write!(f, "not enough memory available"),
Self::MemoryMapFailed => write!(f, "memory map failed"),
Self::NotHostVisible => {
write!(f, "tried to map memory whose type is not host-visible")
}
Self::OutOfRange {
provided_range,
allowed_range,
} => write!(
f,
"the specified `range` ({:?}) was not contained within the allocated or mapped \
memory range ({:?})",
provided_range, allowed_range,
),
Self::RangeNotAlignedToAtomSize { range, atom_size } => write!(
f,
"the memory is not host-coherent, and the specified `range` bounds ({:?}) are not \
a multiple of the `non_coherent_atom_size` device property ({})",
range, atom_size,
),
}
}
}
impl From<VulkanError> for MemoryMapError {
fn from(err: VulkanError) -> Self {
match err {
e @ VulkanError::OutOfHostMemory | e @ VulkanError::OutOfDeviceMemory => {
Self::OomError(e.into())
}
VulkanError::MemoryMapFailed => Self::MemoryMapFailed,
_ => panic!("unexpected error: {:?}", err),
}
}
}
impl From<OomError> for MemoryMapError {
fn from(err: OomError) -> Self {
Self::OomError(err)
}
}
#[cfg(test)]
mod tests {
use super::MemoryAllocateInfo;
use crate::{
memory::{DeviceMemory, DeviceMemoryError},
OomError,
};
#[test]
fn create() {
let (device, _) = gfx_dev_and_queue!();
let _ = DeviceMemory::allocate(
device,
MemoryAllocateInfo {
allocation_size: 256,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
}
#[test]
fn zero_size() {
let (device, _) = gfx_dev_and_queue!();
assert_should_panic!({
let _ = DeviceMemory::allocate(
device.clone(),
MemoryAllocateInfo {
allocation_size: 0,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
});
}
#[test]
#[cfg(target_pointer_width = "64")]
fn oom_single() {
let (device, _) = gfx_dev_and_queue!();
let memory_type_index = device
.physical_device()
.memory_properties()
.memory_types
.iter()
.enumerate()
.find_map(|(i, m)| (!m.property_flags.lazily_allocated).then_some(i as u32))
.unwrap();
match DeviceMemory::allocate(
device,
MemoryAllocateInfo {
allocation_size: 0xffffffffffffffff,
memory_type_index,
..Default::default()
},
) {
Err(DeviceMemoryError::MemoryTypeHeapSizeExceeded { .. }) => (),
_ => panic!(),
}
}
#[test]
#[ignore] fn oom_multi() {
let (device, _) = gfx_dev_and_queue!();
let (memory_type_index, memory_type) = device
.physical_device()
.memory_properties()
.memory_types
.iter()
.enumerate()
.find_map(|(i, m)| (!m.property_flags.lazily_allocated).then_some((i as u32, m)))
.unwrap();
let heap_size = device.physical_device().memory_properties().memory_heaps
[memory_type.heap_index as usize]
.size;
let mut allocs = Vec::new();
for _ in 0..4 {
match DeviceMemory::allocate(
device.clone(),
MemoryAllocateInfo {
allocation_size: heap_size / 3,
memory_type_index,
..Default::default()
},
) {
Err(DeviceMemoryError::OomError(OomError::OutOfDeviceMemory)) => return, Ok(a) => allocs.push(a),
_ => (),
}
}
panic!()
}
#[test]
fn allocation_count() {
let (device, _) = gfx_dev_and_queue!();
assert_eq!(device.allocation_count(), 0);
let _mem1 = DeviceMemory::allocate(
device.clone(),
MemoryAllocateInfo {
allocation_size: 256,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
assert_eq!(device.allocation_count(), 1);
{
let _mem2 = DeviceMemory::allocate(
device.clone(),
MemoryAllocateInfo {
allocation_size: 256,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
assert_eq!(device.allocation_count(), 2);
}
assert_eq!(device.allocation_count(), 1);
}
}