#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResourceType {
GpuBuffer,
HostBuffer,
Texture2D,
Texture3D,
Sampler,
Descriptor,
}
impl ResourceType {
#[must_use]
pub fn is_gpu_memory(&self) -> bool {
matches!(self, Self::GpuBuffer | Self::Texture2D | Self::Texture3D)
}
#[must_use]
pub fn is_texture(&self) -> bool {
matches!(self, Self::Texture2D | Self::Texture3D)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ResourceHandle {
id: u64,
}
impl ResourceHandle {
#[must_use]
pub(crate) fn new(id: u64) -> Self {
Self { id }
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.id != 0
}
#[must_use]
pub fn raw_id(&self) -> u64 {
self.id
}
}
#[derive(Debug, Clone)]
struct ResourceMeta {
resource_type: ResourceType,
size_bytes: usize,
label: String,
}
pub struct GpuResourceManager {
resources: HashMap<ResourceHandle, ResourceMeta>,
next_id: u64,
}
impl GpuResourceManager {
#[must_use]
pub fn new() -> Self {
Self {
resources: HashMap::new(),
next_id: 1,
}
}
pub fn allocate(
&mut self,
resource_type: ResourceType,
size_bytes: usize,
label: impl Into<String>,
) -> Result<ResourceHandle, String> {
if size_bytes == 0 {
return Err("Cannot allocate a zero-byte resource".to_string());
}
let handle = ResourceHandle::new(self.next_id);
self.next_id += 1;
self.resources.insert(
handle,
ResourceMeta {
resource_type,
size_bytes,
label: label.into(),
},
);
Ok(handle)
}
pub fn free(&mut self, handle: ResourceHandle) -> bool {
self.resources.remove(&handle).is_some()
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn total_gpu_bytes(&self) -> usize {
self.resources
.values()
.filter(|m| m.resource_type.is_gpu_memory())
.map(|m| m.size_bytes)
.sum()
}
#[must_use]
pub fn total_bytes(&self) -> usize {
self.resources.values().map(|m| m.size_bytes).sum()
}
#[must_use]
pub fn allocation_count(&self) -> usize {
self.resources.len()
}
#[must_use]
pub fn resource_type(&self, handle: ResourceHandle) -> Option<ResourceType> {
self.resources.get(&handle).map(|m| m.resource_type)
}
#[must_use]
pub fn resource_size(&self, handle: ResourceHandle) -> Option<usize> {
self.resources.get(&handle).map(|m| m.size_bytes)
}
#[must_use]
pub fn resource_label(&self, handle: ResourceHandle) -> Option<&str> {
self.resources.get(&handle).map(|m| m.label.as_str())
}
#[must_use]
pub fn is_alive(&self, handle: ResourceHandle) -> bool {
self.resources.contains_key(&handle)
}
}
impl Default for GpuResourceManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_manager() -> GpuResourceManager {
GpuResourceManager::new()
}
#[test]
fn test_gpu_buffer_is_gpu_memory() {
assert!(ResourceType::GpuBuffer.is_gpu_memory());
}
#[test]
fn test_host_buffer_not_gpu_memory() {
assert!(!ResourceType::HostBuffer.is_gpu_memory());
}
#[test]
fn test_texture2d_is_gpu_memory() {
assert!(ResourceType::Texture2D.is_gpu_memory());
}
#[test]
fn test_sampler_not_gpu_memory() {
assert!(!ResourceType::Sampler.is_gpu_memory());
}
#[test]
fn test_texture3d_is_texture() {
assert!(ResourceType::Texture3D.is_texture());
}
#[test]
fn test_gpu_buffer_not_texture() {
assert!(!ResourceType::GpuBuffer.is_texture());
}
#[test]
fn test_default_handle_invalid() {
let h = ResourceHandle::default();
assert!(!h.is_valid());
}
#[test]
fn test_allocated_handle_valid() {
let mut mgr = make_manager();
let h = mgr
.allocate(ResourceType::GpuBuffer, 1024, "buf")
.expect("allocation should succeed");
assert!(h.is_valid());
}
#[test]
fn test_allocate_returns_valid_handle() {
let mut mgr = make_manager();
let h = mgr
.allocate(ResourceType::Texture2D, 4096, "tex")
.expect("allocation should succeed");
assert!(h.is_valid());
}
#[test]
fn test_allocate_zero_bytes_returns_error() {
let mut mgr = make_manager();
assert!(mgr.allocate(ResourceType::GpuBuffer, 0, "bad").is_err());
}
#[test]
fn test_allocation_count_increases() {
let mut mgr = make_manager();
mgr.allocate(ResourceType::GpuBuffer, 256, "a")
.expect("allocation should succeed");
mgr.allocate(ResourceType::HostBuffer, 512, "b")
.expect("allocation should succeed");
assert_eq!(mgr.allocation_count(), 2);
}
#[test]
fn test_total_gpu_bytes_only_counts_gpu_resources() {
let mut mgr = make_manager();
mgr.allocate(ResourceType::GpuBuffer, 1024, "gpu")
.expect("allocation should succeed");
mgr.allocate(ResourceType::HostBuffer, 2048, "host")
.expect("operation should succeed in test");
assert_eq!(mgr.total_gpu_bytes(), 1024);
}
#[test]
fn test_total_bytes_counts_all() {
let mut mgr = make_manager();
mgr.allocate(ResourceType::GpuBuffer, 1024, "gpu")
.expect("allocation should succeed");
mgr.allocate(ResourceType::HostBuffer, 2048, "host")
.expect("operation should succeed in test");
assert_eq!(mgr.total_bytes(), 3072);
}
#[test]
fn test_free_removes_resource() {
let mut mgr = make_manager();
let h = mgr
.allocate(ResourceType::GpuBuffer, 512, "buf")
.expect("allocation should succeed");
assert!(mgr.free(h));
assert_eq!(mgr.allocation_count(), 0);
}
#[test]
fn test_free_invalid_handle_returns_false() {
let mut mgr = make_manager();
let h = ResourceHandle::default();
assert!(!mgr.free(h));
}
#[test]
fn test_resource_type_query() {
let mut mgr = make_manager();
let h = mgr
.allocate(ResourceType::Sampler, 64, "s")
.expect("allocation should succeed");
assert_eq!(mgr.resource_type(h), Some(ResourceType::Sampler));
}
#[test]
fn test_resource_size_query() {
let mut mgr = make_manager();
let h = mgr
.allocate(ResourceType::Texture2D, 8192, "t")
.expect("allocation should succeed");
assert_eq!(mgr.resource_size(h), Some(8192));
}
#[test]
fn test_resource_label_query() {
let mut mgr = make_manager();
let h = mgr
.allocate(ResourceType::GpuBuffer, 128, "my_label")
.expect("operation should succeed in test");
assert_eq!(mgr.resource_label(h), Some("my_label"));
}
#[test]
fn test_is_alive_after_free_is_false() {
let mut mgr = make_manager();
let h = mgr
.allocate(ResourceType::GpuBuffer, 256, "buf")
.expect("allocation should succeed");
mgr.free(h);
assert!(!mgr.is_alive(h));
}
}