1use crate::HalError;
7use ash::vk;
8
9pub const DEFAULT_BLOCK_SIZE: u64 = 32 * 1024 * 1024; pub const SUBALLOCATION_THRESHOLD: u64 = 16 * 1024 * 1024; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum AllocationType {
16 Linear,
17 Optimal,
18}
19
20#[derive(Debug)]
21pub struct AllocationResult {
22 pub memory: vk::DeviceMemory,
23 pub offset: u64,
24 pub block_id: Option<usize>,
25 pub mapped_ptr: Option<*mut u8>,
26 pub is_coherent: bool,
27}
28
29pub struct MemoryBlock {
30 pub id: usize,
31 pub memory: vk::DeviceMemory,
32 pub size: u64,
33 pub memory_type_index: u32,
34 pub is_host_visible: bool,
35 pub allocation_type: AllocationType,
36 pub mapped_base: Option<*mut u8>,
37 pub is_coherent: bool,
38 pub free_chunks: Vec<(u64, u64)>, }
40
41unsafe impl Send for MemoryBlock {}
42unsafe impl Sync for MemoryBlock {}
43
44impl MemoryBlock {
45 pub fn allocate(&mut self, size: u64, alignment: u64) -> Option<u64> {
46 let align = alignment.max(1);
47 for i in 0..self.free_chunks.len() {
48 let (chunk_offset, chunk_size) = self.free_chunks[i];
49 let aligned_offset = if align.is_power_of_two() {
50 (chunk_offset + align - 1) & !(align - 1)
51 } else {
52 ((chunk_offset + align - 1) / align) * align
53 };
54 let padding = aligned_offset - chunk_offset;
55 if chunk_size >= padding + size {
56 self.free_chunks.remove(i);
58 if padding > 0 {
59 self.free_chunks.push((chunk_offset, padding));
60 }
61 let remaining = chunk_size - (padding + size);
62 if remaining > 0 {
63 self.free_chunks.push((aligned_offset + size, remaining));
64 }
65 self.free_chunks.sort_unstable_by_key(|&(off, _)| off);
66 return Some(aligned_offset);
67 }
68 }
69 None
70 }
71
72 pub fn free(&mut self, offset: u64, size: u64) {
73 self.free_chunks.push((offset, size));
74 self.free_chunks.sort_unstable_by_key(|&(off, _)| off);
75
76 let mut merged: Vec<(u64, u64)> = Vec::with_capacity(self.free_chunks.len());
78 for chunk in &self.free_chunks {
79 if let Some(last) = merged.last_mut() {
80 if last.0 + last.1 == chunk.0 {
81 last.1 += chunk.1;
82 continue;
83 }
84 }
85 merged.push(*chunk);
86 }
87 self.free_chunks = merged;
88 }
89
90 pub fn is_empty(&self) -> bool {
91 self.free_chunks.len() == 1 && self.free_chunks[0] == (0, self.size)
92 }
93}
94
95pub struct VulkanSuballocator {
96 blocks: Vec<MemoryBlock>,
97 next_block_id: usize,
98}
99
100impl VulkanSuballocator {
101 pub fn new() -> Self {
102 Self {
103 blocks: Vec::new(),
104 next_block_id: 0,
105 }
106 }
107
108 pub fn allocate(
109 &mut self,
110 device: &ash::Device,
111 size: u64,
112 alignment: u64,
113 memory_type_index: u32,
114 is_host_visible: bool,
115 is_coherent: bool,
116 allocation_type: AllocationType,
117 ) -> Result<AllocationResult, HalError> {
118 if size >= SUBALLOCATION_THRESHOLD {
120 let alloc_info = vk::MemoryAllocateInfo::default()
121 .allocation_size(size)
122 .memory_type_index(memory_type_index);
123
124 let memory = unsafe {
125 device.allocate_memory(&alloc_info, None).map_err(|e| {
126 HalError::OutOfMemory(format!("vkAllocateMemory dedicated failed: {e}"))
127 })?
128 };
129
130 let mapped_ptr = if is_host_visible {
131 let ptr = unsafe {
132 device
133 .map_memory(memory, 0, size, vk::MemoryMapFlags::empty())
134 .map_err(|e| HalError::MapError(e.to_string()))?
135 };
136 Some(ptr as *mut u8)
137 } else {
138 None
139 };
140
141 return Ok(AllocationResult {
142 memory,
143 offset: 0,
144 block_id: None,
145 mapped_ptr,
146 is_coherent,
147 });
148 }
149
150 for block in &mut self.blocks {
152 if block.memory_type_index == memory_type_index
153 && block.is_host_visible == is_host_visible
154 && block.allocation_type == allocation_type
155 {
156 if let Some(offset) = block.allocate(size, alignment) {
157 let mapped_ptr = block
158 .mapped_base
159 .map(|base| unsafe { base.add(offset as usize) });
160 return Ok(AllocationResult {
161 memory: block.memory,
162 offset,
163 block_id: Some(block.id),
164 mapped_ptr,
165 is_coherent: block.is_coherent,
166 });
167 }
168 }
169 }
170
171 let block_size = DEFAULT_BLOCK_SIZE.max(size);
173 let alloc_info = vk::MemoryAllocateInfo::default()
174 .allocation_size(block_size)
175 .memory_type_index(memory_type_index);
176
177 let memory = unsafe {
178 device
179 .allocate_memory(&alloc_info, None)
180 .map_err(|e| HalError::OutOfMemory(format!("vkAllocateMemory block failed: {e}")))?
181 };
182
183 let mapped_base = if is_host_visible {
184 let ptr = unsafe {
185 device
186 .map_memory(memory, 0, block_size, vk::MemoryMapFlags::empty())
187 .map_err(|e| HalError::MapError(e.to_string()))?
188 };
189 Some(ptr as *mut u8)
190 } else {
191 None
192 };
193
194 let block_id = self.next_block_id;
195 self.next_block_id += 1;
196
197 let mut block = MemoryBlock {
198 id: block_id,
199 memory,
200 size: block_size,
201 memory_type_index,
202 is_host_visible,
203 allocation_type,
204 mapped_base,
205 is_coherent,
206 free_chunks: vec![(0, block_size)],
207 };
208
209 let offset = block
210 .allocate(size, alignment)
211 .expect("Fresh block must have sufficient space for allocation");
212
213 let mapped_ptr = block
214 .mapped_base
215 .map(|base| unsafe { base.add(offset as usize) });
216
217 self.blocks.push(block);
218
219 Ok(AllocationResult {
220 memory,
221 offset,
222 block_id: Some(block_id),
223 mapped_ptr,
224 is_coherent,
225 })
226 }
227
228 pub fn free(&mut self, block_id: usize, offset: u64, size: u64, device: &ash::Device) {
229 let mut block_to_reclaim = None;
230
231 if let Some(pos) = self.blocks.iter().position(|b| b.id == block_id) {
232 self.blocks[pos].free(offset, size);
233
234 if self.blocks[pos].is_empty() {
235 let mem_idx = self.blocks[pos].memory_type_index;
236 let is_host_vis = self.blocks[pos].is_host_visible;
237 let alloc_type = self.blocks[pos].allocation_type;
238
239 let empty_count = self
240 .blocks
241 .iter()
242 .filter(|b| {
243 b.memory_type_index == mem_idx
244 && b.is_host_visible == is_host_vis
245 && b.allocation_type == alloc_type
246 && b.is_empty()
247 })
248 .count();
249
250 if empty_count > 1 {
253 block_to_reclaim = Some(pos);
254 }
255 }
256 }
257
258 if let Some(pos) = block_to_reclaim {
259 let block = self.blocks.remove(pos);
260 unsafe {
261 if block.mapped_base.is_some() {
262 device.unmap_memory(block.memory);
263 }
264 device.free_memory(block.memory, None);
265 }
266 }
267 }
268
269 pub fn trim(&mut self, device: &ash::Device) -> usize {
272 let mut freed_count = 0;
273 let mut i = 0;
274 while i < self.blocks.len() {
275 if self.blocks[i].is_empty() {
276 let block = self.blocks.remove(i);
277 unsafe {
278 if block.mapped_base.is_some() {
279 device.unmap_memory(block.memory);
280 }
281 device.free_memory(block.memory, None);
282 }
283 freed_count += 1;
284 } else {
285 i += 1;
286 }
287 }
288 freed_count
289 }
290
291 pub fn block_count(&self) -> usize {
293 self.blocks.len()
294 }
295
296 pub fn empty_block_count(&self) -> usize {
298 self.blocks.iter().filter(|b| b.is_empty()).count()
299 }
300
301 pub fn total_capacity_bytes(&self) -> u64 {
303 self.blocks.iter().map(|b| b.size).sum()
304 }
305
306 pub fn total_free_bytes(&self) -> u64 {
308 self.blocks
309 .iter()
310 .map(|b| b.free_chunks.iter().map(|&(_, sz)| sz).sum::<u64>())
311 .sum()
312 }
313
314 pub fn destroy(&mut self, device: &ash::Device) {
315 unsafe {
316 for block in self.blocks.drain(..) {
317 if block.mapped_base.is_some() {
318 device.unmap_memory(block.memory);
319 }
320 device.free_memory(block.memory, None);
321 }
322 }
323 }
324}