Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for presser #138

Merged
merged 15 commits into from
May 10, 2023
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
4 changes: 3 additions & 1 deletion 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 @@ -51,6 +52,7 @@ ash-window = "0.10.0"
raw-window-handle = "0.4"
winit = "0.26"
imgui-winit-support = { version = "0.8", default-features = false, features = ["winit-26"] }
presser = "0.3"

[target.'cfg(windows)'.dev-dependencies]
winapi = { version = "0.3.9", features = ["d3d12", "d3d12sdklayers", "dxgi1_6", "winerror", "impl-default", "impl-debug", "winuser", "windowsx", "libloaderapi"] }
Expand Down Expand Up @@ -94,4 +96,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 > `isize::MAX` because
/// this could lead to undefined behavior.
///
/// # Example
///
/// ```rust,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
}
}