1use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum GpuBufferType {
18 Uniform,
20 Storage,
22 Vertex,
24 Index,
26 Texture,
28 Staging,
30}
31
32#[derive(Debug, Clone)]
38pub struct GpuBuffer {
39 pub buffer_type: GpuBufferType,
41 data: Vec<u8>,
43 mapped: bool,
45}
46
47impl GpuBuffer {
48 pub fn new(buffer_type: GpuBufferType, size: usize) -> Self {
50 Self {
51 buffer_type,
52 data: vec![0u8; size],
53 mapped: false,
54 }
55 }
56
57 pub fn size(&self) -> usize {
59 self.data.len()
60 }
61
62 pub fn read(&self, offset: usize, dst: &mut [u8]) -> usize {
64 if offset >= self.data.len() {
65 return 0;
66 }
67 let len = dst.len().min(self.data.len() - offset);
68 dst[..len].copy_from_slice(&self.data[offset..offset + len]);
69 len
70 }
71
72 pub fn write(&mut self, offset: usize, src: &[u8]) -> usize {
74 if offset >= self.data.len() {
75 return 0;
76 }
77 let len = src.len().min(self.data.len() - offset);
78 self.data[offset..offset + len].copy_from_slice(&src[..len]);
79 len
80 }
81
82 pub fn resize(&mut self, new_size: usize) {
84 self.data.resize(new_size, 0);
85 }
86
87 pub fn clear(&mut self) {
89 self.data.fill(0);
90 }
91
92 pub fn map(&mut self) -> Option<&mut [u8]> {
94 if self.mapped {
95 return None;
96 }
97 self.mapped = true;
98 Some(&mut self.data)
99 }
100
101 pub fn unmap(&mut self) {
103 self.mapped = false;
104 }
105
106 pub fn is_mapped(&self) -> bool {
108 self.mapped
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct GpuBufferHandle {
119 pub id: u64,
121 pub size: usize,
123 pub offset: usize,
125 pub buffer_type: GpuBufferType,
127}
128
129impl GpuBufferHandle {
130 pub fn null() -> Self {
132 Self {
133 id: 0,
134 size: 0,
135 offset: 0,
136 buffer_type: GpuBufferType::Staging,
137 }
138 }
139
140 pub fn is_null(&self) -> bool {
142 self.id == 0
143 }
144}
145
146#[derive(Debug)]
152pub struct GpuBufferPool {
153 backing: Vec<u8>,
155 next_offset: usize,
157 allocations: HashMap<u64, GpuBufferHandle>,
159 next_id: u64,
161 capacity: usize,
163}
164
165impl GpuBufferPool {
166 pub fn new(capacity: usize) -> Self {
168 Self {
169 backing: vec![0u8; capacity],
170 next_offset: 0,
171 allocations: HashMap::new(),
172 next_id: 1,
173 capacity,
174 }
175 }
176
177 pub fn alloc(&mut self, size: usize, buffer_type: GpuBufferType) -> Option<GpuBufferHandle> {
179 let aligned = align_up(size, 256);
180 if self.next_offset + aligned > self.capacity {
181 return None;
182 }
183 let handle = GpuBufferHandle {
184 id: self.next_id,
185 size,
186 offset: self.next_offset,
187 buffer_type,
188 };
189 self.next_id += 1;
190 self.next_offset += aligned;
191 self.allocations.insert(handle.id, handle);
192 Some(handle)
193 }
194
195 pub fn free(&mut self, handle: GpuBufferHandle) {
197 self.allocations.remove(&handle.id);
198 }
199
200 pub fn defragment(&mut self) {
204 let mut sorted: Vec<GpuBufferHandle> = self.allocations.values().cloned().collect();
205 sorted.sort_by_key(|h| h.id);
206
207 let old_backing = self.backing.clone();
208 self.backing.fill(0);
209 let mut cursor = 0usize;
210
211 for h in &mut sorted {
212 let old_off = h.offset;
213 let size = h.size;
214 let aligned = align_up(size, 256);
215 if old_off + size <= old_backing.len() {
216 self.backing[cursor..cursor + size]
217 .copy_from_slice(&old_backing[old_off..old_off + size]);
218 }
219 h.offset = cursor;
220 cursor += aligned;
221 }
222 self.next_offset = cursor;
223
224 self.allocations.clear();
226 for h in sorted {
227 self.allocations.insert(h.id, h);
228 }
229 }
230
231 pub fn write(&mut self, handle: &GpuBufferHandle, offset: usize, src: &[u8]) -> usize {
233 let start = handle.offset + offset;
234 if start >= self.backing.len() {
235 return 0;
236 }
237 let len = src
238 .len()
239 .min(self.backing.len() - start)
240 .min(handle.size.saturating_sub(offset));
241 self.backing[start..start + len].copy_from_slice(&src[..len]);
242 len
243 }
244
245 pub fn read(&self, handle: &GpuBufferHandle, offset: usize, dst: &mut [u8]) -> usize {
247 let start = handle.offset + offset;
248 if start >= self.backing.len() {
249 return 0;
250 }
251 let len = dst
252 .len()
253 .min(self.backing.len() - start)
254 .min(handle.size.saturating_sub(offset));
255 dst[..len].copy_from_slice(&self.backing[start..start + len]);
256 len
257 }
258
259 pub fn allocation_count(&self) -> usize {
261 self.allocations.len()
262 }
263
264 pub fn used_bytes(&self) -> usize {
266 self.next_offset
267 }
268
269 pub fn free_bytes(&self) -> usize {
271 self.capacity.saturating_sub(self.next_offset)
272 }
273}
274
275fn align_up(size: usize, align: usize) -> usize {
276 size.div_ceil(align) * align
277}
278
279#[derive(Debug, Clone)]
285pub struct UniformBuffer {
286 pub name: String,
288 fields: HashMap<String, f64>,
290 byte_cache: Option<Vec<u8>>,
292}
293
294impl UniformBuffer {
295 pub fn new(name: impl Into<String>) -> Self {
297 Self {
298 name: name.into(),
299 fields: HashMap::new(),
300 byte_cache: None,
301 }
302 }
303
304 pub fn set_field(&mut self, name: &str, value: f64) {
306 self.fields.insert(name.to_owned(), value);
307 self.byte_cache = None; }
309
310 pub fn get_field(&self, name: &str) -> Option<f64> {
312 self.fields.get(name).copied()
313 }
314
315 pub fn serialize(&mut self) -> &[u8] {
319 if self.byte_cache.is_none() {
320 let mut names: Vec<&String> = self.fields.keys().collect();
321 names.sort();
322 let mut bytes = Vec::with_capacity(names.len() * 8);
323 for name in names {
324 let v = self.fields[name];
325 bytes.extend_from_slice(&v.to_le_bytes());
326 }
327 self.byte_cache = Some(bytes);
328 }
329 self.byte_cache.as_ref().expect("value should be Some")
330 }
331
332 pub fn field_count(&self) -> usize {
334 self.fields.len()
335 }
336}
337
338#[derive(Debug, Clone)]
344pub struct StorageBuffer {
345 pub label: String,
347 data: Vec<u8>,
349 mapped: bool,
351}
352
353impl StorageBuffer {
354 pub fn new(label: impl Into<String>, size: usize) -> Self {
356 Self {
357 label: label.into(),
358 data: vec![0u8; size],
359 mapped: false,
360 }
361 }
362
363 pub fn size(&self) -> usize {
365 self.data.len()
366 }
367
368 pub fn map(&mut self) -> Option<&mut [u8]> {
370 if self.mapped {
371 return None;
372 }
373 self.mapped = true;
374 Some(&mut self.data)
375 }
376
377 pub fn unmap(&mut self) {
379 self.mapped = false;
380 }
381
382 pub fn read_bytes(&self, offset: usize, len: usize) -> Vec<u8> {
384 if offset >= self.data.len() {
385 return vec![];
386 }
387 let end = (offset + len).min(self.data.len());
388 self.data[offset..end].to_vec()
389 }
390
391 pub fn write_bytes(&mut self, offset: usize, src: &[u8]) -> usize {
393 if offset >= self.data.len() {
394 return 0;
395 }
396 let len = src.len().min(self.data.len() - offset);
397 self.data[offset..offset + len].copy_from_slice(&src[..len]);
398 len
399 }
400
401 pub fn clear(&mut self) {
403 self.data.fill(0);
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq)]
413pub struct VertexLayout {
414 pub pos_offset: usize,
416 pub normal_offset: usize,
418 pub uv_offset: usize,
420 pub stride: usize,
422}
423
424impl VertexLayout {
425 pub fn standard() -> Self {
427 Self {
428 pos_offset: 0,
429 normal_offset: 12,
430 uv_offset: 24,
431 stride: 32,
432 }
433 }
434}
435
436#[derive(Debug, Clone)]
438pub struct VertexBuffer {
439 data: Vec<u8>,
441 pub vertex_count: usize,
443 pub layout: VertexLayout,
445}
446
447impl VertexBuffer {
448 pub fn new(layout: VertexLayout) -> Self {
450 Self {
451 data: vec![],
452 vertex_count: 0,
453 layout,
454 }
455 }
456
457 pub fn upload_f32(&mut self, vertices: &[[f32; 8]]) {
459 let stride = self.layout.stride;
460 self.vertex_count = vertices.len();
461 self.data = vec![0u8; self.vertex_count * stride];
462 for (i, v) in vertices.iter().enumerate() {
463 let base = i * stride;
464 for (j, &comp) in v[..3].iter().enumerate() {
466 let bytes = comp.to_le_bytes();
467 self.data[base + j * 4..base + j * 4 + 4].copy_from_slice(&bytes);
468 }
469 let no = self.layout.normal_offset;
471 for (j, &comp) in v[3..6].iter().enumerate() {
472 let bytes = comp.to_le_bytes();
473 self.data[base + no + j * 4..base + no + j * 4 + 4].copy_from_slice(&bytes);
474 }
475 let uo = self.layout.uv_offset;
477 for (j, &comp) in v[6..8].iter().enumerate() {
478 let bytes = comp.to_le_bytes();
479 self.data[base + uo + j * 4..base + uo + j * 4 + 4].copy_from_slice(&bytes);
480 }
481 }
482 }
483
484 pub fn byte_size(&self) -> usize {
486 self.data.len()
487 }
488
489 pub fn get_position(&self, i: usize) -> Option<[f32; 3]> {
491 if i >= self.vertex_count {
492 return None;
493 }
494 let base = i * self.layout.stride + self.layout.pos_offset;
495 if base + 12 > self.data.len() {
496 return None;
497 }
498 Some([
499 f32::from_le_bytes(self.data[base..base + 4].try_into().ok()?),
500 f32::from_le_bytes(self.data[base + 4..base + 8].try_into().ok()?),
501 f32::from_le_bytes(self.data[base + 8..base + 12].try_into().ok()?),
502 ])
503 }
504}
505
506#[derive(Debug, Clone, Copy, PartialEq, Eq)]
512pub enum IndexTopology {
513 Triangles,
515 Lines,
517}
518
519#[derive(Debug, Clone)]
521pub struct IndexBuffer {
522 data: Vec<u8>,
524 pub index_count: usize,
526 pub wide: bool,
528 pub topology: IndexTopology,
530}
531
532impl IndexBuffer {
533 pub fn new_u32(topology: IndexTopology) -> Self {
535 Self {
536 data: vec![],
537 index_count: 0,
538 wide: true,
539 topology,
540 }
541 }
542
543 pub fn new_u16(topology: IndexTopology) -> Self {
545 Self {
546 data: vec![],
547 index_count: 0,
548 wide: false,
549 topology,
550 }
551 }
552
553 pub fn upload_u32(&mut self, indices: &[u32]) {
555 self.wide = true;
556 self.index_count = indices.len();
557 self.data = indices.iter().flat_map(|&i| i.to_le_bytes()).collect();
558 }
559
560 pub fn upload_u16(&mut self, indices: &[u16]) {
562 self.wide = false;
563 self.index_count = indices.len();
564 self.data = indices.iter().flat_map(|&i| i.to_le_bytes()).collect();
565 }
566
567 pub fn get_index(&self, i: usize) -> Option<u32> {
569 if i >= self.index_count {
570 return None;
571 }
572 if self.wide {
573 let base = i * 4;
574 let bytes: [u8; 4] = self.data[base..base + 4].try_into().ok()?;
575 Some(u32::from_le_bytes(bytes))
576 } else {
577 let base = i * 2;
578 let bytes: [u8; 2] = self.data[base..base + 2].try_into().ok()?;
579 Some(u16::from_le_bytes(bytes) as u32)
580 }
581 }
582
583 pub fn byte_size(&self) -> usize {
585 self.data.len()
586 }
587}
588
589#[derive(Debug, Clone, Copy, PartialEq, Eq)]
595pub enum TextureFormat {
596 Rgba8,
598 R32f,
600 Rg32f,
602}
603
604impl TextureFormat {
605 pub fn bytes_per_pixel(self) -> usize {
607 match self {
608 TextureFormat::Rgba8 => 4,
609 TextureFormat::R32f => 4,
610 TextureFormat::Rg32f => 8,
611 }
612 }
613}
614
615#[derive(Debug, Clone)]
617pub struct TextureBuffer {
618 pub width: usize,
620 pub height: usize,
622 pub format: TextureFormat,
624 data: Vec<u8>,
626}
627
628impl TextureBuffer {
629 pub fn new(width: usize, height: usize, format: TextureFormat) -> Self {
631 let size = width * height * format.bytes_per_pixel();
632 Self {
633 width,
634 height,
635 format,
636 data: vec![0u8; size],
637 }
638 }
639
640 pub fn byte_size(&self) -> usize {
642 self.data.len()
643 }
644
645 pub fn write_row(&mut self, row: usize, src: &[u8]) -> usize {
647 let bpp = self.format.bytes_per_pixel();
648 let row_bytes = self.width * bpp;
649 let offset = row * row_bytes;
650 if offset + row_bytes > self.data.len() {
651 return 0;
652 }
653 let len = src.len().min(row_bytes);
654 self.data[offset..offset + len].copy_from_slice(&src[..len]);
655 len
656 }
657
658 pub fn read_row(&self, row: usize) -> Vec<u8> {
660 let bpp = self.format.bytes_per_pixel();
661 let row_bytes = self.width * bpp;
662 let offset = row * row_bytes;
663 if offset + row_bytes > self.data.len() {
664 return vec![];
665 }
666 self.data[offset..offset + row_bytes].to_vec()
667 }
668
669 pub fn write_pixel(&mut self, x: usize, y: usize, pixel: &[u8]) -> bool {
671 let bpp = self.format.bytes_per_pixel();
672 let offset = (y * self.width + x) * bpp;
673 if offset + bpp > self.data.len() || pixel.len() < bpp {
674 return false;
675 }
676 self.data[offset..offset + bpp].copy_from_slice(&pixel[..bpp]);
677 true
678 }
679
680 pub fn read_pixel(&self, x: usize, y: usize) -> Vec<u8> {
682 let bpp = self.format.bytes_per_pixel();
683 let offset = (y * self.width + x) * bpp;
684 if offset + bpp > self.data.len() {
685 return vec![];
686 }
687 self.data[offset..offset + bpp].to_vec()
688 }
689
690 pub fn clear(&mut self) {
692 self.data.fill(0);
693 }
694}
695
696#[derive(Debug, Clone, Default)]
702pub struct GpuMemoryStats {
703 pub total_allocated: usize,
705 pub free_bytes: usize,
707 pub fragmentation: f64,
709 pub buffer_counts: HashMap<String, usize>,
711}
712
713impl GpuMemoryStats {
714 pub fn from_pool(pool: &GpuBufferPool) -> Self {
716 let total = pool.capacity;
717 let used = pool.used_bytes();
718 let free = pool.free_bytes();
719 let live: usize = pool.allocations.values().map(|h| h.size).sum();
720 let frag = if used > 0 {
721 1.0 - (live as f64 / used as f64)
722 } else {
723 0.0
724 };
725 let mut buffer_counts = HashMap::new();
726 for h in pool.allocations.values() {
727 let key = format!("{:?}", h.buffer_type);
728 *buffer_counts.entry(key).or_insert(0) += 1;
729 }
730 Self {
731 total_allocated: total,
732 free_bytes: free,
733 fragmentation: frag.clamp(0.0, 1.0),
734 buffer_counts,
735 }
736 }
737}
738
739#[derive(Debug, Clone)]
745pub struct TransferOp {
746 pub src: Vec<u8>,
748 pub dst_handle: GpuBufferHandle,
750 pub dst_offset: usize,
752 pub upload: bool,
754}
755
756#[derive(Debug, Clone)]
758pub struct DownloadResult {
759 pub handle: GpuBufferHandle,
761 pub data: Vec<u8>,
763}
764
765#[derive(Debug)]
767pub struct TransferQueue {
768 uploads: Vec<TransferOp>,
770 downloads: Vec<DownloadResult>,
772}
773
774impl TransferQueue {
775 pub fn new() -> Self {
777 Self {
778 uploads: vec![],
779 downloads: vec![],
780 }
781 }
782
783 pub fn copy_to_gpu(&mut self, handle: GpuBufferHandle, offset: usize, data: Vec<u8>) {
785 self.uploads.push(TransferOp {
786 src: data,
787 dst_handle: handle,
788 dst_offset: offset,
789 upload: true,
790 });
791 }
792
793 pub fn copy_from_gpu(&mut self, handle: GpuBufferHandle, _offset: usize, _len: usize) {
795 self.downloads.push(DownloadResult {
797 handle,
798 data: vec![0u8; _len],
799 });
800 }
801
802 pub fn execute_transfers(&mut self, pool: &mut GpuBufferPool) -> usize {
806 let n = self.uploads.len();
807 for op in self.uploads.drain(..) {
808 pool.write(&op.dst_handle, op.dst_offset, &op.src);
809 }
810 n
811 }
812
813 pub fn drain_downloads(&mut self) -> Vec<DownloadResult> {
815 std::mem::take(&mut self.downloads)
816 }
817
818 pub fn pending_uploads(&self) -> usize {
820 self.uploads.len()
821 }
822}
823
824impl Default for TransferQueue {
825 fn default() -> Self {
826 Self::new()
827 }
828}
829
830#[cfg(test)]
835mod tests {
836 use super::*;
837
838 #[test]
841 fn test_gpu_buffer_new() {
842 let buf = GpuBuffer::new(GpuBufferType::Storage, 64);
843 assert_eq!(buf.size(), 64);
844 assert!(!buf.is_mapped());
845 }
846
847 #[test]
848 fn test_gpu_buffer_write_read() {
849 let mut buf = GpuBuffer::new(GpuBufferType::Storage, 16);
850 let written = buf.write(4, &[1, 2, 3, 4]);
851 assert_eq!(written, 4);
852 let mut dst = [0u8; 4];
853 buf.read(4, &mut dst);
854 assert_eq!(dst, [1, 2, 3, 4]);
855 }
856
857 #[test]
858 fn test_gpu_buffer_resize_grows() {
859 let mut buf = GpuBuffer::new(GpuBufferType::Uniform, 8);
860 buf.resize(32);
861 assert_eq!(buf.size(), 32);
862 }
863
864 #[test]
865 fn test_gpu_buffer_clear() {
866 let mut buf = GpuBuffer::new(GpuBufferType::Vertex, 8);
867 buf.write(0, &[1, 2, 3, 4, 5, 6, 7, 8]);
868 buf.clear();
869 let mut dst = [0xFFu8; 8];
870 buf.read(0, &mut dst);
871 assert_eq!(dst, [0u8; 8]);
872 }
873
874 #[test]
875 fn test_gpu_buffer_map_unmap() {
876 let mut buf = GpuBuffer::new(GpuBufferType::Staging, 4);
877 assert!(buf.map().is_some());
878 assert!(buf.is_mapped());
879 assert!(buf.map().is_none()); buf.unmap();
881 assert!(!buf.is_mapped());
882 }
883
884 #[test]
887 fn test_pool_alloc_basic() {
888 let mut pool = GpuBufferPool::new(4096);
889 let h = pool.alloc(128, GpuBufferType::Storage);
890 assert!(h.is_some());
891 assert_eq!(pool.allocation_count(), 1);
892 }
893
894 #[test]
895 fn test_pool_alloc_oom() {
896 let mut pool = GpuBufferPool::new(256);
897 let h = pool.alloc(512, GpuBufferType::Uniform);
898 assert!(h.is_none());
899 }
900
901 #[test]
902 fn test_pool_free() {
903 let mut pool = GpuBufferPool::new(4096);
904 let h = pool.alloc(64, GpuBufferType::Vertex).unwrap();
905 pool.free(h);
906 assert_eq!(pool.allocation_count(), 0);
907 }
908
909 #[test]
910 fn test_pool_write_read() {
911 let mut pool = GpuBufferPool::new(4096);
912 let h = pool.alloc(256, GpuBufferType::Storage).unwrap();
913 let src = [0xABu8; 8];
914 pool.write(&h, 0, &src);
915 let mut dst = [0u8; 8];
916 pool.read(&h, 0, &mut dst);
917 assert_eq!(dst, [0xABu8; 8]);
918 }
919
920 #[test]
921 fn test_pool_defragment() {
922 let mut pool = GpuBufferPool::new(4096);
923 let h1 = pool.alloc(256, GpuBufferType::Storage).unwrap();
924 let h2 = pool.alloc(256, GpuBufferType::Storage).unwrap();
925 pool.free(h1);
926 pool.defragment();
927 let h2_new = pool
929 .allocations
930 .values()
931 .find(|h| h.id == h2.id)
932 .copied()
933 .unwrap();
934 assert!(h2_new.offset < 256 + 256);
935 }
936
937 #[test]
940 fn test_uniform_set_get() {
941 let mut ub = UniformBuffer::new("matrices");
942 ub.set_field("time", 1.23);
943 assert!((ub.get_field("time").unwrap() - 1.23).abs() < 1e-10);
944 }
945
946 #[test]
947 fn test_uniform_get_missing() {
948 let ub = UniformBuffer::new("test");
949 assert!(ub.get_field("nonexistent").is_none());
950 }
951
952 #[test]
953 fn test_uniform_serialize_length() {
954 let mut ub = UniformBuffer::new("test");
955 ub.set_field("a", 1.0);
956 ub.set_field("b", 2.0);
957 let bytes = ub.serialize();
958 assert_eq!(bytes.len(), 16); }
960
961 #[test]
962 fn test_uniform_field_count() {
963 let mut ub = UniformBuffer::new("test");
964 ub.set_field("x", 1.0);
965 ub.set_field("y", 2.0);
966 ub.set_field("z", 3.0);
967 assert_eq!(ub.field_count(), 3);
968 }
969
970 #[test]
973 fn test_storage_map_write() {
974 let mut sb = StorageBuffer::new("particles", 64);
975 {
976 let slice = sb.map().unwrap();
977 slice[0] = 42;
978 }
979 sb.unmap();
980 let bytes = sb.read_bytes(0, 1);
981 assert_eq!(bytes[0], 42);
982 }
983
984 #[test]
985 fn test_storage_write_read_bytes() {
986 let mut sb = StorageBuffer::new("buf", 32);
987 sb.write_bytes(4, &[10, 20, 30]);
988 let r = sb.read_bytes(4, 3);
989 assert_eq!(r, vec![10, 20, 30]);
990 }
991
992 #[test]
995 fn test_vertex_buffer_upload_read_pos() {
996 let mut vb = VertexBuffer::new(VertexLayout::standard());
997 let verts = [[0.0f32, 1.0, 2.0, 0.0, 1.0, 0.0, 0.5, 0.5]];
998 vb.upload_f32(&verts);
999 assert_eq!(vb.vertex_count, 1);
1000 let pos = vb.get_position(0).unwrap();
1001 assert!((pos[0]).abs() < 1e-6);
1002 assert!((pos[1] - 1.0).abs() < 1e-6);
1003 assert!((pos[2] - 2.0).abs() < 1e-6);
1004 }
1005
1006 #[test]
1007 fn test_vertex_buffer_out_of_bounds() {
1008 let vb = VertexBuffer::new(VertexLayout::standard());
1009 assert!(vb.get_position(0).is_none());
1010 }
1011
1012 #[test]
1015 fn test_index_buffer_u32() {
1016 let mut ib = IndexBuffer::new_u32(IndexTopology::Triangles);
1017 ib.upload_u32(&[0, 1, 2, 3, 4, 5]);
1018 assert_eq!(ib.index_count, 6);
1019 assert_eq!(ib.get_index(2), Some(2));
1020 }
1021
1022 #[test]
1023 fn test_index_buffer_u16() {
1024 let mut ib = IndexBuffer::new_u16(IndexTopology::Triangles);
1025 ib.upload_u16(&[0, 1, 2]);
1026 assert_eq!(ib.get_index(1), Some(1u32));
1027 }
1028
1029 #[test]
1030 fn test_index_buffer_out_of_bounds() {
1031 let ib = IndexBuffer::new_u32(IndexTopology::Lines);
1032 assert!(ib.get_index(0).is_none());
1033 }
1034
1035 #[test]
1038 fn test_texture_rgba8_size() {
1039 let tex = TextureBuffer::new(4, 4, TextureFormat::Rgba8);
1040 assert_eq!(tex.byte_size(), 4 * 4 * 4);
1041 }
1042
1043 #[test]
1044 fn test_texture_write_read_pixel() {
1045 let mut tex = TextureBuffer::new(2, 2, TextureFormat::Rgba8);
1046 let pixel = [255u8, 0, 128, 255];
1047 assert!(tex.write_pixel(1, 0, &pixel));
1048 let read = tex.read_pixel(1, 0);
1049 assert_eq!(read, pixel);
1050 }
1051
1052 #[test]
1053 fn test_texture_write_read_row() {
1054 let mut tex = TextureBuffer::new(4, 1, TextureFormat::Rgba8);
1055 let row = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
1056 tex.write_row(0, &row);
1057 let r = tex.read_row(0);
1058 assert_eq!(r, row);
1059 }
1060
1061 #[test]
1062 fn test_texture_clear() {
1063 let mut tex = TextureBuffer::new(2, 2, TextureFormat::R32f);
1064 tex.write_pixel(0, 0, &1.0f32.to_le_bytes());
1065 tex.clear();
1066 let p = tex.read_pixel(0, 0);
1067 assert!(p.iter().all(|&b| b == 0));
1068 }
1069
1070 #[test]
1073 fn test_memory_stats_from_pool() {
1074 let mut pool = GpuBufferPool::new(4096);
1075 pool.alloc(128, GpuBufferType::Vertex);
1076 pool.alloc(256, GpuBufferType::Storage);
1077 let stats = GpuMemoryStats::from_pool(&pool);
1078 assert_eq!(stats.total_allocated, 4096);
1079 assert!(stats.free_bytes < 4096);
1080 }
1081
1082 #[test]
1085 fn test_transfer_queue_upload() {
1086 let mut pool = GpuBufferPool::new(4096);
1087 let h = pool.alloc(16, GpuBufferType::Uniform).unwrap();
1088 let mut tq = TransferQueue::new();
1089 tq.copy_to_gpu(h, 0, vec![0xFFu8; 8]);
1090 assert_eq!(tq.pending_uploads(), 1);
1091 let executed = tq.execute_transfers(&mut pool);
1092 assert_eq!(executed, 1);
1093 assert_eq!(tq.pending_uploads(), 0);
1094 let mut dst = [0u8; 8];
1096 pool.read(&h, 0, &mut dst);
1097 assert_eq!(dst, [0xFFu8; 8]);
1098 }
1099
1100 #[test]
1101 fn test_transfer_queue_download() {
1102 let mut pool = GpuBufferPool::new(4096);
1103 let h = pool.alloc(64, GpuBufferType::Storage).unwrap();
1104 let mut tq = TransferQueue::new();
1105 tq.copy_from_gpu(h, 0, 32);
1106 let results = tq.drain_downloads();
1107 assert_eq!(results.len(), 1);
1108 assert_eq!(results[0].data.len(), 32);
1109 }
1110}