diff options
Diffstat (limited to 'src/buffer/cpu_access.rs')
-rw-r--r-- | src/buffer/cpu_access.rs | 659 |
1 files changed, 0 insertions, 659 deletions
diff --git a/src/buffer/cpu_access.rs b/src/buffer/cpu_access.rs deleted file mode 100644 index 5c9bc19..0000000 --- a/src/buffer/cpu_access.rs +++ /dev/null @@ -1,659 +0,0 @@ -// 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. - -//! Buffer whose content is accessible to the CPU. -//! -//! The `CpuAccessibleBuffer` is a basic general-purpose buffer. It can be used in any situation -//! but may not perform as well as other buffer types. -//! -//! Each access from the CPU or from the GPU locks the whole buffer for either reading or writing. -//! You can read the buffer multiple times simultaneously. Trying to read and write simultaneously, -//! or write and write simultaneously will block. - -use crate::buffer::sys::BufferCreationError; -use crate::buffer::sys::UnsafeBuffer; -use crate::buffer::traits::BufferAccess; -use crate::buffer::traits::BufferInner; -use crate::buffer::traits::TypedBufferAccess; -use crate::buffer::BufferUsage; -use crate::device::physical::QueueFamily; -use crate::device::Device; -use crate::device::DeviceOwned; -use crate::device::Queue; -use crate::memory::pool::AllocFromRequirementsFilter; -use crate::memory::pool::AllocLayout; -use crate::memory::pool::MappingRequirement; -use crate::memory::pool::MemoryPool; -use crate::memory::pool::MemoryPoolAlloc; -use crate::memory::pool::PotentialDedicatedAllocation; -use crate::memory::pool::StdMemoryPoolAlloc; -use crate::memory::Content; -use crate::memory::CpuAccess as MemCpuAccess; -use crate::memory::DedicatedAlloc; -use crate::memory::DeviceMemoryAllocError; -use crate::sync::AccessError; -use crate::sync::Sharing; -use crate::DeviceSize; -use parking_lot::RwLock; -use parking_lot::RwLockReadGuard; -use parking_lot::RwLockWriteGuard; -use smallvec::SmallVec; -use std::error; -use std::fmt; -use std::hash::Hash; -use std::hash::Hasher; -use std::iter; -use std::marker::PhantomData; -use std::mem; -use std::ops::Deref; -use std::ops::DerefMut; -use std::ptr; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; -use std::sync::Arc; - -/// Buffer whose content is accessible by the CPU. -/// -/// Setting the `host_cached` field on the various initializers to `true` will make it so -/// the `CpuAccessibleBuffer` prefers to allocate from host_cached memory. Host cached -/// memory caches GPU data on the CPU side. This can be more performant in cases where -/// the cpu needs to read data coming off the GPU. -#[derive(Debug)] -pub struct CpuAccessibleBuffer<T: ?Sized, A = PotentialDedicatedAllocation<StdMemoryPoolAlloc>> { - // Inner content. - inner: UnsafeBuffer, - - // The memory held by the buffer. - memory: A, - - // Access pattern of the buffer. - // Every time the user tries to read or write the buffer from the CPU, this `RwLock` is kept - // locked and its content is checked to verify that we are allowed access. Every time the user - // tries to submit this buffer for the GPU, this `RwLock` is briefly locked and modified. - access: RwLock<CurrentGpuAccess>, - - // Queue families allowed to access this buffer. - queue_families: SmallVec<[u32; 4]>, - - // Necessary to make it compile. - marker: PhantomData<Box<T>>, -} - -#[derive(Debug)] -enum CurrentGpuAccess { - NonExclusive { - // Number of non-exclusive GPU accesses. Can be 0. - num: AtomicUsize, - }, - Exclusive { - // Number of exclusive locks. Cannot be 0. If 0 is reached, we must jump to `NonExclusive`. - num: usize, - }, -} - -impl<T> CpuAccessibleBuffer<T> { - /// Builds a new buffer with some data in it. Only allowed for sized data. - pub fn from_data( - device: Arc<Device>, - usage: BufferUsage, - host_cached: bool, - data: T, - ) -> Result<Arc<CpuAccessibleBuffer<T>>, DeviceMemoryAllocError> - where - T: Content + Copy + 'static, - { - unsafe { - let uninitialized = CpuAccessibleBuffer::raw( - device, - mem::size_of::<T>() as DeviceSize, - usage, - host_cached, - iter::empty(), - )?; - - // Note that we are in panic-unsafety land here. However a panic should never ever - // happen here, so in theory we are safe. - // TODO: check whether that's true ^ - - { - let mut mapping = uninitialized.write().unwrap(); - ptr::write::<T>(&mut *mapping, data) - } - - Ok(uninitialized) - } - } - - /// Builds a new uninitialized buffer. Only allowed for sized data. - #[inline] - pub unsafe fn uninitialized( - device: Arc<Device>, - usage: BufferUsage, - host_cached: bool, - ) -> Result<Arc<CpuAccessibleBuffer<T>>, DeviceMemoryAllocError> { - CpuAccessibleBuffer::raw( - device, - mem::size_of::<T>() as DeviceSize, - usage, - host_cached, - iter::empty(), - ) - } -} - -impl<T> CpuAccessibleBuffer<[T]> { - /// Builds a new buffer that contains an array `T`. The initial data comes from an iterator - /// that produces that list of Ts. - pub fn from_iter<I>( - device: Arc<Device>, - usage: BufferUsage, - host_cached: bool, - data: I, - ) -> Result<Arc<CpuAccessibleBuffer<[T]>>, DeviceMemoryAllocError> - where - I: ExactSizeIterator<Item = T>, - T: Content + 'static, - { - unsafe { - let uninitialized = CpuAccessibleBuffer::uninitialized_array( - device, - data.len() as DeviceSize, - usage, - host_cached, - )?; - - // Note that we are in panic-unsafety land here. However a panic should never ever - // happen here, so in theory we are safe. - // TODO: check whether that's true ^ - - { - let mut mapping = uninitialized.write().unwrap(); - - for (i, o) in data.zip(mapping.iter_mut()) { - ptr::write(o, i); - } - } - - Ok(uninitialized) - } - } - - /// Builds a new buffer. Can be used for arrays. - #[inline] - pub unsafe fn uninitialized_array( - device: Arc<Device>, - len: DeviceSize, - usage: BufferUsage, - host_cached: bool, - ) -> Result<Arc<CpuAccessibleBuffer<[T]>>, DeviceMemoryAllocError> { - CpuAccessibleBuffer::raw( - device, - len * mem::size_of::<T>() as DeviceSize, - usage, - host_cached, - iter::empty(), - ) - } -} - -impl<T: ?Sized> CpuAccessibleBuffer<T> { - /// Builds a new buffer without checking the size. - /// - /// # Safety - /// - /// You must ensure that the size that you pass is correct for `T`. - /// - pub unsafe fn raw<'a, I>( - device: Arc<Device>, - size: DeviceSize, - usage: BufferUsage, - host_cached: bool, - queue_families: I, - ) -> Result<Arc<CpuAccessibleBuffer<T>>, DeviceMemoryAllocError> - where - I: IntoIterator<Item = QueueFamily<'a>>, - { - let queue_families = queue_families - .into_iter() - .map(|f| f.id()) - .collect::<SmallVec<[u32; 4]>>(); - - let (buffer, mem_reqs) = { - let sharing = if queue_families.len() >= 2 { - Sharing::Concurrent(queue_families.iter().cloned()) - } else { - Sharing::Exclusive - }; - - match UnsafeBuffer::new(device.clone(), size, usage, sharing, None) { - Ok(b) => b, - Err(BufferCreationError::AllocError(err)) => return Err(err), - Err(_) => unreachable!(), // We don't use sparse binding, therefore the other - // errors can't happen - } - }; - - let mem = MemoryPool::alloc_from_requirements( - &Device::standard_pool(&device), - &mem_reqs, - AllocLayout::Linear, - MappingRequirement::Map, - DedicatedAlloc::Buffer(&buffer), - |m| { - if m.is_host_cached() { - if host_cached { - AllocFromRequirementsFilter::Preferred - } else { - AllocFromRequirementsFilter::Allowed - } - } else { - if host_cached { - AllocFromRequirementsFilter::Allowed - } else { - AllocFromRequirementsFilter::Preferred - } - } - }, - )?; - debug_assert!((mem.offset() % mem_reqs.alignment) == 0); - debug_assert!(mem.mapped_memory().is_some()); - buffer.bind_memory(mem.memory(), mem.offset())?; - - Ok(Arc::new(CpuAccessibleBuffer { - inner: buffer, - memory: mem, - access: RwLock::new(CurrentGpuAccess::NonExclusive { - num: AtomicUsize::new(0), - }), - queue_families: queue_families, - marker: PhantomData, - })) - } -} - -impl<T: ?Sized, A> CpuAccessibleBuffer<T, A> { - /// Returns the queue families this buffer can be used on. - // TODO: use a custom iterator - #[inline] - pub fn queue_families(&self) -> Vec<QueueFamily> { - self.queue_families - .iter() - .map(|&num| { - self.device() - .physical_device() - .queue_family_by_id(num) - .unwrap() - }) - .collect() - } -} - -impl<T: ?Sized, A> CpuAccessibleBuffer<T, A> -where - T: Content + 'static, - A: MemoryPoolAlloc, -{ - /// Locks the buffer in order to read its content from the CPU. - /// - /// If the buffer is currently used in exclusive mode by the GPU, this function will return - /// an error. Similarly if you called `write()` on the buffer and haven't dropped the lock, - /// this function will return an error as well. - /// - /// After this function successfully locks the buffer, any attempt to submit a command buffer - /// that uses it in exclusive mode will fail. You can still submit this buffer for non-exclusive - /// accesses (ie. reads). - #[inline] - pub fn read(&self) -> Result<ReadLock<T>, ReadLockError> { - let lock = match self.access.try_read() { - Some(l) => l, - // TODO: if a user simultaneously calls .write(), and write() is currently finding out - // that the buffer is in fact GPU locked, then we will return a CpuWriteLocked - // error instead of a GpuWriteLocked ; is this a problem? how do we fix this? - None => return Err(ReadLockError::CpuWriteLocked), - }; - - if let CurrentGpuAccess::Exclusive { .. } = *lock { - return Err(ReadLockError::GpuWriteLocked); - } - - let offset = self.memory.offset(); - let range = offset..offset + self.inner.size(); - - Ok(ReadLock { - inner: unsafe { self.memory.mapped_memory().unwrap().read_write(range) }, - lock: lock, - }) - } - - /// Locks the buffer in order to write its content from the CPU. - /// - /// If the buffer is currently in use by the GPU, this function will return an error. Similarly - /// if you called `read()` on the buffer and haven't dropped the lock, this function will - /// return an error as well. - /// - /// After this function successfully locks the buffer, any attempt to submit a command buffer - /// that uses it and any attempt to call `read()` will return an error. - #[inline] - pub fn write(&self) -> Result<WriteLock<T>, WriteLockError> { - let lock = match self.access.try_write() { - Some(l) => l, - // TODO: if a user simultaneously calls .read() or .write(), and the function is - // currently finding out that the buffer is in fact GPU locked, then we will - // return a CpuLocked error instead of a GpuLocked ; is this a problem? - // how do we fix this? - None => return Err(WriteLockError::CpuLocked), - }; - - match *lock { - CurrentGpuAccess::NonExclusive { ref num } if num.load(Ordering::SeqCst) == 0 => (), - _ => return Err(WriteLockError::GpuLocked), - } - - let offset = self.memory.offset(); - let range = offset..offset + self.inner.size(); - - Ok(WriteLock { - inner: unsafe { self.memory.mapped_memory().unwrap().read_write(range) }, - lock: lock, - }) - } -} - -unsafe impl<T: ?Sized, A> BufferAccess for CpuAccessibleBuffer<T, A> -where - T: 'static + Send + Sync, -{ - #[inline] - fn inner(&self) -> BufferInner { - BufferInner { - buffer: &self.inner, - offset: 0, - } - } - - #[inline] - fn size(&self) -> DeviceSize { - self.inner.size() - } - - #[inline] - fn conflict_key(&self) -> (u64, u64) { - (self.inner.key(), 0) - } - - #[inline] - fn try_gpu_lock(&self, exclusive_access: bool, _: &Queue) -> Result<(), AccessError> { - if exclusive_access { - let mut lock = match self.access.try_write() { - Some(lock) => lock, - None => return Err(AccessError::AlreadyInUse), - }; - - match *lock { - CurrentGpuAccess::NonExclusive { ref num } if num.load(Ordering::SeqCst) == 0 => (), - _ => return Err(AccessError::AlreadyInUse), - }; - - *lock = CurrentGpuAccess::Exclusive { num: 1 }; - Ok(()) - } else { - let lock = match self.access.try_read() { - Some(lock) => lock, - None => return Err(AccessError::AlreadyInUse), - }; - - match *lock { - CurrentGpuAccess::Exclusive { .. } => return Err(AccessError::AlreadyInUse), - CurrentGpuAccess::NonExclusive { ref num } => num.fetch_add(1, Ordering::SeqCst), - }; - - Ok(()) - } - } - - #[inline] - unsafe fn increase_gpu_lock(&self) { - // First, handle if we have a non-exclusive access. - { - // Since the buffer is in use by the GPU, it is invalid to hold a write-lock to - // the buffer. The buffer can still be briefly in a write-locked state for the duration - // of the check though. - let read_lock = self.access.read(); - if let CurrentGpuAccess::NonExclusive { ref num } = *read_lock { - let prev = num.fetch_add(1, Ordering::SeqCst); - debug_assert!(prev >= 1); - return; - } - } - - // If we reach here, this means that `access` contains `CurrentGpuAccess::Exclusive`. - { - // Same remark as above, but for writing. - let mut write_lock = self.access.write(); - if let CurrentGpuAccess::Exclusive { ref mut num } = *write_lock { - *num += 1; - } else { - unreachable!() - } - } - } - - #[inline] - unsafe fn unlock(&self) { - // First, handle if we had a non-exclusive access. - { - // Since the buffer is in use by the GPU, it is invalid to hold a write-lock to - // the buffer. The buffer can still be briefly in a write-locked state for the duration - // of the check though. - let read_lock = self.access.read(); - if let CurrentGpuAccess::NonExclusive { ref num } = *read_lock { - let prev = num.fetch_sub(1, Ordering::SeqCst); - debug_assert!(prev >= 1); - return; - } - } - - // If we reach here, this means that `access` contains `CurrentGpuAccess::Exclusive`. - { - // Same remark as above, but for writing. - let mut write_lock = self.access.write(); - if let CurrentGpuAccess::Exclusive { ref mut num } = *write_lock { - if *num != 1 { - *num -= 1; - return; - } - } else { - // Can happen if we lock in exclusive mode N times, and unlock N+1 times with the - // last two unlocks happen simultaneously. - panic!() - } - - *write_lock = CurrentGpuAccess::NonExclusive { - num: AtomicUsize::new(0), - }; - } - } -} - -unsafe impl<T: ?Sized, A> TypedBufferAccess for CpuAccessibleBuffer<T, A> -where - T: 'static + Send + Sync, -{ - type Content = T; -} - -unsafe impl<T: ?Sized, A> DeviceOwned for CpuAccessibleBuffer<T, A> { - #[inline] - fn device(&self) -> &Arc<Device> { - self.inner.device() - } -} - -impl<T: ?Sized, A> PartialEq for CpuAccessibleBuffer<T, A> -where - T: 'static + Send + Sync, -{ - #[inline] - fn eq(&self, other: &Self) -> bool { - self.inner() == other.inner() && self.size() == other.size() - } -} - -impl<T: ?Sized, A> Eq for CpuAccessibleBuffer<T, A> where T: 'static + Send + Sync {} - -impl<T: ?Sized, A> Hash for CpuAccessibleBuffer<T, A> -where - T: 'static + Send + Sync, -{ - #[inline] - fn hash<H: Hasher>(&self, state: &mut H) { - self.inner().hash(state); - self.size().hash(state); - } -} - -/// Object that can be used to read or write the content of a `CpuAccessibleBuffer`. -/// -/// Note that this object holds a rwlock read guard on the chunk. If another thread tries to access -/// this buffer's content or tries to submit a GPU command that uses this buffer, it will block. -pub struct ReadLock<'a, T: ?Sized + 'a> { - inner: MemCpuAccess<'a, T>, - lock: RwLockReadGuard<'a, CurrentGpuAccess>, -} - -impl<'a, T: ?Sized + 'a> ReadLock<'a, T> { - /// Makes a new `ReadLock` to access a sub-part of the current `ReadLock`. - #[inline] - pub fn map<U: ?Sized + 'a, F>(self, f: F) -> ReadLock<'a, U> - where - F: FnOnce(&mut T) -> &mut U, - { - ReadLock { - inner: self.inner.map(|ptr| unsafe { f(&mut *ptr) as *mut _ }), - lock: self.lock, - } - } -} - -impl<'a, T: ?Sized + 'a> Deref for ReadLock<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - self.inner.deref() - } -} - -/// Error when attempting to CPU-read a buffer. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ReadLockError { - /// The buffer is already locked for write mode by the CPU. - CpuWriteLocked, - /// The buffer is already locked for write mode by the GPU. - GpuWriteLocked, -} - -impl error::Error for ReadLockError {} - -impl fmt::Display for ReadLockError { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!( - fmt, - "{}", - match *self { - ReadLockError::CpuWriteLocked => { - "the buffer is already locked for write mode by the CPU" - } - ReadLockError::GpuWriteLocked => { - "the buffer is already locked for write mode by the GPU" - } - } - ) - } -} - -/// Object that can be used to read or write the content of a `CpuAccessibleBuffer`. -/// -/// Note that this object holds a rwlock write guard on the chunk. If another thread tries to access -/// this buffer's content or tries to submit a GPU command that uses this buffer, it will block. -pub struct WriteLock<'a, T: ?Sized + 'a> { - inner: MemCpuAccess<'a, T>, - lock: RwLockWriteGuard<'a, CurrentGpuAccess>, -} - -impl<'a, T: ?Sized + 'a> WriteLock<'a, T> { - /// Makes a new `WriteLock` to access a sub-part of the current `WriteLock`. - #[inline] - pub fn map<U: ?Sized + 'a, F>(self, f: F) -> WriteLock<'a, U> - where - F: FnOnce(&mut T) -> &mut U, - { - WriteLock { - inner: self.inner.map(|ptr| unsafe { f(&mut *ptr) as *mut _ }), - lock: self.lock, - } - } -} - -impl<'a, T: ?Sized + 'a> Deref for WriteLock<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - self.inner.deref() - } -} - -impl<'a, T: ?Sized + 'a> DerefMut for WriteLock<'a, T> { - #[inline] - fn deref_mut(&mut self) -> &mut T { - self.inner.deref_mut() - } -} - -/// Error when attempting to CPU-write a buffer. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum WriteLockError { - /// The buffer is already locked by the CPU. - CpuLocked, - /// The buffer is already locked by the GPU. - GpuLocked, -} - -impl error::Error for WriteLockError {} - -impl fmt::Display for WriteLockError { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!( - fmt, - "{}", - match *self { - WriteLockError::CpuLocked => "the buffer is already locked by the CPU", - WriteLockError::GpuLocked => "the buffer is already locked by the GPU", - } - ) - } -} - -#[cfg(test)] -mod tests { - use crate::buffer::{BufferUsage, CpuAccessibleBuffer}; - - #[test] - fn create_empty_buffer() { - let (device, queue) = gfx_dev_and_queue!(); - - const EMPTY: [i32; 0] = []; - - let _ = CpuAccessibleBuffer::from_data(device.clone(), BufferUsage::all(), false, EMPTY); - let _ = CpuAccessibleBuffer::from_iter(device, BufferUsage::all(), false, EMPTY.iter()); - } -} |