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<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 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 #[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 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 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 #[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 #[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}