use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShaderId(u32);
impl ShaderId {
pub(crate) const fn raw(self) -> u32 {
self.0
}
pub(crate) const fn from_raw(index: u32) -> Self {
Self(index)
}
}
#[derive(Debug)]
struct Entry {
name: String,
module: wgpu::ShaderModule,
}
#[derive(Debug, Default)]
pub struct ShaderLibrary {
entries: Vec<Entry>,
by_name: HashMap<String, ShaderId>,
}
impl ShaderLibrary {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert_wgsl(
&mut self,
device: &wgpu::Device,
name: impl Into<String>,
src: &str,
) -> ShaderId {
let name = name.into();
if let Some(existing) = self.by_name.get(&name) {
return *existing;
}
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(&format!("frust-gpu shader: {name}")),
source: wgpu::ShaderSource::Wgsl(src.into()),
});
let id = ShaderId::from_raw(self.entries.len() as u32);
self.entries.push(Entry {
name: name.clone(),
module,
});
self.by_name.insert(name, id);
id
}
#[must_use]
pub fn get(&self, id: ShaderId) -> Option<&wgpu::ShaderModule> {
self.entries.get(id.raw() as usize).map(|e| &e.module)
}
#[must_use]
pub fn id_of(&self, name: &str) -> Option<ShaderId> {
self.by_name.get(name).copied()
}
#[must_use]
pub fn name_of(&self, id: ShaderId) -> Option<&str> {
self.entries.get(id.raw() as usize).map(|e| e.name.as_str())
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_empty_library_resolves_nothing() {
let lib = ShaderLibrary::new();
assert!(lib.is_empty());
assert_eq!(lib.len(), 0);
assert!(lib.get(ShaderId::from_raw(0)).is_none());
assert!(lib.name_of(ShaderId::from_raw(0)).is_none());
assert!(lib.id_of("missing").is_none());
}
#[test]
fn ids_round_trip_through_their_raw_index() {
for index in [0u32, 1, 7, u32::MAX] {
assert_eq!(ShaderId::from_raw(index).raw(), index);
}
assert_ne!(ShaderId::from_raw(0), ShaderId::from_raw(1));
}
}