Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
matrix:
include:
- os: ubuntu-latest
features: vulkan,visualizer
features: vulkan,visualizer,presser
- os: windows-latest
features: vulkan,visualizer,d3d12,public-winapi
features: vulkan,visualizer,d3d12,public-winapi,presser
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
Expand All @@ -31,9 +31,9 @@ jobs:
matrix:
include:
- os: ubuntu-latest
features: vulkan,visualizer
features: vulkan,visualizer,presser
- os: windows-latest
features: vulkan,visualizer,d3d12,public-winapi
features: vulkan,visualizer,d3d12,public-winapi,presser
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
Expand Down Expand Up @@ -75,9 +75,9 @@ jobs:
matrix:
include:
- os: ubuntu-latest
features: vulkan,visualizer
features: vulkan,visualizer,presser
- os: windows-latest
features: vulkan,visualizer,d3d12,public-winapi
features: vulkan,visualizer,d3d12,public-winapi,presser
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# will have compiled files and executables
/target/

# A semi-common pattern is to have rust-analyzer compile into a separate target
# directory like /tmp so that it doesn't conflict with other instances of cargo
/tmp

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ include = [
backtrace = "0.3"
log = "0.4"
thiserror = "1.0"
presser = { version = "0.3", optional = true }
# Only needed for vulkan. Disable all default features as good practice,
# such as the ability to link/load a Vulkan library.
ash = { version = ">=0.34, <=0.37", optional = true, default-features = false, features = ["debug"] }
Expand Down Expand Up @@ -72,7 +73,7 @@ required-features = ["vulkan", "ash/loaded"]

[[example]]
name = "vulkan-visualization"
required-features = ["vulkan", "ash/loaded", "visualizer"]
required-features = ["vulkan", "ash/loaded", "visualizer", "presser"]

[[example]]
name = "d3d12-buffer"
Expand All @@ -94,4 +95,4 @@ d3d12 = ["windows"]
# Expose helper functionality for winapi types to interface with gpu-allocator, which is primarily windows-rs driven
public-winapi = ["dep:winapi"]

default = ["d3d12", "vulkan"]
default = ["d3d12", "vulkan", "presser"]
86 changes: 35 additions & 51 deletions examples/vulkan-visualization/imgui_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ impl ImGuiRenderer {
let image_view = unsafe { device.create_image_view(&view_create_info, None) }?;

// Create upload buffer
let (upload_buffer, upload_buffer_memory) = {
let (upload_buffer, mut upload_buffer_memory) = {
let create_info = vk::BufferCreateInfo::builder()
.size((font_atlas.width * font_atlas.height * 4) as u64)
.usage(vk::BufferUsageFlags::TRANSFER_SRC);
Expand Down Expand Up @@ -338,14 +338,8 @@ impl ImGuiRenderer {
};

// Copy font data to upload buffer
let dst = upload_buffer_memory.mapped_ptr().unwrap().cast().as_ptr();
unsafe {
std::ptr::copy_nonoverlapping(
font_atlas.data.as_ptr(),
dst,
(font_atlas.width * font_atlas.height * 4) as usize,
);
}
let mut slab = upload_buffer_memory.as_mapped_slab().unwrap();
presser::copy_from_slice_to_offset(font_atlas.data, &mut slab, 0).unwrap();

// Copy upload buffer to image
record_and_submit_command_buffer(
Expand Down Expand Up @@ -633,13 +627,13 @@ impl ImGuiRenderer {
],
};

unsafe {
std::ptr::copy_nonoverlapping(
&cbuffer_data,
self.cb_allocation.mapped_ptr().unwrap().cast().as_ptr(),
1,
)
};
let copy_record = presser::copy_to_offset(
&cbuffer_data,
&mut self.cb_allocation.as_mapped_slab().unwrap(),
0,
)
.unwrap();
assert_eq!(copy_record.copy_start_offset, 0);
}

let render_pass_begin_info = vk::RenderPassBeginInfo::builder()
Expand Down Expand Up @@ -715,50 +709,40 @@ impl ImGuiRenderer {
let mut vb_offset = 0;
let mut ib_offset = 0;

for draw_list in imgui_draw_data.draw_lists() {
unsafe {
device.cmd_bind_vertex_buffers(
cmd,
0,
&[self.vertex_buffer],
&[vb_offset as u64 * std::mem::size_of::<imgui::DrawVert>() as u64],
)
};
unsafe {
device.cmd_bind_index_buffer(
cmd,
self.index_buffer,
ib_offset as u64 * std::mem::size_of::<imgui::DrawIdx>() as u64,
vk::IndexType::UINT16,
)
};
let mut vb_slab = self.vb_allocation.as_mapped_slab().unwrap();
let mut ib_slab = self.ib_allocation.as_mapped_slab().unwrap();

for draw_list in imgui_draw_data.draw_lists() {
{
let vertices = draw_list.vtx_buffer();
let dst_ptr = self
.vb_allocation
.mapped_ptr()
.unwrap()
.cast::<imgui::DrawVert>()
.as_ptr();
let dst_ptr = unsafe { dst_ptr.offset(vb_offset) };
let copy_record =
presser::copy_from_slice_to_offset(vertices, &mut vb_slab, vb_offset).unwrap();
vb_offset = copy_record.copy_end_offset_padded;

unsafe {
std::ptr::copy_nonoverlapping(vertices.as_ptr(), dst_ptr, vertices.len())
device.cmd_bind_vertex_buffers(
cmd,
0,
&[self.vertex_buffer],
&[copy_record.copy_start_offset as _],
)
};
vb_offset += vertices.len() as isize;
}

{
let indices = draw_list.idx_buffer();
let dst_ptr = self
.ib_allocation
.mapped_ptr()
.unwrap()
.cast::<imgui::DrawIdx>()
.as_ptr();
let dst_ptr = unsafe { dst_ptr.offset(ib_offset) };
unsafe { std::ptr::copy_nonoverlapping(indices.as_ptr(), dst_ptr, indices.len()) };
ib_offset += indices.len() as isize;
let copy_record =
presser::copy_from_slice_to_offset(indices, &mut ib_slab, ib_offset).unwrap();
ib_offset = copy_record.copy_end_offset_padded;

unsafe {
device.cmd_bind_index_buffer(
cmd,
self.index_buffer,
copy_record.copy_start_offset as _,
vk::IndexType::UINT16,
)
};
}

for command in draw_list.commands() {
Expand Down
3 changes: 3 additions & 0 deletions src/vulkan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ mod visualizer;
#[cfg(feature = "visualizer")]
pub use visualizer::AllocatorVisualizer;

#[cfg(feature = "presser")]
mod presser;

use super::allocator;
use super::allocator::AllocationType;
use ash::vk;
Expand Down
90 changes: 90 additions & 0 deletions src/vulkan/presser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use super::Allocation;
use core::convert::TryFrom;

impl Allocation {
/// Borrow the CPU-mapped memory that backs this allocation as a [`presser::Slab`], which you can then
/// use to safely copy data into the raw, potentially-uninitialized buffer.
///
/// Returns [`None`] if `self.mapped_ptr()` is `None`, or if `self.size()` is greater than `isize::MAX` because
/// this could lead to undefined behavior.
///
/// # Example
///
/// ```ignore
/// #[repr(C, align(16))]
/// #[derive(Clone, Copy)]
/// struct MyGpuVector {
/// x: f32,
/// y: f32,
/// z: f32,
/// }
///
/// // Create some data to be sent to the GPU. Note this must be formatted correctly in terms of
/// // alignment of individual items and etc, as usual.
/// let my_gpu_data: &[MyGpuVector] = get_vertex_data();
///
/// // Get a `presser::Slab` from our gpu_allocator::Allocation
/// let mut alloc_slab = my_allocation.as_mapped_slab().unwrap();
///
/// // depending on the type of data you're copying, your vulkan device may have a minimum
/// // alignment requirement for that data
/// let min_gpu_align = my_vulkan_device_specifications.min_alignment_thing;
///
/// let copy_record = presser::copy_from_slice_to_offset_with_align(
/// my_gpu_data,
/// &mut alloc_slab,
/// 0, // start as close to the beginning of the allocation as possible
/// min_gpu_align,
/// );
///
/// // the data may not have actually been copied starting at the requested start offset
/// // depending on the alignment of the underlying allocation, as well as the alignment requirements of
/// // `MyGpuVector` and the `min_gpu_align` we passed in
/// let actual_data_start_offset = copy_record.copy_start_offset;
/// ```
///
/// # Safety
///
/// This is technically not fully safe because we can't validate that the
/// GPU is not using the data in the buffer while `self` is borrowed, however trying
/// to validate this statically is really hard and the community has basically decided
/// that just calling stuff like this is fine. So, as would always be the case, ensure the GPU
/// is not using the data in `self` before calling this function.
pub fn as_mapped_slab(&mut self) -> Option<MappedAllocationSlab<'_>> {
let mapped_ptr = self.mapped_ptr()?.cast().as_ptr();
// size > isize::MAX is disallowed by `Slab` for safety reasons
let size = isize::try_from(self.size()).ok()?;
// this will always succeed since size can only be positive and < isize::MAX
let size = size as usize;

Some(MappedAllocationSlab {
_borrowed_alloc: self,
mapped_ptr,
size,
})
}
}

/// A wrapper struct over a borrowed [`Allocation`] that implements [`presser::Slab`].
///
/// This type should be acquired by calling [`Allocation::as_mapped_slab`].
pub struct MappedAllocationSlab<'a> {
_borrowed_alloc: &'a mut Allocation,
mapped_ptr: *mut u8,
size: usize,
}

// SAFETY: See the safety comment of Allocation::as_mapped_slab above.
unsafe impl<'a> presser::Slab for MappedAllocationSlab<'a> {
fn base_ptr(&self) -> *const u8 {
self.mapped_ptr
}

fn base_ptr_mut(&mut self) -> *mut u8 {
self.mapped_ptr
}

fn size(&self) -> usize {
self.size
}
}