Skip to main content

oxiphysics_gpu/
memory.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! GPU memory management abstractions.
5//!
6//! Provides CPU-side mock implementations of GPU buffer types, a pool allocator,
7//! transfer queue, and memory statistics — all without requiring an actual GPU.
8
9use std::collections::HashMap;
10
11// ---------------------------------------------------------------------------
12// Buffer type tag
13// ---------------------------------------------------------------------------
14
15/// Identifies the logical type of a GPU buffer.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum GpuBufferType {
18    /// Uniform / constant buffer.
19    Uniform,
20    /// Large read-write storage buffer.
21    Storage,
22    /// Vertex data buffer.
23    Vertex,
24    /// Index data buffer.
25    Index,
26    /// Texture / image buffer.
27    Texture,
28    /// Generic staging (upload / download) buffer.
29    Staging,
30}
31
32// ---------------------------------------------------------------------------
33// GpuBuffer — typed CPU-side buffer
34// ---------------------------------------------------------------------------
35
36/// A CPU-side mock for a typed GPU buffer backed by a `Vec`u8`.
37#[derive(Debug, Clone)]
38pub struct GpuBuffer {
39    /// Logical type.
40    pub buffer_type: GpuBufferType,
41    /// Raw byte data.
42    data: Vec<u8>,
43    /// Whether the buffer is mapped (accessible for reads/writes).
44    mapped: bool,
45}
46
47impl GpuBuffer {
48    /// Allocate a new zero-initialised buffer of `size` bytes.
49    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    /// Current size in bytes.
58    pub fn size(&self) -> usize {
59        self.data.len()
60    }
61
62    /// Read bytes at `offset` into `dst`.  Returns the number of bytes copied.
63    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    /// Write bytes from `src` at `offset`.  Returns bytes written.
73    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    /// Resize the buffer (data is zero-initialised for new regions).
83    pub fn resize(&mut self, new_size: usize) {
84        self.data.resize(new_size, 0);
85    }
86
87    /// Zero-fill the entire buffer.
88    pub fn clear(&mut self) {
89        self.data.fill(0);
90    }
91
92    /// Map the buffer for direct slice access (returns `None` if already mapped).
93    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    /// Unmap the buffer.
102    pub fn unmap(&mut self) {
103        self.mapped = false;
104    }
105
106    /// True when the buffer is currently mapped.
107    pub fn is_mapped(&self) -> bool {
108        self.mapped
109    }
110}
111
112// ---------------------------------------------------------------------------
113// GpuBufferHandle
114// ---------------------------------------------------------------------------
115
116/// An opaque handle to an allocated GPU buffer region.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct GpuBufferHandle {
119    /// Unique 64-bit handle value.
120    pub id: u64,
121    /// Size of the allocation in bytes.
122    pub size: usize,
123    /// Byte offset within the backing pool buffer.
124    pub offset: usize,
125    /// Logical buffer type.
126    pub buffer_type: GpuBufferType,
127}
128
129impl GpuBufferHandle {
130    /// Null/invalid handle sentinel.
131    pub fn null() -> Self {
132        Self {
133            id: 0,
134            size: 0,
135            offset: 0,
136            buffer_type: GpuBufferType::Staging,
137        }
138    }
139
140    /// True when this is the null handle.
141    pub fn is_null(&self) -> bool {
142        self.id == 0
143    }
144}
145
146// ---------------------------------------------------------------------------
147// GpuBufferPool — pool allocator
148// ---------------------------------------------------------------------------
149
150/// A pool allocator that sub-allocates from a large backing buffer.
151#[derive(Debug)]
152pub struct GpuBufferPool {
153    /// Backing byte store.
154    backing: Vec<u8>,
155    /// Next free byte offset.
156    next_offset: usize,
157    /// All live allocations.
158    allocations: HashMap<u64, GpuBufferHandle>,
159    /// Next handle ID counter.
160    next_id: u64,
161    /// Total capacity.
162    capacity: usize,
163}
164
165impl GpuBufferPool {
166    /// Create a pool with `capacity` bytes.
167    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    /// Allocate `size` bytes from the pool.  Returns `None` if out of memory.
178    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    /// Free a previously allocated handle.
196    pub fn free(&mut self, handle: GpuBufferHandle) {
197        self.allocations.remove(&handle.id);
198    }
199
200    /// Defragment: compact live allocations to the front of the pool.
201    ///
202    /// Allocations are re-packed in insertion order (by id).
203    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        // Update allocations map with new offsets
225        self.allocations.clear();
226        for h in sorted {
227            self.allocations.insert(h.id, h);
228        }
229    }
230
231    /// Write bytes to a handle's region.
232    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    /// Read bytes from a handle's region.
246    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    /// Number of live allocations.
260    pub fn allocation_count(&self) -> usize {
261        self.allocations.len()
262    }
263
264    /// Bytes used (sum of aligned allocations).
265    pub fn used_bytes(&self) -> usize {
266        self.next_offset
267    }
268
269    /// Free bytes remaining (approximate; does not account for freed holes before defrag).
270    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// ---------------------------------------------------------------------------
280// UniformBuffer
281// ---------------------------------------------------------------------------
282
283/// A structured uniform buffer holding named `f64` fields.
284#[derive(Debug, Clone)]
285pub struct UniformBuffer {
286    /// Name of this uniform block.
287    pub name: String,
288    /// Named field storage.
289    fields: HashMap<String, f64>,
290    /// Serialised byte cache (optional; computed on demand).
291    byte_cache: Option<Vec<u8>>,
292}
293
294impl UniformBuffer {
295    /// Create a new empty uniform buffer.
296    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    /// Set a named field value.
305    pub fn set_field(&mut self, name: &str, value: f64) {
306        self.fields.insert(name.to_owned(), value);
307        self.byte_cache = None; // invalidate cache
308    }
309
310    /// Get a named field value, or `None` if not set.
311    pub fn get_field(&self, name: &str) -> Option<f64> {
312        self.fields.get(name).copied()
313    }
314
315    /// Serialise all fields to a byte vec (8 bytes per field, little-endian f64).
316    ///
317    /// Fields are sorted by name for determinism.
318    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    /// Number of fields.
333    pub fn field_count(&self) -> usize {
334        self.fields.len()
335    }
336}
337
338// ---------------------------------------------------------------------------
339// StorageBuffer
340// ---------------------------------------------------------------------------
341
342/// A large read-write GPU storage buffer.
343#[derive(Debug, Clone)]
344pub struct StorageBuffer {
345    /// Label for debugging.
346    pub label: String,
347    /// Raw byte data.
348    data: Vec<u8>,
349    /// Whether the buffer is mapped.
350    mapped: bool,
351}
352
353impl StorageBuffer {
354    /// Create a zeroed storage buffer.
355    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    /// Size in bytes.
364    pub fn size(&self) -> usize {
365        self.data.len()
366    }
367
368    /// Map for CPU access.  Returns `None` if already mapped.
369    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    /// Unmap the buffer.
378    pub fn unmap(&mut self) {
379        self.mapped = false;
380    }
381
382    /// Read `len` bytes starting at `offset`.
383    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    /// Write `src` at `offset`.
392    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    /// Clear to zero.
402    pub fn clear(&mut self) {
403        self.data.fill(0);
404    }
405}
406
407// ---------------------------------------------------------------------------
408// VertexBuffer
409// ---------------------------------------------------------------------------
410
411/// Vertex layout descriptor.
412#[derive(Debug, Clone, Copy, PartialEq)]
413pub struct VertexLayout {
414    /// Byte offset of the position field within a vertex.
415    pub pos_offset: usize,
416    /// Byte offset of the normal field within a vertex.
417    pub normal_offset: usize,
418    /// Byte offset of the UV field within a vertex.
419    pub uv_offset: usize,
420    /// Total vertex stride in bytes.
421    pub stride: usize,
422}
423
424impl VertexLayout {
425    /// Standard interleaved layout: `\[pos: f32×3, normal: f32×3, uv: f32×2\]`.
426    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/// An interleaved vertex buffer (pos + normal + UV).
437#[derive(Debug, Clone)]
438pub struct VertexBuffer {
439    /// Raw byte storage.
440    data: Vec<u8>,
441    /// Number of vertices.
442    pub vertex_count: usize,
443    /// Vertex layout.
444    pub layout: VertexLayout,
445}
446
447impl VertexBuffer {
448    /// Create an empty vertex buffer.
449    pub fn new(layout: VertexLayout) -> Self {
450        Self {
451            data: vec![],
452            vertex_count: 0,
453            layout,
454        }
455    }
456
457    /// Upload vertices from a slice of `f32` arrays `\[px, py, pz, nx, ny, nz, u, v\]`.
458    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            // Position (3 × f32 = 12 bytes)
465            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            // Normal (3 × f32)
470            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            // UV (2 × f32)
476            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    /// Size in bytes.
485    pub fn byte_size(&self) -> usize {
486        self.data.len()
487    }
488
489    /// Read position of vertex `i` as `\[f32; 3\]`.
490    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// ---------------------------------------------------------------------------
507// IndexBuffer
508// ---------------------------------------------------------------------------
509
510/// Triangle or line primitive topology.
511#[derive(Debug, Clone, Copy, PartialEq, Eq)]
512pub enum IndexTopology {
513    /// Triangle list.
514    Triangles,
515    /// Line list.
516    Lines,
517}
518
519/// An index buffer supporting u16 or u32 indices.
520#[derive(Debug, Clone)]
521pub struct IndexBuffer {
522    /// Raw byte storage.
523    data: Vec<u8>,
524    /// Number of indices stored.
525    pub index_count: usize,
526    /// Whether indices are 32-bit (true) or 16-bit (false).
527    pub wide: bool,
528    /// Primitive topology.
529    pub topology: IndexTopology,
530}
531
532impl IndexBuffer {
533    /// Create an empty u32 triangle index buffer.
534    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    /// Create an empty u16 triangle index buffer.
544    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    /// Upload u32 indices.
554    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    /// Upload u16 indices.
561    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    /// Read the `i`-th index as u32.
568    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    /// Size in bytes.
584    pub fn byte_size(&self) -> usize {
585        self.data.len()
586    }
587}
588
589// ---------------------------------------------------------------------------
590// TextureBuffer
591// ---------------------------------------------------------------------------
592
593/// Pixel format for a texture buffer.
594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
595pub enum TextureFormat {
596    /// 8-bit RGBA (4 bytes per pixel).
597    Rgba8,
598    /// 32-bit single-channel float (4 bytes per pixel).
599    R32f,
600    /// 32-bit two-channel float (8 bytes per pixel).
601    Rg32f,
602}
603
604impl TextureFormat {
605    /// Bytes per pixel.
606    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/// A 2-D texture buffer.
616#[derive(Debug, Clone)]
617pub struct TextureBuffer {
618    /// Width in pixels.
619    pub width: usize,
620    /// Height in pixels.
621    pub height: usize,
622    /// Pixel format.
623    pub format: TextureFormat,
624    /// Raw pixel data in row-major order.
625    data: Vec<u8>,
626}
627
628impl TextureBuffer {
629    /// Create a zero-initialised texture.
630    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    /// Total size in bytes.
641    pub fn byte_size(&self) -> usize {
642        self.data.len()
643    }
644
645    /// Write raw bytes for a row.
646    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    /// Read raw bytes for a row.
659    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    /// Write a single pixel at `(x, y)` from raw bytes.
670    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    /// Read a single pixel at `(x, y)`.
681    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    /// Clear to zero.
691    pub fn clear(&mut self) {
692        self.data.fill(0);
693    }
694}
695
696// ---------------------------------------------------------------------------
697// GpuMemoryStats
698// ---------------------------------------------------------------------------
699
700/// Snapshot of GPU memory usage.
701#[derive(Debug, Clone, Default)]
702pub struct GpuMemoryStats {
703    /// Total bytes allocated.
704    pub total_allocated: usize,
705    /// Total bytes freed / available.
706    pub free_bytes: usize,
707    /// Fragmentation ratio in [0, 1] (0 = no fragmentation).
708    pub fragmentation: f64,
709    /// Count of buffers by type.
710    pub buffer_counts: HashMap<String, usize>,
711}
712
713impl GpuMemoryStats {
714    /// Compute stats from a pool.
715    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// ---------------------------------------------------------------------------
740// TransferQueue
741// ---------------------------------------------------------------------------
742
743/// A pending transfer operation.
744#[derive(Debug, Clone)]
745pub struct TransferOp {
746    /// Source bytes to upload.
747    pub src: Vec<u8>,
748    /// Destination handle.
749    pub dst_handle: GpuBufferHandle,
750    /// Byte offset within the destination.
751    pub dst_offset: usize,
752    /// True for upload (CPU→GPU), false for download (GPU→CPU).
753    pub upload: bool,
754}
755
756/// Pending download result.
757#[derive(Debug, Clone)]
758pub struct DownloadResult {
759    /// The handle that was downloaded.
760    pub handle: GpuBufferHandle,
761    /// The downloaded bytes.
762    pub data: Vec<u8>,
763}
764
765/// A simple upload / download transfer queue for a GPU pool.
766#[derive(Debug)]
767pub struct TransferQueue {
768    /// Pending upload operations.
769    uploads: Vec<TransferOp>,
770    /// Completed download results.
771    downloads: Vec<DownloadResult>,
772}
773
774impl TransferQueue {
775    /// Create an empty transfer queue.
776    pub fn new() -> Self {
777        Self {
778            uploads: vec![],
779            downloads: vec![],
780        }
781    }
782
783    /// Enqueue an upload of `data` to `handle` at `offset`.
784    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    /// Enqueue a download of `len` bytes from `handle` at `offset`.
794    pub fn copy_from_gpu(&mut self, handle: GpuBufferHandle, _offset: usize, _len: usize) {
795        // Simulate by recording a placeholder result
796        self.downloads.push(DownloadResult {
797            handle,
798            data: vec![0u8; _len],
799        });
800    }
801
802    /// Execute all pending transfers against the pool.
803    ///
804    /// Returns the number of upload operations executed.
805    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    /// Drain completed download results.
814    pub fn drain_downloads(&mut self) -> Vec<DownloadResult> {
815        std::mem::take(&mut self.downloads)
816    }
817
818    /// Number of pending uploads.
819    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// ---------------------------------------------------------------------------
831// Tests
832// ---------------------------------------------------------------------------
833
834#[cfg(test)]
835mod tests {
836    use super::*;
837
838    // --- GpuBuffer tests ---
839
840    #[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()); // already mapped
880        buf.unmap();
881        assert!(!buf.is_mapped());
882    }
883
884    // --- GpuBufferPool tests ---
885
886    #[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        // h2 should now be at offset 0 (or near it)
928        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    // --- UniformBuffer tests ---
938
939    #[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); // 2 × 8 bytes
959    }
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    // --- StorageBuffer tests ---
971
972    #[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    // --- VertexBuffer tests ---
993
994    #[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    // --- IndexBuffer tests ---
1013
1014    #[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    // --- TextureBuffer tests ---
1036
1037    #[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    // --- GpuMemoryStats tests ---
1071
1072    #[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    // --- TransferQueue tests ---
1083
1084    #[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        // Verify data landed
1095        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}