use alpha2_gpu::CapabilityTier;
use alpha2_time::MediaTime;
use std::borrow::{Borrow, Cow};
use std::collections::BTreeMap;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum EffectResourceKind {
Lut3d,
}
#[derive(Clone, Debug)]
pub struct EffectId {
id: IdText,
version: u32,
}
#[derive(Clone, Debug)]
enum IdText {
Static(&'static str),
Shared(Arc<str>),
}
impl EffectId {
#[must_use]
pub fn new(id: String, version: u32) -> Self {
Self {
id: IdText::Shared(id.into()),
version,
}
}
#[must_use]
pub const fn from_static(id: &'static str, version: u32) -> Self {
Self {
id: IdText::Static(id),
version,
}
}
#[must_use]
pub fn as_str(&self) -> &str {
match &self.id {
IdText::Static(id) => id,
IdText::Shared(id) => id,
}
}
#[must_use]
pub const fn version(&self) -> u32 {
self.version
}
}
impl PartialEq for EffectId {
fn eq(&self, other: &Self) -> bool {
self.version == other.version && self.as_str() == other.as_str()
}
}
impl Eq for EffectId {}
impl Hash for EffectId {
fn hash<H: Hasher>(&self, state: &mut H) {
(self as &dyn EffectKey).hash(state);
}
}
pub(crate) trait EffectKey {
fn key(&self) -> (&str, u32);
}
impl EffectKey for EffectId {
fn key(&self) -> (&str, u32) {
(self.as_str(), self.version)
}
}
impl EffectKey for (&str, u32) {
fn key(&self) -> (&str, u32) {
*self
}
}
impl<'a> Borrow<dyn EffectKey + 'a> for EffectId {
fn borrow(&self) -> &(dyn EffectKey + 'a) {
self
}
}
impl Hash for dyn EffectKey + '_ {
fn hash<H: Hasher>(&self, state: &mut H) {
let (id, version) = self.key();
id.hash(state);
version.hash(state);
}
}
impl PartialEq for dyn EffectKey + '_ {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}
impl Eq for dyn EffectKey + '_ {}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum EffectKind {
Effect,
Transition,
Generator,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ParamType {
Float,
Vec2,
Vec4,
Color,
Enum,
Bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ParamUnit {
Normalized,
Degrees,
Color,
Enum,
Bool,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParamValue {
Float(f32),
Vec2([f32; 2]),
Vec4([f32; 4]),
Color([f32; 4]),
Enum(u32),
Bool(bool),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct EffectParams<'a> {
values: Vec<(Cow<'a, str>, ParamValue)>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct EffectResources {
values: BTreeMap<String, String>,
}
impl EffectResources {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with(mut self, name: impl Into<String>, id: impl Into<String>) -> Self {
self.values.insert(name.into(), id.into());
self
}
pub(crate) fn get(&self, name: &str) -> Option<&str> {
self.values.get(name).map(String::as_str)
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.values
.iter()
.map(|(name, id)| (name.as_str(), id.as_str()))
}
}
impl<'a> EffectParams<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
values: Vec::with_capacity(capacity),
}
}
#[must_use]
pub fn with(mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) -> Self {
self.insert(name, value);
self
}
pub fn insert(&mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) {
let name = name.into();
match self.position(&name) {
Ok(index) => self.values[index].1 = value,
Err(index) => self.values.insert(index, (name, value)),
}
}
fn position(&self, name: &str) -> Result<usize, usize> {
self.values
.binary_search_by(|(held, _)| held.as_ref().cmp(name))
}
pub(crate) fn get(&self, name: &str) -> Option<&ParamValue> {
self.position(name).ok().map(|index| &self.values[index].1)
}
pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
self.values.iter().map(|(name, _)| name.as_ref())
}
pub(crate) fn sorted_values(&self) -> impl Iterator<Item = (&str, &ParamValue)> {
self.values
.iter()
.map(|(name, value)| (name.as_ref(), value))
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct EffectRuntime {
pub time: MediaTime,
pub progress: f32,
pub ratio: f32,
}
impl Default for EffectRuntime {
fn default() -> Self {
Self {
time: MediaTime::ZERO,
progress: 0.0,
ratio: 1.0,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ParamSpec {
pub name: String,
pub param_type: ParamType,
pub default: ParamValue,
pub min: Option<f32>,
pub max: Option<f32>,
pub step: Option<f32>,
pub unit: ParamUnit,
pub enum_values: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResourceSpec {
pub name: String,
pub kind: EffectResourceKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InputSpec {
pub name: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EffectDef {
pub id: String,
pub version: u32,
pub kind: EffectKind,
pub inputs: Vec<InputSpec>,
pub params: Vec<ParamSpec>,
pub resources: Vec<ResourceSpec>,
pub wgsl_body: String,
pub tier_min: CapabilityTier,
pub license: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct UniformField {
pub name: String,
pub offset: u32,
pub size: u32,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct UniformLayout {
pub fields: Vec<UniformField>,
pub size: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DiagnosticSeverity {
Error,
Warning,
Info,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ShaderDiagnostic {
pub severity: DiagnosticSeverity,
pub message: String,
pub line: Option<u32>,
pub column: Option<u32>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ShaderDiagnostics {
pub messages: Vec<ShaderDiagnostic>,
}
impl ShaderDiagnostics {
pub(crate) fn error(message: impl Into<String>) -> Self {
Self {
messages: vec![ShaderDiagnostic {
severity: DiagnosticSeverity::Error,
message: message.into(),
line: None,
column: None,
}],
}
}
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, HashSet};
use super::{EffectId, EffectKey, EffectParams, ParamValue};
#[test]
fn an_effect_identity_is_its_text_and_version_however_it_holds_the_text() {
let shared = EffectId::new("alpha2.effect.vignette".to_owned(), 1);
let constant = EffectId::from_static("alpha2.effect.vignette", 1);
assert_eq!(shared, constant);
assert_ne!(constant, EffectId::from_static("alpha2.effect.vignette", 2));
assert_ne!(constant, EffectId::from_static("alpha2.effect.sharpen", 1));
let map = HashMap::from([(shared.clone(), "vignette")]);
assert_eq!(map.get(&constant), Some(&"vignette"));
let key: &dyn EffectKey = &("alpha2.effect.vignette", 1);
assert_eq!(map.get_key_value(key).map(|(id, _)| id), Some(&shared));
let wrong: &dyn EffectKey = &("alpha2.effect.vignette", 2);
assert!(!map.contains_key(wrong));
assert_eq!(HashSet::from([shared, constant]).len(), 1);
}
#[test]
fn parameters_keep_one_value_per_name_in_name_order() {
let project_name = String::from("b_owned");
let params = EffectParams::with_capacity(4)
.with("c", ParamValue::Float(3.0))
.with(project_name.as_str(), ParamValue::Float(2.0))
.with(String::from("a"), ParamValue::Float(1.0))
.with("c", ParamValue::Float(4.0));
assert_eq!(
params.sorted_values().collect::<Vec<_>>(),
[
("a", &ParamValue::Float(1.0)),
("b_owned", &ParamValue::Float(2.0)),
("c", &ParamValue::Float(4.0)),
]
);
assert_eq!(params.get("c"), Some(&ParamValue::Float(4.0)));
assert_eq!(params.get("d"), None);
assert_eq!(params.names().collect::<Vec<_>>(), ["a", "b_owned", "c"]);
let again = EffectParams::new()
.with(String::from("c"), ParamValue::Float(4.0))
.with("a", ParamValue::Float(1.0))
.with(String::from("b_owned"), ParamValue::Float(2.0));
assert_eq!(params, again);
}
}