328 lines
10 KiB
Rust
328 lines
10 KiB
Rust
use {
|
|
crate::{align_down, align_up, error::MapError},
|
|
alloc::sync::Arc,
|
|
core::{
|
|
convert::TryFrom as _,
|
|
ptr::{copy_nonoverlapping, NonNull},
|
|
// sync::atomic::{AtomicU8, Ordering::*},
|
|
},
|
|
gpu_alloc_types::{MappedMemoryRange, MemoryDevice, MemoryPropertyFlags},
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
struct Relevant;
|
|
|
|
impl Drop for Relevant {
|
|
fn drop(&mut self) {
|
|
report_error_on_drop!("Memory block wasn't deallocated");
|
|
}
|
|
}
|
|
|
|
/// Memory block allocated by `GpuAllocator`.
|
|
#[derive(Debug)]
|
|
pub struct MemoryBlock<M> {
|
|
memory_type: u32,
|
|
props: MemoryPropertyFlags,
|
|
offset: u64,
|
|
size: u64,
|
|
atom_mask: u64,
|
|
mapped: bool,
|
|
flavor: MemoryBlockFlavor<M>,
|
|
relevant: Relevant,
|
|
}
|
|
|
|
impl<M> MemoryBlock<M> {
|
|
pub(crate) fn new(
|
|
memory_type: u32,
|
|
props: MemoryPropertyFlags,
|
|
offset: u64,
|
|
size: u64,
|
|
atom_mask: u64,
|
|
flavor: MemoryBlockFlavor<M>,
|
|
) -> Self {
|
|
isize::try_from(atom_mask).expect("`atom_mask` is too large");
|
|
MemoryBlock {
|
|
memory_type,
|
|
props,
|
|
offset,
|
|
size,
|
|
atom_mask,
|
|
flavor,
|
|
mapped: false,
|
|
relevant: Relevant,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn deallocate(self) -> MemoryBlockFlavor<M> {
|
|
core::mem::forget(self.relevant);
|
|
self.flavor
|
|
}
|
|
}
|
|
|
|
unsafe impl<M> Sync for MemoryBlock<M> where M: Sync {}
|
|
unsafe impl<M> Send for MemoryBlock<M> where M: Send {}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum MemoryBlockFlavor<M> {
|
|
Dedicated {
|
|
memory: M,
|
|
},
|
|
Buddy {
|
|
chunk: usize,
|
|
index: usize,
|
|
ptr: Option<NonNull<u8>>,
|
|
memory: Arc<M>,
|
|
},
|
|
FreeList {
|
|
chunk: u64,
|
|
ptr: Option<NonNull<u8>>,
|
|
memory: Arc<M>,
|
|
},
|
|
}
|
|
|
|
impl<M> MemoryBlock<M> {
|
|
/// Returns reference to parent memory object.
|
|
#[inline(always)]
|
|
pub fn memory(&self) -> &M {
|
|
match &self.flavor {
|
|
MemoryBlockFlavor::Dedicated { memory } => memory,
|
|
MemoryBlockFlavor::Buddy { memory, .. } => memory,
|
|
MemoryBlockFlavor::FreeList { memory, .. } => memory,
|
|
}
|
|
}
|
|
|
|
/// Returns offset in bytes from start of memory object to start of this block.
|
|
#[inline(always)]
|
|
pub fn offset(&self) -> u64 {
|
|
self.offset
|
|
}
|
|
|
|
/// Returns size of this memory block.
|
|
#[inline(always)]
|
|
pub fn size(&self) -> u64 {
|
|
self.size
|
|
}
|
|
|
|
/// Returns memory property flags for parent memory object.
|
|
#[inline(always)]
|
|
pub fn props(&self) -> MemoryPropertyFlags {
|
|
self.props
|
|
}
|
|
|
|
/// Returns index of type of parent memory object.
|
|
#[inline(always)]
|
|
pub fn memory_type(&self) -> u32 {
|
|
self.memory_type
|
|
}
|
|
|
|
/// Returns pointer to mapped memory range of this block.
|
|
/// This blocks becomes mapped.
|
|
///
|
|
/// The user of returned pointer must guarantee that any previously submitted command that writes to this range has completed
|
|
/// before the host reads from or writes to that range,
|
|
/// and that any previously submitted command that reads from that range has completed
|
|
/// before the host writes to that region.
|
|
/// If the device memory was allocated without the `HOST_COHERENT` property flag set,
|
|
/// these guarantees must be made for an extended range:
|
|
/// the user must round down the start of the range to the nearest multiple of `non_coherent_atom_size`,
|
|
/// and round the end of the range up to the nearest multiple of `non_coherent_atom_size`.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function panics if block is currently mapped.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `block` must have been allocated from specified `device`.
|
|
#[inline(always)]
|
|
pub unsafe fn map(
|
|
&mut self,
|
|
device: &impl MemoryDevice<M>,
|
|
offset: u64,
|
|
size: usize,
|
|
) -> Result<NonNull<u8>, MapError> {
|
|
let size_u64 = u64::try_from(size).expect("`size` doesn't fit device address space");
|
|
assert!(offset < self.size, "`offset` is out of memory block bounds");
|
|
assert!(
|
|
size_u64 <= self.size - offset,
|
|
"`offset + size` is out of memory block bounds"
|
|
);
|
|
|
|
let ptr = match &mut self.flavor {
|
|
MemoryBlockFlavor::Dedicated { memory } => {
|
|
let end = align_up(offset + size_u64, self.atom_mask)
|
|
.expect("mapping end doesn't fit device address space");
|
|
let aligned_offset = align_down(offset, self.atom_mask);
|
|
|
|
if !acquire_mapping(&mut self.mapped) {
|
|
return Err(MapError::AlreadyMapped);
|
|
}
|
|
let result =
|
|
device.map_memory(memory, self.offset + aligned_offset, end - aligned_offset);
|
|
|
|
match result {
|
|
// the overflow is checked in `Self::new()`
|
|
Ok(ptr) => {
|
|
let ptr_offset = (offset - aligned_offset) as isize;
|
|
ptr.as_ptr().offset(ptr_offset)
|
|
}
|
|
Err(err) => {
|
|
release_mapping(&mut self.mapped);
|
|
return Err(err.into());
|
|
}
|
|
}
|
|
}
|
|
MemoryBlockFlavor::FreeList { ptr: Some(ptr), .. }
|
|
| MemoryBlockFlavor::Buddy { ptr: Some(ptr), .. } => {
|
|
if !acquire_mapping(&mut self.mapped) {
|
|
return Err(MapError::AlreadyMapped);
|
|
}
|
|
let offset_isize = isize::try_from(offset)
|
|
.expect("Buddy and linear block should fit host address space");
|
|
ptr.as_ptr().offset(offset_isize)
|
|
}
|
|
_ => return Err(MapError::NonHostVisible),
|
|
};
|
|
|
|
Ok(NonNull::new_unchecked(ptr))
|
|
}
|
|
|
|
/// Unmaps memory range of this block that was previously mapped with `Block::map`.
|
|
/// This block becomes unmapped.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function panics if this block is not currently mapped.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `block` must have been allocated from specified `device`.
|
|
#[inline(always)]
|
|
pub unsafe fn unmap(&mut self, device: &impl MemoryDevice<M>) -> bool {
|
|
if !release_mapping(&mut self.mapped) {
|
|
return false;
|
|
}
|
|
match &mut self.flavor {
|
|
MemoryBlockFlavor::Dedicated { memory } => {
|
|
device.unmap_memory(memory);
|
|
}
|
|
MemoryBlockFlavor::Buddy { .. } => {}
|
|
MemoryBlockFlavor::FreeList { .. } => {}
|
|
}
|
|
true
|
|
}
|
|
|
|
/// Transiently maps block memory range and copies specified data
|
|
/// to the mapped memory range.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function panics if block is currently mapped.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `block` must have been allocated from specified `device`.
|
|
/// The caller must guarantee that any previously submitted command that reads or writes to this range has completed.
|
|
#[inline(always)]
|
|
pub unsafe fn write_bytes(
|
|
&mut self,
|
|
device: &impl MemoryDevice<M>,
|
|
offset: u64,
|
|
data: &[u8],
|
|
) -> Result<(), MapError> {
|
|
let size = data.len();
|
|
let ptr = self.map(device, offset, size)?;
|
|
|
|
copy_nonoverlapping(data.as_ptr(), ptr.as_ptr(), size);
|
|
let result = if !self.coherent() {
|
|
let aligned_offset = align_down(offset, self.atom_mask);
|
|
let end = align_up(offset + data.len() as u64, self.atom_mask).unwrap();
|
|
|
|
device.flush_memory_ranges(&[MappedMemoryRange {
|
|
memory: self.memory(),
|
|
offset: self.offset + aligned_offset,
|
|
size: end - aligned_offset,
|
|
}])
|
|
} else {
|
|
Ok(())
|
|
};
|
|
|
|
self.unmap(device);
|
|
result.map_err(Into::into)
|
|
}
|
|
|
|
/// Transiently maps block memory range and copies specified data
|
|
/// from the mapped memory range.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function panics if block is currently mapped.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `block` must have been allocated from specified `device`.
|
|
/// The caller must guarantee that any previously submitted command that reads to this range has completed.
|
|
#[inline(always)]
|
|
pub unsafe fn read_bytes(
|
|
&mut self,
|
|
device: &impl MemoryDevice<M>,
|
|
offset: u64,
|
|
data: &mut [u8],
|
|
) -> Result<(), MapError> {
|
|
#[cfg(feature = "tracing")]
|
|
{
|
|
if !self.cached() {
|
|
tracing::warn!("Reading from non-cached memory may be slow. Consider allocating HOST_CACHED memory block for host reads.")
|
|
}
|
|
}
|
|
|
|
let size = data.len();
|
|
let ptr = self.map(device, offset, size)?;
|
|
let result = if !self.coherent() {
|
|
let aligned_offset = align_down(offset, self.atom_mask);
|
|
let end = align_up(offset + data.len() as u64, self.atom_mask).unwrap();
|
|
|
|
device.invalidate_memory_ranges(&[MappedMemoryRange {
|
|
memory: self.memory(),
|
|
offset: self.offset + aligned_offset,
|
|
size: end - aligned_offset,
|
|
}])
|
|
} else {
|
|
Ok(())
|
|
};
|
|
if result.is_ok() {
|
|
copy_nonoverlapping(ptr.as_ptr(), data.as_mut_ptr(), size);
|
|
}
|
|
|
|
self.unmap(device);
|
|
result.map_err(Into::into)
|
|
}
|
|
|
|
fn coherent(&self) -> bool {
|
|
self.props.contains(MemoryPropertyFlags::HOST_COHERENT)
|
|
}
|
|
|
|
#[cfg(feature = "tracing")]
|
|
fn cached(&self) -> bool {
|
|
self.props.contains(MemoryPropertyFlags::HOST_CACHED)
|
|
}
|
|
}
|
|
|
|
fn acquire_mapping(mapped: &mut bool) -> bool {
|
|
if *mapped {
|
|
false
|
|
} else {
|
|
*mapped = true;
|
|
true
|
|
}
|
|
}
|
|
|
|
fn release_mapping(mapped: &mut bool) -> bool {
|
|
if *mapped {
|
|
*mapped = false;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|