@@ -62,6 +62,90 @@ pub struct AllocatorCreateDesc {
62
62
pub buffer_device_address : bool ,
63
63
}
64
64
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
65
149
#[ derive( Debug ) ]
66
150
pub struct Allocation {
67
151
chunk_id : Option < std:: num:: NonZeroU64 > ,
@@ -77,6 +161,39 @@ pub struct Allocation {
77
161
}
78
162
79
163
impl 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
+
80
197
pub fn chunk_id ( & self ) -> Option < std:: num:: NonZeroU64 > {
81
198
self . chunk_id
82
199
}
@@ -152,6 +269,55 @@ impl Default for Allocation {
152
269
}
153
270
}
154
271
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
+
155
321
#[ derive( Debug ) ]
156
322
pub ( crate ) struct MemoryBlock {
157
323
pub ( crate ) device_memory : vk:: DeviceMemory ,
0 commit comments