blob: 5f2bf0e8a8f73558d1ac161cdcdc9648d5ea44b6 [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.
use crate::check_errors;
use crate::device::physical::MemoryType;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::memory::Content;
use crate::memory::DedicatedAlloc;
use crate::memory::ExternalMemoryHandleType;
use crate::DeviceSize;
use crate::Error;
use crate::OomError;
use crate::Version;
use crate::VulkanObject;
use std::error;
use std::fmt;
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::fs::File;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::ops::DerefMut;
use std::ops::Range;
use std::os::raw::c_void;
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::ptr;
use std::sync::Arc;
use std::sync::Mutex;
#[repr(C)]
pub struct BaseOutStructure {
pub s_type: i32,
pub p_next: *mut BaseOutStructure,
}
pub(crate) unsafe fn ptr_chain_iter<T>(ptr: &mut T) -> impl Iterator<Item = *mut BaseOutStructure> {
let ptr: *mut BaseOutStructure = ptr as *mut T as _;
(0..).scan(ptr, |p_ptr, _| {
if p_ptr.is_null() {
return None;
}
let n_ptr = (**p_ptr).p_next as *mut BaseOutStructure;
let old = *p_ptr;
*p_ptr = n_ptr;
Some(old)
})
}
pub unsafe trait ExtendsMemoryAllocateInfo {}
unsafe impl ExtendsMemoryAllocateInfo for ash::vk::MemoryDedicatedAllocateInfoKHR {}
unsafe impl ExtendsMemoryAllocateInfo for ash::vk::ExportMemoryAllocateInfo {}
unsafe impl ExtendsMemoryAllocateInfo for ash::vk::ImportMemoryFdInfoKHR {}
/// Represents memory that has been allocated.
///
/// The destructor of `DeviceMemory` automatically frees the memory.
///
/// # Example
///
/// ```
/// use vulkano::memory::DeviceMemory;
///
/// # let device: std::sync::Arc<vulkano::device::Device> = return;
/// let mem_ty = device.physical_device().memory_types().next().unwrap();
///
/// // Allocates 1KB of memory.
/// let memory = DeviceMemory::alloc(device.clone(), mem_ty, 1024).unwrap();
/// ```
pub struct DeviceMemory {
memory: ash::vk::DeviceMemory,
device: Arc<Device>,
size: DeviceSize,
memory_type_index: u32,
handle_types: ExternalMemoryHandleType,
mapped: Mutex<bool>,
}
/// Represents a builder for the device memory object.
///
/// # Example
///
/// ```
/// use vulkano::memory::DeviceMemoryBuilder;
///
/// # let device: std::sync::Arc<vulkano::device::Device> = return;
/// let mem_ty = device.physical_device().memory_types().next().unwrap();
///
/// // Allocates 1KB of memory.
/// let memory = DeviceMemoryBuilder::new(device, mem_ty.id(), 1024).build().unwrap();
/// ```
pub struct DeviceMemoryBuilder<'a> {
device: Arc<Device>,
allocate: ash::vk::MemoryAllocateInfo,
dedicated_info: Option<ash::vk::MemoryDedicatedAllocateInfoKHR>,
export_info: Option<ash::vk::ExportMemoryAllocateInfo>,
import_info: Option<ash::vk::ImportMemoryFdInfoKHR>,
marker: PhantomData<&'a ()>,
}
impl<'a> DeviceMemoryBuilder<'a> {
/// Returns a new `DeviceMemoryBuilder` given the required device, memory type and size fields.
/// Validation of parameters is done when the builder is built.
pub fn new(
device: Arc<Device>,
memory_index: u32,
size: DeviceSize,
) -> DeviceMemoryBuilder<'a> {
let allocate = ash::vk::MemoryAllocateInfo {
allocation_size: size,
memory_type_index: memory_index,
..Default::default()
};
DeviceMemoryBuilder {
device,
allocate,
dedicated_info: None,
export_info: None,
import_info: None,
marker: PhantomData,
}
}
/// Sets an optional field for dedicated allocations in the `DeviceMemoryBuilder`. To maintain
/// backwards compatibility, this function does nothing when dedicated allocation has not been
/// enabled on the device.
///
/// # Panic
///
/// - Panics if the dedicated allocation info has already been set.
pub fn dedicated_info(mut self, dedicated: DedicatedAlloc<'a>) -> DeviceMemoryBuilder {
assert!(self.dedicated_info.is_none());
if !(self.device.api_version() >= Version::V1_1
|| self.device.enabled_extensions().khr_dedicated_allocation)
{
return self;
}
let mut dedicated_info = match dedicated {
DedicatedAlloc::Buffer(buffer) => ash::vk::MemoryDedicatedAllocateInfoKHR {
image: ash::vk::Image::null(),
buffer: buffer.internal_object(),
..Default::default()
},
DedicatedAlloc::Image(image) => ash::vk::MemoryDedicatedAllocateInfoKHR {
image: image.internal_object(),
buffer: ash::vk::Buffer::null(),
..Default::default()
},
DedicatedAlloc::None => return self,
};
self = self.push_next(&mut dedicated_info);
self.dedicated_info = Some(dedicated_info);
self
}
/// Sets an optional field for exportable allocations in the `DeviceMemoryBuilder`.
///
/// # Panic
///
/// - Panics if the export info has already been set.
pub fn export_info(
mut self,
handle_types: ExternalMemoryHandleType,
) -> DeviceMemoryBuilder<'a> {
assert!(self.export_info.is_none());
let mut export_info = ash::vk::ExportMemoryAllocateInfo {
handle_types: handle_types.into(),
..Default::default()
};
self = self.push_next(&mut export_info);
self.export_info = Some(export_info);
self
}
/// Sets an optional field for importable DeviceMemory in the `DeviceMemoryBuilder`.
///
/// # Panic
///
/// - Panics if the import info has already been set.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn import_info(
mut self,
fd: File,
handle_types: ExternalMemoryHandleType,
) -> DeviceMemoryBuilder<'a> {
assert!(self.import_info.is_none());
let mut import_info = ash::vk::ImportMemoryFdInfoKHR {
handle_type: handle_types.into(),
fd: fd.into_raw_fd(),
..Default::default()
};
self = self.push_next(&mut import_info);
self.import_info = Some(import_info);
self
}
// Private function copied shamelessly from Ash.
// https://github.com/MaikKlein/ash/blob/4ba8637d018fec6d6e3a90d7fa47d11c085f6b4a/generator/src/lib.rs
#[allow(unused_assignments)]
fn push_next<T: ExtendsMemoryAllocateInfo>(self, next: &mut T) -> DeviceMemoryBuilder<'a> {
unsafe {
// `next` here can contain a pointer chain. This means that we must correctly
// attach he head to the root and the tail to the rest of the chain
// For example:
//
// next = A -> B
// Before: `Root -> C -> D -> E`
// After: `Root -> A -> B -> C -> D -> E`
// Convert next to our ptr structure
let next_ptr = next as *mut T as *mut BaseOutStructure;
// Previous head (can be null)
let mut prev_head = self.allocate.p_next as *mut BaseOutStructure;
// Retrieve end of next chain
let last_next = ptr_chain_iter(next).last().unwrap();
// Set end of next chain's next to be previous head only if previous head's next'
if !prev_head.is_null() {
(*last_next).p_next = (*prev_head).p_next;
}
// Set next ptr to be first one
prev_head = next_ptr;
}
self
}
/// Creates a `DeviceMemory` object on success, consuming the `DeviceMemoryBuilder`. An error
/// is returned if the requested allocation is too large or if the total number of allocations
/// would exceed per-device limits.
pub fn build(self) -> Result<Arc<DeviceMemory>, DeviceMemoryAllocError> {
if self.allocate.allocation_size == 0 {
return Err(DeviceMemoryAllocError::InvalidSize)?;
}
// VUID-vkAllocateMemory-pAllocateInfo-01714: "pAllocateInfo->memoryTypeIndex must be less
// than VkPhysicalDeviceMemoryProperties::memoryTypeCount as returned by
// vkGetPhysicalDeviceMemoryProperties for the VkPhysicalDevice that device was created
// from."
let memory_type = self
.device
.physical_device()
.memory_type_by_id(self.allocate.memory_type_index)
.ok_or(DeviceMemoryAllocError::SpecViolation(1714))?;
if self.device.physical_device().internal_object()
!= memory_type.physical_device().internal_object()
{
return Err(DeviceMemoryAllocError::SpecViolation(1714));
}
// Note: This check is disabled because MoltenVK doesn't report correct heap sizes yet.
// This check was re-enabled because Mesa aborts if `size` is Very Large.
//
// Conversions won't panic since it's based on `vkDeviceSize`, which is a u64 in the VK
// header. Not sure why we bother with usizes.
// VUID-vkAllocateMemory-pAllocateInfo-01713: "pAllocateInfo->allocationSize must be less than
// or equal to VkPhysicalDeviceMemoryProperties::memoryHeaps[memindex].size where memindex =
// VkPhysicalDeviceMemoryProperties::memoryTypes[pAllocateInfo->memoryTypeIndex].heapIndex as
// returned by vkGetPhysicalDeviceMemoryProperties for the VkPhysicalDevice that device was created
// from".
let reported_heap_size = memory_type.heap().size();
if reported_heap_size != 0 && self.allocate.allocation_size > reported_heap_size {
return Err(DeviceMemoryAllocError::SpecViolation(1713));
}
let mut export_handle_bits = ash::vk::ExternalMemoryHandleTypeFlags::empty();
if self.export_info.is_some() || self.import_info.is_some() {
// TODO: check exportFromImportedHandleTypes
export_handle_bits = match self.export_info {
Some(export_info) => export_info.handle_types,
None => ash::vk::ExternalMemoryHandleTypeFlags::empty(),
};
let import_handle_bits = match self.import_info {
Some(import_info) => import_info.handle_type,
None => ash::vk::ExternalMemoryHandleTypeFlags::empty(),
};
if !(export_handle_bits & ash::vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
.is_empty()
{
if !self.device.enabled_extensions().ext_external_memory_dma_buf {
return Err(DeviceMemoryAllocError::MissingExtension(
"ext_external_memory_dmabuf",
));
};
}
if !(export_handle_bits & ash::vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD).is_empty()
{
if !self.device.enabled_extensions().khr_external_memory_fd {
return Err(DeviceMemoryAllocError::MissingExtension(
"khr_external_memory_fd",
));
}
}
if !(import_handle_bits & ash::vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
.is_empty()
{
if !self.device.enabled_extensions().ext_external_memory_dma_buf {
return Err(DeviceMemoryAllocError::MissingExtension(
"ext_external_memory_dmabuf",
));
}
}
if !(import_handle_bits & ash::vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD).is_empty()
{
if !self.device.enabled_extensions().khr_external_memory_fd {
return Err(DeviceMemoryAllocError::MissingExtension(
"khr_external_memory_fd",
));
}
}
}
let memory = unsafe {
let physical_device = self.device.physical_device();
let mut allocation_count = self
.device
.allocation_count()
.lock()
.expect("Poisoned mutex");
if *allocation_count
>= physical_device
.properties()
.max_memory_allocation_count
{
return Err(DeviceMemoryAllocError::TooManyObjects);
}
let fns = self.device.fns();
let mut output = MaybeUninit::uninit();
check_errors(fns.v1_0.allocate_memory(
self.device.internal_object(),
&self.allocate,
ptr::null(),
output.as_mut_ptr(),
))?;
*allocation_count += 1;
output.assume_init()
};
Ok(Arc::new(DeviceMemory {
memory: memory,
device: self.device,
size: self.allocate.allocation_size,
memory_type_index: self.allocate.memory_type_index,
handle_types: ExternalMemoryHandleType::from(export_handle_bits),
mapped: Mutex::new(false),
}))
}
}
impl DeviceMemory {
/// Allocates a chunk of memory from the device.
///
/// Some platforms may have a limit on the maximum size of a single allocation. For example,
/// certain systems may fail to create allocations with a size greater than or equal to 4GB.
///
/// # Panic
///
/// - Panics if `size` is 0.
/// - Panics if `memory_type` doesn't belong to the same physical device as `device`.
///
#[inline]
pub fn alloc(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
) -> Result<DeviceMemory, DeviceMemoryAllocError> {
let memory = DeviceMemoryBuilder::new(device, memory_type.id(), size).build()?;
// Will never panic because we call the DeviceMemoryBuilder internally, and that only
// returns an atomically refcounted DeviceMemory object on success.
Ok(Arc::try_unwrap(memory).unwrap())
}
/// Same as `alloc`, but allows specifying a resource that will be bound to the memory.
///
/// If a buffer or an image is specified in `resource`, then the returned memory must not be
/// bound to a different buffer or image.
///
/// If the `VK_KHR_dedicated_allocation` extension is enabled on the device, then it will be
/// used by this method. Otherwise the `resource` parameter will be ignored.
#[inline]
pub fn dedicated_alloc(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
resource: DedicatedAlloc,
) -> Result<DeviceMemory, DeviceMemoryAllocError> {
let memory = DeviceMemoryBuilder::new(device, memory_type.id(), size)
.dedicated_info(resource)
.build()?;
// Will never panic because we call the DeviceMemoryBuilder internally, and that only
// returns an atomically refcounted DeviceMemory object on success.
Ok(Arc::try_unwrap(memory).unwrap())
}
/// Allocates a chunk of memory and maps it.
///
/// # Panic
///
/// - Panics if `memory_type` doesn't belong to the same physical device as `device`.
/// - Panics if the memory type is not host-visible.
///
#[inline]
pub fn alloc_and_map(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
) -> Result<MappedDeviceMemory, DeviceMemoryAllocError> {
DeviceMemory::dedicated_alloc_and_map(device, memory_type, size, DedicatedAlloc::None)
}
/// Equivalent of `dedicated_alloc` for `alloc_and_map`.
pub fn dedicated_alloc_and_map(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
resource: DedicatedAlloc,
) -> Result<MappedDeviceMemory, DeviceMemoryAllocError> {
let fns = device.fns();
assert!(memory_type.is_host_visible());
let mem = DeviceMemory::dedicated_alloc(device.clone(), memory_type, size, resource)?;
Self::map_allocation(device.clone(), mem)
}
/// Same as `alloc`, but allows exportable file descriptor on Linux.
#[inline]
#[cfg(target_os = "linux")]
pub fn alloc_with_exportable_fd(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
) -> Result<DeviceMemory, DeviceMemoryAllocError> {
let memory = DeviceMemoryBuilder::new(device, memory_type.id(), size)
.export_info(ExternalMemoryHandleType {
opaque_fd: true,
..ExternalMemoryHandleType::none()
})
.build()?;
// Will never panic because we call the DeviceMemoryBuilder internally, and that only
// returns an atomically refcounted DeviceMemory object on success.
Ok(Arc::try_unwrap(memory).unwrap())
}
/// Same as `dedicated_alloc`, but allows exportable file descriptor on Linux.
#[inline]
#[cfg(target_os = "linux")]
pub fn dedicated_alloc_with_exportable_fd(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
resource: DedicatedAlloc,
) -> Result<DeviceMemory, DeviceMemoryAllocError> {
let memory = DeviceMemoryBuilder::new(device, memory_type.id(), size)
.export_info(ExternalMemoryHandleType {
opaque_fd: true,
..ExternalMemoryHandleType::none()
})
.dedicated_info(resource)
.build()?;
// Will never panic because we call the DeviceMemoryBuilder internally, and that only
// returns an atomically refcounted DeviceMemory object on success.
Ok(Arc::try_unwrap(memory).unwrap())
}
/// Same as `alloc_and_map`, but allows exportable file descriptor on Linux.
#[inline]
#[cfg(target_os = "linux")]
pub fn alloc_and_map_with_exportable_fd(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
) -> Result<MappedDeviceMemory, DeviceMemoryAllocError> {
DeviceMemory::dedicated_alloc_and_map_with_exportable_fd(
device,
memory_type,
size,
DedicatedAlloc::None,
)
}
/// Same as `dedicated_alloc_and_map`, but allows exportable file descriptor on Linux.
#[inline]
#[cfg(target_os = "linux")]
pub fn dedicated_alloc_and_map_with_exportable_fd(
device: Arc<Device>,
memory_type: MemoryType,
size: DeviceSize,
resource: DedicatedAlloc,
) -> Result<MappedDeviceMemory, DeviceMemoryAllocError> {
let fns = device.fns();
assert!(memory_type.is_host_visible());
let mem = DeviceMemory::dedicated_alloc_with_exportable_fd(
device.clone(),
memory_type,
size,
resource,
)?;
Self::map_allocation(device.clone(), mem)
}
fn map_allocation(
device: Arc<Device>,
mem: DeviceMemory,
) -> Result<MappedDeviceMemory, DeviceMemoryAllocError> {
let fns = device.fns();
let coherent = mem.memory_type().is_host_coherent();
let ptr = unsafe {
let mut output = MaybeUninit::uninit();
check_errors(fns.v1_0.map_memory(
device.internal_object(),
mem.memory,
0,
mem.size,
ash::vk::MemoryMapFlags::empty(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(MappedDeviceMemory {
memory: mem,
pointer: ptr,
coherent,
})
}
/// Returns the memory type this chunk was allocated on.
#[inline]
pub fn memory_type(&self) -> MemoryType {
self.device
.physical_device()
.memory_type_by_id(self.memory_type_index)
.unwrap()
}
/// Returns the size in bytes of that memory chunk.
#[inline]
pub fn size(&self) -> DeviceSize {
self.size
}
/// Exports the device memory into a Unix file descriptor. The caller retains ownership of the
/// file, as per the Vulkan spec.
///
/// # Panic
///
/// - Panics if the user requests an invalid handle type for this device memory object.
#[inline]
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn export_fd(
&self,
handle_type: ExternalMemoryHandleType,
) -> Result<File, DeviceMemoryAllocError> {
let fns = self.device.fns();
// VUID-VkMemoryGetFdInfoKHR-handleType-00672: "handleType must be defined as a POSIX file
// descriptor handle".
let bits = ash::vk::ExternalMemoryHandleTypeFlags::from(handle_type);
if bits != ash::vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT
&& bits != ash::vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD
{
return Err(DeviceMemoryAllocError::SpecViolation(672))?;
}
// VUID-VkMemoryGetFdInfoKHR-handleType-00671: "handleType must have been included in
// VkExportMemoryAllocateInfo::handleTypes when memory was created".
if (bits & ash::vk::ExternalMemoryHandleTypeFlags::from(self.handle_types)).is_empty() {
return Err(DeviceMemoryAllocError::SpecViolation(671))?;
}
let fd = unsafe {
let info = ash::vk::MemoryGetFdInfoKHR {
memory: self.memory,
handle_type: handle_type.into(),
..Default::default()
};
let mut output = MaybeUninit::uninit();
check_errors(fns.khr_external_memory_fd.get_memory_fd_khr(
self.device.internal_object(),
&info,
output.as_mut_ptr(),
))?;
output.assume_init()
};
let file = unsafe { File::from_raw_fd(fd) };
Ok(file)
}
}
unsafe impl DeviceOwned for DeviceMemory {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl fmt::Debug for DeviceMemory {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("DeviceMemory")
.field("device", &*self.device)
.field("memory_type", &self.memory_type())
.field("size", &self.size)
.finish()
}
}
unsafe impl VulkanObject for DeviceMemory {
type Object = ash::vk::DeviceMemory;
#[inline]
fn internal_object(&self) -> ash::vk::DeviceMemory {
self.memory
}
}
impl Drop for DeviceMemory {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
fns.v1_0
.free_memory(self.device.internal_object(), self.memory, ptr::null());
let mut allocation_count = self
.device
.allocation_count()
.lock()
.expect("Poisoned mutex");
*allocation_count -= 1;
}
}
}
/// Represents memory that has been allocated and mapped in CPU accessible space.
///
/// Can be obtained with `DeviceMemory::alloc_and_map`. The function will panic if the memory type
/// is not host-accessible.
///
/// In order to access the content of the allocated memory, you can use the `read_write` method.
/// This method returns a guard object that derefs to the content.
///
/// # Example
///
/// ```
/// use vulkano::memory::DeviceMemory;
///
/// # let device: std::sync::Arc<vulkano::device::Device> = return;
/// // The memory type must be mappable.
/// let mem_ty = device.physical_device().memory_types()
/// .filter(|t| t.is_host_visible())
/// .next().unwrap(); // Vk specs guarantee that this can't fail
///
/// // Allocates 1KB of memory.
/// let memory = DeviceMemory::alloc_and_map(device.clone(), mem_ty, 1024).unwrap();
///
/// // Get access to the content. Note that this is very unsafe for two reasons: 1) the content is
/// // uninitialized, and 2) the access is unsynchronized.
/// unsafe {
/// let mut content = memory.read_write::<[u8]>(0 .. 1024);
/// content[12] = 54; // `content` derefs to a `&[u8]` or a `&mut [u8]`
/// }
/// ```
pub struct MappedDeviceMemory {
memory: DeviceMemory,
pointer: *mut c_void,
coherent: bool,
}
// Note that `MappedDeviceMemory` doesn't implement `Drop`, as we don't need to unmap memory before
// freeing it.
//
// Vulkan specs, documentation of `vkFreeMemory`:
// > If a memory object is mapped at the time it is freed, it is implicitly unmapped.
//
impl MappedDeviceMemory {
/// Unmaps the memory. It will no longer be accessible from the CPU.
pub fn unmap(self) -> DeviceMemory {
unsafe {
let device = self.memory.device();
let fns = device.fns();
fns.v1_0
.unmap_memory(device.internal_object(), self.memory.memory);
}
self.memory
}
/// Gives access to the content of the memory.
///
/// This function takes care of calling `vkInvalidateMappedMemoryRanges` and
/// `vkFlushMappedMemoryRanges` on the given range. You are therefore encouraged to use the
/// smallest range as possible, and to not call this function multiple times in a row for
/// several small changes.
///
/// # Safety
///
/// - Type safety is not checked. You must ensure that `T` corresponds to the content of the
/// buffer.
/// - Accesses are not synchronized. Synchronization must be handled outside of
/// the `MappedDeviceMemory`.
///
#[inline]
pub unsafe fn read_write<T: ?Sized>(&self, range: Range<DeviceSize>) -> CpuAccess<T>
where
T: Content,
{
let fns = self.memory.device().fns();
let pointer = T::ref_from_ptr(
(self.pointer as usize + range.start as usize) as *mut _,
(range.end - range.start) as usize,
)
.unwrap(); // TODO: error
if !self.coherent {
let range = ash::vk::MappedMemoryRange {
memory: self.memory.internal_object(),
offset: range.start,
size: range.end - range.start,
..Default::default()
};
// TODO: return result instead?
check_errors(fns.v1_0.invalidate_mapped_memory_ranges(
self.memory.device().internal_object(),
1,
&range,
))
.unwrap();
}
CpuAccess {
pointer: pointer,
mem: self,
coherent: self.coherent,
range,
}
}
}
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 {}
impl fmt::Debug for MappedDeviceMemory {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_tuple("MappedDeviceMemory")
.field(&self.memory)
.finish()
}
}
unsafe impl Send for DeviceMemoryMapping {}
unsafe impl Sync for DeviceMemoryMapping {}
/// Represents memory mapped in CPU accessible space.
///
/// Takes an additional reference on the underlying device memory and device.
pub struct DeviceMemoryMapping {
device: Arc<Device>,
memory: Arc<DeviceMemory>,
pointer: *mut c_void,
coherent: bool,
}
impl DeviceMemoryMapping {
/// Creates a new `DeviceMemoryMapping` object given the previously allocated `device` and `memory`.
pub fn new(
device: Arc<Device>,
memory: Arc<DeviceMemory>,
offset: DeviceSize,
size: DeviceSize,
flags: u32,
) -> Result<DeviceMemoryMapping, DeviceMemoryAllocError> {
// VUID-vkMapMemory-memory-00678: "memory must not be currently host mapped".
let mut mapped = memory.mapped.lock().expect("Poisoned mutex");
if *mapped {
return Err(DeviceMemoryAllocError::SpecViolation(678));
}
// VUID-vkMapMemory-offset-00679: "offset must be less than the size of memory"
if size != ash::vk::WHOLE_SIZE && offset >= memory.size() {
return Err(DeviceMemoryAllocError::SpecViolation(679));
}
// VUID-vkMapMemory-size-00680: "If size is not equal to VK_WHOLE_SIZE, size must be
// greater than 0".
if size != ash::vk::WHOLE_SIZE && size == 0 {
return Err(DeviceMemoryAllocError::SpecViolation(680));
}
// VUID-vkMapMemory-size-00681: "If size is not equal to VK_WHOLE_SIZE, size must be less
// than or equal to the size of the memory minus offset".
if size != ash::vk::WHOLE_SIZE && size > memory.size() - offset {
return Err(DeviceMemoryAllocError::SpecViolation(681));
}
// VUID-vkMapMemory-memory-00682: "memory must have been created with a memory type
// that reports VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT"
let coherent = memory.memory_type().is_host_coherent();
if !coherent {
return Err(DeviceMemoryAllocError::SpecViolation(682));
}
// VUID-vkMapMemory-memory-00683: "memory must not have been allocated with multiple instances".
// Confused about this one, so not implemented.
// VUID-vkMapMemory-memory-parent: "memory must have been created, allocated or retrieved
// from device"
if device.internal_object() != memory.device().internal_object() {
return Err(DeviceMemoryAllocError::ImplicitSpecViolation(
"VUID-vkMapMemory-memory-parent",
));
}
// VUID-vkMapMemory-flags-zerobitmask: "flags must be 0".
if flags != 0 {
return Err(DeviceMemoryAllocError::ImplicitSpecViolation(
"VUID-vkMapMemory-flags-zerobitmask",
));
}
// VUID-vkMapMemory-device-parameter, VUID-vkMapMemory-memory-parameter and
// VUID-vkMapMemory-ppData-parameter satisfied via Vulkano internally.
let fns = device.fns();
let ptr = unsafe {
let mut output = MaybeUninit::uninit();
check_errors(fns.v1_0.map_memory(
device.internal_object(),
memory.memory,
0,
memory.size,
ash::vk::MemoryMapFlags::empty(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
*mapped = true;
Ok(DeviceMemoryMapping {
device: device.clone(),
memory: memory.clone(),
pointer: ptr,
coherent,
})
}
/// Returns the raw pointer associated with the `DeviceMemoryMapping`.
///
/// # Safety
///
/// The caller of this function must ensure that the use of the raw pointer does not outlive
/// the associated `DeviceMemoryMapping`.
pub unsafe fn as_ptr(&self) -> *mut u8 {
self.pointer as *mut u8
}
}
impl Drop for DeviceMemoryMapping {
#[inline]
fn drop(&mut self) {
let mut mapped = self.memory.mapped.lock().expect("Poisoned mutex");
unsafe {
let fns = self.device.fns();
fns.v1_0
.unmap_memory(self.device.internal_object(), self.memory.memory);
}
*mapped = false;
}
}
/// Object that can be used to read or write the content of a `MappedDeviceMemory`.
///
/// This object derefs to the content, just like a `MutexGuard` for example.
pub struct CpuAccess<'a, T: ?Sized + 'a> {
pointer: *mut T,
mem: &'a MappedDeviceMemory,
coherent: bool,
range: Range<DeviceSize>,
}
impl<'a, T: ?Sized + 'a> CpuAccess<'a, T> {
/// Builds a new `CpuAccess` to access a sub-part of the current `CpuAccess`.
///
/// This function is unstable. Don't use it directly.
// TODO: unsafe?
// TODO: decide what to do with this
#[doc(hidden)]
#[inline]
pub fn map<U: ?Sized + 'a, F>(self, f: F) -> CpuAccess<'a, U>
where
F: FnOnce(*mut T) -> *mut U,
{
CpuAccess {
pointer: f(self.pointer),
mem: self.mem,
coherent: self.coherent,
range: self.range.clone(), // TODO: ?
}
}
}
unsafe impl<'a, T: ?Sized + 'a> Send for CpuAccess<'a, T> {}
unsafe impl<'a, T: ?Sized + 'a> Sync for CpuAccess<'a, T> {}
impl<'a, T: ?Sized + 'a> Deref for CpuAccess<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.pointer }
}
}
impl<'a, T: ?Sized + 'a> DerefMut for CpuAccess<'a, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.pointer }
}
}
impl<'a, T: ?Sized + 'a> Drop for CpuAccess<'a, T> {
#[inline]
fn drop(&mut self) {
// If the memory doesn't have the `coherent` flag, we need to flush the data.
if !self.coherent {
let fns = self.mem.as_ref().device().fns();
let range = ash::vk::MappedMemoryRange {
memory: self.mem.as_ref().internal_object(),
offset: self.range.start,
size: self.range.end - self.range.start,
..Default::default()
};
unsafe {
check_errors(fns.v1_0.flush_mapped_memory_ranges(
self.mem.as_ref().device().internal_object(),
1,
&range,
))
.unwrap();
}
}
}
}
/// Error type returned by functions related to `DeviceMemory`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DeviceMemoryAllocError {
/// Not enough memory available.
OomError(OomError),
/// The maximum number of allocations has been exceeded.
TooManyObjects,
/// Memory map failed.
MemoryMapFailed,
/// Invalid Memory Index
MemoryIndexInvalid,
/// Invalid Structure Type
StructureTypeAlreadyPresent,
/// Spec violation, containing the Valid Usage ID (VUID) from the Vulkan spec.
SpecViolation(u32),
/// An implicit violation that's convered in the Vulkan spec.
ImplicitSpecViolation(&'static str),
/// An extension is missing.
MissingExtension(&'static str),
/// Invalid Size
InvalidSize,
}
impl error::Error for DeviceMemoryAllocError {
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
DeviceMemoryAllocError::OomError(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for DeviceMemoryAllocError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
DeviceMemoryAllocError::OomError(_) => write!(fmt, "not enough memory available"),
DeviceMemoryAllocError::TooManyObjects => {
write!(fmt, "the maximum number of allocations has been exceeded")
}
DeviceMemoryAllocError::MemoryMapFailed => write!(fmt, "memory map failed"),
DeviceMemoryAllocError::MemoryIndexInvalid => write!(fmt, "memory index invalid"),
DeviceMemoryAllocError::StructureTypeAlreadyPresent => {
write!(fmt, "structure type already present")
}
DeviceMemoryAllocError::SpecViolation(u) => {
write!(fmt, "valid usage ID check {} failed", u)
}
DeviceMemoryAllocError::MissingExtension(s) => {
write!(fmt, "Missing the following extension: {}", s)
}
DeviceMemoryAllocError::ImplicitSpecViolation(e) => {
write!(fmt, "Implicit spec violation failed {}", e)
}
DeviceMemoryAllocError::InvalidSize => write!(fmt, "invalid size"),
}
}
}
impl From<Error> for DeviceMemoryAllocError {
#[inline]
fn from(err: Error) -> DeviceMemoryAllocError {
match err {
e @ Error::OutOfHostMemory | e @ Error::OutOfDeviceMemory => {
DeviceMemoryAllocError::OomError(e.into())
}
Error::TooManyObjects => DeviceMemoryAllocError::TooManyObjects,
Error::MemoryMapFailed => DeviceMemoryAllocError::MemoryMapFailed,
_ => panic!("unexpected error: {:?}", err),
}
}
}
impl From<OomError> for DeviceMemoryAllocError {
#[inline]
fn from(err: OomError) -> DeviceMemoryAllocError {
DeviceMemoryAllocError::OomError(err)
}
}
#[cfg(test)]
mod tests {
use crate::memory::DeviceMemory;
use crate::memory::DeviceMemoryAllocError;
use crate::OomError;
#[test]
fn create() {
let (device, _) = gfx_dev_and_queue!();
let mem_ty = device.physical_device().memory_types().next().unwrap();
let _ = DeviceMemory::alloc(device.clone(), mem_ty, 256).unwrap();
}
#[test]
fn zero_size() {
let (device, _) = gfx_dev_and_queue!();
let mem_ty = device.physical_device().memory_types().next().unwrap();
assert_should_panic!({
let _ = DeviceMemory::alloc(device.clone(), mem_ty, 0).unwrap();
});
}
#[test]
#[cfg(target_pointer_width = "64")]
fn oom_single() {
let (device, _) = gfx_dev_and_queue!();
let mem_ty = device
.physical_device()
.memory_types()
.filter(|m| !m.is_lazily_allocated())
.next()
.unwrap();
match DeviceMemory::alloc(device.clone(), mem_ty, 0xffffffffffffffff) {
Err(DeviceMemoryAllocError::SpecViolation(u)) => (),
_ => panic!(),
}
}
#[test]
#[ignore] // TODO: test fails for now on Mesa+Intel
fn oom_multi() {
let (device, _) = gfx_dev_and_queue!();
let mem_ty = device
.physical_device()
.memory_types()
.filter(|m| !m.is_lazily_allocated())
.next()
.unwrap();
let heap_size = mem_ty.heap().size();
let mut allocs = Vec::new();
for _ in 0..4 {
match DeviceMemory::alloc(device.clone(), mem_ty, heap_size / 3) {
Err(DeviceMemoryAllocError::OomError(OomError::OutOfDeviceMemory)) => return, // test succeeded
Ok(a) => allocs.push(a),
_ => (),
}
}
panic!()
}
#[test]
fn allocation_count() {
let (device, _) = gfx_dev_and_queue!();
let mem_ty = device.physical_device().memory_types().next().unwrap();
assert_eq!(*device.allocation_count().lock().unwrap(), 0);
let mem1 = DeviceMemory::alloc(device.clone(), mem_ty, 256).unwrap();
assert_eq!(*device.allocation_count().lock().unwrap(), 1);
{
let mem2 = DeviceMemory::alloc(device.clone(), mem_ty, 256).unwrap();
assert_eq!(*device.allocation_count().lock().unwrap(), 2);
}
assert_eq!(*device.allocation_count().lock().unwrap(), 1);
}
}