oximedia_gpu/
resource_manager.rs1#![allow(dead_code)]
3
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum ResourceType {
9 GpuBuffer,
11 HostBuffer,
13 Texture2D,
15 Texture3D,
17 Sampler,
19 Descriptor,
21}
22
23impl ResourceType {
24 #[must_use]
26 pub fn is_gpu_memory(&self) -> bool {
27 matches!(self, Self::GpuBuffer | Self::Texture2D | Self::Texture3D)
28 }
29
30 #[must_use]
32 pub fn is_texture(&self) -> bool {
33 matches!(self, Self::Texture2D | Self::Texture3D)
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
39pub struct ResourceHandle {
40 id: u64,
41}
42
43impl ResourceHandle {
44 #[must_use]
46 pub(crate) fn new(id: u64) -> Self {
47 Self { id }
48 }
49
50 #[must_use]
53 pub fn is_valid(&self) -> bool {
54 self.id != 0
55 }
56
57 #[must_use]
59 pub fn raw_id(&self) -> u64 {
60 self.id
61 }
62}
63
64#[derive(Debug, Clone)]
66struct ResourceMeta {
67 resource_type: ResourceType,
68 size_bytes: usize,
69 label: String,
70}
71
72pub struct GpuResourceManager {
74 resources: HashMap<ResourceHandle, ResourceMeta>,
75 next_id: u64,
76}
77
78impl GpuResourceManager {
79 #[must_use]
81 pub fn new() -> Self {
82 Self {
83 resources: HashMap::new(),
84 next_id: 1,
85 }
86 }
87
88 pub fn allocate(
93 &mut self,
94 resource_type: ResourceType,
95 size_bytes: usize,
96 label: impl Into<String>,
97 ) -> Result<ResourceHandle, String> {
98 if size_bytes == 0 {
99 return Err("Cannot allocate a zero-byte resource".to_string());
100 }
101 let handle = ResourceHandle::new(self.next_id);
102 self.next_id += 1;
103 self.resources.insert(
104 handle,
105 ResourceMeta {
106 resource_type,
107 size_bytes,
108 label: label.into(),
109 },
110 );
111 Ok(handle)
112 }
113
114 pub fn free(&mut self, handle: ResourceHandle) -> bool {
118 self.resources.remove(&handle).is_some()
119 }
120
121 #[allow(clippy::cast_precision_loss)]
123 #[must_use]
124 pub fn total_gpu_bytes(&self) -> usize {
125 self.resources
126 .values()
127 .filter(|m| m.resource_type.is_gpu_memory())
128 .map(|m| m.size_bytes)
129 .sum()
130 }
131
132 #[must_use]
134 pub fn total_bytes(&self) -> usize {
135 self.resources.values().map(|m| m.size_bytes).sum()
136 }
137
138 #[must_use]
140 pub fn allocation_count(&self) -> usize {
141 self.resources.len()
142 }
143
144 #[must_use]
146 pub fn resource_type(&self, handle: ResourceHandle) -> Option<ResourceType> {
147 self.resources.get(&handle).map(|m| m.resource_type)
148 }
149
150 #[must_use]
152 pub fn resource_size(&self, handle: ResourceHandle) -> Option<usize> {
153 self.resources.get(&handle).map(|m| m.size_bytes)
154 }
155
156 #[must_use]
158 pub fn resource_label(&self, handle: ResourceHandle) -> Option<&str> {
159 self.resources.get(&handle).map(|m| m.label.as_str())
160 }
161
162 #[must_use]
164 pub fn is_alive(&self, handle: ResourceHandle) -> bool {
165 self.resources.contains_key(&handle)
166 }
167}
168
169impl Default for GpuResourceManager {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 fn make_manager() -> GpuResourceManager {
180 GpuResourceManager::new()
181 }
182
183 #[test]
186 fn test_gpu_buffer_is_gpu_memory() {
187 assert!(ResourceType::GpuBuffer.is_gpu_memory());
188 }
189
190 #[test]
191 fn test_host_buffer_not_gpu_memory() {
192 assert!(!ResourceType::HostBuffer.is_gpu_memory());
193 }
194
195 #[test]
196 fn test_texture2d_is_gpu_memory() {
197 assert!(ResourceType::Texture2D.is_gpu_memory());
198 }
199
200 #[test]
201 fn test_sampler_not_gpu_memory() {
202 assert!(!ResourceType::Sampler.is_gpu_memory());
203 }
204
205 #[test]
206 fn test_texture3d_is_texture() {
207 assert!(ResourceType::Texture3D.is_texture());
208 }
209
210 #[test]
211 fn test_gpu_buffer_not_texture() {
212 assert!(!ResourceType::GpuBuffer.is_texture());
213 }
214
215 #[test]
218 fn test_default_handle_invalid() {
219 let h = ResourceHandle::default();
220 assert!(!h.is_valid());
221 }
222
223 #[test]
224 fn test_allocated_handle_valid() {
225 let mut mgr = make_manager();
226 let h = mgr
227 .allocate(ResourceType::GpuBuffer, 1024, "buf")
228 .expect("allocation should succeed");
229 assert!(h.is_valid());
230 }
231
232 #[test]
235 fn test_allocate_returns_valid_handle() {
236 let mut mgr = make_manager();
237 let h = mgr
238 .allocate(ResourceType::Texture2D, 4096, "tex")
239 .expect("allocation should succeed");
240 assert!(h.is_valid());
241 }
242
243 #[test]
244 fn test_allocate_zero_bytes_returns_error() {
245 let mut mgr = make_manager();
246 assert!(mgr.allocate(ResourceType::GpuBuffer, 0, "bad").is_err());
247 }
248
249 #[test]
250 fn test_allocation_count_increases() {
251 let mut mgr = make_manager();
252 mgr.allocate(ResourceType::GpuBuffer, 256, "a")
253 .expect("allocation should succeed");
254 mgr.allocate(ResourceType::HostBuffer, 512, "b")
255 .expect("allocation should succeed");
256 assert_eq!(mgr.allocation_count(), 2);
257 }
258
259 #[test]
260 fn test_total_gpu_bytes_only_counts_gpu_resources() {
261 let mut mgr = make_manager();
262 mgr.allocate(ResourceType::GpuBuffer, 1024, "gpu")
263 .expect("allocation should succeed");
264 mgr.allocate(ResourceType::HostBuffer, 2048, "host")
265 .expect("operation should succeed in test");
266 assert_eq!(mgr.total_gpu_bytes(), 1024);
267 }
268
269 #[test]
270 fn test_total_bytes_counts_all() {
271 let mut mgr = make_manager();
272 mgr.allocate(ResourceType::GpuBuffer, 1024, "gpu")
273 .expect("allocation should succeed");
274 mgr.allocate(ResourceType::HostBuffer, 2048, "host")
275 .expect("operation should succeed in test");
276 assert_eq!(mgr.total_bytes(), 3072);
277 }
278
279 #[test]
280 fn test_free_removes_resource() {
281 let mut mgr = make_manager();
282 let h = mgr
283 .allocate(ResourceType::GpuBuffer, 512, "buf")
284 .expect("allocation should succeed");
285 assert!(mgr.free(h));
286 assert_eq!(mgr.allocation_count(), 0);
287 }
288
289 #[test]
290 fn test_free_invalid_handle_returns_false() {
291 let mut mgr = make_manager();
292 let h = ResourceHandle::default();
293 assert!(!mgr.free(h));
294 }
295
296 #[test]
297 fn test_resource_type_query() {
298 let mut mgr = make_manager();
299 let h = mgr
300 .allocate(ResourceType::Sampler, 64, "s")
301 .expect("allocation should succeed");
302 assert_eq!(mgr.resource_type(h), Some(ResourceType::Sampler));
303 }
304
305 #[test]
306 fn test_resource_size_query() {
307 let mut mgr = make_manager();
308 let h = mgr
309 .allocate(ResourceType::Texture2D, 8192, "t")
310 .expect("allocation should succeed");
311 assert_eq!(mgr.resource_size(h), Some(8192));
312 }
313
314 #[test]
315 fn test_resource_label_query() {
316 let mut mgr = make_manager();
317 let h = mgr
318 .allocate(ResourceType::GpuBuffer, 128, "my_label")
319 .expect("operation should succeed in test");
320 assert_eq!(mgr.resource_label(h), Some("my_label"));
321 }
322
323 #[test]
324 fn test_is_alive_after_free_is_false() {
325 let mut mgr = make_manager();
326 let h = mgr
327 .allocate(ResourceType::GpuBuffer, 256, "buf")
328 .expect("allocation should succeed");
329 mgr.free(h);
330 assert!(!mgr.is_alive(h));
331 }
332}