#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParamType {
Float,
Int,
Vec2,
Vec3,
Vec4,
Matrix4x4,
Texture,
}
impl ParamType {
#[must_use]
pub fn byte_size(&self) -> usize {
match self {
Self::Float => 4,
Self::Int => 4,
Self::Vec2 => 8,
Self::Vec3 => 12,
Self::Vec4 => 16,
Self::Matrix4x4 => 64,
Self::Texture => 8,
}
}
}
#[derive(Debug, Clone)]
pub struct ShaderParam {
pub name: String,
pub param_type: ParamType,
pub offset: u32,
}
impl ShaderParam {
#[must_use]
pub fn new(name: impl Into<String>, param_type: ParamType, offset: u32) -> Self {
Self {
name: name.into(),
param_type,
offset,
}
}
#[must_use]
pub fn end_offset(&self) -> u32 {
self.offset + self.param_type.byte_size() as u32
}
}
#[derive(Debug)]
pub struct UniformBlock {
pub params: Vec<ShaderParam>,
pub name: String,
}
impl UniformBlock {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
params: Vec::new(),
name: name.into(),
}
}
pub fn add_param(&mut self, param: ShaderParam) {
self.params.push(param);
}
#[must_use]
pub fn find(&self, name: &str) -> Option<&ShaderParam> {
self.params.iter().find(|p| p.name == name)
}
#[must_use]
pub fn total_size_bytes(&self) -> u32 {
self.params
.iter()
.map(|p| p.param_type.byte_size() as u32)
.sum()
}
#[must_use]
pub fn param_count(&self) -> usize {
self.params.len()
}
#[must_use]
pub fn is_aligned(&self) -> bool {
self.total_size_bytes() % 16 == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_float_byte_size() {
assert_eq!(ParamType::Float.byte_size(), 4);
}
#[test]
fn test_int_byte_size() {
assert_eq!(ParamType::Int.byte_size(), 4);
}
#[test]
fn test_vec2_byte_size() {
assert_eq!(ParamType::Vec2.byte_size(), 8);
}
#[test]
fn test_vec3_byte_size() {
assert_eq!(ParamType::Vec3.byte_size(), 12);
}
#[test]
fn test_vec4_byte_size() {
assert_eq!(ParamType::Vec4.byte_size(), 16);
}
#[test]
fn test_matrix4x4_byte_size() {
assert_eq!(ParamType::Matrix4x4.byte_size(), 64);
}
#[test]
fn test_texture_byte_size() {
assert_eq!(ParamType::Texture.byte_size(), 8);
}
#[test]
fn test_shader_param_end_offset() {
let p = ShaderParam::new("brightness", ParamType::Float, 0);
assert_eq!(p.end_offset(), 4);
}
#[test]
fn test_shader_param_end_offset_vec4() {
let p = ShaderParam::new("color", ParamType::Vec4, 16);
assert_eq!(p.end_offset(), 32);
}
#[test]
fn test_shader_param_end_offset_matrix() {
let p = ShaderParam::new("mvp", ParamType::Matrix4x4, 64);
assert_eq!(p.end_offset(), 128);
}
#[test]
fn test_uniform_block_add_and_count() {
let mut block = UniformBlock::new("Globals");
block.add_param(ShaderParam::new("time", ParamType::Float, 0));
block.add_param(ShaderParam::new("resolution", ParamType::Vec2, 4));
assert_eq!(block.param_count(), 2);
}
#[test]
fn test_uniform_block_find_existing() {
let mut block = UniformBlock::new("Params");
block.add_param(ShaderParam::new("gamma", ParamType::Float, 0));
let found = block.find("gamma");
assert!(found.is_some());
assert_eq!(found.expect("parameter should be found").offset, 0);
}
#[test]
fn test_uniform_block_find_missing() {
let block = UniformBlock::new("Params");
assert!(block.find("nonexistent").is_none());
}
#[test]
fn test_uniform_block_total_size() {
let mut block = UniformBlock::new("Mix");
block.add_param(ShaderParam::new("alpha", ParamType::Float, 0));
block.add_param(ShaderParam::new("tint", ParamType::Vec4, 4));
assert_eq!(block.total_size_bytes(), 20);
}
#[test]
fn test_uniform_block_is_aligned_true() {
let mut block = UniformBlock::new("Aligned");
block.add_param(ShaderParam::new("v", ParamType::Vec4, 0));
assert!(block.is_aligned());
}
#[test]
fn test_uniform_block_is_aligned_false() {
let mut block = UniformBlock::new("Unaligned");
block.add_param(ShaderParam::new("x", ParamType::Float, 0));
assert!(!block.is_aligned());
}
#[test]
fn test_uniform_block_matrix_is_aligned() {
let mut block = UniformBlock::new("MVP");
block.add_param(ShaderParam::new("mvp", ParamType::Matrix4x4, 0));
assert!(block.is_aligned());
}
}