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#[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 #[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 #[inline(always)]
95 pub fn offset(&self) -> u64 {
96 self.offset
97 }
98
99 #[inline(always)]
101 pub fn size(&self) -> u64 {
102 self.size
103 }
104
105 #[inline(always)]
107 pub fn props(&self) -> MemoryPropertyFlags {
108 self.props
109 }
110
111 #[inline(always)]
113 pub fn memory_type(&self) -> u32 {
114 self.memory_type
115 }
116
117 #[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 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 #[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 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 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 #[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 #[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}