use std::collections::HashMap;
use std::sync::OnceLock;
pub struct BlendModeRegistration {
pub type_id: &'static str,
pub display_name: &'static str,
pub category: &'static str,
pub gpu_value: u32,
pub wgsl_math: &'static str,
}
pub struct BlendModeRegistry {
entries: Vec<BlendModeRegistration>,
by_type_id: HashMap<&'static str, usize>,
ordered: Vec<usize>,
}
impl Default for BlendModeRegistry {
fn default() -> Self {
Self::new()
}
}
impl BlendModeRegistry {
pub fn new() -> Self {
let entries: Vec<BlendModeRegistration> = super::blend_modes::registrations();
let mut by_type_id = HashMap::with_capacity(entries.len());
for (i, reg) in entries.iter().enumerate() {
by_type_id.insert(reg.type_id, i);
}
let mut ordered: Vec<usize> = (0..entries.len()).collect();
ordered.sort_by_key(|&i| entries[i].gpu_value);
BlendModeRegistry {
entries,
by_type_id,
ordered,
}
}
pub fn get(&'static self, type_id: &str) -> Option<&'static BlendModeRegistration> {
self.by_type_id.get(type_id).map(|&i| &self.entries[i])
}
pub fn default(&'static self) -> &'static BlendModeRegistration {
self.get("normal")
.expect("blend mode 'normal' must be registered")
}
pub fn all(&'static self) -> Vec<&'static BlendModeRegistration> {
self.ordered.iter().map(|&i| &self.entries[i]).collect()
}
}
pub fn registry() -> &'static BlendModeRegistry {
static REGISTRY: OnceLock<BlendModeRegistry> = OnceLock::new();
REGISTRY.get_or_init(BlendModeRegistry::new)
}
pub fn build_composite_source() -> String {
const TEMPLATE: &str = include_str!("../../shaders/composite.wgsl");
const MARKER: &str = "// @blend-switch";
let mut arms = String::new();
for reg in registry().all() {
arms.push_str(&format!(
" case {}u: {{ {} }} // {}\n",
reg.gpu_value, reg.wgsl_math, reg.display_name
));
}
arms.push_str(" default: { Cs = fg.rgb; }\n");
let trimmed_arms = arms.trim_end_matches('\n');
TEMPLATE.replacen(MARKER, trimmed_arms, 1)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn normal_is_registered_and_is_the_default() {
let r = registry();
let normal = r.get("normal").expect("normal must be registered");
assert_eq!(normal.type_id, "normal");
assert!(std::ptr::eq(normal, r.default()));
}
#[test]
fn registry_type_ids_and_gpu_values_are_unique() {
let r = registry();
let all = r.all();
let ids: HashSet<&'static str> = all.iter().map(|reg| reg.type_id).collect();
assert_eq!(ids.len(), all.len(), "duplicate type_id in registry");
let values: HashSet<u32> = all.iter().map(|reg| reg.gpu_value).collect();
assert_eq!(values.len(), all.len(), "duplicate gpu_value in registry");
}
#[test]
fn ordered_by_gpu_value() {
let r = registry();
let all = r.all();
for w in all.windows(2) {
assert!(w[0].gpu_value < w[1].gpu_value);
}
}
}