Skip to main content

oxiphysics_gpu/
memory.rs

1#![allow(clippy::needless_range_loop)]
2// Copyright 2026 COOLJAPAN OU (Team KitaSan)
3// SPDX-License-Identifier: Apache-2.0
4
5//! GPU memory management abstractions.
6//!
7//! Provides CPU-side mock implementations of GPU buffer types, a pool allocator,
8//! transfer queue, and memory statistics — all without requiring an actual GPU.
9
10#![allow(dead_code)]
11#![allow(missing_docs)]
12
13use std::collections::HashMap;
14
15// ---------------------------------------------------------------------------
16// Buffer type tag
17// ---------------------------------------------------------------------------
18
19/// Identifies the logical type of a GPU buffer.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum GpuBufferType {
22    /// Uniform / constant buffer.
23    Uniform,
24    /// Large read-write storage buffer.
25    Storage,
26    /// Vertex data buffer.
27    Vertex,
28    /// Index data buffer.
29    Index,
30    /// Texture / image buffer.
31    Texture,
32    /// Generic staging (upload / download) buffer.
33    Staging,
34}
35
36// ---------------------------------------------------------------------------
37// GpuBuffer — typed CPU-side buffer
38// ---------------------------------------------------------------------------
39
40/// A CPU-side mock for a typed GPU buffer backed by a `Vec`u8`.
41#[derive(Debug, Clone)]
42pub struct GpuBuffer {
43    /// Logical type.
44    pub buffer_type: GpuBufferType,
45    /// Raw byte data.
46    data: Vec<u8>,
47    /// Whether the buffer is mapped (accessible for reads/writes).
48    mapped: bool,
49}
50
51impl GpuBuffer {
52    /// Allocate a new zero-initialised buffer of `size` bytes.
53    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    /// Current size in bytes.
62    pub fn size(&self) -> usize {
63        self.data.len()
64    }
65
66    /// Read bytes at `offset` into `dst`.  Returns the number of bytes copied.
67    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    /// Write bytes from `src` at `offset`.  Returns bytes written.
77    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    /// Resize the buffer (data is zero-initialised for new regions).
87    pub fn resize(&mut self, new_size: usize) {
88        self.data.resize(new_size, 0);
89    }
90
91    /// Zero-fill the entire buffer.
92    pub fn clear(&mut self) {
93        self.data.fill(0);
94    }
95
96    /// Map the buffer for direct slice access (returns `None` if already mapped).
97    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    /// Unmap the buffer.
106    pub fn unmap(&mut self) {
107        self.mapped = false;
108    }
109
110    /// True when the buffer is currently mapped.
111    pub fn is_mapped(&self) -> bool {
112        self.mapped
113    }
114}
115
116// ---------------------------------------------------------------------------
117// GpuBufferHandle
118// ---------------------------------------------------------------------------
119
120/// An opaque handle to an allocated GPU buffer region.
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub struct GpuBufferHandle {
123    /// Unique 64-bit handle value.
124    pub id: u64,
125    /// Size of the allocation in bytes.
126    pub size: usize,
127    /// Byte offset within the backing pool buffer.
128    pub offset: usize,
129    /// Logical buffer type.
130    pub buffer_type: GpuBufferType,
131}
132
133impl GpuBufferHandle {
134    /// Null/invalid handle sentinel.
135    pub fn null() -> Self {
136        Self {
137            id: 0,
138            size: 0,
139            offset: 0,
140            buffer_type: GpuBufferType::Staging,
141        }
142    }
143
144    /// True when this is the null handle.
145    pub fn is_null(&self) -> bool {
146        self.id == 0
147    }
148}
149
150// ---------------------------------------------------------------------------
151// GpuBufferPool — pool allocator
152// ---------------------------------------------------------------------------
153
154/// A pool allocator that sub-allocates from a large backing buffer.
155#[derive(Debug)]
156pub struct GpuBufferPool {
157    /// Backing byte store.
158    backing: Vec<u8>,
159    /// Next free byte offset.
160    next_offset: usize,
161    /// All live allocations.
162    allocations: HashMap<u64, GpuBufferHandle>,
163    /// Next handle ID counter.
164    next_id: u64,
165    /// Total capacity.
166    capacity: usize,
167}
168
169impl GpuBufferPool {
170    /// Create a pool with `capacity` bytes.
171    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    /// Allocate `size` bytes from the pool.  Returns `None` if out of memory.
182    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    /// Free a previously allocated handle.
200    pub fn free(&mut self, handle: GpuBufferHandle) {
201        self.allocations.remove(&handle.id);
202    }
203
204    /// Defragment: compact live allocations to the front of the pool.
205    ///
206    /// Allocations are re-packed in insertion order (by id).
207    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        // Update allocations map with new offsets
229        self.allocations.clear();
230        for h in sorted {
231            self.allocations.insert(h.id, h);
232        }
233    }
234
235    /// Write bytes to a handle's region.
236    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    /// Read bytes from a handle's region.
250    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    /// Number of live allocations.
264    pub fn allocation_count(&self) -> usize {
265        self.allocations.len()
266    }
267
268    /// Bytes used (sum of aligned allocations).
269    pub fn used_bytes(&self) -> usize {
270        self.next_offset
271    }
272
273    /// Free bytes remaining (approximate; does not account for freed holes before defrag).
274    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// ---------------------------------------------------------------------------
284// UniformBuffer
285// ---------------------------------------------------------------------------
286
287/// A structured uniform buffer holding named `f64` fields.
288#[derive(Debug, Clone)]
289pub struct UniformBuffer {
290    /// Name of this uniform block.
291    pub name: String,
292    /// Named field storage.
293    fields: HashMap<String, f64>,
294    /// Serialised byte cache (optional; computed on demand).
295    byte_cache: Option<Vec<u8>>,
296}
297
298impl UniformBuffer {
299    /// Create a new empty uniform buffer.
300    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    /// Set a named field value.
309    pub fn set_field(&mut self, name: &str, value: f64) {
310        self.fields.insert(name.to_owned(), value);
311        self.byte_cache = None; // invalidate cache
312    }
313
314    /// Get a named field value, or `None` if not set.
315    pub fn get_field(&self, name: &str) -> Option<f64> {
316        self.fields.get(name).copied()
317    }
318
319    /// Serialise all fields to a byte vec (8 bytes per field, little-endian f64).
320    ///
321    /// Fields are sorted by name for determinism.
322    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    /// Number of fields.
337    pub fn field_count(&self) -> usize {
338        self.fields.len()
339    }
340}
341
342// ---------------------------------------------------------------------------
343// StorageBuffer
344// ---------------------------------------------------------------------------
345
346/// A large read-write GPU storage buffer.
347#[derive(Debug, Clone)]
348pub struct StorageBuffer {
349    /// Label for debugging.
350    pub label: String,
351    /// Raw byte data.
352    data: Vec<u8>,
353    /// Whether the buffer is mapped.
354    mapped: bool,
355}
356
357impl StorageBuffer {
358    /// Create a zeroed storage buffer.
359    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    /// Size in bytes.
368    pub fn size(&self) -> usize {
369        self.data.len()
370    }
371
372    /// Map for CPU access.  Returns `None` if already mapped.
373    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    /// Unmap the buffer.
382    pub fn unmap(&mut self) {
383        self.mapped = false;
384    }
385
386    /// Read `len` bytes starting at `offset`.
387    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    /// Write `src` at `offset`.
396    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    /// Clear to zero.
406    pub fn clear(&mut self) {
407        self.data.fill(0);
408    }
409}
410
411// ---------------------------------------------------------------------------
412// VertexBuffer
413// ---------------------------------------------------------------------------
414
415/// Vertex layout descriptor.
416#[derive(Debug, Clone, Copy, PartialEq)]
417pub struct VertexLayout {
418    /// Byte offset of the position field within a vertex.
419    pub pos_offset: usize,
420    /// Byte offset of the normal field within a vertex.
421    pub normal_offset: usize,
422    /// Byte offset of the UV field within a vertex.
423    pub uv_offset: usize,
424    /// Total vertex stride in bytes.
425    pub stride: usize,
426}
427
428impl VertexLayout {
429    /// Standard interleaved layout: `\[pos: f32×3, normal: f32×3, uv: f32×2\]`.
430    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/// An interleaved vertex buffer (pos + normal + UV).
441#[derive(Debug, Clone)]
442pub struct VertexBuffer {
443    /// Raw byte storage.
444    data: Vec<u8>,
445    /// Number of vertices.
446    pub vertex_count: usize,
447    /// Vertex layout.
448    pub layout: VertexLayout,
449}
450
451impl VertexBuffer {
452    /// Create an empty vertex buffer.
453    pub fn new(layout: VertexLayout) -> Self {
454        Self {
455            data: vec![],
456            vertex_count: 0,
457            layout,
458        }
459    }
460
461    /// Upload vertices from a slice of `f32` arrays `\[px, py, pz, nx, ny, nz, u, v\]`.
462    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            // Position (3 × f32 = 12 bytes)
469            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            // Normal (3 × f32)
474            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            // UV (2 × f32)
480            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    /// Size in bytes.
489    pub fn byte_size(&self) -> usize {
490        self.data.len()
491    }
492
493    /// Read position of vertex `i` as `\[f32; 3\]`.
494    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// ---------------------------------------------------------------------------
511// IndexBuffer
512// ---------------------------------------------------------------------------
513
514/// Triangle or line primitive topology.
515#[derive(Debug, Clone, Copy, PartialEq, Eq)]
516pub enum IndexTopology {
517    /// Triangle list.
518    Triangles,
519    /// Line list.
520    Lines,
521}
522
523/// An index buffer supporting u16 or u32 indices.
524#[derive(Debug, Clone)]
525pub struct IndexBuffer {
526    /// Raw byte storage.
527    data: Vec<u8>,
528    /// Number of indices stored.
529    pub index_count: usize,
530    /// Whether indices are 32-bit (true) or 16-bit (false).
531    pub wide: bool,
532    /// Primitive topology.
533    pub topology: IndexTopology,
534}
535
536impl IndexBuffer {
537    /// Create an empty u32 triangle index buffer.
538    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    /// Create an empty u16 triangle index buffer.
548    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    /// Upload u32 indices.
558    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    /// Upload u16 indices.
565    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    /// Read the `i`-th index as u32.
572    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    /// Size in bytes.
588    pub fn byte_size(&self) -> usize {
589        self.data.len()
590    }
591}
592
593// ---------------------------------------------------------------------------
594// TextureBuffer
595// ---------------------------------------------------------------------------
596
597/// Pixel format for a texture buffer.
598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599pub enum TextureFormat {
600    /// 8-bit RGBA (4 bytes per pixel).
601    Rgba8,
602    /// 32-bit single-channel float (4 bytes per pixel).
603    R32f,
604    /// 32-bit two-channel float (8 bytes per pixel).
605    Rg32f,
606}
607
608impl TextureFormat {
609    /// Bytes per pixel.
610    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/// A 2-D texture buffer.
620#[derive(Debug, Clone)]
621pub struct TextureBuffer {
622    /// Width in pixels.
623    pub width: usize,
624    /// Height in pixels.
625    pub height: usize,
626    /// Pixel format.
627    pub format: TextureFormat,
628    /// Raw pixel data in row-major order.
629    data: Vec<u8>,
630}
631
632impl TextureBuffer {
633    /// Create a zero-initialised texture.
634    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    /// Total size in bytes.
645    pub fn byte_size(&self) -> usize {
646        self.data.len()
647    }
648
649    /// Write raw bytes for a row.
650    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    /// Read raw bytes for a row.
663    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    /// Write a single pixel at `(x, y)` from raw bytes.
674    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    /// Read a single pixel at `(x, y)`.
685    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    /// Clear to zero.
695    pub fn clear(&mut self) {
696        self.data.fill(0);
697    }
698}
699
700// ---------------------------------------------------------------------------
701// GpuMemoryStats
702// ---------------------------------------------------------------------------
703
704/// Snapshot of GPU memory usage.
705#[derive(Debug, Clone, Default)]
706pub struct GpuMemoryStats {
707    /// Total bytes allocated.
708    pub total_allocated: usize,
709    /// Total bytes freed / available.
710    pub free_bytes: usize,
711    /// Fragmentation ratio in [0, 1] (0 = no fragmentation).
712    pub fragmentation: f64,
713    /// Count of buffers by type.
714    pub buffer_counts: HashMap<String, usize>,
715}
716
717impl GpuMemoryStats {
718    /// Compute stats from a pool.
719    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// ---------------------------------------------------------------------------
744// TransferQueue
745// ---------------------------------------------------------------------------
746
747/// A pending transfer operation.
748#[derive(Debug, Clone)]
749pub struct TransferOp {
750    /// Source bytes to upload.
751    pub src: Vec<u8>,
752    /// Destination handle.
753    pub dst_handle: GpuBufferHandle,
754    /// Byte offset within the destination.
755    pub dst_offset: usize,
756    /// True for upload (CPU→GPU), false for download (GPU→CPU).
757    pub upload: bool,
758}
759
760/// Pending download result.
761#[derive(Debug, Clone)]
762pub struct DownloadResult {
763    /// The handle that was downloaded.
764    pub handle: GpuBufferHandle,
765    /// The downloaded bytes.
766    pub data: Vec<u8>,
767}
768
769/// A simple upload / download transfer queue for a GPU pool.
770#[derive(Debug)]
771pub struct TransferQueue {
772    /// Pending upload operations.
773    uploads: Vec<TransferOp>,
774    /// Completed download results.
775    downloads: Vec<DownloadResult>,
776}
777
778impl TransferQueue {
779    /// Create an empty transfer queue.
780    pub fn new() -> Self {
781        Self {
782            uploads: vec![],
783            downloads: vec![],
784        }
785    }
786
787    /// Enqueue an upload of `data` to `handle` at `offset`.
788    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    /// Enqueue a download of `len` bytes from `handle` at `offset`.
798    pub fn copy_from_gpu(&mut self, handle: GpuBufferHandle, _offset: usize, _len: usize) {
799        // Simulate by recording a placeholder result
800        self.downloads.push(DownloadResult {
801            handle,
802            data: vec![0u8; _len],
803        });
804    }
805
806    /// Execute all pending transfers against the pool.
807    ///
808    /// Returns the number of upload operations executed.
809    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    /// Drain completed download results.
818    pub fn drain_downloads(&mut self) -> Vec<DownloadResult> {
819        std::mem::take(&mut self.downloads)
820    }
821
822    /// Number of pending uploads.
823    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// ---------------------------------------------------------------------------
835// Tests
836// ---------------------------------------------------------------------------
837
838#[cfg(test)]
839mod tests {
840    use super::*;
841
842    // --- GpuBuffer tests ---
843
844    #[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()); // already mapped
884        buf.unmap();
885        assert!(!buf.is_mapped());
886    }
887
888    // --- GpuBufferPool tests ---
889
890    #[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        // h2 should now be at offset 0 (or near it)
932        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    // --- UniformBuffer tests ---
942
943    #[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); // 2 × 8 bytes
963    }
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    // --- StorageBuffer tests ---
975
976    #[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    // --- VertexBuffer tests ---
997
998    #[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    // --- IndexBuffer tests ---
1017
1018    #[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    // --- TextureBuffer tests ---
1040
1041    #[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    // --- GpuMemoryStats tests ---
1075
1076    #[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    // --- TransferQueue tests ---
1087
1088    #[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        // Verify data landed
1099        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}