Skip to main content

gpu_alloc/
block.rs

1use {
2    crate::{align_down, align_up, error::MapError},
3    alloc::sync::Arc,
4    core::{
5        convert::TryFrom as _,
6        ptr::{copy_nonoverlapping, NonNull},
7    },
8    gpu_alloc_types::{MappedMemoryRange, MemoryDevice, MemoryPropertyFlags},
9};
10
11#[derive(Debug)]
12struct Relevant;
13
14impl Drop for Relevant {
15    fn drop(&mut self) {
16        report_error_on_drop!("Memory block wasn't deallocated");
17    }
18}
19
20/// Memory block allocated by `GpuAllocator`.
21#[derive(Debug)]
22pub struct MemoryBlock<M> {
23    memory_type: u32,
24    props: MemoryPropertyFlags,
25    offset: u64,
26    size: u64,
27    atom_mask: u64,
28    mapped: bool,
29    flavor: MemoryBlockFlavor<M>,
30    relevant: Relevant,
31}
32
33impl<M> MemoryBlock<M> {
34    pub(crate) fn new(
35        memory_type: u32,
36        props: MemoryPropertyFlags,
37        offset: u64,
38        size: u64,
39        atom_mask: u64,
40        flavor: MemoryBlockFlavor<M>,
41    ) -> Self {
42        isize::try_from(atom_mask).expect("`atom_mask` is too large");
43        MemoryBlock {
44            memory_type,
45            props,
46            offset,
47            size,
48            atom_mask,
49            flavor,
50            mapped: false,
51            relevant: Relevant,
52        }
53    }
54
55    pub(crate) fn deallocate(self) -> MemoryBlockFlavor<M> {
56        core::mem::forget(self.relevant);
57        self.flavor
58    }
59}
60
61unsafe impl<M> Sync for MemoryBlock<M> where M: Sync {}
62unsafe impl<M> Send for MemoryBlock<M> where M: Send {}
63
64#[derive(Debug)]
65pub(crate) enum MemoryBlockFlavor<M> {
66    Dedicated {
67        memory: M,
68    },
69    Buddy {
70        chunk: usize,
71        index: usize,
72        ptr: Option<NonNull<u8>>,
73        memory: Arc<M>,
74    },
75    FreeList {
76        chunk: u64,
77        ptr: Option<NonNull<u8>>,
78        memory: Arc<M>,
79    },
80}
81
82impl<M> MemoryBlock<M> {
83    /// Returns reference to parent memory object.
84    #[inline(always)]
85    pub fn memory(&self) -> &M {
86        match &self.flavor {
87            MemoryBlockFlavor::Dedicated { memory } => memory,
88            MemoryBlockFlavor::Buddy { memory, .. } => memory,
89            MemoryBlockFlavor::FreeList { memory, .. } => memory,
90        }
91    }
92
93    /// Returns offset in bytes from start of memory object to start of this block.
94    #[inline(always)]
95    pub fn offset(&self) -> u64 {
96        self.offset
97    }
98
99    /// Returns size of this memory block.
100    #[inline(always)]
101    pub fn size(&self) -> u64 {
102        self.size
103    }
104
105    /// Returns memory property flags for parent memory object.
106    #[inline(always)]
107    pub fn props(&self) -> MemoryPropertyFlags {
108        self.props
109    }
110
111    /// Returns index of type of parent memory object.
112    #[inline(always)]
113    pub fn memory_type(&self) -> u32 {
114        self.memory_type
115    }
116
117    /// Returns pointer to mapped memory range of this block.
118    /// This blocks becomes mapped.
119    ///
120    /// The user of returned pointer must guarantee that any previously submitted command that writes to this range has completed
121    /// before the host reads from or writes to that range,
122    /// and that any previously submitted command that reads from that range has completed
123    /// before the host writes to that region.
124    /// If the device memory was allocated without the `HOST_COHERENT` property flag set,
125    /// these guarantees must be made for an extended range:
126    /// the user must round down the start of the range to the nearest multiple of `non_coherent_atom_size`,
127    /// and round the end of the range up to the nearest multiple of `non_coherent_atom_size`.
128    ///
129    /// # Panics
130    ///
131    /// This function panics if block is currently mapped.
132    ///
133    /// # Safety
134    ///
135    /// `block` must have been allocated from specified `device`.
136    #[inline(always)]
137    pub unsafe fn map(
138        &mut self,
139        device: &impl MemoryDevice<M>,
140        offset: u64,
141        size: usize,
142    ) -> Result<NonNull<u8>, MapError>
143    {
144        if !self.host_visible() {
145            return Err(MapError::NonHostVisible);
146        }
147
148        if !acquire_mapping(&mut self.mapped) {
149            return Err(MapError::AlreadyMapped);
150        }
151
152        let size_u64 = u64::try_from(size).expect("`size` doesn't fit device address space");
153        debug_assert!(offset < self.size, "`offset` is out of memory block bounds");
154        debug_assert!(
155            size_u64 <= self.size - offset,
156            "`offset + size` is out of memory block bounds"
157        );
158
159        let ptr = match &mut self.flavor {
160            MemoryBlockFlavor::Dedicated { memory } => {
161                let end = align_up(offset + size_u64, self.atom_mask)
162                    .expect("mapping end doesn't fit device address space");
163                let aligned_offset = align_down(offset, self.atom_mask);
164
165                let result =
166                    device.map_memory(memory, self.offset + aligned_offset, end - aligned_offset);
167
168                match result {
169                    // the overflow is checked in `Self::new()`
170                    Ok(ptr) => {
171                        let ptr_offset = (offset - aligned_offset) as isize;
172                        ptr.as_ptr().offset(ptr_offset)
173                    }
174                    Err(err) => {
175                        release_mapping(&mut self.mapped);
176                        return Err(err.into());
177                    }
178                }
179            }
180            MemoryBlockFlavor::FreeList { ptr: Some(ptr), .. }
181            | MemoryBlockFlavor::Buddy { ptr: Some(ptr), .. } => {
182                let offset_isize = isize::try_from(offset)
183                    .expect("Buddy and linear block should fit host address space");
184                ptr.as_ptr().offset(offset_isize)
185            }
186            MemoryBlockFlavor::FreeList { ptr: None, .. } | MemoryBlockFlavor::Buddy { ptr: None, .. } => {
187                panic!("Buddy and linear block should always have a valid pointer when allocated in host-visible memory");
188            }
189        };
190
191        Ok(NonNull::new_unchecked(ptr))
192    }
193
194    /// Unmaps memory range of this block that was previously mapped with `Block::map`.
195    /// This block becomes unmapped.
196    ///
197    /// # Panics
198    ///
199    /// This function panics if this block is not currently mapped.
200    ///
201    /// # Safety
202    ///
203    /// `block` must have been allocated from specified `device`.
204    #[inline(always)]
205    pub unsafe fn unmap(&mut self, device: &impl MemoryDevice<M>) -> bool
206    {
207        if !release_mapping(&mut self.mapped) {
208            return false;
209        }
210
211        match &mut self.flavor {
212            MemoryBlockFlavor::Dedicated { memory } => {
213                device.unmap_memory(memory);
214            }
215            MemoryBlockFlavor::Buddy { .. } => {}
216            MemoryBlockFlavor::FreeList { .. } => {}
217        }
218        true
219    }
220
221    /// Flushes memory range of this block that was previously written to by the host.
222    /// This function is a no-op if the memory is host-coherent.
223    pub unsafe fn flush_range(&mut self, device: &impl MemoryDevice<M>, offset: u64, size: u64) -> Result<(), MapError>
224    {
225        if !self.host_visible() {
226            return Err(MapError::NonHostVisible);
227        }
228
229        if !self.coherent() {
230            let aligned_offset = align_down(offset, self.atom_mask);
231            let end = align_up(offset + size, self.atom_mask).unwrap();
232
233            device.flush_memory_ranges(&[MappedMemoryRange {
234                memory: self.memory(),
235                offset: self.offset + aligned_offset,
236                size: end - aligned_offset,
237            }])?;
238        }
239
240        Ok(())
241    }
242
243    /// Invalidates memory range of this block that was previously written to by the device.
244    /// This function is a no-op if the memory is host-coherent.
245    pub unsafe fn invalidate_range(&mut self, device: &impl MemoryDevice<M>, offset: u64, size: u64) -> Result<(), MapError>
246    {
247        if !self.host_visible() {
248            return Err(MapError::NonHostVisible);
249        }
250
251        if !self.coherent() {
252            let aligned_offset = align_down(offset, self.atom_mask);
253            let end = align_up(offset + size, self.atom_mask).unwrap();
254
255            device.invalidate_memory_ranges(&[MappedMemoryRange {
256                memory: self.memory(),
257                offset: self.offset + aligned_offset,
258                size: end - aligned_offset,
259            }])?;
260        }
261
262        Ok(())
263    }
264
265    /// Transiently maps block memory range and copies specified data
266    /// to the mapped memory range.
267    ///
268    /// # Panics
269    ///
270    /// This function panics if block is currently mapped.
271    ///
272    /// # Safety
273    ///
274    /// `block` must have been allocated from specified `device`.
275    /// The caller must guarantee that any previously submitted command that reads or writes to this range has completed.
276    #[inline(always)]
277    pub unsafe fn write_bytes(
278        &mut self,
279        device: &impl MemoryDevice<M>,
280        offset: u64,
281        data: &[u8],
282    ) -> Result<(), MapError>
283    {
284        let size = data.len();
285        let ptr = self.map(device, offset, size)?;
286
287        copy_nonoverlapping(data.as_ptr(), ptr.as_ptr(), size);
288        
289        let result = self.flush_range(device, offset, size as u64);
290
291        self.unmap(device);
292        result
293    }
294
295    /// Transiently maps block memory range and copies specified data
296    /// from the mapped memory range.
297    ///
298    /// # Panics
299    ///
300    /// This function panics if block is currently mapped.
301    ///
302    /// # Safety
303    ///
304    /// `block` must have been allocated from specified `device`.
305    /// The caller must guarantee that any previously submitted command that reads to this range has completed.
306    #[inline(always)]
307    pub unsafe fn read_bytes(
308        &mut self,
309        device: &impl MemoryDevice<M>,
310        offset: u64,
311        data: &mut [u8],
312    ) -> Result<(), MapError>
313    {
314        #[cfg(feature = "tracing")]
315        {
316            if !self.cached() {
317                tracing::warn!("Reading from non-cached memory may be slow. Consider allocating HOST_CACHED memory block for host reads.")
318            }
319        }
320
321        let size = data.len();
322        let ptr = self.map(device, offset, size)?;
323        let result = self.invalidate_range(device, offset, size as u64);
324
325        if result.is_ok() {
326            copy_nonoverlapping(ptr.as_ptr(), data.as_mut_ptr(), size);
327        }
328
329        self.unmap(device);
330        result
331    }
332
333    fn host_visible(&self) -> bool {
334        self.props.contains(MemoryPropertyFlags::HOST_VISIBLE)
335    }
336
337    fn coherent(&self) -> bool {
338        self.props.contains(MemoryPropertyFlags::HOST_COHERENT)
339    }
340
341    #[cfg(feature = "tracing")]
342    fn cached(&self) -> bool {
343        self.props.contains(MemoryPropertyFlags::HOST_CACHED)
344    }
345}
346
347fn acquire_mapping(mapped: &mut bool) -> bool {
348    if *mapped {
349        false
350    } else {
351        *mapped = true;
352        true
353    }
354}
355
356fn release_mapping(mapped: &mut bool) -> bool {
357    if *mapped {
358        *mapped = false;
359        true
360    } else {
361        false
362    }
363}