#[derive(Copy, Clone, Debug, Eq, PartialEq)] pubenum AllocationScheme { /// Perform a dedicated, driver-managed allocation for the given buffer, allowing /// it to perform optimizations on this type of allocation.
DedicatedBuffer(vk::Buffer), /// Perform a dedicated, driver-managed allocation for the given image, allowing /// it to perform optimizations on this type of allocation.
DedicatedImage(vk::Image), /// The memory for this resource will be allocated and managed by gpu-allocator.
GpuAllocatorManaged,
}
#[derive(Clone, Debug)] pubstruct AllocationCreateDesc<'a> { /// Name of the allocation, for tracking and debugging purposes pub name: &'a str, /// Vulkan memory requirements for an allocation pub requirements: vk::MemoryRequirements, /// Location where the memory allocation should be stored pub location: MemoryLocation, /// If the resource is linear (buffer / linear texture) or a regular (tiled) texture. pub linear: bool, /// Determines how this allocation should be managed. pub allocation_scheme: AllocationScheme,
}
/// Wrapper type to only mark a raw pointer [`Send`] + [`Sync`] without having to /// mark the entire [`Allocation`] as such, instead relying on the compiler to /// auto-implement this or fail if fields are added that violate this constraint #[derive(Clone, Copy, Debug)] pub(crate) struct SendSyncPtr(std::ptr::NonNull<std::ffi::c_void>); // Sending is fine because mapped_ptr does not change based on the thread we are in unsafeimpl Send for SendSyncPtr {} // Sync is also okay because Sending &Allocation is safe: a mutable reference // to the data in mapped_ptr is never exposed while `self` is immutably borrowed. // In order to break safety guarantees, the user needs to `unsafe`ly dereference // `mapped_ptr` themselves. unsafeimpl Sync for SendSyncPtr {}
/// A piece of allocated memory. /// /// Could be contained in its own individual underlying memory object or as a sub-region /// of a larger allocation. /// /// # Copying data into a CPU-mapped [`Allocation`] /// /// You'll very likely want to copy data into CPU-mapped [`Allocation`]s in order to send that data to the GPU. /// Doing this data transfer correctly without invoking undefined behavior can be quite fraught and non-obvious<sup>[\[1\]]</sup>. /// /// To help you do this correctly, [`Allocation`] implements [`presser::Slab`], which means you can directly /// pass it in to many of `presser`'s [helper functions] (for example, [`copy_from_slice_to_offset`]). /// /// In most cases, this will work perfectly. However, note that if you try to use an [`Allocation`] as a /// [`Slab`] and it is not valid to do so (if it is not CPU-mapped or if its `size > isize::MAX`), /// you will cause a panic. If you aren't sure about these conditions, you may use [`Allocation::try_as_mapped_slab`]. /// /// ## Example /// /// Say we've created an [`Allocation`] called `my_allocation`, which is CPU-mapped. /// ```ignore /// let mut my_allocation: Allocation = my_allocator.allocate(...)?; /// ``` /// /// And we want to fill it with some data in the form of a `my_gpu_data: Vec<MyGpuVector>`, defined as such: /// /// ```ignore /// // note that this is size(12) but align(16), thus we have 4 padding bytes. /// // this would mean a `&[MyGpuVector]` is invalid to cast as a `&[u8]`, but /// // we can still use `presser` to copy it directly in a valid manner. /// #[repr(C, align(16))] /// #[derive(Clone, Copy)] /// struct MyGpuVertex { /// x: f32, /// y: f32, /// z: f32, /// } /// /// let my_gpu_data: Vec<MyGpuData> = make_vertex_data(); /// ``` /// /// Depending on how the data we're copying will be used, the Vulkan device may have a minimum /// alignment requirement for that data: /// /// ```ignore /// let min_gpu_align = my_vulkan_device_specifications.min_alignment_thing; /// ``` /// /// Finally, we can use [`presser::copy_from_slice_to_offset_with_align`] to perform the copy, /// simply passing `&mut my_allocation` since [`Allocation`] implements [`Slab`]. /// /// ```ignore /// let copy_record = presser::copy_from_slice_to_offset_with_align( /// &my_gpu_data[..], // a slice containing all elements of my_gpu_data /// &mut my_allocation, // our Allocation /// 0, // start as close to the beginning of the allocation as possible /// min_gpu_align, // the minimum alignment we queried previously /// )?; /// ``` /// /// It's important to note that the data may not have actually been copied starting at the requested /// `start_offset` (0 in the example above) depending on the alignment of the underlying allocation /// as well as the alignment requirements of `MyGpuVector` and the `min_gpu_align` we passed in. Thus, /// we can query the `copy_record` for the actual starting offset: /// /// ```ignore /// let actual_data_start_offset = copy_record.copy_start_offset; /// ``` /// /// ## Safety /// /// It is technically not fully safe to use an [`Allocation`] as a [`presser::Slab`] because we can't validate that the /// GPU is not using the data in the buffer while `self` is borrowed. However, trying /// to validate this statically is really hard and the community has basically decided that /// requiring `unsafe` for functions like this creates too much "unsafe-noise", ultimately making it /// harder to debug more insidious unsafety that is unrelated to GPU-CPU sync issues. /// /// So, as would always be the case, you must ensure the GPU /// is not using the data in `self` for the duration that you hold the returned [`MappedAllocationSlab`]. /// /// [`Slab`]: presser::Slab /// [`copy_from_slice_to_offset`]: presser::copy_from_slice_to_offset /// [helper functions]: presser#functions /// [\[1\]]: presser#motivation #[derive(Debug)] pubstruct Allocation {
chunk_id: Option<std::num::NonZeroU64>,
offset: u64,
size: u64,
memory_block_index: usize,
memory_type_index: usize,
device_memory: vk::DeviceMemory,
mapped_ptr: Option<SendSyncPtr>,
dedicated_allocation: bool,
memory_properties: vk::MemoryPropertyFlags,
name: Option<Box<str>>,
}
impl Allocation { /// Tries to borrow the CPU-mapped memory that backs this allocation as a [`presser::Slab`], which you can then /// use to safely copy data into the raw, potentially-uninitialized buffer. /// See [the documentation of Allocation][Allocation#example] for an example of this. /// /// Returns [`None`] if `self.mapped_ptr()` is `None`, or if `self.size()` is greater than `isize::MAX` because /// this could lead to undefined behavior. /// /// Note that [`Allocation`] implements [`Slab`] natively, so you can actually pass this allocation as a [`Slab`] /// directly. However, if `self` is not actually a valid [`Slab`] (this function would return `None` as described above), /// then trying to use it as a [`Slab`] will panic. /// /// # Safety /// /// See the note about safety in [the documentation of Allocation][Allocation#safety] /// /// [`Slab`]: presser::Slab // best to be explicit where the lifetime is coming from since we're doing unsafe things // and relying on an inferred lifetime type in the PhantomData below #[allow(clippy::needless_lifetimes)] pubfn try_as_mapped_slab<'a>(&'a mutself) -> Option<MappedAllocationSlab<'a>> { let mapped_ptr = self.mapped_ptr()?.cast().as_ptr();
ifself.size > isize::MAX as _ { return None;
}
// this will always succeed since size is <= isize::MAX which is < usize::MAX let size = self.size as usize;
///Returns the [`vk::MemoryPropertyFlags`] of this allocation. pubfn memory_properties(&self) -> vk::MemoryPropertyFlags { self.memory_properties
}
/// Returns the [`vk::DeviceMemory`] object that is backing this allocation. /// This memory object can be shared with multiple other allocations and shouldn't be freed (or allocated from) /// without this library, because that will lead to undefined behavior. /// /// # Safety /// The result of this function can safely be used to pass into [`ash::Device::bind_buffer_memory()`], /// [`ash::Device::bind_image_memory()`] etc. It is exposed for this reason. Keep in mind to also /// pass [`Self::offset()`] along to those. pubunsafefn memory(&self) -> vk::DeviceMemory { self.device_memory
}
/// Returns [`true`] if this allocation is using a dedicated underlying allocation. pubfn is_dedicated(&self) -> bool { self.dedicated_allocation
}
/// Returns the offset of the allocation on the [`vk::DeviceMemory`]. /// When binding the memory to a buffer or image, this offset needs to be supplied as well. pubfn offset(&self) -> u64 { self.offset
}
/// Returns the size of the allocation pubfn size(&self) -> u64 { self.size
}
/// Returns a valid mapped pointer if the memory is host visible, otherwise it will return None. /// The pointer already points to the exact memory region of the suballocation, so no offset needs to be applied. pubfn mapped_ptr(&self) -> Option<std::ptr::NonNull<std::ffi::c_void>> { self.mapped_ptr.map(|SendSyncPtr(p)| p)
}
/// Returns a valid mapped slice if the memory is host visible, otherwise it will return None. /// The slice already references the exact memory region of the allocation, so no offset needs to be applied. pubfn mapped_slice(&self) -> Option<&[u8]> { self.mapped_ptr().map(|ptr| unsafe {
std::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize)
})
}
/// Returns a valid mapped mutable slice if the memory is host visible, otherwise it will return None. /// The slice already references the exact memory region of the allocation, so no offset needs to be applied. pubfn mapped_slice_mut(&mutself) -> Option<&mut [u8]> { self.mapped_ptr().map(|ptr| unsafe {
std::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize)
})
}
/// A wrapper struct over a borrowed [`Allocation`] that infallibly implements [`presser::Slab`]. /// /// This type should be acquired by calling [`Allocation::try_as_mapped_slab`]. pubstruct MappedAllocationSlab<'a> {
_borrowed_alloc: PhantomData<&'a mut Allocation>,
mapped_ptr: *mut u8,
size: usize,
}
// SAFETY: See the safety comment of Allocation::as_mapped_slab above. unsafeimpl<'a> presser::Slab for MappedAllocationSlab<'a> { fn base_ptr(&self) -> *const u8 { self.mapped_ptr
}
// SAFETY: See the safety comment of Allocation::as_mapped_slab above. unsafeimpl presser::Slab for Allocation { fn base_ptr(&self) -> *const u8 { self.mapped_ptr
.expect("tried to use a non-mapped Allocation as a Slab")
.0
.as_ptr()
.cast()
}
fn base_ptr_mut(&mutself) -> *mut u8 { self.mapped_ptr
.expect("tried to use a non-mapped Allocation as a Slab")
.0
.as_ptr()
.cast()
}
fn size(&self) -> usize { ifself.size > isize::MAX as _ {
panic!("tried to use an Allocation with size > isize::MAX as a Slab")
} // this will always work if the above passed self.size as usize
}
}
let allocation_flags = vk::MemoryAllocateFlags::DEVICE_ADDRESS; letmut flags_info = vk::MemoryAllocateFlagsInfo::default().flags(allocation_flags); // TODO(manon): Test this based on if the device has this feature enabled or not let alloc_info = if buffer_device_address {
alloc_info.push_next(&mut flags_info)
} else {
alloc_info
};
// Flag the memory as dedicated if required. letmut dedicated_memory_info = vk::MemoryDedicatedAllocateInfo::default(); let alloc_info = match allocation_scheme {
AllocationScheme::DedicatedBuffer(buffer) => {
dedicated_memory_info = dedicated_memory_info.buffer(buffer);
alloc_info.push_next(&mut dedicated_memory_info)
}
AllocationScheme::DedicatedImage(image) => {
dedicated_memory_info = dedicated_memory_info.image(image);
alloc_info.push_next(&mut dedicated_memory_info)
}
AllocationScheme::GpuAllocatorManaged => alloc_info,
};
unsafe { device.allocate_memory(&alloc_info, None) }.map_err(|e| match e {
vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => AllocationError::OutOfMemory,
e => AllocationError::Internal(format!( "Unexpected error in vkAllocateMemory: {:?}",
e
)),
})?
};
let size = desc.requirements.size; let alignment = desc.requirements.alignment;
let dedicated_allocation = desc.allocation_scheme != AllocationScheme::GpuAllocatorManaged; let requires_personal_block = size > memblock_size;
// Create a dedicated block for large memory allocations or allocations that require dedicated memory allocations. if dedicated_allocation || requires_personal_block { let mem_block = MemoryBlock::new(
device,
size, self.memory_type_index, self.mappable, self.buffer_device_address,
desc.allocation_scheme,
requires_personal_block,
)?;
letmut block_index = None; for (i, block) inself.memory_blocks.iter().enumerate() { if block.is_none() {
block_index = Some(i); break;
}
}
let block_index = match block_index {
Some(i) => { self.memory_blocks[i].replace(mem_block);
i
}
None => { self.memory_blocks.push(Some(mem_block)); self.memory_blocks.len() - 1
}
};
let mem_block = self.memory_blocks[block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
let mem_block = self.memory_blocks[new_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?; let allocation = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type,
granularity,
desc.name,
backtrace,
); let (offset, chunk_id) = match allocation {
Ok(value) => value,
Err(err) => match err {
AllocationError::OutOfMemory => { return Err(AllocationError::Internal( "Allocation that must succeed failed. This is a bug in the allocator."
.into(),
))
}
_ => return Err(err),
},
};
let mapped_ptr = iflet Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr { let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) };
std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr)
} else {
None
};
if mem_block.sub_allocator.is_empty() { if mem_block.sub_allocator.supports_general_allocations() { ifself.active_general_blocks > 1 { let block = self.memory_blocks[block_idx].take(); let block = block.ok_or_else(|| {
AllocationError::Internal("Memory block must be Some.".into())
})?;
block.destroy(device);
self.active_general_blocks -= 1;
}
} else { let block = self.memory_blocks[block_idx].take(); let block = block.ok_or_else(|| {
AllocationError::Internal("Memory block must be Some.".into())
})?;
block.destroy(device);
}
}
let memory_type_index = match memory_type_index_opt {
Some(x) => x as usize,
None => return Err(AllocationError::NoCompatibleMemoryTypeFound),
};
//Do not try to create a block if the heap is smaller than the required size (avoids validation warnings). let memory_type = &mutself.memory_types[memory_type_index]; let allocation = if size > self.memory_heaps[memory_type.heap_index].size {
Err(AllocationError::OutOfMemory)
} else {
memory_type.allocate(
&self.device,
desc, self.buffer_image_granularity,
backtrace.clone(),
&self.allocation_sizes,
)
};
if desc.location == MemoryLocation::CpuToGpu { if allocation.is_err() { let mem_loc_preferred_bits =
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
let memory_type_index_opt = self.find_memorytype_index(&desc.requirements, mem_loc_preferred_bits);
let memory_type_index = match memory_type_index_opt {
Some(x) => x as usize,
None => return Err(AllocationError::NoCompatibleMemoryTypeFound),
};
let mem_type = &mutself.memory_types[allocation.memory_type_index]; let mem_block = mem_type.memory_blocks[allocation.memory_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.