1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
11#![allow(missing_docs)]
12
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum GpuBufferType {
22 Uniform,
24 Storage,
26 Vertex,
28 Index,
30 Texture,
32 Staging,
34}
35
36#[derive(Debug, Clone)]
42pub struct GpuBuffer {
43 pub buffer_type: GpuBufferType,
45 data: Vec<u8>,
47 mapped: bool,
49}
50
51impl GpuBuffer {
52 pub fn new(buffer_type: GpuBufferType, size: usize) -> Self {
54 Self {
55 buffer_type,
56 data: vec![0u8; size],
57 mapped: false,
58 }
59 }
60
61 pub fn size(&self) -> usize {
63 self.data.len()
64 }
65
66 pub fn read(&self, offset: usize, dst: &mut [u8]) -> usize {
68 if offset >= self.data.len() {
69 return 0;
70 }
71 let len = dst.len().min(self.data.len() - offset);
72 dst[..len].copy_from_slice(&self.data[offset..offset + len]);
73 len
74 }
75
76 pub fn write(&mut self, offset: usize, src: &[u8]) -> usize {
78 if offset >= self.data.len() {
79 return 0;
80 }
81 let len = src.len().min(self.data.len() - offset);
82 self.data[offset..offset + len].copy_from_slice(&src[..len]);
83 len
84 }
85
86 pub fn resize(&mut self, new_size: usize) {
88 self.data.resize(new_size, 0);
89 }
90
91 pub fn clear(&mut self) {
93 self.data.fill(0);
94 }
95
96 pub fn map(&mut self) -> Option<&mut [u8]> {
98 if self.mapped {
99 return None;
100 }
101 self.mapped = true;
102 Some(&mut self.data)
103 }
104
105 pub fn unmap(&mut self) {
107 self.mapped = false;
108 }
109
110 pub fn is_mapped(&self) -> bool {
112 self.mapped
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub struct GpuBufferHandle {
123 pub id: u64,
125 pub size: usize,
127 pub offset: usize,
129 pub buffer_type: GpuBufferType,
131}
132
133impl GpuBufferHandle {
134 pub fn null() -> Self {
136 Self {
137 id: 0,
138 size: 0,
139 offset: 0,
140 buffer_type: GpuBufferType::Staging,
141 }
142 }
143
144 pub fn is_null(&self) -> bool {
146 self.id == 0
147 }
148}
149
150#[derive(Debug)]
156pub struct GpuBufferPool {
157 backing: Vec<u8>,
159 next_offset: usize,
161 allocations: HashMap<u64, GpuBufferHandle>,
163 next_id: u64,
165 capacity: usize,
167}
168
169impl GpuBufferPool {
170 pub fn new(capacity: usize) -> Self {
172 Self {
173 backing: vec![0u8; capacity],
174 next_offset: 0,
175 allocations: HashMap::new(),
176 next_id: 1,
177 capacity,
178 }
179 }
180
181 pub fn alloc(&mut self, size: usize, buffer_type: GpuBufferType) -> Option<GpuBufferHandle> {
183 let aligned = align_up(size, 256);
184 if self.next_offset + aligned > self.capacity {
185 return None;
186 }
187 let handle = GpuBufferHandle {
188 id: self.next_id,
189 size,
190 offset: self.next_offset,
191 buffer_type,
192 };
193 self.next_id += 1;
194 self.next_offset += aligned;
195 self.allocations.insert(handle.id, handle);
196 Some(handle)
197 }
198
199 pub fn free(&mut self, handle: GpuBufferHandle) {
201 self.allocations.remove(&handle.id);
202 }
203
204 pub fn defragment(&mut self) {
208 let mut sorted: Vec<GpuBufferHandle> = self.allocations.values().cloned().collect();
209 sorted.sort_by_key(|h| h.id);
210
211 let old_backing = self.backing.clone();
212 self.backing.fill(0);
213 let mut cursor = 0usize;
214
215 for h in &mut sorted {
216 let old_off = h.offset;
217 let size = h.size;
218 let aligned = align_up(size, 256);
219 if old_off + size <= old_backing.len() {
220 self.backing[cursor..cursor + size]
221 .copy_from_slice(&old_backing[old_off..old_off + size]);
222 }
223 h.offset = cursor;
224 cursor += aligned;
225 }
226 self.next_offset = cursor;
227
228 self.allocations.clear();
230 for h in sorted {
231 self.allocations.insert(h.id, h);
232 }
233 }
234
235 pub fn write(&mut self, handle: &GpuBufferHandle, offset: usize, src: &[u8]) -> usize {
237 let start = handle.offset + offset;
238 if start >= self.backing.len() {
239 return 0;
240 }
241 let len = src
242 .len()
243 .min(self.backing.len() - start)
244 .min(handle.size.saturating_sub(offset));
245 self.backing[start..start + len].copy_from_slice(&src[..len]);
246 len
247 }
248
249 pub fn read(&self, handle: &GpuBufferHandle, offset: usize, dst: &mut [u8]) -> usize {
251 let start = handle.offset + offset;
252 if start >= self.backing.len() {
253 return 0;
254 }
255 let len = dst
256 .len()
257 .min(self.backing.len() - start)
258 .min(handle.size.saturating_sub(offset));
259 dst[..len].copy_from_slice(&self.backing[start..start + len]);
260 len
261 }
262
263 pub fn allocation_count(&self) -> usize {
265 self.allocations.len()
266 }
267
268 pub fn used_bytes(&self) -> usize {
270 self.next_offset
271 }
272
273 pub fn free_bytes(&self) -> usize {
275 self.capacity.saturating_sub(self.next_offset)
276 }
277}
278
279fn align_up(size: usize, align: usize) -> usize {
280 size.div_ceil(align) * align
281}
282
283#[derive(Debug, Clone)]
289pub struct UniformBuffer {
290 pub name: String,
292 fields: HashMap<String, f64>,
294 byte_cache: Option<Vec<u8>>,
296}
297
298impl UniformBuffer {
299 pub fn new(name: impl Into<String>) -> Self {
301 Self {
302 name: name.into(),
303 fields: HashMap::new(),
304 byte_cache: None,
305 }
306 }
307
308 pub fn set_field(&mut self, name: &str, value: f64) {
310 self.fields.insert(name.to_owned(), value);
311 self.byte_cache = None; }
313
314 pub fn get_field(&self, name: &str) -> Option<f64> {
316 self.fields.get(name).copied()
317 }
318
319 pub fn serialize(&mut self) -> &[u8] {
323 if self.byte_cache.is_none() {
324 let mut names: Vec<&String> = self.fields.keys().collect();
325 names.sort();
326 let mut bytes = Vec::with_capacity(names.len() * 8);
327 for name in names {
328 let v = self.fields[name];
329 bytes.extend_from_slice(&v.to_le_bytes());
330 }
331 self.byte_cache = Some(bytes);
332 }
333 self.byte_cache.as_ref().expect("value should be Some")
334 }
335
336 pub fn field_count(&self) -> usize {
338 self.fields.len()
339 }
340}
341
342#[derive(Debug, Clone)]
348pub struct StorageBuffer {
349 pub label: String,
351 data: Vec<u8>,
353 mapped: bool,
355}
356
357impl StorageBuffer {
358 pub fn new(label: impl Into<String>, size: usize) -> Self {
360 Self {
361 label: label.into(),
362 data: vec![0u8; size],
363 mapped: false,
364 }
365 }
366
367 pub fn size(&self) -> usize {
369 self.data.len()
370 }
371
372 pub fn map(&mut self) -> Option<&mut [u8]> {
374 if self.mapped {
375 return None;
376 }
377 self.mapped = true;
378 Some(&mut self.data)
379 }
380
381 pub fn unmap(&mut self) {
383 self.mapped = false;
384 }
385
386 pub fn read_bytes(&self, offset: usize, len: usize) -> Vec<u8> {
388 if offset >= self.data.len() {
389 return vec![];
390 }
391 let end = (offset + len).min(self.data.len());
392 self.data[offset..end].to_vec()
393 }
394
395 pub fn write_bytes(&mut self, offset: usize, src: &[u8]) -> usize {
397 if offset >= self.data.len() {
398 return 0;
399 }
400 let len = src.len().min(self.data.len() - offset);
401 self.data[offset..offset + len].copy_from_slice(&src[..len]);
402 len
403 }
404
405 pub fn clear(&mut self) {
407 self.data.fill(0);
408 }
409}
410
411#[derive(Debug, Clone, Copy, PartialEq)]
417pub struct VertexLayout {
418 pub pos_offset: usize,
420 pub normal_offset: usize,
422 pub uv_offset: usize,
424 pub stride: usize,
426}
427
428impl VertexLayout {
429 pub fn standard() -> Self {
431 Self {
432 pos_offset: 0,
433 normal_offset: 12,
434 uv_offset: 24,
435 stride: 32,
436 }
437 }
438}
439
440#[derive(Debug, Clone)]
442pub struct VertexBuffer {
443 data: Vec<u8>,
445 pub vertex_count: usize,
447 pub layout: VertexLayout,
449}
450
451impl VertexBuffer {
452 pub fn new(layout: VertexLayout) -> Self {
454 Self {
455 data: vec![],
456 vertex_count: 0,
457 layout,
458 }
459 }
460
461 pub fn upload_f32(&mut self, vertices: &[[f32; 8]]) {
463 let stride = self.layout.stride;
464 self.vertex_count = vertices.len();
465 self.data = vec![0u8; self.vertex_count * stride];
466 for (i, v) in vertices.iter().enumerate() {
467 let base = i * stride;
468 for j in 0..3 {
470 let bytes = v[j].to_le_bytes();
471 self.data[base + j * 4..base + j * 4 + 4].copy_from_slice(&bytes);
472 }
473 let no = self.layout.normal_offset;
475 for j in 0..3 {
476 let bytes = v[3 + j].to_le_bytes();
477 self.data[base + no + j * 4..base + no + j * 4 + 4].copy_from_slice(&bytes);
478 }
479 let uo = self.layout.uv_offset;
481 for j in 0..2 {
482 let bytes = v[6 + j].to_le_bytes();
483 self.data[base + uo + j * 4..base + uo + j * 4 + 4].copy_from_slice(&bytes);
484 }
485 }
486 }
487
488 pub fn byte_size(&self) -> usize {
490 self.data.len()
491 }
492
493 pub fn get_position(&self, i: usize) -> Option<[f32; 3]> {
495 if i >= self.vertex_count {
496 return None;
497 }
498 let base = i * self.layout.stride + self.layout.pos_offset;
499 if base + 12 > self.data.len() {
500 return None;
501 }
502 Some([
503 f32::from_le_bytes(self.data[base..base + 4].try_into().ok()?),
504 f32::from_le_bytes(self.data[base + 4..base + 8].try_into().ok()?),
505 f32::from_le_bytes(self.data[base + 8..base + 12].try_into().ok()?),
506 ])
507 }
508}
509
510#[derive(Debug, Clone, Copy, PartialEq, Eq)]
516pub enum IndexTopology {
517 Triangles,
519 Lines,
521}
522
523#[derive(Debug, Clone)]
525pub struct IndexBuffer {
526 data: Vec<u8>,
528 pub index_count: usize,
530 pub wide: bool,
532 pub topology: IndexTopology,
534}
535
536impl IndexBuffer {
537 pub fn new_u32(topology: IndexTopology) -> Self {
539 Self {
540 data: vec![],
541 index_count: 0,
542 wide: true,
543 topology,
544 }
545 }
546
547 pub fn new_u16(topology: IndexTopology) -> Self {
549 Self {
550 data: vec![],
551 index_count: 0,
552 wide: false,
553 topology,
554 }
555 }
556
557 pub fn upload_u32(&mut self, indices: &[u32]) {
559 self.wide = true;
560 self.index_count = indices.len();
561 self.data = indices.iter().flat_map(|&i| i.to_le_bytes()).collect();
562 }
563
564 pub fn upload_u16(&mut self, indices: &[u16]) {
566 self.wide = false;
567 self.index_count = indices.len();
568 self.data = indices.iter().flat_map(|&i| i.to_le_bytes()).collect();
569 }
570
571 pub fn get_index(&self, i: usize) -> Option<u32> {
573 if i >= self.index_count {
574 return None;
575 }
576 if self.wide {
577 let base = i * 4;
578 let bytes: [u8; 4] = self.data[base..base + 4].try_into().ok()?;
579 Some(u32::from_le_bytes(bytes))
580 } else {
581 let base = i * 2;
582 let bytes: [u8; 2] = self.data[base..base + 2].try_into().ok()?;
583 Some(u16::from_le_bytes(bytes) as u32)
584 }
585 }
586
587 pub fn byte_size(&self) -> usize {
589 self.data.len()
590 }
591}
592
593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599pub enum TextureFormat {
600 Rgba8,
602 R32f,
604 Rg32f,
606}
607
608impl TextureFormat {
609 pub fn bytes_per_pixel(self) -> usize {
611 match self {
612 TextureFormat::Rgba8 => 4,
613 TextureFormat::R32f => 4,
614 TextureFormat::Rg32f => 8,
615 }
616 }
617}
618
619#[derive(Debug, Clone)]
621pub struct TextureBuffer {
622 pub width: usize,
624 pub height: usize,
626 pub format: TextureFormat,
628 data: Vec<u8>,
630}
631
632impl TextureBuffer {
633 pub fn new(width: usize, height: usize, format: TextureFormat) -> Self {
635 let size = width * height * format.bytes_per_pixel();
636 Self {
637 width,
638 height,
639 format,
640 data: vec![0u8; size],
641 }
642 }
643
644 pub fn byte_size(&self) -> usize {
646 self.data.len()
647 }
648
649 pub fn write_row(&mut self, row: usize, src: &[u8]) -> usize {
651 let bpp = self.format.bytes_per_pixel();
652 let row_bytes = self.width * bpp;
653 let offset = row * row_bytes;
654 if offset + row_bytes > self.data.len() {
655 return 0;
656 }
657 let len = src.len().min(row_bytes);
658 self.data[offset..offset + len].copy_from_slice(&src[..len]);
659 len
660 }
661
662 pub fn read_row(&self, row: usize) -> Vec<u8> {
664 let bpp = self.format.bytes_per_pixel();
665 let row_bytes = self.width * bpp;
666 let offset = row * row_bytes;
667 if offset + row_bytes > self.data.len() {
668 return vec![];
669 }
670 self.data[offset..offset + row_bytes].to_vec()
671 }
672
673 pub fn write_pixel(&mut self, x: usize, y: usize, pixel: &[u8]) -> bool {
675 let bpp = self.format.bytes_per_pixel();
676 let offset = (y * self.width + x) * bpp;
677 if offset + bpp > self.data.len() || pixel.len() < bpp {
678 return false;
679 }
680 self.data[offset..offset + bpp].copy_from_slice(&pixel[..bpp]);
681 true
682 }
683
684 pub fn read_pixel(&self, x: usize, y: usize) -> Vec<u8> {
686 let bpp = self.format.bytes_per_pixel();
687 let offset = (y * self.width + x) * bpp;
688 if offset + bpp > self.data.len() {
689 return vec![];
690 }
691 self.data[offset..offset + bpp].to_vec()
692 }
693
694 pub fn clear(&mut self) {
696 self.data.fill(0);
697 }
698}
699
700#[derive(Debug, Clone, Default)]
706pub struct GpuMemoryStats {
707 pub total_allocated: usize,
709 pub free_bytes: usize,
711 pub fragmentation: f64,
713 pub buffer_counts: HashMap<String, usize>,
715}
716
717impl GpuMemoryStats {
718 pub fn from_pool(pool: &GpuBufferPool) -> Self {
720 let total = pool.capacity;
721 let used = pool.used_bytes();
722 let free = pool.free_bytes();
723 let live: usize = pool.allocations.values().map(|h| h.size).sum();
724 let frag = if used > 0 {
725 1.0 - (live as f64 / used as f64)
726 } else {
727 0.0
728 };
729 let mut buffer_counts = HashMap::new();
730 for h in pool.allocations.values() {
731 let key = format!("{:?}", h.buffer_type);
732 *buffer_counts.entry(key).or_insert(0) += 1;
733 }
734 Self {
735 total_allocated: total,
736 free_bytes: free,
737 fragmentation: frag.clamp(0.0, 1.0),
738 buffer_counts,
739 }
740 }
741}
742
743#[derive(Debug, Clone)]
749pub struct TransferOp {
750 pub src: Vec<u8>,
752 pub dst_handle: GpuBufferHandle,
754 pub dst_offset: usize,
756 pub upload: bool,
758}
759
760#[derive(Debug, Clone)]
762pub struct DownloadResult {
763 pub handle: GpuBufferHandle,
765 pub data: Vec<u8>,
767}
768
769#[derive(Debug)]
771pub struct TransferQueue {
772 uploads: Vec<TransferOp>,
774 downloads: Vec<DownloadResult>,
776}
777
778impl TransferQueue {
779 pub fn new() -> Self {
781 Self {
782 uploads: vec![],
783 downloads: vec![],
784 }
785 }
786
787 pub fn copy_to_gpu(&mut self, handle: GpuBufferHandle, offset: usize, data: Vec<u8>) {
789 self.uploads.push(TransferOp {
790 src: data,
791 dst_handle: handle,
792 dst_offset: offset,
793 upload: true,
794 });
795 }
796
797 pub fn copy_from_gpu(&mut self, handle: GpuBufferHandle, _offset: usize, _len: usize) {
799 self.downloads.push(DownloadResult {
801 handle,
802 data: vec![0u8; _len],
803 });
804 }
805
806 pub fn execute_transfers(&mut self, pool: &mut GpuBufferPool) -> usize {
810 let n = self.uploads.len();
811 for op in self.uploads.drain(..) {
812 pool.write(&op.dst_handle, op.dst_offset, &op.src);
813 }
814 n
815 }
816
817 pub fn drain_downloads(&mut self) -> Vec<DownloadResult> {
819 std::mem::take(&mut self.downloads)
820 }
821
822 pub fn pending_uploads(&self) -> usize {
824 self.uploads.len()
825 }
826}
827
828impl Default for TransferQueue {
829 fn default() -> Self {
830 Self::new()
831 }
832}
833
834#[cfg(test)]
839mod tests {
840 use super::*;
841
842 #[test]
845 fn test_gpu_buffer_new() {
846 let buf = GpuBuffer::new(GpuBufferType::Storage, 64);
847 assert_eq!(buf.size(), 64);
848 assert!(!buf.is_mapped());
849 }
850
851 #[test]
852 fn test_gpu_buffer_write_read() {
853 let mut buf = GpuBuffer::new(GpuBufferType::Storage, 16);
854 let written = buf.write(4, &[1, 2, 3, 4]);
855 assert_eq!(written, 4);
856 let mut dst = [0u8; 4];
857 buf.read(4, &mut dst);
858 assert_eq!(dst, [1, 2, 3, 4]);
859 }
860
861 #[test]
862 fn test_gpu_buffer_resize_grows() {
863 let mut buf = GpuBuffer::new(GpuBufferType::Uniform, 8);
864 buf.resize(32);
865 assert_eq!(buf.size(), 32);
866 }
867
868 #[test]
869 fn test_gpu_buffer_clear() {
870 let mut buf = GpuBuffer::new(GpuBufferType::Vertex, 8);
871 buf.write(0, &[1, 2, 3, 4, 5, 6, 7, 8]);
872 buf.clear();
873 let mut dst = [0xFFu8; 8];
874 buf.read(0, &mut dst);
875 assert_eq!(dst, [0u8; 8]);
876 }
877
878 #[test]
879 fn test_gpu_buffer_map_unmap() {
880 let mut buf = GpuBuffer::new(GpuBufferType::Staging, 4);
881 assert!(buf.map().is_some());
882 assert!(buf.is_mapped());
883 assert!(buf.map().is_none()); buf.unmap();
885 assert!(!buf.is_mapped());
886 }
887
888 #[test]
891 fn test_pool_alloc_basic() {
892 let mut pool = GpuBufferPool::new(4096);
893 let h = pool.alloc(128, GpuBufferType::Storage);
894 assert!(h.is_some());
895 assert_eq!(pool.allocation_count(), 1);
896 }
897
898 #[test]
899 fn test_pool_alloc_oom() {
900 let mut pool = GpuBufferPool::new(256);
901 let h = pool.alloc(512, GpuBufferType::Uniform);
902 assert!(h.is_none());
903 }
904
905 #[test]
906 fn test_pool_free() {
907 let mut pool = GpuBufferPool::new(4096);
908 let h = pool.alloc(64, GpuBufferType::Vertex).unwrap();
909 pool.free(h);
910 assert_eq!(pool.allocation_count(), 0);
911 }
912
913 #[test]
914 fn test_pool_write_read() {
915 let mut pool = GpuBufferPool::new(4096);
916 let h = pool.alloc(256, GpuBufferType::Storage).unwrap();
917 let src = [0xABu8; 8];
918 pool.write(&h, 0, &src);
919 let mut dst = [0u8; 8];
920 pool.read(&h, 0, &mut dst);
921 assert_eq!(dst, [0xABu8; 8]);
922 }
923
924 #[test]
925 fn test_pool_defragment() {
926 let mut pool = GpuBufferPool::new(4096);
927 let h1 = pool.alloc(256, GpuBufferType::Storage).unwrap();
928 let h2 = pool.alloc(256, GpuBufferType::Storage).unwrap();
929 pool.free(h1);
930 pool.defragment();
931 let h2_new = pool
933 .allocations
934 .values()
935 .find(|h| h.id == h2.id)
936 .copied()
937 .unwrap();
938 assert!(h2_new.offset < 256 + 256);
939 }
940
941 #[test]
944 fn test_uniform_set_get() {
945 let mut ub = UniformBuffer::new("matrices");
946 ub.set_field("time", 1.23);
947 assert!((ub.get_field("time").unwrap() - 1.23).abs() < 1e-10);
948 }
949
950 #[test]
951 fn test_uniform_get_missing() {
952 let ub = UniformBuffer::new("test");
953 assert!(ub.get_field("nonexistent").is_none());
954 }
955
956 #[test]
957 fn test_uniform_serialize_length() {
958 let mut ub = UniformBuffer::new("test");
959 ub.set_field("a", 1.0);
960 ub.set_field("b", 2.0);
961 let bytes = ub.serialize();
962 assert_eq!(bytes.len(), 16); }
964
965 #[test]
966 fn test_uniform_field_count() {
967 let mut ub = UniformBuffer::new("test");
968 ub.set_field("x", 1.0);
969 ub.set_field("y", 2.0);
970 ub.set_field("z", 3.0);
971 assert_eq!(ub.field_count(), 3);
972 }
973
974 #[test]
977 fn test_storage_map_write() {
978 let mut sb = StorageBuffer::new("particles", 64);
979 {
980 let slice = sb.map().unwrap();
981 slice[0] = 42;
982 }
983 sb.unmap();
984 let bytes = sb.read_bytes(0, 1);
985 assert_eq!(bytes[0], 42);
986 }
987
988 #[test]
989 fn test_storage_write_read_bytes() {
990 let mut sb = StorageBuffer::new("buf", 32);
991 sb.write_bytes(4, &[10, 20, 30]);
992 let r = sb.read_bytes(4, 3);
993 assert_eq!(r, vec![10, 20, 30]);
994 }
995
996 #[test]
999 fn test_vertex_buffer_upload_read_pos() {
1000 let mut vb = VertexBuffer::new(VertexLayout::standard());
1001 let verts = [[0.0f32, 1.0, 2.0, 0.0, 1.0, 0.0, 0.5, 0.5]];
1002 vb.upload_f32(&verts);
1003 assert_eq!(vb.vertex_count, 1);
1004 let pos = vb.get_position(0).unwrap();
1005 assert!((pos[0]).abs() < 1e-6);
1006 assert!((pos[1] - 1.0).abs() < 1e-6);
1007 assert!((pos[2] - 2.0).abs() < 1e-6);
1008 }
1009
1010 #[test]
1011 fn test_vertex_buffer_out_of_bounds() {
1012 let vb = VertexBuffer::new(VertexLayout::standard());
1013 assert!(vb.get_position(0).is_none());
1014 }
1015
1016 #[test]
1019 fn test_index_buffer_u32() {
1020 let mut ib = IndexBuffer::new_u32(IndexTopology::Triangles);
1021 ib.upload_u32(&[0, 1, 2, 3, 4, 5]);
1022 assert_eq!(ib.index_count, 6);
1023 assert_eq!(ib.get_index(2), Some(2));
1024 }
1025
1026 #[test]
1027 fn test_index_buffer_u16() {
1028 let mut ib = IndexBuffer::new_u16(IndexTopology::Triangles);
1029 ib.upload_u16(&[0, 1, 2]);
1030 assert_eq!(ib.get_index(1), Some(1u32));
1031 }
1032
1033 #[test]
1034 fn test_index_buffer_out_of_bounds() {
1035 let ib = IndexBuffer::new_u32(IndexTopology::Lines);
1036 assert!(ib.get_index(0).is_none());
1037 }
1038
1039 #[test]
1042 fn test_texture_rgba8_size() {
1043 let tex = TextureBuffer::new(4, 4, TextureFormat::Rgba8);
1044 assert_eq!(tex.byte_size(), 4 * 4 * 4);
1045 }
1046
1047 #[test]
1048 fn test_texture_write_read_pixel() {
1049 let mut tex = TextureBuffer::new(2, 2, TextureFormat::Rgba8);
1050 let pixel = [255u8, 0, 128, 255];
1051 assert!(tex.write_pixel(1, 0, &pixel));
1052 let read = tex.read_pixel(1, 0);
1053 assert_eq!(read, pixel);
1054 }
1055
1056 #[test]
1057 fn test_texture_write_read_row() {
1058 let mut tex = TextureBuffer::new(4, 1, TextureFormat::Rgba8);
1059 let row = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
1060 tex.write_row(0, &row);
1061 let r = tex.read_row(0);
1062 assert_eq!(r, row);
1063 }
1064
1065 #[test]
1066 fn test_texture_clear() {
1067 let mut tex = TextureBuffer::new(2, 2, TextureFormat::R32f);
1068 tex.write_pixel(0, 0, &1.0f32.to_le_bytes());
1069 tex.clear();
1070 let p = tex.read_pixel(0, 0);
1071 assert!(p.iter().all(|&b| b == 0));
1072 }
1073
1074 #[test]
1077 fn test_memory_stats_from_pool() {
1078 let mut pool = GpuBufferPool::new(4096);
1079 pool.alloc(128, GpuBufferType::Vertex);
1080 pool.alloc(256, GpuBufferType::Storage);
1081 let stats = GpuMemoryStats::from_pool(&pool);
1082 assert_eq!(stats.total_allocated, 4096);
1083 assert!(stats.free_bytes < 4096);
1084 }
1085
1086 #[test]
1089 fn test_transfer_queue_upload() {
1090 let mut pool = GpuBufferPool::new(4096);
1091 let h = pool.alloc(16, GpuBufferType::Uniform).unwrap();
1092 let mut tq = TransferQueue::new();
1093 tq.copy_to_gpu(h, 0, vec![0xFFu8; 8]);
1094 assert_eq!(tq.pending_uploads(), 1);
1095 let executed = tq.execute_transfers(&mut pool);
1096 assert_eq!(executed, 1);
1097 assert_eq!(tq.pending_uploads(), 0);
1098 let mut dst = [0u8; 8];
1100 pool.read(&h, 0, &mut dst);
1101 assert_eq!(dst, [0xFFu8; 8]);
1102 }
1103
1104 #[test]
1105 fn test_transfer_queue_download() {
1106 let mut pool = GpuBufferPool::new(4096);
1107 let h = pool.alloc(64, GpuBufferType::Storage).unwrap();
1108 let mut tq = TransferQueue::new();
1109 tq.copy_from_gpu(h, 0, 32);
1110 let results = tq.drain_downloads();
1111 assert_eq!(results.len(), 1);
1112 assert_eq!(results[0].data.len(), 32);
1113 }
1114}