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
282            .get_mapped_range()
283            .map_err(|e| GpuError::buffer_mapping(e.to_string()))?;
284        let mut result: Vec<T> = bytemuck::cast_slice(&data).to_vec();
285
286        // The GPU buffer byte size is rounded up to COPY_BUFFER_ALIGNMENT in
287        // `calculate_size`, so for element types whose size does not evenly
288        // divide the alignment (e.g. `u8` with a non-multiple-of-4 length, or
289        // `u16`/`f16` with an odd length) the mapped range spans additional
290        // padding bytes. Truncate to the logical element count so the returned
291        // Vec upholds the documented `result.len() == self.len` contract and
292        // never leaks padding as bogus values.
293        result.truncate(self.len);
294
295        // Unmap the buffer
296        drop(data);
297        self.buffer.unmap();
298
299        debug!("Read {} elements from GPU buffer", result.len());
300        Ok(result)
301    }
302
303    /// Read data from the GPU buffer asynchronously using a `Future`.
304    ///
305    /// This is the primary async entry-point for GPU→CPU readback. It uses a
306    /// `futures::channel::oneshot` channel so the mapping callback resolves
307    /// the returned `Future` without blocking any thread.
308    ///
309    /// Equivalent to calling `.read().await`, but named explicitly so downstream
310    /// code can refer to the async path by a stable, unambiguous name when both
311    /// `read_blocking` and the async variant need to co-exist in the same scope.
312    ///
313    /// # Errors
314    ///
315    /// Returns an error if the buffer doesn't support reads (`MAP_READ` usage
316    /// must be set), if the oneshot channel is dropped before resolution
317    /// (`GpuError::BufferMapping("Channel closed")`), or if `wgpu` reports a
318    /// mapping failure.
319    pub async fn read_async(&self) -> GpuResult<Vec<T>> {
320        self.read().await
321    }
322
323    /// Read data from the GPU buffer synchronously (blocking).
324    ///
325    /// # Errors
326    ///
327    /// Returns an error if the buffer doesn't support reads or mapping fails.
328    pub fn read_blocking(&self) -> GpuResult<Vec<T>> {
329        pollster::block_on(self.read())
330    }
331
332    /// Copy data from another GPU buffer.
333    ///
334    /// # Errors
335    ///
336    /// Returns an error if buffer sizes don't match or copy is not supported.
337    pub fn copy_from(&mut self, source: &GpuBuffer<T>) -> GpuResult<()> {
338        if self.len != source.len {
339            return Err(GpuError::invalid_buffer(format!(
340                "Buffer size mismatch: {} != {}",
341                self.len, source.len
342            )));
343        }
344
345        if !source.usage.contains(BufferUsages::COPY_SRC) {
346            return Err(GpuError::invalid_buffer(
347                "Source buffer not copyable (missing COPY_SRC usage)",
348            ));
349        }
350
351        if !self.usage.contains(BufferUsages::COPY_DST) {
352            return Err(GpuError::invalid_buffer(
353                "Destination buffer not copyable (missing COPY_DST usage)",
354            ));
355        }
356
357        let mut encoder =
358            self.context
359                .device()
360                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
361                    label: Some("Buffer Copy"),
362                });
363
364        let size = Self::calculate_size(self.len)?;
365        encoder.copy_buffer_to_buffer(&source.buffer, 0, &self.buffer, 0, size);
366
367        self.context.queue().submit(Some(encoder.finish()));
368
369        debug!("Copied {} elements between GPU buffers", self.len);
370        Ok(())
371    }
372
373    /// Get the number of elements in the buffer.
374    pub fn len(&self) -> usize {
375        self.len
376    }
377
378    /// Check if the buffer is empty.
379    pub fn is_empty(&self) -> bool {
380        self.len == 0
381    }
382
383    /// Get the buffer size in bytes.
384    pub fn size_bytes(&self) -> u64 {
385        Self::calculate_size(self.len).unwrap_or(0)
386    }
387
388    /// Get the underlying WGPU buffer.
389    pub fn buffer(&self) -> &Buffer {
390        &self.buffer
391    }
392
393    /// Get buffer usage flags.
394    pub fn usage(&self) -> BufferUsages {
395        self.usage
396    }
397
398    /// Convert buffer mapping error to string.
399    fn map_error_to_string(error: BufferAsyncError) -> String {
400        error.to_string()
401    }
402}
403
404impl<T: Pod> Clone for GpuBuffer<T> {
405    fn clone(&self) -> Self {
406        Self {
407            buffer: Arc::clone(&self.buffer),
408            context: self.context.clone(),
409            len: self.len,
410            usage: self.usage,
411            _phantom: PhantomData,
412        }
413    }
414}
415
416impl<T: Pod> std::fmt::Debug for GpuBuffer<T> {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        f.debug_struct("GpuBuffer")
419            .field("len", &self.len)
420            .field("size_bytes", &self.size_bytes())
421            .field("usage", &self.usage)
422            .field("type", &std::any::type_name::<T>())
423            .finish()
424    }
425}
426
427/// GPU raster buffer for multi-band raster data.
428///
429/// This struct manages GPU buffers for multi-band raster data with
430/// efficient interleaved or planar storage.
431pub struct GpuRasterBuffer<T: Pod> {
432    /// GPU buffers for each band.
433    bands: Vec<GpuBuffer<T>>,
434    /// Width of the raster.
435    width: u32,
436    /// Height of the raster.
437    height: u32,
438}
439
440impl<T: Pod + Zeroable> GpuRasterBuffer<T> {
441    /// Create a new GPU raster buffer.
442    ///
443    /// # Errors
444    ///
445    /// Returns an error if buffer creation fails.
446    pub fn new(
447        context: &GpuContext,
448        width: u32,
449        height: u32,
450        num_bands: usize,
451        usage: BufferUsages,
452    ) -> GpuResult<Self> {
453        let pixels_per_band = (width as usize)
454            .checked_mul(height as usize)
455            .ok_or_else(|| GpuError::invalid_buffer("Raster size overflow"))?;
456
457        let bands = (0..num_bands)
458            .map(|_| GpuBuffer::new(context, pixels_per_band, usage))
459            .collect::<GpuResult<Vec<_>>>()?;
460
461        debug!(
462            "Created GPU raster buffer: {}x{} with {} bands",
463            width, height, num_bands
464        );
465
466        Ok(Self {
467            bands,
468            width,
469            height,
470        })
471    }
472
473    /// Create a GPU raster buffer from data.
474    ///
475    /// # Errors
476    ///
477    /// Returns an error if buffer creation or upload fails.
478    pub fn from_bands(
479        context: &GpuContext,
480        width: u32,
481        height: u32,
482        bands_data: &[Vec<T>],
483        usage: BufferUsages,
484    ) -> GpuResult<Self> {
485        let expected_size = (width as usize) * (height as usize);
486
487        for (i, band) in bands_data.iter().enumerate() {
488            if band.len() != expected_size {
489                return Err(GpuError::invalid_buffer(format!(
490                    "Band {} size mismatch: expected {}, got {}",
491                    i,
492                    expected_size,
493                    band.len()
494                )));
495            }
496        }
497
498        let bands = bands_data
499            .iter()
500            .map(|data| GpuBuffer::from_data(context, data, usage))
501            .collect::<GpuResult<Vec<_>>>()?;
502
503        Ok(Self {
504            bands,
505            width,
506            height,
507        })
508    }
509
510    /// Get a specific band buffer.
511    pub fn band(&self, index: usize) -> Option<&GpuBuffer<T>> {
512        self.bands.get(index)
513    }
514
515    /// Get mutable reference to a specific band buffer.
516    pub fn band_mut(&mut self, index: usize) -> Option<&mut GpuBuffer<T>> {
517        self.bands.get_mut(index)
518    }
519
520    /// Get all band buffers.
521    pub fn bands(&self) -> &[GpuBuffer<T>] {
522        &self.bands
523    }
524
525    /// Get the number of bands.
526    pub fn num_bands(&self) -> usize {
527        self.bands.len()
528    }
529
530    /// Get raster dimensions.
531    pub fn dimensions(&self) -> (u32, u32) {
532        (self.width, self.height)
533    }
534
535    /// Get raster width.
536    pub fn width(&self) -> u32 {
537        self.width
538    }
539
540    /// Get raster height.
541    pub fn height(&self) -> u32 {
542        self.height
543    }
544
545    /// Read all bands from GPU asynchronously.
546    ///
547    /// # Errors
548    ///
549    /// Returns an error if reading fails.
550    pub async fn read_all_bands(&self) -> GpuResult<Vec<Vec<T>>> {
551        let mut results = Vec::with_capacity(self.bands.len());
552
553        for band in &self.bands {
554            results.push(band.read().await?);
555        }
556
557        Ok(results)
558    }
559
560    /// Read all bands from GPU synchronously.
561    ///
562    /// # Errors
563    ///
564    /// Returns an error if reading fails.
565    pub fn read_all_bands_blocking(&self) -> GpuResult<Vec<Vec<T>>> {
566        pollster::block_on(self.read_all_bands())
567    }
568}
569
570impl<T: Pod> std::fmt::Debug for GpuRasterBuffer<T> {
571    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
572        f.debug_struct("GpuRasterBuffer")
573            .field("width", &self.width)
574            .field("height", &self.height)
575            .field("num_bands", &self.num_bands())
576            .field("type", &std::any::type_name::<T>())
577            .finish()
578    }
579}
580
581#[cfg(test)]
582#[allow(clippy::panic)]
583mod tests {
584    use super::*;
585
586    #[tokio::test]
587    async fn test_gpu_buffer_creation() {
588        if let Ok(context) = GpuContext::new().await {
589            let buffer: GpuBuffer<f32> = GpuBuffer::new(&context, 1024, BufferUsages::STORAGE)
590                .unwrap_or_else(|e| {
591                    panic!("Failed to create buffer: {}", e);
592                });
593
594            assert_eq!(buffer.len(), 1024);
595            assert!(!buffer.is_empty());
596        }
597    }
598
599    #[tokio::test]
600    #[ignore]
601    async fn test_gpu_buffer_write_read() {
602        if let Ok(context) = GpuContext::new().await {
603            let data: Vec<f32> = (0..100).map(|i| i as f32).collect();
604
605            let buffer = GpuBuffer::from_data(
606                &context,
607                &data,
608                BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
609            )
610            .unwrap_or_else(|e| {
611                panic!("Failed to create buffer: {}", e);
612            });
613
614            // Create staging buffer for reading
615            let mut staging = GpuBuffer::staging(&context, 100).unwrap_or_else(|e| {
616                panic!("Failed to create staging buffer: {}", e);
617            });
618
619            staging.copy_from(&buffer).unwrap_or_else(|e| {
620                panic!("Failed to copy buffer: {}", e);
621            });
622
623            let result = staging.read().await.unwrap_or_else(|e| {
624                panic!("Failed to read buffer: {}", e);
625            });
626
627            assert_eq!(result.len(), data.len());
628            for (a, b) in result.iter().zip(data.iter()) {
629                assert!((a - b).abs() < 1e-6);
630            }
631        }
632    }
633
634    #[tokio::test]
635    async fn test_read_truncates_u8_padding() {
636        // `u8` with a length that is not a multiple of COPY_BUFFER_ALIGNMENT (4)
637        // forces the aligned GPU allocation to be larger than the logical size
638        // (len 5 -> 8 bytes). read() must return exactly `len` elements, not the
639        // padded byte count. We write an *aligned* 8-byte payload directly into
640        // the underlying buffer because `GpuBuffer::write` would reject a 5-byte
641        // copy under COPY_BUFFER_ALIGNMENT — the last 3 bytes are padding that
642        // read() must NOT surface.
643        if let Ok(context) = GpuContext::new().await {
644            let staging: GpuBuffer<u8> = GpuBuffer::staging(&context, 5).unwrap_or_else(|e| {
645                panic!("Failed to create staging buffer: {}", e);
646            });
647            let padded: [u8; 8] = [1, 2, 3, 4, 5, 0xAA, 0xBB, 0xCC];
648            context.queue().write_buffer(&staging.buffer, 0, &padded);
649
650            let result = staging
651                .read()
652                .await
653                .unwrap_or_else(|e| panic!("Failed to read buffer: {}", e));
654
655            assert_eq!(result.len(), 5, "read() must not return padding");
656            assert_eq!(result, vec![1, 2, 3, 4, 5]);
657        }
658    }
659
660    #[tokio::test]
661    async fn test_read_truncates_u16_padding() {
662        // `u16` with an odd length: 3 * 2 = 6 bytes -> aligned up to 8 bytes,
663        // which would decode as 4 elements without truncation. The trailing
664        // u16 is padding that read() must drop.
665        if let Ok(context) = GpuContext::new().await {
666            let staging: GpuBuffer<u16> = GpuBuffer::staging(&context, 3).unwrap_or_else(|e| {
667                panic!("Failed to create staging buffer: {}", e);
668            });
669            let padded: [u16; 4] = [100, 200, 300, 0xFFFF];
670            context
671                .queue()
672                .write_buffer(&staging.buffer, 0, bytemuck::cast_slice(&padded));
673
674            let result = staging
675                .read()
676                .await
677                .unwrap_or_else(|e| panic!("Failed to read buffer: {}", e));
678
679            assert_eq!(result.len(), 3, "read() must not return padding");
680            assert_eq!(result, vec![100, 200, 300]);
681        }
682    }
683
684    #[tokio::test]
685    async fn test_gpu_raster_buffer() {
686        if let Ok(context) = GpuContext::new().await {
687            let width = 64;
688            let height = 64;
689            let num_bands = 3;
690
691            let raster: GpuRasterBuffer<f32> =
692                GpuRasterBuffer::new(&context, width, height, num_bands, BufferUsages::STORAGE)
693                    .unwrap_or_else(|e| {
694                        panic!("Failed to create raster buffer: {}", e);
695                    });
696
697            assert_eq!(raster.width(), width);
698            assert_eq!(raster.height(), height);
699            assert_eq!(raster.num_bands(), num_bands);
700        }
701    }
702}