// 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. //! Buffer that is written once then read for as long as it is alive. //! //! Use this buffer when you have data that you never modify. //! //! Only the first ever command buffer that uses this buffer can write to it (for example by //! copying from another buffer). Any subsequent command buffer **must** only read from the buffer, //! or a panic will happen. //! //! The buffer will be stored in device-local memory if possible //! 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::buffer::CpuAccessibleBuffer; use crate::command_buffer::AutoCommandBufferBuilder; use crate::command_buffer::CommandBufferExecFuture; use crate::command_buffer::CommandBufferUsage; use crate::command_buffer::PrimaryAutoCommandBuffer; use crate::command_buffer::PrimaryCommandBuffer; 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::DedicatedAlloc; use crate::memory::DeviceMemoryAllocError; use crate::sync::AccessError; use crate::sync::NowFuture; use crate::sync::Sharing; use crate::DeviceSize; use smallvec::SmallVec; use std::hash::Hash; use std::hash::Hasher; use std::marker::PhantomData; use std::mem; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::Arc; /// Buffer that is written once then read for as long as it is alive. // TODO: implement Debug pub struct ImmutableBuffer> { // Inner content. inner: UnsafeBuffer, // Memory allocated for the buffer. memory: A, // True if the `ImmutableBufferInitialization` object was used by the GPU then dropped. // This means that the `ImmutableBuffer` can be used as much as we want without any restriction. initialized: AtomicBool, // Queue families allowed to access this buffer. queue_families: SmallVec<[u32; 4]>, // Necessary to have the appropriate template parameter. marker: PhantomData>, } // TODO: make this prettier type ImmutableBufferFromBufferFuture = CommandBufferExecFuture; impl ImmutableBuffer { /// Builds an `ImmutableBuffer` from some data. /// /// This function builds a memory-mapped intermediate buffer, writes the data to it, builds a /// command buffer that copies from this intermediate buffer to the final buffer, and finally /// submits the command buffer as a future. /// /// This function returns two objects: the newly-created buffer, and a future representing /// the initial upload operation. In order to be allowed to use the `ImmutableBuffer`, you must /// either submit your operation after this future, or execute this future and wait for it to /// be finished before submitting your own operation. pub fn from_data( data: T, usage: BufferUsage, queue: Arc, ) -> Result<(Arc>, ImmutableBufferFromBufferFuture), DeviceMemoryAllocError> where T: 'static + Copy + Send + Sync + Sized, { let source = CpuAccessibleBuffer::from_data( queue.device().clone(), BufferUsage::transfer_source(), false, data, )?; ImmutableBuffer::from_buffer(source, usage, queue) } /// Builds an `ImmutableBuffer` that copies its data from another buffer. /// /// This function returns two objects: the newly-created buffer, and a future representing /// the initial upload operation. In order to be allowed to use the `ImmutableBuffer`, you must /// either submit your operation after this future, or execute this future and wait for it to /// be finished before submitting your own operation. pub fn from_buffer( source: B, usage: BufferUsage, queue: Arc, ) -> Result<(Arc>, ImmutableBufferFromBufferFuture), DeviceMemoryAllocError> where B: BufferAccess + TypedBufferAccess + 'static + Clone + Send + Sync, T: 'static + Send + Sync, { unsafe { // We automatically set `transfer_destination` to true in order to avoid annoying errors. let actual_usage = BufferUsage { transfer_destination: true, ..usage }; let (buffer, init) = ImmutableBuffer::raw( source.device().clone(), source.size(), actual_usage, source.device().active_queue_families(), )?; let mut cbb = AutoCommandBufferBuilder::primary( source.device().clone(), queue.family(), CommandBufferUsage::MultipleSubmit, )?; cbb.copy_buffer(source, init).unwrap(); // TODO: return error? let cb = cbb.build().unwrap(); // TODO: return OomError let future = match cb.execute(queue) { Ok(f) => f, Err(_) => unreachable!(), }; Ok((buffer, future)) } } } impl ImmutableBuffer { /// Builds a new buffer with uninitialized data. Only allowed for sized data. /// /// Returns two things: the buffer, and a special access that should be used for the initial /// upload to the buffer. /// /// You will get an error if you try to use the buffer before using the initial upload access. /// However this function doesn't check whether you actually used this initial upload to fill /// the buffer like you're supposed to do. /// /// You will also get an error if you try to get exclusive access to the final buffer. /// /// # Safety /// /// - The `ImmutableBufferInitialization` should be used to fill the buffer with some initial /// data, otherwise the content is undefined. /// #[inline] pub unsafe fn uninitialized( device: Arc, usage: BufferUsage, ) -> Result<(Arc>, ImmutableBufferInitialization), DeviceMemoryAllocError> { ImmutableBuffer::raw( device.clone(), mem::size_of::() as DeviceSize, usage, device.active_queue_families(), ) } } impl ImmutableBuffer<[T]> { pub fn from_iter( data: D, usage: BufferUsage, queue: Arc, ) -> Result<(Arc>, ImmutableBufferFromBufferFuture), DeviceMemoryAllocError> where D: ExactSizeIterator, T: 'static + Send + Sync + Sized, { let source = CpuAccessibleBuffer::from_iter( queue.device().clone(), BufferUsage::transfer_source(), false, data, )?; ImmutableBuffer::from_buffer(source, usage, queue) } /// Builds a new buffer with uninitialized data. Can be used for arrays. /// /// Returns two things: the buffer, and a special access that should be used for the initial /// upload to the buffer. /// /// You will get an error if you try to use the buffer before using the initial upload access. /// However this function doesn't check whether you actually used this initial upload to fill /// the buffer like you're supposed to do. /// /// You will also get an error if you try to get exclusive access to the final buffer. /// /// # Safety /// /// - The `ImmutableBufferInitialization` should be used to fill the buffer with some initial /// data, otherwise the content is undefined. /// #[inline] pub unsafe fn uninitialized_array( device: Arc, len: DeviceSize, usage: BufferUsage, ) -> Result< ( Arc>, ImmutableBufferInitialization<[T]>, ), DeviceMemoryAllocError, > { ImmutableBuffer::raw( device.clone(), len * mem::size_of::() as DeviceSize, usage, device.active_queue_families(), ) } } impl ImmutableBuffer { /// Builds a new buffer without checking the size and granting free access for the initial /// upload. /// /// Returns two things: the buffer, and a special access that should be used for the initial /// upload to the buffer. /// You will get an error if you try to use the buffer before using the initial upload access. /// However this function doesn't check whether you used this initial upload to fill the buffer. /// You will also get an error if you try to get exclusive access to the final buffer. /// /// # Safety /// /// - You must ensure that the size that you pass is correct for `T`. /// - The `ImmutableBufferInitialization` should be used to fill the buffer with some initial /// data. /// #[inline] pub unsafe fn raw<'a, I>( device: Arc, size: DeviceSize, usage: BufferUsage, queue_families: I, ) -> Result<(Arc>, ImmutableBufferInitialization), DeviceMemoryAllocError> where I: IntoIterator>, { let queue_families = queue_families.into_iter().map(|f| f.id()).collect(); ImmutableBuffer::raw_impl(device, size, usage, queue_families) } // Internal implementation of `raw`. This is separated from `raw` so that it doesn't need to be // inlined. unsafe fn raw_impl( device: Arc, size: DeviceSize, usage: BufferUsage, queue_families: SmallVec<[u32; 4]>, ) -> Result<(Arc>, ImmutableBufferInitialization), DeviceMemoryAllocError> { 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::DoNotMap, DedicatedAlloc::Buffer(&buffer), |t| { if t.is_device_local() { AllocFromRequirementsFilter::Preferred } else { AllocFromRequirementsFilter::Allowed } }, )?; debug_assert!((mem.offset() % mem_reqs.alignment) == 0); buffer.bind_memory(mem.memory(), mem.offset())?; let final_buf = Arc::new(ImmutableBuffer { inner: buffer, memory: mem, queue_families: queue_families, initialized: AtomicBool::new(false), marker: PhantomData, }); let initialization = ImmutableBufferInitialization { buffer: final_buf.clone(), used: Arc::new(AtomicBool::new(false)), }; Ok((final_buf, initialization)) } } impl ImmutableBuffer { /// Returns the device used to create this buffer. #[inline] pub fn device(&self) -> &Arc { self.inner.device() } /// Returns the queue families this buffer can be used on. // TODO: use a custom iterator #[inline] pub fn queue_families(&self) -> Vec { self.queue_families .iter() .map(|&num| { self.device() .physical_device() .queue_family_by_id(num) .unwrap() }) .collect() } } unsafe impl BufferAccess for ImmutableBuffer { #[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 { return Err(AccessError::ExclusiveDenied); } if !self.initialized.load(Ordering::Relaxed) { return Err(AccessError::BufferNotInitialized); } Ok(()) } #[inline] unsafe fn increase_gpu_lock(&self) {} #[inline] unsafe fn unlock(&self) {} } unsafe impl TypedBufferAccess for ImmutableBuffer { type Content = T; } unsafe impl DeviceOwned for ImmutableBuffer { #[inline] fn device(&self) -> &Arc { self.inner.device() } } impl PartialEq for ImmutableBuffer { #[inline] fn eq(&self, other: &Self) -> bool { self.inner() == other.inner() && self.size() == other.size() } } impl Eq for ImmutableBuffer {} impl Hash for ImmutableBuffer { #[inline] fn hash(&self, state: &mut H) { self.inner().hash(state); self.size().hash(state); } } /// Access to the immutable buffer that can be used for the initial upload. //#[derive(Debug)] // TODO: pub struct ImmutableBufferInitialization< T: ?Sized, A = PotentialDedicatedAllocation, > { buffer: Arc>, used: Arc, } unsafe impl BufferAccess for ImmutableBufferInitialization { #[inline] fn inner(&self) -> BufferInner { self.buffer.inner() } #[inline] fn size(&self) -> DeviceSize { self.buffer.size() } #[inline] fn conflict_key(&self) -> (u64, u64) { (self.buffer.inner.key(), 0) } #[inline] fn try_gpu_lock(&self, _: bool, _: &Queue) -> Result<(), AccessError> { if self.buffer.initialized.load(Ordering::Relaxed) { return Err(AccessError::AlreadyInUse); } if !self .used .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .unwrap_or_else(|e| e) { Ok(()) } else { Err(AccessError::AlreadyInUse) } } #[inline] unsafe fn increase_gpu_lock(&self) { debug_assert!(self.used.load(Ordering::Relaxed)); } #[inline] unsafe fn unlock(&self) { self.buffer.initialized.store(true, Ordering::Relaxed); } } unsafe impl TypedBufferAccess for ImmutableBufferInitialization { type Content = T; } unsafe impl DeviceOwned for ImmutableBufferInitialization { #[inline] fn device(&self) -> &Arc { self.buffer.inner.device() } } impl Clone for ImmutableBufferInitialization { #[inline] fn clone(&self) -> ImmutableBufferInitialization { ImmutableBufferInitialization { buffer: self.buffer.clone(), used: self.used.clone(), } } } impl PartialEq for ImmutableBufferInitialization { #[inline] fn eq(&self, other: &Self) -> bool { self.inner() == other.inner() && self.size() == other.size() } } impl Eq for ImmutableBufferInitialization {} impl Hash for ImmutableBufferInitialization { #[inline] fn hash(&self, state: &mut H) { self.inner().hash(state); self.size().hash(state); } } #[cfg(test)] mod tests { use crate::buffer::cpu_access::CpuAccessibleBuffer; use crate::buffer::immutable::ImmutableBuffer; use crate::buffer::BufferUsage; use crate::command_buffer::AutoCommandBufferBuilder; use crate::command_buffer::CommandBufferUsage; use crate::command_buffer::PrimaryCommandBuffer; use crate::sync::GpuFuture; #[test] fn from_data_working() { let (device, queue) = gfx_dev_and_queue!(); let (buffer, _) = ImmutableBuffer::from_data(12u32, BufferUsage::all(), queue.clone()).unwrap(); let destination = CpuAccessibleBuffer::from_data(device.clone(), BufferUsage::all(), false, 0).unwrap(); let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::MultipleSubmit, ) .unwrap(); cbb.copy_buffer(buffer, destination.clone()).unwrap(); let _ = cbb .build() .unwrap() .execute(queue.clone()) .unwrap() .then_signal_fence_and_flush() .unwrap(); let destination_content = destination.read().unwrap(); assert_eq!(*destination_content, 12); } #[test] fn from_iter_working() { let (device, queue) = gfx_dev_and_queue!(); let (buffer, _) = ImmutableBuffer::from_iter( (0..512u32).map(|n| n * 2), BufferUsage::all(), queue.clone(), ) .unwrap(); let destination = CpuAccessibleBuffer::from_iter( device.clone(), BufferUsage::all(), false, (0..512).map(|_| 0u32), ) .unwrap(); let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::MultipleSubmit, ) .unwrap(); cbb.copy_buffer(buffer, destination.clone()).unwrap(); let _ = cbb .build() .unwrap() .execute(queue.clone()) .unwrap() .then_signal_fence_and_flush() .unwrap(); let destination_content = destination.read().unwrap(); for (n, &v) in destination_content.iter().enumerate() { assert_eq!(n * 2, v as usize); } } #[test] fn writing_forbidden() { let (device, queue) = gfx_dev_and_queue!(); let (buffer, _) = ImmutableBuffer::from_data(12u32, BufferUsage::all(), queue.clone()).unwrap(); assert_should_panic!({ // TODO: check Result error instead of panicking let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::MultipleSubmit, ) .unwrap(); cbb.fill_buffer(buffer, 50).unwrap(); let _ = cbb .build() .unwrap() .execute(queue.clone()) .unwrap() .then_signal_fence_and_flush() .unwrap(); }); } #[test] fn read_uninitialized_forbidden() { let (device, queue) = gfx_dev_and_queue!(); let (buffer, _) = unsafe { ImmutableBuffer::::uninitialized(device.clone(), BufferUsage::all()).unwrap() }; let source = CpuAccessibleBuffer::from_data(device.clone(), BufferUsage::all(), false, 0).unwrap(); assert_should_panic!({ // TODO: check Result error instead of panicking let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::MultipleSubmit, ) .unwrap(); cbb.copy_buffer(source, buffer).unwrap(); let _ = cbb .build() .unwrap() .execute(queue.clone()) .unwrap() .then_signal_fence_and_flush() .unwrap(); }); } #[test] fn init_then_read_same_cb() { let (device, queue) = gfx_dev_and_queue!(); let (buffer, init) = unsafe { ImmutableBuffer::::uninitialized(device.clone(), BufferUsage::all()).unwrap() }; let source = CpuAccessibleBuffer::from_data(device.clone(), BufferUsage::all(), false, 0).unwrap(); let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::MultipleSubmit, ) .unwrap(); cbb.copy_buffer(source.clone(), init) .unwrap() .copy_buffer(buffer, source.clone()) .unwrap(); let _ = cbb .build() .unwrap() .execute(queue.clone()) .unwrap() .then_signal_fence_and_flush() .unwrap(); } #[test] #[ignore] // TODO: doesn't work because the submit sync layer isn't properly implemented fn init_then_read_same_future() { let (device, queue) = gfx_dev_and_queue!(); let (buffer, init) = unsafe { ImmutableBuffer::::uninitialized(device.clone(), BufferUsage::all()).unwrap() }; let source = CpuAccessibleBuffer::from_data(device.clone(), BufferUsage::all(), false, 0).unwrap(); let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::MultipleSubmit, ) .unwrap(); cbb.copy_buffer(source.clone(), init).unwrap(); let cb1 = cbb.build().unwrap(); let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::MultipleSubmit, ) .unwrap(); cbb.copy_buffer(buffer, source.clone()).unwrap(); let cb2 = cbb.build().unwrap(); let _ = cb1 .execute(queue.clone()) .unwrap() .then_execute(queue.clone(), cb2) .unwrap() .then_signal_fence_and_flush() .unwrap(); } #[test] fn create_buffer_zero_size_data() { let (device, queue) = gfx_dev_and_queue!(); let _ = ImmutableBuffer::from_data((), BufferUsage::all(), queue.clone()); } // TODO: write tons of tests that try to exploit loopholes // this isn't possible yet because checks aren't correctly implemented yet }