// Copyright (c) 2016 The vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // 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(ptr: &mut T) -> impl Iterator { 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 = 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, size: DeviceSize, memory_type_index: u32, handle_types: ExternalMemoryHandleType, mapped: Mutex, } /// Represents a builder for the device memory object. /// /// # Example /// /// ``` /// use vulkano::memory::DeviceMemoryBuilder; /// /// # let device: std::sync::Arc = 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, allocate: ash::vk::MemoryAllocateInfo, dedicated_info: Option, export_info: Option, import_info: Option, 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, 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(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, 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, memory_type: MemoryType, size: DeviceSize, ) -> Result { 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, memory_type: MemoryType, size: DeviceSize, resource: DedicatedAlloc, ) -> Result { 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, memory_type: MemoryType, size: DeviceSize, ) -> Result { 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, memory_type: MemoryType, size: DeviceSize, resource: DedicatedAlloc, ) -> Result { 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, memory_type: MemoryType, size: DeviceSize, ) -> Result { 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, memory_type: MemoryType, size: DeviceSize, resource: DedicatedAlloc, ) -> Result { 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, memory_type: MemoryType, size: DeviceSize, ) -> Result { 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, memory_type: MemoryType, size: DeviceSize, resource: DedicatedAlloc, ) -> Result { 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, mem: DeviceMemory, ) -> Result { 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 { 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 { &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 = 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(&self, range: Range) -> CpuAccess 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 for MappedDeviceMemory { #[inline] fn as_ref(&self) -> &DeviceMemory { &self.memory } } impl AsMut for MappedDeviceMemory { #[inline] fn as_mut(&mut self) -> &mut DeviceMemory { &mut self.memory } } unsafe impl DeviceOwned for MappedDeviceMemory { #[inline] fn device(&self) -> &Arc { 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, memory: Arc, 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, memory: Arc, offset: DeviceSize, size: DeviceSize, flags: u32, ) -> Result { // 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, } 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(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 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 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); } }