@@ -62,6 +62,90 @@ pub struct AllocatorCreateDesc {
6262 pub buffer_device_address : bool ,
6363}
6464
65+ /// A piece of allocated memory.
66+ ///
67+ /// Could be contained in its own individual underlying memory object or as a sub-region
68+ /// of a larger allocation.
69+ ///
70+ /// # Copying data into a CPU-mapped [`Allocation`]
71+ ///
72+ /// You'll very likely want to copy data into CPU-mapped [`Allocation`]s in order to send that data to the GPU.
73+ /// Doing this data transfer correctly without invoking undefined behavior can be quite fraught and non-obvious<sup>[\[1\]]</sup>.
74+ ///
75+ /// To help you do this correctly, [`Allocation`] implements [`presser::Slab`], which means you can directly
76+ /// pass it in to many of `presser`'s [helper functions] (for example, [`copy_from_slice_to_offset`]).
77+ ///
78+ /// In most cases, this will work perfectly. However, note that if you try to use an [`Allocation`] as a
79+ /// [`Slab`] and it is not valid to do so (if it is not CPU-mapped or if its `size > isize::MAX`),
80+ /// you will cause a panic. If you aren't sure about these conditions, you may use [`Allocation::try_as_mapped_slab`].
81+ ///
82+ /// ## Example
83+ ///
84+ /// Say we've created an [`Allocation`] called `my_allocation`, which is CPU-mapped.
85+ /// ```ignore
86+ /// let mut my_allocation: Allocation = my_allocator.allocate(...)?;
87+ /// ```
88+ ///
89+ /// And we want to fill it with some data in the form of a `my_gpu_data: Vec<MyGpuVector>`, defined as such:
90+ ///
91+ /// ```ignore
92+ /// // note that this is size(12) but align(16), thus we have 4 padding bytes.
93+ /// // this would mean a `&[MyGpuVector]` is invalid to cast as a `&[u8]`, but
94+ /// // we can still use `presser` to copy it directly in a valid manner.
95+ /// #[repr(C, align(16))]
96+ /// #[derive(Clone, Copy)]
97+ /// struct MyGpuVertex {
98+ /// x: f32,
99+ /// y: f32,
100+ /// z: f32,
101+ /// }
102+ ///
103+ /// let my_gpu_data: Vec<MyGpuData> = make_vertex_data();
104+ /// ```
105+ ///
106+ /// Depending on how the data we're copying will be used, the vulkan device may have a minimum
107+ /// alignment requirement for that data:
108+ ///
109+ /// ```ignore
110+ /// let min_gpu_align = my_vulkan_device_specifications.min_alignment_thing;
111+ /// ```
112+ ///
113+ /// Finally, we can use [`presser::copy_from_slice_to_offset_with_align`] to perform the copy,
114+ /// simply passing `&mut my_allocation` since [`Allocation`] implements [`Slab`].
115+ ///
116+ /// ```ignore
117+ /// let copy_record = presser::copy_from_slice_to_offset_with_align(
118+ /// &my_gpu_data[..], // a slice containing all elements of my_gpu_data
119+ /// &mut my_allocation, // our Allocation
120+ /// 0, // start as close to the beginning of the allocation as possible
121+ /// min_gpu_align, // the minimum alignment we queried previously
122+ /// )?;
123+ /// ```
124+ ///
125+ /// It's important to note that the data may not have actually been copied starting at the requested
126+ /// `start_offset` (0 in the example above) depending on the alignment of the underlying allocation
127+ /// as well as the alignment requirements of `MyGpuVector` and the `min_gpu_align` we passed in. Thus,
128+ /// we can query the `copy_record` for the actual starting offset:
129+ ///
130+ /// ```ignore
131+ /// let actual_data_start_offset = copy_record.copy_start_offset;
132+ /// ```
133+ ///
134+ /// ## Safety
135+ ///
136+ /// It is technically not fully safe to use an [`Allocation`] as a [`presser::Slab`] because we can't validate that the
137+ /// GPU is not using the data in the buffer while `self` is borrowed. However, trying
138+ /// to validate this statically is really hard and the community has basically decided that
139+ /// requiring `unsafe` for functions like this creates too much "unsafe-noise", ultimately making it
140+ /// harder to debug more insidious unsafety that is unrelated to GPU-CPU sync issues.
141+ ///
142+ /// So, as would always be the case, you must ensure the GPU
143+ /// is not using the data in `self` for the duration that you hold the returned [`MappedAllocationSlab`].
144+ ///
145+ /// [`Slab`]: presser::Slab
146+ /// [`copy_from_slice_to_offset`]: presser::copy_from_slice_to_offset
147+ /// [helper functions]: presser#functions
148+ /// [\[1\]]: presser#motivation
65149#[ derive( Debug ) ]
66150pub struct Allocation {
67151 chunk_id : Option < std:: num:: NonZeroU64 > ,
@@ -77,6 +161,39 @@ pub struct Allocation {
77161}
78162
79163impl Allocation {
164+ /// Tries to borrow the CPU-mapped memory that backs this allocation as a [`presser::Slab`], which you can then
165+ /// use to safely copy data into the raw, potentially-uninitialized buffer.
166+ /// See [the documentation of Allocation][Allocation#example] for an example of this.
167+ ///
168+ /// Returns [`None`] if `self.mapped_ptr()` is `None`, or if `self.size()` is greater than `isize::MAX` because
169+ /// this could lead to undefined behavior.
170+ ///
171+ /// Note that [`Allocation`] implements [`Slab`] natively, so you can actually pass this allocation as a [`Slab`]
172+ /// directly. However, if `self` is not actually a valid [`Slab`] (this function would return `None` as described above),
173+ /// then trying to use it as a [`Slab`] will panic.
174+ ///
175+ /// # Safety
176+ ///
177+ /// See the note about safety in [the documentation of Allocation][Allocation#safety]
178+ ///
179+ /// [`Slab`]: presser::Slab
180+ pub fn try_as_mapped_slab ( & mut self ) -> Option < MappedAllocationSlab < ' _ > > {
181+ let mapped_ptr = self . mapped_ptr ( ) ?. cast ( ) . as_ptr ( ) ;
182+
183+ if self . size > isize:: MAX as _ {
184+ return None ;
185+ }
186+
187+ // this will always succeed since size is <= isize::MAX which is < usize::MAX
188+ let size = self . size as usize ;
189+
190+ Some ( MappedAllocationSlab {
191+ _borrowed_alloc : self ,
192+ mapped_ptr,
193+ size,
194+ } )
195+ }
196+
80197 pub fn chunk_id ( & self ) -> Option < std:: num:: NonZeroU64 > {
81198 self . chunk_id
82199 }
@@ -152,6 +269,55 @@ impl Default for Allocation {
152269 }
153270}
154271
272+ /// A wrapper struct over a borrowed [`Allocation`] that infallibly implements [`presser::Slab`].
273+ ///
274+ /// This type should be acquired by calling [`Allocation::try_as_mapped_slab`].
275+ pub struct MappedAllocationSlab < ' a > {
276+ _borrowed_alloc : & ' a mut Allocation ,
277+ mapped_ptr : * mut u8 ,
278+ size : usize ,
279+ }
280+
281+ // SAFETY: See the safety comment of Allocation::as_mapped_slab above.
282+ unsafe impl < ' a > presser:: Slab for MappedAllocationSlab < ' a > {
283+ fn base_ptr ( & self ) -> * const u8 {
284+ self . mapped_ptr
285+ }
286+
287+ fn base_ptr_mut ( & mut self ) -> * mut u8 {
288+ self . mapped_ptr
289+ }
290+
291+ fn size ( & self ) -> usize {
292+ self . size
293+ }
294+ }
295+
296+ // SAFETY: See the safety comment of Allocation::as_mapped_slab above.
297+ unsafe impl presser:: Slab for Allocation {
298+ fn base_ptr ( & self ) -> * const u8 {
299+ self . mapped_ptr
300+ . expect ( "tried to use a non-mapped Allocation as a Slab" )
301+ . as_ptr ( )
302+ . cast ( )
303+ }
304+
305+ fn base_ptr_mut ( & mut self ) -> * mut u8 {
306+ self . mapped_ptr
307+ . expect ( "tried to use a non-mapped Allocation as a Slab" )
308+ . as_ptr ( )
309+ . cast ( )
310+ }
311+
312+ fn size ( & self ) -> usize {
313+ if self . size > isize:: MAX as _ {
314+ panic ! ( "tried to use an Allocation with size > isize::MAX as a Slab" )
315+ }
316+ // this will always work if the above passed
317+ self . size as usize
318+ }
319+ }
320+
155321#[ derive( Debug ) ]
156322pub ( crate ) struct MemoryBlock {
157323 pub ( crate ) device_memory : vk:: DeviceMemory ,
0 commit comments