use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FilterMode {
#[default]
Linear,
Nearest,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AddressMode {
#[default]
ClampToEdge,
Repeat,
MirrorRepeat,
}
#[derive(Debug, Clone)]
pub struct TextureConfig {
pub data: Vec<u8>,
pub width: u32,
pub height: u32,
pub filter: FilterMode,
pub address_mode: AddressMode,
}
impl TextureConfig {
pub fn from_rgba(data: Vec<u8>, width: u32, height: u32) -> Self {
assert_eq!(
data.len(),
(width * height * 4) as usize,
"RGBA data size mismatch"
);
Self {
data,
width,
height,
filter: FilterMode::Linear,
address_mode: AddressMode::ClampToEdge,
}
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
Self::try_from_file(path.as_ref())
.unwrap_or_else(|e| panic!("Failed to load texture '{}': {}", path.as_ref().display(), e))
}
pub fn try_from_file<P: AsRef<Path>>(path: P) -> Result<Self, crate::error::TextureError> {
let img = image::open(path.as_ref())?.into_rgba8();
let (width, height) = img.dimensions();
Ok(Self {
data: img.into_raw(),
width,
height,
filter: FilterMode::Linear,
address_mode: AddressMode::ClampToEdge,
})
}
pub fn with_filter(mut self, filter: FilterMode) -> Self {
self.filter = filter;
self
}
pub fn with_address_mode(mut self, mode: AddressMode) -> Self {
self.address_mode = mode;
self
}
pub fn solid(r: u8, g: u8, b: u8, a: u8) -> Self {
Self {
data: vec![r, g, b, a],
width: 1,
height: 1,
filter: FilterMode::Nearest,
address_mode: AddressMode::ClampToEdge,
}
}
pub fn gradient(width: u32, start: [u8; 4], end: [u8; 4]) -> Self {
let mut data = Vec::with_capacity((width * 4) as usize);
for x in 0..width {
let t = x as f32 / (width - 1).max(1) as f32;
data.push(lerp_u8(start[0], end[0], t));
data.push(lerp_u8(start[1], end[1], t));
data.push(lerp_u8(start[2], end[2], t));
data.push(lerp_u8(start[3], end[3], t));
}
Self {
data,
width,
height: 1,
filter: FilterMode::Linear,
address_mode: AddressMode::ClampToEdge,
}
}
pub fn checkerboard(size: u32, cell_size: u32, color1: [u8; 4], color2: [u8; 4]) -> Self {
let mut data = Vec::with_capacity((size * size * 4) as usize);
for y in 0..size {
for x in 0..size {
let cx = x / cell_size;
let cy = y / cell_size;
let color = if (cx + cy).is_multiple_of(2) { color1 } else { color2 };
data.extend_from_slice(&color);
}
}
Self {
data,
width: size,
height: size,
filter: FilterMode::Nearest,
address_mode: AddressMode::Repeat,
}
}
pub fn noise(size: u32, seed: u32) -> Self {
let mut data = Vec::with_capacity((size * size * 4) as usize);
for y in 0..size {
for x in 0..size {
let v = hash_noise(x, y, seed);
data.push(v);
data.push(v);
data.push(v);
data.push(255);
}
}
Self {
data,
width: size,
height: size,
filter: FilterMode::Linear,
address_mode: AddressMode::Repeat,
}
}
}
impl From<&str> for TextureConfig {
fn from(path: &str) -> Self {
TextureConfig::from_file(path)
}
}
impl From<String> for TextureConfig {
fn from(path: String) -> Self {
TextureConfig::from_file(path)
}
}
impl From<&Path> for TextureConfig {
fn from(path: &Path) -> Self {
TextureConfig::from_file(path)
}
}
fn lerp_u8(a: u8, b: u8, t: f32) -> u8 {
let a = a as f32;
let b = b as f32;
(a + (b - a) * t).round() as u8
}
fn hash_noise(x: u32, y: u32, seed: u32) -> u8 {
let mut n = x.wrapping_mul(374761393)
.wrapping_add(y.wrapping_mul(668265263))
.wrapping_add(seed.wrapping_mul(1013904223));
n = (n ^ (n >> 13)).wrapping_mul(1274126177);
n = n ^ (n >> 16);
(n & 255) as u8
}
#[derive(Debug, Clone, Default)]
pub struct TextureRegistry {
pub textures: Vec<(String, TextureConfig)>,
}
impl TextureRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, name: impl Into<String>, config: impl Into<TextureConfig>) {
self.textures.push((name.into(), config.into()));
}
pub fn len(&self) -> usize {
self.textures.len()
}
pub fn is_empty(&self) -> bool {
self.textures.is_empty()
}
pub fn to_wgsl_declarations(&self, start_binding: u32) -> String {
let mut code = String::new();
let mut binding = start_binding;
for (name, _config) in &self.textures {
code.push_str(&format!(
"@group(1) @binding({binding})\nvar tex_{name}: texture_2d<f32>;\n"
));
binding += 1;
code.push_str(&format!(
"@group(1) @binding({binding})\nvar tex_{name}_sampler: sampler;\n"
));
binding += 1;
}
code
}
}