diff options
Diffstat (limited to 'src/buffer/sys.rs')
-rw-r--r-- | src/buffer/sys.rs | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/buffer/sys.rs b/src/buffer/sys.rs new file mode 100644 index 0000000..f462e00 --- /dev/null +++ b/src/buffer/sys.rs @@ -0,0 +1,591 @@ +// 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. + +//! Low level implementation of buffers. +//! +//! Wraps directly around Vulkan buffers, with the exceptions of a few safety checks. +//! +//! The `UnsafeBuffer` type is the lowest-level buffer object provided by this library. It is used +//! internally by the higher-level buffer types. You are strongly encouraged to have excellent +//! knowledge of the Vulkan specs if you want to use an `UnsafeBuffer`. +//! +//! Here is what you must take care of when you use an `UnsafeBuffer`: +//! +//! - Synchronization, ie. avoid reading and writing simultaneously to the same buffer. +//! - Memory aliasing considerations. If you use the same memory to back multiple resources, you +//! must ensure that they are not used together and must enable some additional flags. +//! - Binding memory correctly and only once. If you use sparse binding, respect the rules of +//! sparse binding. +//! - Type safety. + +use crate::check_errors; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::memory::DeviceMemory; +use crate::memory::DeviceMemoryAllocError; +use crate::memory::MemoryRequirements; +use crate::sync::Sharing; +use crate::DeviceSize; +use crate::Error; +use crate::OomError; +use crate::VulkanObject; +use crate::{buffer::BufferUsage, Version}; +use ash::vk::Handle; +use smallvec::SmallVec; +use std::error; +use std::fmt; +use std::hash::Hash; +use std::hash::Hasher; +use std::mem::MaybeUninit; +use std::ptr; +use std::sync::Arc; + +/// Data storage in a GPU-accessible location. +pub struct UnsafeBuffer { + buffer: ash::vk::Buffer, + device: Arc<Device>, + size: DeviceSize, + usage: BufferUsage, +} + +impl UnsafeBuffer { + /// Creates a new buffer of the given size. + /// + /// See the module's documentation for information about safety. + /// + /// # Panic + /// + /// - Panics if `sparse.sparse` is false and `sparse.sparse_residency` or `sparse.sparse_aliased` is true. + /// - Panics if `usage` is empty. + /// + pub unsafe fn new<'a, I>( + device: Arc<Device>, + size: DeviceSize, + mut usage: BufferUsage, + sharing: Sharing<I>, + sparse: Option<SparseLevel>, + ) -> Result<(UnsafeBuffer, MemoryRequirements), BufferCreationError> + where + I: Iterator<Item = u32>, + { + let fns = device.fns(); + + // Ensure we're not trying to create an empty buffer. + let size = if size == 0 { + // To avoid panicking when allocating 0 bytes, use a 1-byte buffer. + 1 + } else { + size + }; + + // Checking sparse features. + let flags = if let Some(sparse_level) = sparse { + if !device.enabled_features().sparse_binding { + return Err(BufferCreationError::SparseBindingFeatureNotEnabled); + } + + if sparse_level.sparse_residency && !device.enabled_features().sparse_residency_buffer { + return Err(BufferCreationError::SparseResidencyBufferFeatureNotEnabled); + } + + if sparse_level.sparse_aliased && !device.enabled_features().sparse_residency_aliased { + return Err(BufferCreationError::SparseResidencyAliasedFeatureNotEnabled); + } + + sparse_level.into() + } else { + ash::vk::BufferCreateFlags::empty() + }; + + if usage.device_address && !device.enabled_features().buffer_device_address { + usage.device_address = false; + if ash::vk::BufferUsageFlags::from(usage).is_empty() { + // return an error iff device_address was the only requested usage and the + // feature isn't enabled. Otherwise we'll hit that assert below. + // TODO: This is weird, why not just return an error always if the feature is not enabled? + // You can't use BufferUsage::all() anymore, but is that a good idea anyway? + return Err(BufferCreationError::DeviceAddressFeatureNotEnabled); + } + } + + let usage_bits = ash::vk::BufferUsageFlags::from(usage); + // Checking for empty BufferUsage. + assert!( + !usage_bits.is_empty(), + "Can't create buffer with empty BufferUsage" + ); + + let buffer = { + let (sh_mode, sh_indices) = match sharing { + Sharing::Exclusive => { + (ash::vk::SharingMode::EXCLUSIVE, SmallVec::<[u32; 8]>::new()) + } + Sharing::Concurrent(ids) => (ash::vk::SharingMode::CONCURRENT, ids.collect()), + }; + + let infos = ash::vk::BufferCreateInfo { + flags, + size, + usage: usage_bits, + sharing_mode: sh_mode, + queue_family_index_count: sh_indices.len() as u32, + p_queue_family_indices: sh_indices.as_ptr(), + ..Default::default() + }; + + let mut output = MaybeUninit::uninit(); + check_errors(fns.v1_0.create_buffer( + device.internal_object(), + &infos, + ptr::null(), + output.as_mut_ptr(), + ))?; + output.assume_init() + }; + + let mem_reqs = { + #[inline] + fn align(val: DeviceSize, al: DeviceSize) -> DeviceSize { + al * (1 + (val - 1) / al) + } + + let mut output = if device.api_version() >= Version::V1_1 + || device.enabled_extensions().khr_get_memory_requirements2 + { + let infos = ash::vk::BufferMemoryRequirementsInfo2 { + buffer: buffer, + ..Default::default() + }; + + let mut output2 = if device.api_version() >= Version::V1_1 + || device.enabled_extensions().khr_dedicated_allocation + { + Some(ash::vk::MemoryDedicatedRequirementsKHR::default()) + } else { + None + }; + + let mut output = ash::vk::MemoryRequirements2 { + p_next: output2 + .as_mut() + .map(|o| o as *mut ash::vk::MemoryDedicatedRequirementsKHR) + .unwrap_or(ptr::null_mut()) as *mut _, + ..Default::default() + }; + + if device.api_version() >= Version::V1_1 { + fns.v1_1.get_buffer_memory_requirements2( + device.internal_object(), + &infos, + &mut output, + ); + } else { + fns.khr_get_memory_requirements2 + .get_buffer_memory_requirements2_khr( + device.internal_object(), + &infos, + &mut output, + ); + } + + debug_assert!(output.memory_requirements.size >= size); + debug_assert!(output.memory_requirements.memory_type_bits != 0); + + let mut out = MemoryRequirements::from(output.memory_requirements); + if let Some(output2) = output2 { + debug_assert_eq!(output2.requires_dedicated_allocation, 0); + out.prefer_dedicated = output2.prefers_dedicated_allocation != 0; + } + out + } else { + let mut output: MaybeUninit<ash::vk::MemoryRequirements> = MaybeUninit::uninit(); + fns.v1_0.get_buffer_memory_requirements( + device.internal_object(), + buffer, + output.as_mut_ptr(), + ); + let output = output.assume_init(); + debug_assert!(output.size >= size); + debug_assert!(output.memory_type_bits != 0); + MemoryRequirements::from(output) + }; + + // We have to manually enforce some additional requirements for some buffer types. + let properties = device.physical_device().properties(); + if usage.uniform_texel_buffer || usage.storage_texel_buffer { + output.alignment = align( + output.alignment, + properties.min_texel_buffer_offset_alignment, + ); + } + + if usage.storage_buffer { + output.alignment = align( + output.alignment, + properties.min_storage_buffer_offset_alignment, + ); + } + + if usage.uniform_buffer { + output.alignment = align( + output.alignment, + properties.min_uniform_buffer_offset_alignment, + ); + } + + output + }; + + let obj = UnsafeBuffer { + buffer, + device: device.clone(), + size, + usage, + }; + + Ok((obj, mem_reqs)) + } + + /// Binds device memory to this buffer. + pub unsafe fn bind_memory( + &self, + memory: &DeviceMemory, + offset: DeviceSize, + ) -> Result<(), OomError> { + let fns = self.device.fns(); + + // We check for correctness in debug mode. + debug_assert!({ + let mut mem_reqs = MaybeUninit::uninit(); + fns.v1_0.get_buffer_memory_requirements( + self.device.internal_object(), + self.buffer, + mem_reqs.as_mut_ptr(), + ); + + let mem_reqs = mem_reqs.assume_init(); + mem_reqs.size <= (memory.size() - offset) + && (offset % mem_reqs.alignment) == 0 + && mem_reqs.memory_type_bits & (1 << memory.memory_type().id()) != 0 + }); + + // Check for alignment correctness. + { + let properties = self.device().physical_device().properties(); + if self.usage().uniform_texel_buffer || self.usage().storage_texel_buffer { + debug_assert!(offset % properties.min_texel_buffer_offset_alignment == 0); + } + if self.usage().storage_buffer { + debug_assert!(offset % properties.min_storage_buffer_offset_alignment == 0); + } + if self.usage().uniform_buffer { + debug_assert!(offset % properties.min_uniform_buffer_offset_alignment == 0); + } + } + + check_errors(fns.v1_0.bind_buffer_memory( + self.device.internal_object(), + self.buffer, + memory.internal_object(), + offset, + ))?; + Ok(()) + } + + /// Returns the size of the buffer in bytes. + #[inline] + pub fn size(&self) -> DeviceSize { + self.size + } + + /// Returns the buffer the image was created with. + #[inline] + pub fn usage(&self) -> BufferUsage { + self.usage + } + + /// Returns a key unique to each `UnsafeBuffer`. Can be used for the `conflicts_key` method. + #[inline] + pub fn key(&self) -> u64 { + self.buffer.as_raw() + } +} + +unsafe impl VulkanObject for UnsafeBuffer { + type Object = ash::vk::Buffer; + + #[inline] + fn internal_object(&self) -> ash::vk::Buffer { + self.buffer + } +} + +unsafe impl DeviceOwned for UnsafeBuffer { + #[inline] + fn device(&self) -> &Arc<Device> { + &self.device + } +} + +impl fmt::Debug for UnsafeBuffer { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "<Vulkan buffer {:?}>", self.buffer) + } +} + +impl Drop for UnsafeBuffer { + #[inline] + fn drop(&mut self) { + unsafe { + let fns = self.device.fns(); + fns.v1_0 + .destroy_buffer(self.device.internal_object(), self.buffer, ptr::null()); + } + } +} + +impl PartialEq for UnsafeBuffer { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.buffer == other.buffer && self.device == other.device + } +} + +impl Eq for UnsafeBuffer {} + +impl Hash for UnsafeBuffer { + #[inline] + fn hash<H: Hasher>(&self, state: &mut H) { + self.buffer.hash(state); + self.device.hash(state); + } +} + +/// The level of sparse binding that a buffer should be created with. +#[derive(Debug, Copy, Clone)] +pub struct SparseLevel { + pub sparse_residency: bool, + pub sparse_aliased: bool, +} + +impl SparseLevel { + #[inline] + pub fn none() -> SparseLevel { + SparseLevel { + sparse_residency: false, + sparse_aliased: false, + } + } +} + +impl From<SparseLevel> for ash::vk::BufferCreateFlags { + #[inline] + fn from(val: SparseLevel) -> Self { + let mut result = ash::vk::BufferCreateFlags::SPARSE_BINDING; + if val.sparse_residency { + result |= ash::vk::BufferCreateFlags::SPARSE_RESIDENCY; + } + if val.sparse_aliased { + result |= ash::vk::BufferCreateFlags::SPARSE_ALIASED; + } + result + } +} + +/// The device address usage flag was not set. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DeviceAddressUsageNotEnabledError; +impl error::Error for DeviceAddressUsageNotEnabledError {} +impl fmt::Display for DeviceAddressUsageNotEnabledError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("the device address usage flag was not set on this buffer") + } +} + +/// Error that can happen when creating a buffer. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BufferCreationError { + /// Allocating memory failed. + AllocError(DeviceMemoryAllocError), + /// Sparse binding was requested but the corresponding feature wasn't enabled. + SparseBindingFeatureNotEnabled, + /// Sparse residency was requested but the corresponding feature wasn't enabled. + SparseResidencyBufferFeatureNotEnabled, + /// Sparse aliasing was requested but the corresponding feature wasn't enabled. + SparseResidencyAliasedFeatureNotEnabled, + /// Device address was requested but the corresponding feature wasn't enabled. + DeviceAddressFeatureNotEnabled, +} + +impl error::Error for BufferCreationError { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + BufferCreationError::AllocError(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for BufferCreationError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + BufferCreationError::AllocError(_) => "allocating memory failed", + BufferCreationError::SparseBindingFeatureNotEnabled => { + "sparse binding was requested but the corresponding feature wasn't enabled" + } + BufferCreationError::SparseResidencyBufferFeatureNotEnabled => { + "sparse residency was requested but the corresponding feature wasn't enabled" + } + BufferCreationError::SparseResidencyAliasedFeatureNotEnabled => { + "sparse aliasing was requested but the corresponding feature wasn't enabled" + } + BufferCreationError::DeviceAddressFeatureNotEnabled => { + "device address was requested but the corresponding feature wasn't enabled" + } + } + ) + } +} + +impl From<OomError> for BufferCreationError { + #[inline] + fn from(err: OomError) -> BufferCreationError { + BufferCreationError::AllocError(err.into()) + } +} + +impl From<Error> for BufferCreationError { + #[inline] + fn from(err: Error) -> BufferCreationError { + match err { + err @ Error::OutOfHostMemory => { + BufferCreationError::AllocError(DeviceMemoryAllocError::from(err)) + } + err @ Error::OutOfDeviceMemory => { + BufferCreationError::AllocError(DeviceMemoryAllocError::from(err)) + } + _ => panic!("unexpected error: {:?}", err), + } + } +} + +#[cfg(test)] +mod tests { + use std::iter::Empty; + + use super::BufferCreationError; + use super::BufferUsage; + use super::SparseLevel; + use super::UnsafeBuffer; + + use crate::device::Device; + use crate::device::DeviceOwned; + use crate::sync::Sharing; + + #[test] + fn create() { + let (device, _) = gfx_dev_and_queue!(); + let (buf, reqs) = unsafe { + UnsafeBuffer::new( + device.clone(), + 128, + BufferUsage::all(), + Sharing::Exclusive::<Empty<_>>, + None, + ) + } + .unwrap(); + + assert!(reqs.size >= 128); + assert_eq!(buf.size(), 128); + assert_eq!(&**buf.device() as *const Device, &*device as *const Device); + } + + #[test] + fn missing_feature_sparse_binding() { + let (device, _) = gfx_dev_and_queue!(); + let sparse = Some(SparseLevel::none()); + unsafe { + match UnsafeBuffer::new( + device, + 128, + BufferUsage::all(), + Sharing::Exclusive::<Empty<_>>, + sparse, + ) { + Err(BufferCreationError::SparseBindingFeatureNotEnabled) => (), + _ => panic!(), + } + }; + } + + #[test] + fn missing_feature_sparse_residency() { + let (device, _) = gfx_dev_and_queue!(sparse_binding); + let sparse = Some(SparseLevel { + sparse_residency: true, + sparse_aliased: false, + }); + unsafe { + match UnsafeBuffer::new( + device, + 128, + BufferUsage::all(), + Sharing::Exclusive::<Empty<_>>, + sparse, + ) { + Err(BufferCreationError::SparseResidencyBufferFeatureNotEnabled) => (), + _ => panic!(), + } + }; + } + + #[test] + fn missing_feature_sparse_aliased() { + let (device, _) = gfx_dev_and_queue!(sparse_binding); + let sparse = Some(SparseLevel { + sparse_residency: false, + sparse_aliased: true, + }); + unsafe { + match UnsafeBuffer::new( + device, + 128, + BufferUsage::all(), + Sharing::Exclusive::<Empty<_>>, + sparse, + ) { + Err(BufferCreationError::SparseResidencyAliasedFeatureNotEnabled) => (), + _ => panic!(), + } + }; + } + + #[test] + fn create_empty_buffer() { + let (device, _) = gfx_dev_and_queue!(); + + unsafe { + let _ = UnsafeBuffer::new( + device, + 0, + BufferUsage::all(), + Sharing::Exclusive::<Empty<_>>, + None, + ); + }; + } +} |