1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Allocator traits to implement for using your own custom allocator with phobos
use std::ffi::c_void;
use std::ptr::NonNull;
use anyhow::Result;
use ash::vk;
use crate::allocator::memory_type::MemoryType;
/// To supply custom allocators to phobos, this trait must be implemented.
/// Note that all allocators must be `Clone`, `Send` and `Sync`. To do this, wrap internal state in
/// `Arc<Mutex<T>>` or similar where applicable.
pub trait Allocator: Clone + Send + Sync {
/// Allocation type for this allocator. Must implement [`Allocation`]
type Allocation: Allocation;
/// Allocates raw memory of a specific memory type. The given name is used for internal tracking and
/// debug logging. To get proper [`VkMemoryRequirements`](crate::vk::MemoryRequirements),
/// call [`vkGetBufferMemoryRequirements`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetBufferMemoryRequirements.html) or
/// [`vkGetImageMemoryRequirements`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetImageMemoryRequirements.html) with your buffer or image.
/// # Example
/// ```
/// # use phobos::*;
/// # use anyhow::Result;
/// # fn vk_get_memory_requirements(device: &Device, buffer: &Buffer) -> vk::MemoryRequirements { unimplemented!() }
/// # fn vk_bind_buffer_memory(device: &Device, buffer: &Buffer, memory: vk::DeviceMemory, offset: u64) { unimplemented!() }
/// fn use_allocator<A: Allocator>(device: Device, allocator: &mut A, buffer: Buffer) -> Result<()> {
/// let requirements = vk_get_memory_requirements(&device, &buffer);
/// let memory = allocator.allocate("buffer_memory", &requirements, MemoryType::GpuOnly)?;
/// // SAFETY: We are passing `memory.offset()` correctly.
/// vk_bind_buffer_memory(&device, &buffer, unsafe { memory.memory() }, memory.offset());
/// Ok(())
/// }
/// ```
fn allocate(
&mut self,
name: &'static str,
requirements: &vk::MemoryRequirements,
ty: MemoryType,
) -> Result<Self::Allocation>;
/// Free some memory allocated from this allocator. It's allowed for this function to do nothing, and instead
/// use [`Drop`] to do this. Note that in this case, the allocation is still dropped. because it is moved into the function.
/// # Example
/// ```
/// # use phobos::*;
/// # use anyhow::Result;
/// # fn vk_get_memory_requirements(device: &Device, buffer: &Buffer) -> vk::MemoryRequirements { unimplemented!() } ///
/// // Admittedly, this is function kind of silly.
/// fn use_allocator<A: Allocator>(device: Device, allocator: &mut A, buffer: Buffer) -> Result<()> {
/// let requirements = vk_get_memory_requirements(&device, &buffer);
/// let memory = allocator.allocate("buffer_memory", &requirements, MemoryType::GpuOnly)?;
/// allocator.free(memory)
/// }
/// ```
fn free(&mut self, allocation: Self::Allocation) -> Result<()>;
}
/// Represents an allocation. This trait exposes methods for accessing the underlying device memory, obtain a mapped pointer, etc.
pub trait Allocation: Default {
/// Get unsafe access to the underlying [`VkDeviceMemory`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDeviceMemory.html).
/// Should always be used together with [`Allocation::offset()`].
/// # Example
/// This is useful when binding memory to a buffer or image. For [`Buffer`](crate::Buffer) and [`Image`](crate::Image) this is already
/// done internally.
/// ```
/// # use phobos::*;
/// # use anyhow::Result;
/// # use ash::prelude::VkResult;
/// fn bind_allocation_to_buffer<A: Allocation>(device: Device, allocation: &mut A, buffer: vk::Buffer) -> VkResult<()> {
/// // SAFETY:
/// // * User passed in a valid Vulkan device.
/// // * User passed in a valid allocation.
/// // * We offset into allocation.memory() using allocation.offset()
/// unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }
/// }
/// ```
/// # Safety
/// The user must not free this memory or access a range outside of (`allocation.offset()..allocation.offset() + allocation.size())`.
unsafe fn memory(&self) -> vk::DeviceMemory;
/// Get the offset in this [`VkDeviceMemory`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDeviceMemory.html) this allocation refers to.
/// This is exposed because the allocator implementation may choose to subdivide large memory blocks into smaller allocations.
/// # Example
/// This is useful when binding memory to a buffer or image.
/// ```
/// # use phobos::*;
/// # use anyhow::Result;
/// # use ash::prelude::VkResult;
/// fn bind_allocation_to_buffer<A: Allocation>(device: Device, allocation: &mut A, buffer: vk::Buffer) -> VkResult<()> {
/// // SAFETY:
/// // * User passed in a valid Vulkan device.
/// // * User passed in a valid allocation.
/// // * We offset into allocation.memory() using allocation.offset()
/// unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }
/// }
/// ```
fn offset(&self) -> vk::DeviceSize;
/// Obtain a mapped pointer to this allocation. This pointer can be used to directly write into the owned memory.
/// This pointer already points into the exact memory region of the suballocation, so no offset must be applied.
///
/// * Returns `None` if this memory was not mappable (not [`HOST_VISIBLE`](ash::vk::MemoryPropertyFlags::HOST_VISIBLE)). Memory allocated with [`MemoryType::CpuToGpu`] is always mappable.
/// * If this memory comes from a [`HOST_VISIBLE`](ash::vk::MemoryPropertyFlags::HOST_VISIBLE) heap, this returns `Some(ptr)`, with `ptr` a non-null pointer pointing to the allocation memory.
/// # Example
/// ```
/// # use phobos::*;
/// // Writes the integer '5' into the first std::mem::size_of::<i32>() bytes of the allocation.
/// // Assumes this allocation is mappable and at least std::mem::size_of::<i32>() bytes large.
/// unsafe fn write_five<A: Allocation>(allocation: &A) {
/// let memory = allocation.mapped_ptr().expect("Expected allocation to be HOST_VISIBLE");
/// // SAFETY: Assume this allocation is at least std::mem::size_of::<i32>() bytes large.
/// *memory.cast::<i32>().as_mut() = 5;
/// }
/// ```
fn mapped_ptr(&self) -> Option<NonNull<c_void>>;
}