| // 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); |
| } |
| } |