Skip to content

Commit 006ade8

Browse files
fu5haMarijnS95
andauthored
Add support for presser (#138)
* Add `presser` support for vulkan Allocation * ignore tmp dir * fmt + slight doc update * user presser in imgui example renderer * make it prettier and add presser to dev-dependencies * fix example code a bit * add presser feature to ci * Update src/vulkan/presser.rs Co-authored-by: Marijn Suijten <[email protected]> * > to greater than * add "presser" to vulkan-visualization required features * remove ```rust * make presser not optional * remove redundant presser mod * impl Slab on Allocation directly * revert gitignore --------- Co-authored-by: Marijn Suijten <[email protected]>
1 parent 1800231 commit 006ade8

File tree

3 files changed

+202
-51
lines changed

3 files changed

+202
-51
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include = [
2222
backtrace = "0.3"
2323
log = "0.4"
2424
thiserror = "1.0"
25+
presser = { version = "0.3" }
2526
# Only needed for vulkan. Disable all default features as good practice,
2627
# such as the ability to link/load a Vulkan library.
2728
ash = { version = ">=0.34, <=0.37", optional = true, default-features = false, features = ["debug"] }

examples/vulkan-visualization/imgui_renderer.rs

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ impl ImGuiRenderer {
310310
let image_view = unsafe { device.create_image_view(&view_create_info, None) }?;
311311

312312
// Create upload buffer
313-
let (upload_buffer, upload_buffer_memory) = {
313+
let (upload_buffer, mut upload_buffer_memory) = {
314314
let create_info = vk::BufferCreateInfo::builder()
315315
.size((font_atlas.width * font_atlas.height * 4) as u64)
316316
.usage(vk::BufferUsageFlags::TRANSFER_SRC);
@@ -340,14 +340,10 @@ impl ImGuiRenderer {
340340
};
341341

342342
// Copy font data to upload buffer
343-
let dst = upload_buffer_memory.mapped_ptr().unwrap().cast().as_ptr();
344-
unsafe {
345-
std::ptr::copy_nonoverlapping(
346-
font_atlas.data.as_ptr(),
347-
dst,
348-
(font_atlas.width * font_atlas.height * 4) as usize,
349-
);
350-
}
343+
let copy_record =
344+
presser::copy_from_slice_to_offset(font_atlas.data, &mut upload_buffer_memory, 0)
345+
.unwrap();
346+
assert_eq!(copy_record.copy_start_offset, 0);
351347

352348
// Copy upload buffer to image
353349
record_and_submit_command_buffer(
@@ -638,13 +634,9 @@ impl ImGuiRenderer {
638634
],
639635
};
640636

641-
unsafe {
642-
std::ptr::copy_nonoverlapping(
643-
&cbuffer_data,
644-
self.cb_allocation.mapped_ptr().unwrap().cast().as_ptr(),
645-
1,
646-
)
647-
};
637+
let copy_record =
638+
presser::copy_to_offset(&cbuffer_data, &mut self.cb_allocation, 0).unwrap();
639+
assert_eq!(copy_record.copy_start_offset, 0);
648640
}
649641

650642
let render_pass_begin_info = vk::RenderPassBeginInfo::builder()
@@ -721,49 +713,41 @@ impl ImGuiRenderer {
721713
let mut ib_offset = 0;
722714

723715
for draw_list in imgui_draw_data.draw_lists() {
724-
unsafe {
725-
device.cmd_bind_vertex_buffers(
726-
cmd,
727-
0,
728-
&[self.vertex_buffer],
729-
&[vb_offset as u64 * std::mem::size_of::<imgui::DrawVert>() as u64],
730-
)
731-
};
732-
unsafe {
733-
device.cmd_bind_index_buffer(
734-
cmd,
735-
self.index_buffer,
736-
ib_offset as u64 * std::mem::size_of::<imgui::DrawIdx>() as u64,
737-
vk::IndexType::UINT16,
738-
)
739-
};
740-
741716
{
742717
let vertices = draw_list.vtx_buffer();
743-
let dst_ptr = self
744-
.vb_allocation
745-
.mapped_ptr()
746-
.unwrap()
747-
.cast::<imgui::DrawVert>()
748-
.as_ptr();
749-
let dst_ptr = unsafe { dst_ptr.offset(vb_offset) };
718+
let copy_record = presser::copy_from_slice_to_offset(
719+
vertices,
720+
&mut self.vb_allocation,
721+
vb_offset,
722+
)
723+
.unwrap();
724+
vb_offset = copy_record.copy_end_offset_padded;
725+
750726
unsafe {
751-
std::ptr::copy_nonoverlapping(vertices.as_ptr(), dst_ptr, vertices.len())
727+
device.cmd_bind_vertex_buffers(
728+
cmd,
729+
0,
730+
&[self.vertex_buffer],
731+
&[copy_record.copy_start_offset as _],
732+
)
752733
};
753-
vb_offset += vertices.len() as isize;
754734
}
755735

756736
{
757737
let indices = draw_list.idx_buffer();
758-
let dst_ptr = self
759-
.ib_allocation
760-
.mapped_ptr()
761-
.unwrap()
762-
.cast::<imgui::DrawIdx>()
763-
.as_ptr();
764-
let dst_ptr = unsafe { dst_ptr.offset(ib_offset) };
765-
unsafe { std::ptr::copy_nonoverlapping(indices.as_ptr(), dst_ptr, indices.len()) };
766-
ib_offset += indices.len() as isize;
738+
let copy_record =
739+
presser::copy_from_slice_to_offset(indices, &mut self.ib_allocation, ib_offset)
740+
.unwrap();
741+
ib_offset = copy_record.copy_end_offset_padded;
742+
743+
unsafe {
744+
device.cmd_bind_index_buffer(
745+
cmd,
746+
self.index_buffer,
747+
copy_record.copy_start_offset as _,
748+
vk::IndexType::UINT16,
749+
)
750+
};
767751
}
768752

769753
for command in draw_list.commands() {

src/vulkan/mod.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
66150
pub struct Allocation {
67151
chunk_id: Option<std::num::NonZeroU64>,
@@ -77,6 +161,39 @@ pub struct Allocation {
77161
}
78162

79163
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+
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)]
156322
pub(crate) struct MemoryBlock {
157323
pub(crate) device_memory: vk::DeviceMemory,

0 commit comments

Comments
 (0)