sable-gpu 0.1.0

GPU abstraction layer for Sable Engine - wgpu-based rendering primitives
Documentation
//! Vertex type definitions and layouts.
//!
//! This module provides the [`Vertex`] trait and common vertex types.

use bytemuck::{Pod, Zeroable};

/// A trait for vertex types that can be used in GPU buffers.
///
/// Types implementing this trait must be `Pod` (plain old data) and provide
/// their vertex buffer layout for use in render pipelines.
pub trait Vertex: Pod {
    /// Returns the vertex buffer layout describing the attributes.
    fn layout() -> wgpu::VertexBufferLayout<'static>;
}

/// A simple vertex with position and color.
///
/// This is the most basic vertex type, suitable for simple colored geometry.
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct PositionColorVertex {
    /// Position in 3D space (x, y, z).
    pub position: [f32; 3],
    /// RGB color values (r, g, b).
    pub color: [f32; 3],
}

impl PositionColorVertex {
    /// Create a new vertex with position and color.
    #[must_use]
    pub const fn new(position: [f32; 3], color: [f32; 3]) -> Self {
        Self { position, color }
    }
}

impl Vertex for PositionColorVertex {
    fn layout() -> wgpu::VertexBufferLayout<'static> {
        const ATTRIBUTES: &[wgpu::VertexAttribute] = &[
            // Position at location 0
            wgpu::VertexAttribute {
                format: wgpu::VertexFormat::Float32x3,
                offset: 0,
                shader_location: 0,
            },
            // Color at location 1
            wgpu::VertexAttribute {
                format: wgpu::VertexFormat::Float32x3,
                offset: std::mem::size_of::<[f32; 3]>() as u64,
                shader_location: 1,
            },
        ];

        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<PositionColorVertex>() as u64,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: ATTRIBUTES,
        }
    }
}

/// A vertex with position, color, and texture coordinates.
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct PositionColorTexVertex {
    /// Position in 3D space (x, y, z).
    pub position: [f32; 3],
    /// RGB color values (r, g, b).
    pub color: [f32; 3],
    /// Texture coordinates (u, v).
    pub tex_coords: [f32; 2],
}

impl PositionColorTexVertex {
    /// Create a new vertex with position, color, and texture coordinates.
    #[must_use]
    pub const fn new(position: [f32; 3], color: [f32; 3], tex_coords: [f32; 2]) -> Self {
        Self {
            position,
            color,
            tex_coords,
        }
    }
}

impl Vertex for PositionColorTexVertex {
    fn layout() -> wgpu::VertexBufferLayout<'static> {
        const ATTRIBUTES: &[wgpu::VertexAttribute] = &[
            // Position at location 0
            wgpu::VertexAttribute {
                format: wgpu::VertexFormat::Float32x3,
                offset: 0,
                shader_location: 0,
            },
            // Color at location 1
            wgpu::VertexAttribute {
                format: wgpu::VertexFormat::Float32x3,
                offset: std::mem::size_of::<[f32; 3]>() as u64,
                shader_location: 1,
            },
            // Texture coordinates at location 2
            wgpu::VertexAttribute {
                format: wgpu::VertexFormat::Float32x2,
                offset: (std::mem::size_of::<[f32; 3]>() * 2) as u64,
                shader_location: 2,
            },
        ];

        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<PositionColorTexVertex>() as u64,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: ATTRIBUTES,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_position_color_vertex_new() {
        let vertex = PositionColorVertex::new([1.0, 2.0, 3.0], [0.5, 0.6, 0.7]);
        assert_eq!(vertex.position, [1.0, 2.0, 3.0]);
        assert_eq!(vertex.color, [0.5, 0.6, 0.7]);
    }

    #[test]
    fn test_position_color_vertex_layout() {
        let layout = PositionColorVertex::layout();
        assert_eq!(
            layout.array_stride,
            std::mem::size_of::<PositionColorVertex>() as u64
        );
        assert_eq!(layout.step_mode, wgpu::VertexStepMode::Vertex);
        assert_eq!(layout.attributes.len(), 2);

        // Position attribute
        assert_eq!(layout.attributes[0].shader_location, 0);
        assert_eq!(layout.attributes[0].format, wgpu::VertexFormat::Float32x3);
        assert_eq!(layout.attributes[0].offset, 0);

        // Color attribute
        assert_eq!(layout.attributes[1].shader_location, 1);
        assert_eq!(layout.attributes[1].format, wgpu::VertexFormat::Float32x3);
        assert_eq!(
            layout.attributes[1].offset,
            std::mem::size_of::<[f32; 3]>() as u64
        );
    }

    #[test]
    fn test_position_color_tex_vertex_new() {
        let vertex = PositionColorTexVertex::new([1.0, 2.0, 3.0], [0.5, 0.6, 0.7], [0.0, 1.0]);
        assert_eq!(vertex.position, [1.0, 2.0, 3.0]);
        assert_eq!(vertex.color, [0.5, 0.6, 0.7]);
        assert_eq!(vertex.tex_coords, [0.0, 1.0]);
    }

    #[test]
    fn test_position_color_tex_vertex_layout() {
        let layout = PositionColorTexVertex::layout();
        assert_eq!(
            layout.array_stride,
            std::mem::size_of::<PositionColorTexVertex>() as u64
        );
        assert_eq!(layout.attributes.len(), 3);

        // Texture coordinate attribute
        assert_eq!(layout.attributes[2].shader_location, 2);
        assert_eq!(layout.attributes[2].format, wgpu::VertexFormat::Float32x2);
    }

    #[test]
    fn test_vertex_size_alignment() {
        // Ensure proper memory layout for GPU
        assert_eq!(std::mem::size_of::<PositionColorVertex>(), 24); // 6 * 4 bytes
        assert_eq!(std::mem::size_of::<PositionColorTexVertex>(), 32); // 8 * 4 bytes
    }

    #[test]
    fn test_vertex_is_pod() {
        // Verify vertices can be safely cast to bytes
        let vertex = PositionColorVertex::new([1.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
        let bytes: &[u8] = bytemuck::bytes_of(&vertex);
        assert_eq!(bytes.len(), 24);
    }
}