oximedia_gpu/
gpu_buffer.rs1#![allow(dead_code)]
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum BufferUsage {
12 StorageRead,
14 StorageReadWrite,
16 Uniform,
18 Upload,
20 Readback,
22 Index,
24 Vertex,
26}
27
28impl BufferUsage {
29 #[must_use]
31 pub fn is_writable(&self) -> bool {
32 matches!(self, Self::StorageReadWrite | Self::Upload | Self::Readback)
33 }
34
35 #[must_use]
38 pub fn is_staging(&self) -> bool {
39 matches!(self, Self::Upload | Self::Readback)
40 }
41
42 #[must_use]
44 pub fn label(&self) -> &'static str {
45 match self {
46 Self::StorageRead => "storage_read",
47 Self::StorageReadWrite => "storage_read_write",
48 Self::Uniform => "uniform",
49 Self::Upload => "upload",
50 Self::Readback => "readback",
51 Self::Index => "index",
52 Self::Vertex => "vertex",
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct GpuBuffer {
60 pub id: u64,
62 pub usage: BufferUsage,
64 size: usize,
66 mapped: bool,
68 pub label: Option<String>,
70}
71
72impl GpuBuffer {
73 #[must_use]
75 pub fn new(id: u64, usage: BufferUsage, size: usize) -> Self {
76 Self {
77 id,
78 usage,
79 size,
80 mapped: false,
81 label: None,
82 }
83 }
84
85 #[must_use]
87 pub fn with_label(mut self, label: impl Into<String>) -> Self {
88 self.label = Some(label.into());
89 self
90 }
91
92 #[must_use]
94 pub fn size_bytes(&self) -> usize {
95 self.size
96 }
97
98 #[must_use]
100 pub fn is_mapped(&self) -> bool {
101 self.mapped
102 }
103
104 pub fn map(&mut self) -> Result<(), String> {
108 if !self.usage.is_staging() {
109 return Err(format!(
110 "Buffer (usage={}) cannot be mapped; only Upload/Readback buffers support mapping.",
111 self.usage.label()
112 ));
113 }
114 self.mapped = true;
115 Ok(())
116 }
117
118 pub fn unmap(&mut self) {
120 self.mapped = false;
121 }
122
123 #[must_use]
125 pub fn aligned_size(&self) -> usize {
126 (self.size + 3) & !3
127 }
128}
129
130#[derive(Debug, Default)]
132pub struct GpuBufferPool {
133 next_id: u64,
134 active: Vec<GpuBuffer>,
135 free_list: Vec<GpuBuffer>,
136}
137
138impl GpuBufferPool {
139 #[must_use]
141 pub fn new() -> Self {
142 Self::default()
143 }
144
145 pub fn allocate(&mut self, usage: BufferUsage, size: usize) -> GpuBuffer {
150 if let Some(pos) = self
152 .free_list
153 .iter()
154 .position(|b| b.usage == usage && b.size_bytes() >= size)
155 {
156 return self.free_list.remove(pos);
157 }
158 let id = self.next_id;
160 self.next_id += 1;
161 let buf = GpuBuffer::new(id, usage, size);
162 self.active.push(buf.clone());
163 buf
164 }
165
166 pub fn release(&mut self, mut buf: GpuBuffer) {
168 buf.unmap(); self.active.retain(|b| b.id != buf.id);
171 self.free_list.push(buf);
172 }
173
174 #[must_use]
177 pub fn total_allocated(&self) -> usize {
178 let active: usize = self.active.iter().map(GpuBuffer::size_bytes).sum();
179 let free: usize = self.free_list.iter().map(GpuBuffer::size_bytes).sum();
180 active + free
181 }
182
183 #[must_use]
185 pub fn active_count(&self) -> usize {
186 self.active.len()
187 }
188
189 #[must_use]
191 pub fn free_count(&self) -> usize {
192 self.free_list.len()
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_buffer_usage_is_writable_storage_rw() {
202 assert!(BufferUsage::StorageReadWrite.is_writable());
203 }
204
205 #[test]
206 fn test_buffer_usage_not_writable_storage_read() {
207 assert!(!BufferUsage::StorageRead.is_writable());
208 }
209
210 #[test]
211 fn test_buffer_usage_is_staging_upload() {
212 assert!(BufferUsage::Upload.is_staging());
213 }
214
215 #[test]
216 fn test_buffer_usage_not_staging_uniform() {
217 assert!(!BufferUsage::Uniform.is_staging());
218 }
219
220 #[test]
221 fn test_buffer_usage_label_non_empty() {
222 for usage in [
223 BufferUsage::StorageRead,
224 BufferUsage::StorageReadWrite,
225 BufferUsage::Uniform,
226 BufferUsage::Upload,
227 BufferUsage::Readback,
228 BufferUsage::Index,
229 BufferUsage::Vertex,
230 ] {
231 assert!(!usage.label().is_empty());
232 }
233 }
234
235 #[test]
236 fn test_gpu_buffer_size_bytes() {
237 let b = GpuBuffer::new(0, BufferUsage::Uniform, 256);
238 assert_eq!(b.size_bytes(), 256);
239 }
240
241 #[test]
242 fn test_gpu_buffer_not_mapped_by_default() {
243 let b = GpuBuffer::new(0, BufferUsage::Upload, 1024);
244 assert!(!b.is_mapped());
245 }
246
247 #[test]
248 fn test_gpu_buffer_map_upload_ok() {
249 let mut b = GpuBuffer::new(0, BufferUsage::Upload, 1024);
250 assert!(b.map().is_ok());
251 assert!(b.is_mapped());
252 }
253
254 #[test]
255 fn test_gpu_buffer_map_non_staging_err() {
256 let mut b = GpuBuffer::new(0, BufferUsage::StorageRead, 512);
257 assert!(b.map().is_err());
258 }
259
260 #[test]
261 fn test_gpu_buffer_unmap_clears_flag() {
262 let mut b = GpuBuffer::new(0, BufferUsage::Readback, 512);
263 b.map().expect("buffer map should succeed");
264 b.unmap();
265 assert!(!b.is_mapped());
266 }
267
268 #[test]
269 fn test_gpu_buffer_aligned_size() {
270 let b = GpuBuffer::new(0, BufferUsage::Uniform, 13);
271 assert_eq!(b.aligned_size(), 16);
272 }
273
274 #[test]
275 fn test_pool_allocate_creates_buffer() {
276 let mut pool = GpuBufferPool::new();
277 let buf = pool.allocate(BufferUsage::StorageRead, 1024);
278 assert_eq!(buf.size_bytes(), 1024);
279 assert_eq!(buf.usage, BufferUsage::StorageRead);
280 }
281
282 #[test]
283 fn test_pool_release_and_reuse() {
284 let mut pool = GpuBufferPool::new();
285 let buf = pool.allocate(BufferUsage::Upload, 512);
286 let id = buf.id;
287 pool.release(buf);
288 let reused = pool.allocate(BufferUsage::Upload, 512);
289 assert_eq!(reused.id, id); }
291
292 #[test]
293 fn test_pool_total_allocated() {
294 let mut pool = GpuBufferPool::new();
295 pool.allocate(BufferUsage::Uniform, 256);
296 pool.allocate(BufferUsage::Uniform, 512);
297 assert_eq!(pool.total_allocated(), 768);
298 }
299
300 #[test]
301 fn test_pool_free_count_after_release() {
302 let mut pool = GpuBufferPool::new();
303 let buf = pool.allocate(BufferUsage::Readback, 1024);
304 pool.release(buf);
305 assert_eq!(pool.free_count(), 1);
306 }
307}