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<MD>(
138        &mut self,
139        device: &impl AsRef<MD>,
140        offset: u64,
141        size: usize,
142    ) -> Result<NonNull<u8>, MapError>
143    where
144        MD: MemoryDevice<M>,
145    {
146        if !self.host_visible() {
147            return Err(MapError::NonHostVisible);
148        }
149
150        if !acquire_mapping(&mut self.mapped) {
151            return Err(MapError::AlreadyMapped);
152        }
153
154        let size_u64 = u64::try_from(size).expect("`size` doesn't fit device address space");
155        debug_assert!(offset < self.size, "`offset` is out of memory block bounds");
156        debug_assert!(
157            size_u64 <= self.size - offset,
158            "`offset + size` is out of memory block bounds"
159        );
160
161        let ptr = match &mut self.flavor {
162            MemoryBlockFlavor::Dedicated { memory } => {
163                let end = align_up(offset + size_u64, self.atom_mask)
164                    .expect("mapping end doesn't fit device address space");
165                let aligned_offset = align_down(offset, self.atom_mask);
166
167                let result =
168                    device.as_ref().map_memory(memory, self.offset + aligned_offset, end - aligned_offset);
169
170                match result {
171                    // the overflow is checked in `Self::new()`
172                    Ok(ptr) => {
173                        let ptr_offset = (offset - aligned_offset) as isize;
174                        ptr.as_ptr().offset(ptr_offset)
175                    }
176                    Err(err) => {
177                        release_mapping(&mut self.mapped);
178                        return Err(err.into());
179                    }
180                }
181            }
182            MemoryBlockFlavor::FreeList { ptr: Some(ptr), .. }
183            | MemoryBlockFlavor::Buddy { ptr: Some(ptr), .. } => {
184                let offset_isize = isize::try_from(offset)
185                    .expect("Buddy and linear block should fit host address space");
186                ptr.as_ptr().offset(offset_isize)
187            }
188            MemoryBlockFlavor::FreeList { ptr: None, .. } | MemoryBlockFlavor::Buddy { ptr: None, .. } => {
189                panic!("Buddy and linear block should always have a valid pointer when allocated in host-visible memory");
190            }
191        };
192
193        Ok(NonNull::new_unchecked(ptr))
194    }
195
196    /// Unmaps memory range of this block that was previously mapped with `Block::map`.
197    /// This block becomes unmapped.
198    ///
199    /// # Panics
200    ///
201    /// This function panics if this block is not currently mapped.
202    ///
203    /// # Safety
204    ///
205    /// `block` must have been allocated from specified `device`.
206    #[inline(always)]
207    pub unsafe fn unmap<MD>(&mut self, device: &impl AsRef<MD>) -> bool
208    where
209        MD: MemoryDevice<M>,
210    {
211        if !release_mapping(&mut self.mapped) {
212            return false;
213        }
214
215        match &mut self.flavor {
216            MemoryBlockFlavor::Dedicated { memory } => {
217                device.as_ref().unmap_memory(memory);
218            }
219            MemoryBlockFlavor::Buddy { .. } => {}
220            MemoryBlockFlavor::FreeList { .. } => {}
221        }
222        true
223    }
224
225    /// Flushes memory range of this block that was previously written to by the host.
226    /// This function is a no-op if the memory is host-coherent.
227    pub unsafe fn flush_range<MD>(&mut self, device: &impl AsRef<MD>, offset: u64, size: u64) -> Result<(), MapError>
228    where
229        MD: MemoryDevice<M>,
230    {
231        if !self.host_visible() {
232            return Err(MapError::NonHostVisible);
233        }
234
235        if !self.coherent() {
236            let aligned_offset = align_down(offset, self.atom_mask);
237            let end = align_up(offset + size, self.atom_mask).unwrap();
238
239            device.as_ref().flush_memory_ranges(&[MappedMemoryRange {
240                memory: self.memory(),
241                offset: self.offset + aligned_offset,
242                size: end - aligned_offset,
243            }])?;
244        }
245
246        Ok(())
247    }
248
249    /// Invalidates memory range of this block that was previously written to by the device.
250    /// This function is a no-op if the memory is host-coherent.
251    pub unsafe fn invalidate_range<MD>(&mut self, device: &impl AsRef<MD>, offset: u64, size: u64) -> Result<(), MapError>
252    where
253        MD: MemoryDevice<M>,
254    {
255        if !self.host_visible() {
256            return Err(MapError::NonHostVisible);
257        }
258
259        if !self.coherent() {
260            let aligned_offset = align_down(offset, self.atom_mask);
261            let end = align_up(offset + size, self.atom_mask).unwrap();
262
263            device.as_ref().invalidate_memory_ranges(&[MappedMemoryRange {
264                memory: self.memory(),
265                offset: self.offset + aligned_offset,
266                size: end - aligned_offset,
267            }])?;
268        }
269
270        Ok(())
271    }
272
273    /// Transiently maps block memory range and copies specified data
274    /// to the mapped memory range.
275    ///
276    /// # Panics
277    ///
278    /// This function panics if block is currently mapped.
279    ///
280    /// # Safety
281    ///
282    /// `block` must have been allocated from specified `device`.
283    /// The caller must guarantee that any previously submitted command that reads or writes to this range has completed.
284    #[inline(always)]
285    pub unsafe fn write_bytes<MD>(
286        &mut self,
287        device: &impl AsRef<MD>,
288        offset: u64,
289        data: &[u8],
290    ) -> Result<(), MapError>
291    where
292        MD: MemoryDevice<M>,
293    {
294        let size = data.len();
295        let ptr = self.map(device, offset, size)?;
296
297        copy_nonoverlapping(data.as_ptr(), ptr.as_ptr(), size);
298        
299        let result = self.flush_range(device, offset, size as u64);
300
301        self.unmap(device);
302        result
303    }
304
305    /// Transiently maps block memory range and copies specified data
306    /// from the mapped memory range.
307    ///
308    /// # Panics
309    ///
310    /// This function panics if block is currently mapped.
311    ///
312    /// # Safety
313    ///
314    /// `block` must have been allocated from specified `device`.
315    /// The caller must guarantee that any previously submitted command that reads to this range has completed.
316    #[inline(always)]
317    pub unsafe fn read_bytes<MD>(
318        &mut self,
319        device: &impl AsRef<MD>,
320        offset: u64,
321        data: &mut [u8],
322    ) -> Result<(), MapError>
323    where
324        MD: MemoryDevice<M>,
325    {
326        #[cfg(feature = "tracing")]
327        {
328            if !self.cached() {
329                tracing::warn!("Reading from non-cached memory may be slow. Consider allocating HOST_CACHED memory block for host reads.")
330            }
331        }
332
333        let size = data.len();
334        let ptr = self.map(device, offset, size)?;
335        let result = self.invalidate_range(device, offset, size as u64);
336
337        if result.is_ok() {
338            copy_nonoverlapping(ptr.as_ptr(), data.as_mut_ptr(), size);
339        }
340
341        self.unmap(device);
342        result
343    }
344
345    fn host_visible(&self) -> bool {
346        self.props.contains(MemoryPropertyFlags::HOST_VISIBLE)
347    }
348
349    fn coherent(&self) -> bool {
350        self.props.contains(MemoryPropertyFlags::HOST_COHERENT)
351    }
352
353    #[cfg(feature = "tracing")]
354    fn cached(&self) -> bool {
355        self.props.contains(MemoryPropertyFlags::HOST_CACHED)
356    }
357}
358
359fn acquire_mapping(mapped: &mut bool) -> bool {
360    if *mapped {
361        false
362    } else {
363        *mapped = true;
364        true
365    }
366}
367
368fn release_mapping(mapped: &mut bool) -> bool {
369    if *mapped {
370        *mapped = false;
371        true
372    } else {
373        false
374    }
375}