use std::sync::Arc; use ash::vk; use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme}; use gpu_allocator::MemoryLocation; use parking_lot::Mutex; use crate::*; static DOWNLOAD_NOT_ALLOWED_ERROR: &str = "Download from the GPU buffer is not allowed"; static UPLOAD_NOT_ALLOWED_ERROR: &str = "Upload to the GPU buffer is not allowed"; /// Buffer is a GPU resource that represents a linear memory region. pub struct Buffer { /// Device that owns the buffer. device: Arc, /// Vulkan buffer handle. vk_buffer: vk::Buffer, /// Buffer type. It defines how the buffer can be used. buffer_type: BufferType, /// Buffer size in bytes. size: usize, /// GPU memory allocation that backs the buffer. allocation: Mutex, } /// Buffer type defines how the buffer can be used. #[derive(PartialEq, Eq, Clone, Copy)] pub enum BufferType { /// Uniform data for a shader. Uniform, /// Storage buffer can be used as a large read/write buffer. Storage, /// CpuToGpu buffer can be used as a source for transfer operations only. CpuToGpu, /// GpuToCpu buffer can be used as a destination for transfer operations only. GpuToCpu, } // Mark `Buffer` as a GPU resource that should be kept alive while it's in use by the GPU context. impl Resource for Buffer {} impl Buffer { pub fn new( device: Arc, // Device that owns the buffer. name: impl AsRef, // Name of the buffer for tracking and debugging purposes. buffer_type: BufferType, size: usize, ) -> GpuResult> { if size == 0 { return Err(GpuError::NotSupported( "Zero-sized GPU buffers are not supported".to_string(), )); } // Vulkan API requires buffer usage flags to be specified during the buffer creation. let vk_usage_flags = match buffer_type { BufferType::Uniform => { vk::BufferUsageFlags::UNIFORM_BUFFER // mark as uniform buffer. | vk::BufferUsageFlags::TRANSFER_DST // For uploading. | vk::BufferUsageFlags::TRANSFER_SRC // For downloading. } BufferType::Storage => { vk::BufferUsageFlags::STORAGE_BUFFER // mark as storage buffer. | vk::BufferUsageFlags::TRANSFER_DST // For uploading. | vk::BufferUsageFlags::TRANSFER_SRC // For downloading. } // CpuToGpu buffer can be used as a source for transfer operations only. BufferType::CpuToGpu => vk::BufferUsageFlags::TRANSFER_SRC, // GpuToCpu buffer can be used as a destination for transfer operations only. BufferType::GpuToCpu => vk::BufferUsageFlags::TRANSFER_DST, }; // Memory location depends on the buffer type. let location = match buffer_type { // Allocate Uniform/Storage buffers in GPU memory only. BufferType::Uniform => MemoryLocation::GpuOnly, BufferType::Storage => MemoryLocation::GpuOnly, // Transfer buffers will be visible to both CPU and GPU. BufferType::CpuToGpu => MemoryLocation::CpuToGpu, BufferType::GpuToCpu => MemoryLocation::GpuToCpu, }; // Create a Vulkan buffer. let vk_create_buffer_info = vk::BufferCreateInfo::default() .size(size as vk::DeviceSize) .usage(vk_usage_flags) .sharing_mode(vk::SharingMode::EXCLUSIVE); let vk_buffer = unsafe { device .vk_device() .create_buffer(&vk_create_buffer_info, device.cpu_allocation_callbacks()) .unwrap() }; // Allocate memory for the buffer. let buffer_allocation_requirements = unsafe { device.vk_device().get_buffer_memory_requirements(vk_buffer) }; let allocation_result = device.allocate(&AllocationCreateDesc { name: name.as_ref(), requirements: buffer_allocation_requirements, location, linear: true, // Buffers are always linear. allocation_scheme: AllocationScheme::GpuAllocatorManaged, }); // Check if the allocation was successful. let allocation = match allocation_result { Ok(allocation) => allocation, Err(e) => { unsafe { // Because vulkan buffers lifetime is managed manually, // we need to destroy the buffer in case of an allocation error. device .vk_device() .destroy_buffer(vk_buffer, device.cpu_allocation_callbacks()); } return Err(e); } }; // Bind the buffer to the allocated memory. let bind_result = unsafe { device.vk_device().bind_buffer_memory( vk_buffer, allocation.memory(), allocation.offset(), ) }; if let Err(e) = bind_result { // Free the allocated memory in case of an error. device.free(allocation); unsafe { // Destroy the buffer. device .vk_device() .destroy_buffer(vk_buffer, device.cpu_allocation_callbacks()); } return Err(GpuError::from(e)); } Ok(Arc::new(Self { device, vk_buffer, buffer_type, size, allocation: Mutex::new(allocation), })) } pub fn size(&self) -> usize { self.size } pub fn vk_buffer(&self) -> vk::Buffer { self.vk_buffer } pub fn buffer_type(&self) -> BufferType { self.buffer_type } /// Download data from the buffer to the RAM. pub fn download(&self, data: &mut T, offset: usize) -> GpuResult<()> { if self.buffer_type != BufferType::GpuToCpu { return Err(GpuError::Other(DOWNLOAD_NOT_ALLOWED_ERROR.to_string())); } let bytes = memory::mmap_ops::transmute_to_u8_mut(data); self.download_bytes(bytes, offset) } /// Download data from the buffer to the RAM. pub fn download_slice(&self, data: &mut [T], offset: usize) -> GpuResult<()> { if self.buffer_type != BufferType::GpuToCpu { return Err(GpuError::Other(DOWNLOAD_NOT_ALLOWED_ERROR.to_string())); } let bytes = memory::mmap_ops::transmute_to_u8_slice_mut(data); self.download_bytes(bytes, offset) } /// Download data from the buffer to the RAM. pub fn download_bytes(&self, data: &mut [u8], offset: usize) -> GpuResult<()> { if self.buffer_type != BufferType::GpuToCpu { return Err(GpuError::Other(DOWNLOAD_NOT_ALLOWED_ERROR.to_string())); } if data.len() + offset > self.size { return Err(GpuError::OutOfBounds( "Out of bounds while downloading from GPU".to_string(), )); } let allocation = self.allocation.lock(); if let Some(slice) = allocation.mapped_slice() { data.copy_from_slice(&slice[offset..offset + data.len()]); Ok(()) } else { Err(GpuError::Other(DOWNLOAD_NOT_ALLOWED_ERROR.to_string())) } } /// Upload data from the RAM to the buffer. pub fn upload(&self, data: &T, offset: usize) -> GpuResult<()> { if self.buffer_type != BufferType::CpuToGpu { return Err(GpuError::Other(UPLOAD_NOT_ALLOWED_ERROR.to_string())); } let bytes = memory::mmap_ops::transmute_to_u8(data); self.upload_bytes(bytes, offset) } /// Upload data from the RAM to the buffer. pub fn upload_slice(&self, data: &[T], offset: usize) -> GpuResult<()> { if self.buffer_type != BufferType::CpuToGpu { return Err(GpuError::Other(UPLOAD_NOT_ALLOWED_ERROR.to_string())); } let bytes = memory::mmap_ops::transmute_to_u8_slice(data); self.upload_bytes(bytes, offset) } /// Upload data from the RAM to the buffer. pub fn upload_bytes(&self, data: &[u8], offset: usize) -> GpuResult<()> { if self.buffer_type != BufferType::CpuToGpu { return Err(GpuError::Other(UPLOAD_NOT_ALLOWED_ERROR.to_string())); } if data.len() + offset > self.size { return Err(GpuError::OutOfBounds( "Out of bounds while uploading to GPU".to_string(), )); } let mut allocation = self.allocation.lock(); if let Some(slice) = allocation.mapped_slice_mut() { slice[offset..offset + data.len()].copy_from_slice(data); Ok(()) } else { Err(GpuError::Other(DOWNLOAD_NOT_ALLOWED_ERROR.to_string())) } } } impl Drop for Buffer { fn drop(&mut self) { if self.vk_buffer != vk::Buffer::null() { // Drop the allocation and free the allocated memory. let mut allocation = Mutex::new(Allocation::default()); std::mem::swap(&mut allocation, &mut self.allocation); let allocation = allocation.into_inner(); self.device.free(allocation); // Destroy the buffer. unsafe { self.device .vk_device() .destroy_buffer(self.vk_buffer, self.device.cpu_allocation_callbacks()) }; self.vk_buffer = vk::Buffer::null(); } } }