sable-gpu 0.1.0

GPU abstraction layer for Sable Engine - wgpu-based rendering primitives
Documentation
//! GPU buffer types for vertex, index, and uniform data.

#![allow(clippy::cast_possible_truncation)]

use bytemuck::Pod;
use wgpu::{Device, util::DeviceExt};

use crate::vertex::Vertex;

/// Buffer usage flags.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferUsage {
    /// Vertex buffer.
    Vertex,
    /// Index buffer.
    Index,
    /// Uniform buffer.
    Uniform,
    /// Storage buffer (read/write from shaders).
    Storage,
    /// Indirect buffer for indirect draw commands.
    Indirect,
}

impl From<BufferUsage> for wgpu::BufferUsages {
    fn from(usage: BufferUsage) -> Self {
        match usage {
            BufferUsage::Vertex => wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
            BufferUsage::Index => wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
            BufferUsage::Uniform => wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            BufferUsage::Storage => {
                wgpu::BufferUsages::STORAGE
                    | wgpu::BufferUsages::COPY_DST
                    | wgpu::BufferUsages::COPY_SRC
            }
            BufferUsage::Indirect => wgpu::BufferUsages::INDIRECT | wgpu::BufferUsages::COPY_DST,
        }
    }
}

/// A GPU buffer with type information.
#[derive(Debug)]
pub struct Buffer {
    inner: wgpu::Buffer,
    usage: BufferUsage,
    size: u64,
}

impl Buffer {
    /// Create a new buffer with initial data.
    #[must_use]
    pub fn new<T: Pod>(
        device: &Device,
        usage: BufferUsage,
        data: &[T],
        label: Option<&str>,
    ) -> Self {
        let inner = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label,
            contents: bytemuck::cast_slice(data),
            usage: usage.into(),
        });

        Self {
            inner,
            usage,
            size: std::mem::size_of_val(data) as u64,
        }
    }

    /// Create an empty buffer with the specified size.
    #[must_use]
    pub fn empty(device: &Device, usage: BufferUsage, size: u64, label: Option<&str>) -> Self {
        let inner = device.create_buffer(&wgpu::BufferDescriptor {
            label,
            size,
            usage: usage.into(),
            mapped_at_creation: false,
        });

        Self { inner, usage, size }
    }

    /// Get a reference to the underlying wgpu buffer.
    #[must_use]
    pub fn inner(&self) -> &wgpu::Buffer {
        &self.inner
    }

    /// Get the buffer usage.
    #[must_use]
    pub fn usage(&self) -> BufferUsage {
        self.usage
    }

    /// Get the buffer size in bytes.
    #[must_use]
    pub fn size(&self) -> u64 {
        self.size
    }

    /// Get a buffer slice for the entire buffer.
    #[must_use]
    pub fn slice(&self) -> wgpu::BufferSlice<'_> {
        self.inner.slice(..)
    }
}

/// A typed vertex buffer.
#[derive(Debug)]
pub struct VertexBuffer<V: Vertex> {
    buffer: Buffer,
    vertex_count: u32,
    _marker: std::marker::PhantomData<V>,
}

impl<V: Vertex> VertexBuffer<V> {
    /// Create a new vertex buffer from vertices.
    #[must_use]
    pub fn new(device: &Device, vertices: &[V]) -> Self {
        let buffer = Buffer::new(device, BufferUsage::Vertex, vertices, Some("Vertex Buffer"));

        Self {
            buffer,
            vertex_count: vertices.len() as u32,
            _marker: std::marker::PhantomData,
        }
    }

    /// Create a new vertex buffer with a custom label.
    #[must_use]
    pub fn with_label(device: &Device, vertices: &[V], label: &str) -> Self {
        let buffer = Buffer::new(device, BufferUsage::Vertex, vertices, Some(label));

        Self {
            buffer,
            vertex_count: vertices.len() as u32,
            _marker: std::marker::PhantomData,
        }
    }

    /// Get the number of vertices in the buffer.
    #[must_use]
    pub fn vertex_count(&self) -> u32 {
        self.vertex_count
    }

    /// Get a reference to the underlying buffer.
    #[must_use]
    pub fn buffer(&self) -> &Buffer {
        &self.buffer
    }

    /// Get a buffer slice for the entire buffer.
    #[must_use]
    pub fn slice(&self) -> wgpu::BufferSlice<'_> {
        self.buffer.slice()
    }
}

/// An index buffer for indexed drawing.
#[derive(Debug)]
pub struct IndexBuffer {
    buffer: Buffer,
    index_count: u32,
    format: wgpu::IndexFormat,
}

impl IndexBuffer {
    /// Create a new index buffer from 16-bit indices.
    #[must_use]
    pub fn new_u16(device: &Device, indices: &[u16]) -> Self {
        let buffer = Buffer::new(device, BufferUsage::Index, indices, Some("Index Buffer"));

        Self {
            buffer,
            index_count: indices.len() as u32,
            format: wgpu::IndexFormat::Uint16,
        }
    }

    /// Create a new index buffer from 32-bit indices.
    #[must_use]
    pub fn new_u32(device: &Device, indices: &[u32]) -> Self {
        let buffer = Buffer::new(device, BufferUsage::Index, indices, Some("Index Buffer"));

        Self {
            buffer,
            index_count: indices.len() as u32,
            format: wgpu::IndexFormat::Uint32,
        }
    }

    /// Get the number of indices in the buffer.
    #[must_use]
    pub fn index_count(&self) -> u32 {
        self.index_count
    }

    /// Get the index format.
    #[must_use]
    pub fn format(&self) -> wgpu::IndexFormat {
        self.format
    }

    /// Get a reference to the underlying buffer.
    #[must_use]
    pub fn buffer(&self) -> &Buffer {
        &self.buffer
    }

    /// Get a buffer slice for the entire buffer.
    #[must_use]
    pub fn slice(&self) -> wgpu::BufferSlice<'_> {
        self.buffer.slice()
    }
}