#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BufferUsage {
StorageRead,
StorageReadWrite,
Uniform,
Upload,
Readback,
Index,
Vertex,
}
impl BufferUsage {
#[must_use]
pub fn is_writable(&self) -> bool {
matches!(self, Self::StorageReadWrite | Self::Upload | Self::Readback)
}
#[must_use]
pub fn is_staging(&self) -> bool {
matches!(self, Self::Upload | Self::Readback)
}
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::StorageRead => "storage_read",
Self::StorageReadWrite => "storage_read_write",
Self::Uniform => "uniform",
Self::Upload => "upload",
Self::Readback => "readback",
Self::Index => "index",
Self::Vertex => "vertex",
}
}
}
#[derive(Debug, Clone)]
pub struct GpuBuffer {
pub id: u64,
pub usage: BufferUsage,
size: usize,
mapped: bool,
pub label: Option<String>,
}
impl GpuBuffer {
#[must_use]
pub fn new(id: u64, usage: BufferUsage, size: usize) -> Self {
Self {
id,
usage,
size,
mapped: false,
label: None,
}
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
#[must_use]
pub fn size_bytes(&self) -> usize {
self.size
}
#[must_use]
pub fn is_mapped(&self) -> bool {
self.mapped
}
pub fn map(&mut self) -> Result<(), String> {
if !self.usage.is_staging() {
return Err(format!(
"Buffer (usage={}) cannot be mapped; only Upload/Readback buffers support mapping.",
self.usage.label()
));
}
self.mapped = true;
Ok(())
}
pub fn unmap(&mut self) {
self.mapped = false;
}
#[must_use]
pub fn aligned_size(&self) -> usize {
(self.size + 3) & !3
}
}
#[derive(Debug, Default)]
pub struct GpuBufferPool {
next_id: u64,
active: Vec<GpuBuffer>,
free_list: Vec<GpuBuffer>,
}
impl GpuBufferPool {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn allocate(&mut self, usage: BufferUsage, size: usize) -> GpuBuffer {
if let Some(pos) = self
.free_list
.iter()
.position(|b| b.usage == usage && b.size_bytes() >= size)
{
return self.free_list.remove(pos);
}
let id = self.next_id;
self.next_id += 1;
let buf = GpuBuffer::new(id, usage, size);
self.active.push(buf.clone());
buf
}
pub fn release(&mut self, mut buf: GpuBuffer) {
buf.unmap(); self.active.retain(|b| b.id != buf.id);
self.free_list.push(buf);
}
#[must_use]
pub fn total_allocated(&self) -> usize {
let active: usize = self.active.iter().map(GpuBuffer::size_bytes).sum();
let free: usize = self.free_list.iter().map(GpuBuffer::size_bytes).sum();
active + free
}
#[must_use]
pub fn active_count(&self) -> usize {
self.active.len()
}
#[must_use]
pub fn free_count(&self) -> usize {
self.free_list.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffer_usage_is_writable_storage_rw() {
assert!(BufferUsage::StorageReadWrite.is_writable());
}
#[test]
fn test_buffer_usage_not_writable_storage_read() {
assert!(!BufferUsage::StorageRead.is_writable());
}
#[test]
fn test_buffer_usage_is_staging_upload() {
assert!(BufferUsage::Upload.is_staging());
}
#[test]
fn test_buffer_usage_not_staging_uniform() {
assert!(!BufferUsage::Uniform.is_staging());
}
#[test]
fn test_buffer_usage_label_non_empty() {
for usage in [
BufferUsage::StorageRead,
BufferUsage::StorageReadWrite,
BufferUsage::Uniform,
BufferUsage::Upload,
BufferUsage::Readback,
BufferUsage::Index,
BufferUsage::Vertex,
] {
assert!(!usage.label().is_empty());
}
}
#[test]
fn test_gpu_buffer_size_bytes() {
let b = GpuBuffer::new(0, BufferUsage::Uniform, 256);
assert_eq!(b.size_bytes(), 256);
}
#[test]
fn test_gpu_buffer_not_mapped_by_default() {
let b = GpuBuffer::new(0, BufferUsage::Upload, 1024);
assert!(!b.is_mapped());
}
#[test]
fn test_gpu_buffer_map_upload_ok() {
let mut b = GpuBuffer::new(0, BufferUsage::Upload, 1024);
assert!(b.map().is_ok());
assert!(b.is_mapped());
}
#[test]
fn test_gpu_buffer_map_non_staging_err() {
let mut b = GpuBuffer::new(0, BufferUsage::StorageRead, 512);
assert!(b.map().is_err());
}
#[test]
fn test_gpu_buffer_unmap_clears_flag() {
let mut b = GpuBuffer::new(0, BufferUsage::Readback, 512);
b.map().expect("buffer map should succeed");
b.unmap();
assert!(!b.is_mapped());
}
#[test]
fn test_gpu_buffer_aligned_size() {
let b = GpuBuffer::new(0, BufferUsage::Uniform, 13);
assert_eq!(b.aligned_size(), 16);
}
#[test]
fn test_pool_allocate_creates_buffer() {
let mut pool = GpuBufferPool::new();
let buf = pool.allocate(BufferUsage::StorageRead, 1024);
assert_eq!(buf.size_bytes(), 1024);
assert_eq!(buf.usage, BufferUsage::StorageRead);
}
#[test]
fn test_pool_release_and_reuse() {
let mut pool = GpuBufferPool::new();
let buf = pool.allocate(BufferUsage::Upload, 512);
let id = buf.id;
pool.release(buf);
let reused = pool.allocate(BufferUsage::Upload, 512);
assert_eq!(reused.id, id); }
#[test]
fn test_pool_total_allocated() {
let mut pool = GpuBufferPool::new();
pool.allocate(BufferUsage::Uniform, 256);
pool.allocate(BufferUsage::Uniform, 512);
assert_eq!(pool.total_allocated(), 768);
}
#[test]
fn test_pool_free_count_after_release() {
let mut pool = GpuBufferPool::new();
let buf = pool.allocate(BufferUsage::Readback, 1024);
pool.release(buf);
assert_eq!(pool.free_count(), 1);
}
}