Skip to main content

oximedia_gpu/
buffer.rs

1//! GPU buffer management for staging and device memory
2
3use crate::{GpuDevice, GpuError, Result};
4use std::sync::Arc;
5use wgpu::{Buffer, BufferUsages};
6
7/// Type of GPU buffer
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum BufferType {
10    /// Staging buffer for CPU to GPU transfer
11    Staging,
12    /// Storage buffer for compute operations
13    Storage,
14    /// Uniform buffer for shader parameters
15    Uniform,
16    /// Read-back buffer for GPU to CPU transfer
17    ReadBack,
18}
19
20/// GPU buffer wrapper with automatic memory management
21pub struct GpuBuffer {
22    buffer: Buffer,
23    size: u64,
24    buffer_type: BufferType,
25    device: Arc<wgpu::Device>,
26}
27
28impl GpuBuffer {
29    /// Create a new GPU buffer
30    ///
31    /// # Arguments
32    ///
33    /// * `device` - GPU device
34    /// * `size` - Buffer size in bytes
35    /// * `buffer_type` - Type of buffer to create
36    ///
37    /// # Errors
38    ///
39    /// Returns an error if buffer creation fails.
40    pub fn new(device: &GpuDevice, size: u64, buffer_type: BufferType) -> Result<Self> {
41        let usage = Self::buffer_usage(buffer_type);
42
43        let buffer = device.device().create_buffer(&wgpu::BufferDescriptor {
44            label: Some(&format!("OxiMedia {buffer_type:?} Buffer")),
45            size,
46            usage,
47            mapped_at_creation: false,
48        });
49
50        Ok(Self {
51            buffer,
52            size,
53            buffer_type,
54            device: Arc::clone(device.device()),
55        })
56    }
57
58    /// Create a buffer initialized with data
59    ///
60    /// # Arguments
61    ///
62    /// * `device` - GPU device
63    /// * `data` - Initial data
64    /// * `buffer_type` - Type of buffer to create
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if buffer creation fails.
69    pub fn with_data(device: &GpuDevice, data: &[u8], buffer_type: BufferType) -> Result<Self> {
70        let size = data.len() as u64;
71        let usage = Self::buffer_usage(buffer_type);
72
73        let buffer = device
74            .device()
75            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
76                label: Some(&format!("OxiMedia {buffer_type:?} Buffer")),
77                contents: data,
78                usage,
79            });
80
81        Ok(Self {
82            buffer,
83            size,
84            buffer_type,
85            device: Arc::clone(device.device()),
86        })
87    }
88
89    /// Write data to the buffer
90    ///
91    /// # Arguments
92    ///
93    /// * `queue` - GPU queue for data transfer
94    /// * `offset` - Offset in bytes
95    /// * `data` - Data to write
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if the write operation fails or if offset + data size
100    /// exceeds buffer size.
101    pub fn write(&self, queue: &wgpu::Queue, offset: u64, data: &[u8]) -> Result<()> {
102        if offset + data.len() as u64 > self.size {
103            return Err(GpuError::InvalidBufferSize {
104                expected: self.size as usize,
105                actual: (offset + data.len() as u64) as usize,
106            });
107        }
108
109        queue.write_buffer(&self.buffer, offset, data);
110        Ok(())
111    }
112
113    /// Read data from the buffer (asynchronously)
114    ///
115    /// # Arguments
116    ///
117    /// * `device` - GPU device
118    /// * `offset` - Offset in bytes
119    /// * `size` - Number of bytes to read
120    ///
121    /// # Errors
122    ///
123    /// Returns an error if the buffer type doesn't support reading or if
124    /// the read operation fails.
125    pub async fn read_async(&self, _device: &GpuDevice, offset: u64, size: u64) -> Result<Vec<u8>> {
126        if self.buffer_type != BufferType::ReadBack {
127            return Err(GpuError::NotSupported(
128                "Can only read from ReadBack buffers".to_string(),
129            ));
130        }
131
132        if offset + size > self.size {
133            return Err(GpuError::InvalidBufferSize {
134                expected: self.size as usize,
135                actual: (offset + size) as usize,
136            });
137        }
138
139        let slice = self.buffer.slice(offset..offset + size);
140        let (sender, receiver) = futures_channel::oneshot::channel();
141
142        slice.map_async(wgpu::MapMode::Read, move |result| {
143            let _ = sender.send(result);
144        });
145
146        let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
147
148        receiver
149            .await
150            .map_err(|e| GpuError::BufferMapping(e.to_string()))?
151            .map_err(|e| GpuError::BufferMapping(e.to_string()))?;
152
153        let data = slice.get_mapped_range();
154        let result = data.to_vec();
155
156        drop(data);
157        self.buffer.unmap();
158
159        Ok(result)
160    }
161
162    /// Read data from the buffer (blocking)
163    ///
164    /// # Arguments
165    ///
166    /// * `device` - GPU device
167    /// * `offset` - Offset in bytes
168    /// * `size` - Number of bytes to read
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the buffer type doesn't support reading or if
173    /// the read operation fails.
174    pub fn read(&self, device: &GpuDevice, offset: u64, size: u64) -> Result<Vec<u8>> {
175        pollster::block_on(self.read_async(device, offset, size))
176    }
177
178    /// Get the underlying WGPU buffer
179    #[must_use]
180    pub fn buffer(&self) -> &Buffer {
181        &self.buffer
182    }
183
184    /// Get buffer size in bytes
185    #[must_use]
186    pub fn size(&self) -> u64 {
187        self.size
188    }
189
190    /// Get buffer type
191    #[must_use]
192    pub fn buffer_type(&self) -> BufferType {
193        self.buffer_type
194    }
195
196    /// Copy data from this buffer to another buffer
197    ///
198    /// # Arguments
199    ///
200    /// * `encoder` - Command encoder for recording the copy operation
201    /// * `dst` - Destination buffer
202    /// * `src_offset` - Source offset in bytes
203    /// * `dst_offset` - Destination offset in bytes
204    /// * `size` - Number of bytes to copy
205    ///
206    /// # Errors
207    ///
208    /// Returns an error if offsets or size are invalid.
209    #[allow(clippy::too_many_arguments)]
210    pub fn copy_to(
211        &self,
212        encoder: &mut wgpu::CommandEncoder,
213        dst: &Self,
214        src_offset: u64,
215        dst_offset: u64,
216        size: u64,
217    ) -> Result<()> {
218        if src_offset + size > self.size {
219            return Err(GpuError::InvalidBufferSize {
220                expected: self.size as usize,
221                actual: (src_offset + size) as usize,
222            });
223        }
224
225        if dst_offset + size > dst.size {
226            return Err(GpuError::InvalidBufferSize {
227                expected: dst.size as usize,
228                actual: (dst_offset + size) as usize,
229            });
230        }
231
232        encoder.copy_buffer_to_buffer(&self.buffer, src_offset, &dst.buffer, dst_offset, size);
233        Ok(())
234    }
235
236    fn buffer_usage(buffer_type: BufferType) -> BufferUsages {
237        match buffer_type {
238            BufferType::Staging => BufferUsages::MAP_WRITE | BufferUsages::COPY_SRC,
239            BufferType::Storage => {
240                BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC
241            }
242            BufferType::Uniform => BufferUsages::UNIFORM | BufferUsages::COPY_DST,
243            BufferType::ReadBack => BufferUsages::MAP_READ | BufferUsages::COPY_DST,
244        }
245    }
246}
247
248impl std::fmt::Debug for GpuBuffer {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        f.debug_struct("GpuBuffer")
251            .field("size", &self.size)
252            .field("buffer_type", &self.buffer_type)
253            .finish()
254    }
255}
256
257// Add the missing dependency
258use wgpu::util::DeviceExt;