aboutsummaryrefslogtreecommitdiff
path: root/src/buffer/sys.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer/sys.rs')
-rw-r--r--src/buffer/sys.rs591
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,
+ );
+ };
+ }
+}