blob: bb9c8e52d7494f86ca5386e19ddf6cfb1ac14f41 [file] [log] [blame]
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! 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<T: ?Sized, A = PotentialDedicatedAllocation<StdMemoryPoolAlloc>> {
// 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<Box<T>>,
}
// TODO: make this prettier
type ImmutableBufferFromBufferFuture = CommandBufferExecFuture<NowFuture, PrimaryAutoCommandBuffer>;
impl<T: ?Sized> ImmutableBuffer<T> {
/// 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<Queue>,
) -> Result<(Arc<ImmutableBuffer<T>>, 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<B>(
source: B,
usage: BufferUsage,
queue: Arc<Queue>,
) -> Result<(Arc<ImmutableBuffer<T>>, ImmutableBufferFromBufferFuture), DeviceMemoryAllocError>
where
B: BufferAccess + TypedBufferAccess<Content = T> + '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<T> ImmutableBuffer<T> {
/// 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<Device>,
usage: BufferUsage,
) -> Result<(Arc<ImmutableBuffer<T>>, ImmutableBufferInitialization<T>), DeviceMemoryAllocError>
{
ImmutableBuffer::raw(
device.clone(),
mem::size_of::<T>() as DeviceSize,
usage,
device.active_queue_families(),
)
}
}
impl<T> ImmutableBuffer<[T]> {
pub fn from_iter<D>(
data: D,
usage: BufferUsage,
queue: Arc<Queue>,
) -> Result<(Arc<ImmutableBuffer<[T]>>, ImmutableBufferFromBufferFuture), DeviceMemoryAllocError>
where
D: ExactSizeIterator<Item = T>,
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<Device>,
len: DeviceSize,
usage: BufferUsage,
) -> Result<
(
Arc<ImmutableBuffer<[T]>>,
ImmutableBufferInitialization<[T]>,
),
DeviceMemoryAllocError,
> {
ImmutableBuffer::raw(
device.clone(),
len * mem::size_of::<T>() as DeviceSize,
usage,
device.active_queue_families(),
)
}
}
impl<T: ?Sized> ImmutableBuffer<T> {
/// 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<Device>,
size: DeviceSize,
usage: BufferUsage,
queue_families: I,
) -> Result<(Arc<ImmutableBuffer<T>>, ImmutableBufferInitialization<T>), DeviceMemoryAllocError>
where
I: IntoIterator<Item = QueueFamily<'a>>,
{
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<Device>,
size: DeviceSize,
usage: BufferUsage,
queue_families: SmallVec<[u32; 4]>,
) -> Result<(Arc<ImmutableBuffer<T>>, ImmutableBufferInitialization<T>), 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<T: ?Sized, A> ImmutableBuffer<T, A> {
/// Returns the device used to create this buffer.
#[inline]
pub fn device(&self) -> &Arc<Device> {
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<QueueFamily> {
self.queue_families
.iter()
.map(|&num| {
self.device()
.physical_device()
.queue_family_by_id(num)
.unwrap()
})
.collect()
}
}
unsafe impl<T: ?Sized, A> BufferAccess for ImmutableBuffer<T, A> {
#[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<T: ?Sized, A> TypedBufferAccess for ImmutableBuffer<T, A> {
type Content = T;
}
unsafe impl<T: ?Sized, A> DeviceOwned for ImmutableBuffer<T, A> {
#[inline]
fn device(&self) -> &Arc<Device> {
self.inner.device()
}
}
impl<T: ?Sized, A> PartialEq for ImmutableBuffer<T, A> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner() == other.inner() && self.size() == other.size()
}
}
impl<T: ?Sized, A> Eq for ImmutableBuffer<T, A> {}
impl<T: ?Sized, A> Hash for ImmutableBuffer<T, A> {
#[inline]
fn hash<H: Hasher>(&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<StdMemoryPoolAlloc>,
> {
buffer: Arc<ImmutableBuffer<T, A>>,
used: Arc<AtomicBool>,
}
unsafe impl<T: ?Sized, A> BufferAccess for ImmutableBufferInitialization<T, A> {
#[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<T: ?Sized, A> TypedBufferAccess for ImmutableBufferInitialization<T, A> {
type Content = T;
}
unsafe impl<T: ?Sized, A> DeviceOwned for ImmutableBufferInitialization<T, A> {
#[inline]
fn device(&self) -> &Arc<Device> {
self.buffer.inner.device()
}
}
impl<T: ?Sized, A> Clone for ImmutableBufferInitialization<T, A> {
#[inline]
fn clone(&self) -> ImmutableBufferInitialization<T, A> {
ImmutableBufferInitialization {
buffer: self.buffer.clone(),
used: self.used.clone(),
}
}
}
impl<T: ?Sized, A> PartialEq for ImmutableBufferInitialization<T, A> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner() == other.inner() && self.size() == other.size()
}
}
impl<T: ?Sized, A> Eq for ImmutableBufferInitialization<T, A> {}
impl<T: ?Sized, A> Hash for ImmutableBufferInitialization<T, A> {
#[inline]
fn hash<H: Hasher>(&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::<u32>::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::<u32>::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::<u32>::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
}