Skip to main content

oxigdal_gpu/
buffer.rs

1//! GPU buffer management for OxiGDAL.
2//!
3//! This module provides efficient GPU buffer management for raster data,
4//! including upload, download, and memory mapping operations.
5
6use crate::context::GpuContext;
7use crate::error::{GpuError, GpuResult};
8use bytemuck::{Pod, Zeroable};
9use std::marker::PhantomData;
10use std::sync::Arc;
11use tracing::{debug, trace};
12use wgpu::{
13    Buffer, BufferAsyncError, BufferDescriptor, BufferUsages, COPY_BUFFER_ALIGNMENT, MapMode,
14};
15
16// ---------------------------------------------------------------------------
17// BufferElementType
18// ---------------------------------------------------------------------------
19
20/// Describes the scalar element type stored in a GPU buffer.
21///
22/// Used to select the correct WGSL type string and to compute byte-level
23/// buffer sizes without depending on Rust's generic type system.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum BufferElementType {
26    /// 32-bit IEEE-754 single-precision float.
27    F32,
28    /// 16-bit IEEE-754 half-precision float (requires `SHADER_F16` feature).
29    F16,
30    /// Unsigned 8-bit integer.
31    U8,
32    /// Unsigned 16-bit integer.
33    U16,
34    /// Unsigned 32-bit integer.
35    U32,
36    /// Signed 32-bit integer.
37    I32,
38}
39
40impl BufferElementType {
41    /// Number of bytes per element.
42    pub fn byte_size(self) -> usize {
43        match self {
44            Self::F32 | Self::I32 | Self::U32 => 4,
45            Self::F16 | Self::U16 => 2,
46            Self::U8 => 1,
47        }
48    }
49
50    /// The WGSL type name for this element type.
51    ///
52    /// Note: WGSL does not have `u8` or `u16` native types — those map to
53    /// `u32` (the caller is responsible for packing/unpacking if necessary).
54    pub fn wgsl_type(self) -> &'static str {
55        match self {
56            Self::F32 => "f32",
57            Self::F16 => "f16",
58            Self::U32 => "u32",
59            Self::I32 => "i32",
60            Self::U8 => "u32",  // WGSL has no u8; caller must pack
61            Self::U16 => "u32", // WGSL has no u16; caller must pack
62        }
63    }
64}
65
66// ---------------------------------------------------------------------------
67// f16 conversion helpers (free functions)
68// ---------------------------------------------------------------------------
69
70/// Convert a slice of `half::f16` values to a `Vec<f32>` by widening.
71///
72/// Every `f16` value is exactly representable as `f32`, so this conversion
73/// is lossless.
74pub fn f16_to_f32_slice(data: &[half::f16]) -> Vec<f32> {
75    data.iter().map(|h| f32::from(*h)).collect()
76}
77
78/// Convert a slice of `f32` values to a `Vec<half::f16>` by narrowing.
79///
80/// Values that are not exactly representable in half precision are rounded
81/// to the nearest `f16` using the default round-to-nearest-even mode
82/// provided by the `half` crate.
83pub fn f32_to_f16_slice(data: &[f32]) -> Vec<half::f16> {
84    data.iter().map(|f| half::f16::from_f32(*f)).collect()
85}
86
87// ---------------------------------------------------------------------------
88// f16-specific GpuBuffer constructors and read-back helpers
89// ---------------------------------------------------------------------------
90
91/// Upload a `half::f16` slice to the GPU by widening each element to `f32`.
92///
93/// This path works on all adapters regardless of whether they expose the
94/// `SHADER_F16` feature.  The buffer element type on the GPU is `f32`.
95///
96/// # Errors
97///
98/// Returns an error if buffer creation fails (e.g. out-of-memory).
99pub fn from_f16_slice_widening(
100    context: &GpuContext,
101    data: &[half::f16],
102) -> GpuResult<GpuBuffer<f32>> {
103    let f32_data = f16_to_f32_slice(data);
104    GpuBuffer::<f32>::from_data(
105        context,
106        &f32_data,
107        BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
108    )
109}
110
111/// Upload a `half::f16` slice to the GPU as raw bytes (`u8` buffer).
112///
113/// This is the *native* path — each `f16` occupies exactly 2 bytes on the
114/// GPU.  To use this buffer in a WGSL shader, the shader source must begin
115/// with `enable f16;` and the adapter must expose `wgpu::Features::SHADER_F16`.
116///
117/// # Errors
118///
119/// Returns an error if buffer creation fails.
120pub fn from_f16_slice_native(context: &GpuContext, data: &[half::f16]) -> GpuResult<GpuBuffer<u8>> {
121    let bytes: &[u8] = bytemuck::cast_slice(data);
122    GpuBuffer::<u8>::from_data(
123        context,
124        bytes,
125        BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
126    )
127}
128
129/// Read back a `GpuBuffer<f32>` and narrow each element to `half::f16`.
130///
131/// This is the counterpart of [`from_f16_slice_widening`] — it performs the
132/// inverse narrowing conversion after the GPU computation has completed.
133///
134/// # Errors
135///
136/// Returns an error if the blocking read fails.
137pub fn read_f16_from_f32_buffer(buf: &GpuBuffer<f32>) -> GpuResult<Vec<half::f16>> {
138    let f32_vals = buf.read_blocking()?;
139    Ok(f32_to_f16_slice(&f32_vals))
140}
141
142/// GPU buffer wrapper with type safety.
143///
144/// This struct wraps a WGPU buffer and provides type-safe operations
145/// for uploading and downloading data to/from the GPU.
146pub struct GpuBuffer<T: Pod> {
147    /// The underlying WGPU buffer.
148    buffer: Arc<Buffer>,
149    /// GPU context.
150    context: GpuContext,
151    /// Number of elements in the buffer.
152    len: usize,
153    /// Buffer usage flags.
154    usage: BufferUsages,
155    /// Phantom data for type parameter.
156    _phantom: PhantomData<T>,
157}
158
159impl<T: Pod> GpuBuffer<T> {
160    /// Create a new GPU buffer with the specified size and usage.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if buffer creation fails or size is invalid.
165    pub fn new(context: &GpuContext, len: usize, usage: BufferUsages) -> GpuResult<Self> {
166        let size = Self::calculate_size(len)?;
167
168        trace!("Creating GPU buffer: {} elements, {} bytes", len, size);
169
170        let buffer = context.device().create_buffer(&BufferDescriptor {
171            label: Some("GpuBuffer"),
172            size,
173            usage,
174            mapped_at_creation: false,
175        });
176
177        Ok(Self {
178            buffer: Arc::new(buffer),
179            context: context.clone(),
180            len,
181            usage,
182            _phantom: PhantomData,
183        })
184    }
185
186    /// Create a GPU buffer from existing data.
187    ///
188    /// # Errors
189    ///
190    /// Returns an error if buffer creation or upload fails.
191    pub fn from_data(context: &GpuContext, data: &[T], usage: BufferUsages) -> GpuResult<Self> {
192        let mut buffer = Self::new(context, data.len(), usage | BufferUsages::COPY_DST)?;
193        buffer.write(data)?;
194        Ok(buffer)
195    }
196
197    /// Create a staging buffer for CPU-GPU transfers.
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if buffer creation fails.
202    pub fn staging(context: &GpuContext, len: usize) -> GpuResult<Self> {
203        Self::new(
204            context,
205            len,
206            BufferUsages::MAP_READ | BufferUsages::COPY_DST,
207        )
208    }
209
210    /// Calculate the aligned buffer size in bytes.
211    fn calculate_size(len: usize) -> GpuResult<u64> {
212        let element_size = std::mem::size_of::<T>();
213        let size = len
214            .checked_mul(element_size)
215            .ok_or_else(|| GpuError::invalid_buffer("Buffer size overflow"))?;
216
217        // Align to COPY_BUFFER_ALIGNMENT for efficient transfers
218        let aligned_size = ((size as u64 + COPY_BUFFER_ALIGNMENT - 1) / COPY_BUFFER_ALIGNMENT)
219            * COPY_BUFFER_ALIGNMENT;
220
221        Ok(aligned_size)
222    }
223
224    /// Write data to the GPU buffer.
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if the buffer doesn't support writes or data size
229    /// doesn't match buffer size.
230    pub fn write(&mut self, data: &[T]) -> GpuResult<()> {
231        if data.len() != self.len {
232            return Err(GpuError::invalid_buffer(format!(
233                "Data size mismatch: expected {}, got {}",
234                self.len,
235                data.len()
236            )));
237        }
238
239        if !self.usage.contains(BufferUsages::COPY_DST) {
240            return Err(GpuError::invalid_buffer(
241                "Buffer not writable (missing COPY_DST usage)",
242            ));
243        }
244
245        let bytes = bytemuck::cast_slice(data);
246        self.context.queue().write_buffer(&self.buffer, 0, bytes);
247
248        debug!("Wrote {} bytes to GPU buffer", bytes.len());
249        Ok(())
250    }
251
252    /// Read data from the GPU buffer asynchronously.
253    ///
254    /// # Errors
255    ///
256    /// Returns an error if the buffer doesn't support reads or mapping fails.
257    pub async fn read(&self) -> GpuResult<Vec<T>> {
258        if !self.usage.contains(BufferUsages::MAP_READ) {
259            return Err(GpuError::invalid_buffer(
260                "Buffer not readable (missing MAP_READ usage)",
261            ));
262        }
263
264        let buffer_slice = self.buffer.slice(..);
265
266        // Map the buffer for reading
267        let (tx, rx) = futures::channel::oneshot::channel();
268        buffer_slice.map_async(MapMode::Read, move |result| {
269            let _ = tx.send(result);
270        });
271
272        // Poll the device until the buffer is mapped
273        self.context.poll(true);
274
275        // Wait for mapping to complete
276        rx.await
277            .map_err(|_| GpuError::buffer_mapping("Channel closed"))?
278            .map_err(|e| GpuError::buffer_mapping(Self::map_error_to_string(e)))?;
279
280        // Read the data
281        let data = buffer_slice.get_mapped_range();
282        let result: Vec<T> = bytemuck::cast_slice(&data).to_vec();
283
284        // Unmap the buffer
285        drop(data);
286        self.buffer.unmap();
287
288        debug!("Read {} elements from GPU buffer", result.len());
289        Ok(result)
290    }
291
292    /// Read data from the GPU buffer asynchronously using a `Future`.
293    ///
294    /// This is the primary async entry-point for GPU→CPU readback. It uses a
295    /// `futures::channel::oneshot` channel so the mapping callback resolves
296    /// the returned `Future` without blocking any thread.
297    ///
298    /// Equivalent to calling `.read().await`, but named explicitly so downstream
299    /// code can refer to the async path by a stable, unambiguous name when both
300    /// `read_blocking` and the async variant need to co-exist in the same scope.
301    ///
302    /// # Errors
303    ///
304    /// Returns an error if the buffer doesn't support reads (`MAP_READ` usage
305    /// must be set), if the oneshot channel is dropped before resolution
306    /// (`GpuError::BufferMapping("Channel closed")`), or if `wgpu` reports a
307    /// mapping failure.
308    pub async fn read_async(&self) -> GpuResult<Vec<T>> {
309        self.read().await
310    }
311
312    /// Read data from the GPU buffer synchronously (blocking).
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if the buffer doesn't support reads or mapping fails.
317    pub fn read_blocking(&self) -> GpuResult<Vec<T>> {
318        pollster::block_on(self.read())
319    }
320
321    /// Copy data from another GPU buffer.
322    ///
323    /// # Errors
324    ///
325    /// Returns an error if buffer sizes don't match or copy is not supported.
326    pub fn copy_from(&mut self, source: &GpuBuffer<T>) -> GpuResult<()> {
327        if self.len != source.len {
328            return Err(GpuError::invalid_buffer(format!(
329                "Buffer size mismatch: {} != {}",
330                self.len, source.len
331            )));
332        }
333
334        if !source.usage.contains(BufferUsages::COPY_SRC) {
335            return Err(GpuError::invalid_buffer(
336                "Source buffer not copyable (missing COPY_SRC usage)",
337            ));
338        }
339
340        if !self.usage.contains(BufferUsages::COPY_DST) {
341            return Err(GpuError::invalid_buffer(
342                "Destination buffer not copyable (missing COPY_DST usage)",
343            ));
344        }
345
346        let mut encoder =
347            self.context
348                .device()
349                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
350                    label: Some("Buffer Copy"),
351                });
352
353        let size = Self::calculate_size(self.len)?;
354        encoder.copy_buffer_to_buffer(&source.buffer, 0, &self.buffer, 0, size);
355
356        self.context.queue().submit(Some(encoder.finish()));
357
358        debug!("Copied {} elements between GPU buffers", self.len);
359        Ok(())
360    }
361
362    /// Get the number of elements in the buffer.
363    pub fn len(&self) -> usize {
364        self.len
365    }
366
367    /// Check if the buffer is empty.
368    pub fn is_empty(&self) -> bool {
369        self.len == 0
370    }
371
372    /// Get the buffer size in bytes.
373    pub fn size_bytes(&self) -> u64 {
374        Self::calculate_size(self.len).unwrap_or(0)
375    }
376
377    /// Get the underlying WGPU buffer.
378    pub fn buffer(&self) -> &Buffer {
379        &self.buffer
380    }
381
382    /// Get buffer usage flags.
383    pub fn usage(&self) -> BufferUsages {
384        self.usage
385    }
386
387    /// Convert buffer mapping error to string.
388    fn map_error_to_string(error: BufferAsyncError) -> String {
389        error.to_string()
390    }
391}
392
393impl<T: Pod> Clone for GpuBuffer<T> {
394    fn clone(&self) -> Self {
395        Self {
396            buffer: Arc::clone(&self.buffer),
397            context: self.context.clone(),
398            len: self.len,
399            usage: self.usage,
400            _phantom: PhantomData,
401        }
402    }
403}
404
405impl<T: Pod> std::fmt::Debug for GpuBuffer<T> {
406    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407        f.debug_struct("GpuBuffer")
408            .field("len", &self.len)
409            .field("size_bytes", &self.size_bytes())
410            .field("usage", &self.usage)
411            .field("type", &std::any::type_name::<T>())
412            .finish()
413    }
414}
415
416/// GPU raster buffer for multi-band raster data.
417///
418/// This struct manages GPU buffers for multi-band raster data with
419/// efficient interleaved or planar storage.
420pub struct GpuRasterBuffer<T: Pod> {
421    /// GPU buffers for each band.
422    bands: Vec<GpuBuffer<T>>,
423    /// Width of the raster.
424    width: u32,
425    /// Height of the raster.
426    height: u32,
427}
428
429impl<T: Pod + Zeroable> GpuRasterBuffer<T> {
430    /// Create a new GPU raster buffer.
431    ///
432    /// # Errors
433    ///
434    /// Returns an error if buffer creation fails.
435    pub fn new(
436        context: &GpuContext,
437        width: u32,
438        height: u32,
439        num_bands: usize,
440        usage: BufferUsages,
441    ) -> GpuResult<Self> {
442        let pixels_per_band = (width as usize)
443            .checked_mul(height as usize)
444            .ok_or_else(|| GpuError::invalid_buffer("Raster size overflow"))?;
445
446        let bands = (0..num_bands)
447            .map(|_| GpuBuffer::new(context, pixels_per_band, usage))
448            .collect::<GpuResult<Vec<_>>>()?;
449
450        debug!(
451            "Created GPU raster buffer: {}x{} with {} bands",
452            width, height, num_bands
453        );
454
455        Ok(Self {
456            bands,
457            width,
458            height,
459        })
460    }
461
462    /// Create a GPU raster buffer from data.
463    ///
464    /// # Errors
465    ///
466    /// Returns an error if buffer creation or upload fails.
467    pub fn from_bands(
468        context: &GpuContext,
469        width: u32,
470        height: u32,
471        bands_data: &[Vec<T>],
472        usage: BufferUsages,
473    ) -> GpuResult<Self> {
474        let expected_size = (width as usize) * (height as usize);
475
476        for (i, band) in bands_data.iter().enumerate() {
477            if band.len() != expected_size {
478                return Err(GpuError::invalid_buffer(format!(
479                    "Band {} size mismatch: expected {}, got {}",
480                    i,
481                    expected_size,
482                    band.len()
483                )));
484            }
485        }
486
487        let bands = bands_data
488            .iter()
489            .map(|data| GpuBuffer::from_data(context, data, usage))
490            .collect::<GpuResult<Vec<_>>>()?;
491
492        Ok(Self {
493            bands,
494            width,
495            height,
496        })
497    }
498
499    /// Get a specific band buffer.
500    pub fn band(&self, index: usize) -> Option<&GpuBuffer<T>> {
501        self.bands.get(index)
502    }
503
504    /// Get mutable reference to a specific band buffer.
505    pub fn band_mut(&mut self, index: usize) -> Option<&mut GpuBuffer<T>> {
506        self.bands.get_mut(index)
507    }
508
509    /// Get all band buffers.
510    pub fn bands(&self) -> &[GpuBuffer<T>] {
511        &self.bands
512    }
513
514    /// Get the number of bands.
515    pub fn num_bands(&self) -> usize {
516        self.bands.len()
517    }
518
519    /// Get raster dimensions.
520    pub fn dimensions(&self) -> (u32, u32) {
521        (self.width, self.height)
522    }
523
524    /// Get raster width.
525    pub fn width(&self) -> u32 {
526        self.width
527    }
528
529    /// Get raster height.
530    pub fn height(&self) -> u32 {
531        self.height
532    }
533
534    /// Read all bands from GPU asynchronously.
535    ///
536    /// # Errors
537    ///
538    /// Returns an error if reading fails.
539    pub async fn read_all_bands(&self) -> GpuResult<Vec<Vec<T>>> {
540        let mut results = Vec::with_capacity(self.bands.len());
541
542        for band in &self.bands {
543            results.push(band.read().await?);
544        }
545
546        Ok(results)
547    }
548
549    /// Read all bands from GPU synchronously.
550    ///
551    /// # Errors
552    ///
553    /// Returns an error if reading fails.
554    pub fn read_all_bands_blocking(&self) -> GpuResult<Vec<Vec<T>>> {
555        pollster::block_on(self.read_all_bands())
556    }
557}
558
559impl<T: Pod> std::fmt::Debug for GpuRasterBuffer<T> {
560    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561        f.debug_struct("GpuRasterBuffer")
562            .field("width", &self.width)
563            .field("height", &self.height)
564            .field("num_bands", &self.num_bands())
565            .field("type", &std::any::type_name::<T>())
566            .finish()
567    }
568}
569
570#[cfg(test)]
571#[allow(clippy::panic)]
572mod tests {
573    use super::*;
574
575    #[tokio::test]
576    async fn test_gpu_buffer_creation() {
577        if let Ok(context) = GpuContext::new().await {
578            let buffer: GpuBuffer<f32> = GpuBuffer::new(&context, 1024, BufferUsages::STORAGE)
579                .unwrap_or_else(|e| {
580                    panic!("Failed to create buffer: {}", e);
581                });
582
583            assert_eq!(buffer.len(), 1024);
584            assert!(!buffer.is_empty());
585        }
586    }
587
588    #[tokio::test]
589    #[ignore]
590    async fn test_gpu_buffer_write_read() {
591        if let Ok(context) = GpuContext::new().await {
592            let data: Vec<f32> = (0..100).map(|i| i as f32).collect();
593
594            let buffer = GpuBuffer::from_data(
595                &context,
596                &data,
597                BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
598            )
599            .unwrap_or_else(|e| {
600                panic!("Failed to create buffer: {}", e);
601            });
602
603            // Create staging buffer for reading
604            let mut staging = GpuBuffer::staging(&context, 100).unwrap_or_else(|e| {
605                panic!("Failed to create staging buffer: {}", e);
606            });
607
608            staging.copy_from(&buffer).unwrap_or_else(|e| {
609                panic!("Failed to copy buffer: {}", e);
610            });
611
612            let result = staging.read().await.unwrap_or_else(|e| {
613                panic!("Failed to read buffer: {}", e);
614            });
615
616            assert_eq!(result.len(), data.len());
617            for (a, b) in result.iter().zip(data.iter()) {
618                assert!((a - b).abs() < 1e-6);
619            }
620        }
621    }
622
623    #[tokio::test]
624    async fn test_gpu_raster_buffer() {
625        if let Ok(context) = GpuContext::new().await {
626            let width = 64;
627            let height = 64;
628            let num_bands = 3;
629
630            let raster: GpuRasterBuffer<f32> =
631                GpuRasterBuffer::new(&context, width, height, num_bands, BufferUsages::STORAGE)
632                    .unwrap_or_else(|e| {
633                        panic!("Failed to create raster buffer: {}", e);
634                    });
635
636            assert_eq!(raster.width(), width);
637            assert_eq!(raster.height(), height);
638            assert_eq!(raster.num_bands(), num_bands);
639        }
640    }
641}