1use alpha2_gpu::CapabilityTier;
2use alpha2_time::MediaTime;
3use std::borrow::{Borrow, Cow};
4use std::collections::BTreeMap;
5use std::hash::{Hash, Hasher};
6use std::sync::Arc;
7
8#[derive(Copy, Clone, Debug, Eq, PartialEq)]
10pub enum EffectResourceKind {
11 Lut3d,
13}
14
15#[derive(Clone, Debug)]
21pub struct EffectId {
22 id: IdText,
23 version: u32,
24}
25
26#[derive(Clone, Debug)]
27enum IdText {
28 Static(&'static str),
29 Shared(Arc<str>),
30}
31
32impl EffectId {
33 #[must_use]
35 pub fn new(id: String, version: u32) -> Self {
36 Self {
37 id: IdText::Shared(id.into()),
38 version,
39 }
40 }
41
42 #[must_use]
45 pub const fn from_static(id: &'static str, version: u32) -> Self {
46 Self {
47 id: IdText::Static(id),
48 version,
49 }
50 }
51
52 #[must_use]
54 pub fn as_str(&self) -> &str {
55 match &self.id {
56 IdText::Static(id) => id,
57 IdText::Shared(id) => id,
58 }
59 }
60
61 #[must_use]
63 pub const fn version(&self) -> u32 {
64 self.version
65 }
66}
67
68impl PartialEq for EffectId {
69 fn eq(&self, other: &Self) -> bool {
70 self.version == other.version && self.as_str() == other.as_str()
71 }
72}
73
74impl Eq for EffectId {}
75
76impl Hash for EffectId {
77 fn hash<H: Hasher>(&self, state: &mut H) {
78 (self as &dyn EffectKey).hash(state);
80 }
81}
82
83pub(crate) trait EffectKey {
86 fn key(&self) -> (&str, u32);
87}
88
89impl EffectKey for EffectId {
90 fn key(&self) -> (&str, u32) {
91 (self.as_str(), self.version)
92 }
93}
94
95impl EffectKey for (&str, u32) {
96 fn key(&self) -> (&str, u32) {
97 *self
98 }
99}
100
101impl<'a> Borrow<dyn EffectKey + 'a> for EffectId {
102 fn borrow(&self) -> &(dyn EffectKey + 'a) {
103 self
104 }
105}
106
107impl Hash for dyn EffectKey + '_ {
108 fn hash<H: Hasher>(&self, state: &mut H) {
109 let (id, version) = self.key();
110 id.hash(state);
111 version.hash(state);
112 }
113}
114
115impl PartialEq for dyn EffectKey + '_ {
116 fn eq(&self, other: &Self) -> bool {
117 self.key() == other.key()
118 }
119}
120
121impl Eq for dyn EffectKey + '_ {}
122
123#[derive(Copy, Clone, Debug, Eq, PartialEq)]
125pub enum EffectKind {
126 Effect,
128 Transition,
130 Generator,
133}
134
135#[derive(Copy, Clone, Debug, Eq, PartialEq)]
137pub enum EffectInputs {
138 One,
140 Two,
142 None,
144}
145
146#[derive(Copy, Clone, Debug, Eq, PartialEq)]
148pub enum ParamType {
149 Float,
151 Vec2,
153 Vec4,
156 Color,
160 Enum,
162 Bool,
164}
165
166#[derive(Copy, Clone, Debug, Eq, PartialEq)]
168pub enum ParamUnit {
169 Normalized,
171 Degrees,
173 Color,
175 Enum,
177 Bool,
179}
180
181#[derive(Clone, Debug, PartialEq)]
183pub enum ParamValue {
184 Float(f32),
186 Vec2([f32; 2]),
188 Vec4([f32; 4]),
190 Color([f32; 4]),
192 Enum(u32),
194 Bool(bool),
196}
197
198#[derive(Clone, Debug, Default, PartialEq)]
205pub struct EffectParams<'a> {
206 values: Vec<(Cow<'a, str>, ParamValue)>,
207}
208
209#[derive(Clone, Debug, Default, Eq, PartialEq)]
211pub struct EffectResources {
212 values: BTreeMap<String, String>,
213}
214
215impl EffectResources {
216 #[must_use]
218 pub fn new() -> Self {
219 Self::default()
220 }
221
222 #[must_use]
224 pub fn with(mut self, name: impl Into<String>, id: impl Into<String>) -> Self {
225 self.values.insert(name.into(), id.into());
226 self
227 }
228
229 pub(crate) fn get(&self, name: &str) -> Option<&str> {
230 self.values.get(name).map(String::as_str)
231 }
232
233 pub(crate) fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
234 self.values
235 .iter()
236 .map(|(name, id)| (name.as_str(), id.as_str()))
237 }
238}
239
240impl<'a> EffectParams<'a> {
241 #[must_use]
243 pub fn new() -> Self {
244 Self::default()
245 }
246
247 #[must_use]
249 pub fn with_capacity(capacity: usize) -> Self {
250 Self {
251 values: Vec::with_capacity(capacity),
252 }
253 }
254
255 #[must_use]
257 pub fn with(mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) -> Self {
258 self.insert(name, value);
259 self
260 }
261
262 pub fn insert(&mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) {
264 let name = name.into();
265 match self.position(&name) {
266 Ok(index) => self.values[index].1 = value,
267 Err(index) => self.values.insert(index, (name, value)),
268 }
269 }
270
271 fn position(&self, name: &str) -> Result<usize, usize> {
272 self.values
273 .binary_search_by(|(held, _)| held.as_ref().cmp(name))
274 }
275
276 pub(crate) fn get(&self, name: &str) -> Option<&ParamValue> {
277 self.position(name).ok().map(|index| &self.values[index].1)
278 }
279
280 pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
281 self.values.iter().map(|(name, _)| name.as_ref())
282 }
283
284 pub(crate) fn sorted_values(&self) -> impl Iterator<Item = (&str, &ParamValue)> {
286 self.values
287 .iter()
288 .map(|(name, value)| (name.as_ref(), value))
289 }
290}
291
292#[derive(Copy, Clone, Debug, PartialEq)]
294pub struct EffectRuntime {
295 pub time: MediaTime,
305 pub progress: f32,
307 pub ratio: f32,
309}
310
311impl Default for EffectRuntime {
312 fn default() -> Self {
313 Self {
314 time: MediaTime::ZERO,
315 progress: 0.0,
316 ratio: 1.0,
317 }
318 }
319}
320
321#[derive(Clone, Debug, PartialEq)]
323pub struct ParamSpec {
324 pub name: String,
326 pub param_type: ParamType,
328 pub default: ParamValue,
330 pub min: Option<f32>,
332 pub max: Option<f32>,
334 pub step: Option<f32>,
336 pub unit: ParamUnit,
338 pub enum_values: Vec<String>,
340}
341
342#[derive(Clone, Debug, Eq, PartialEq)]
344pub struct ResourceSpec {
345 pub name: String,
347 pub kind: EffectResourceKind,
349}
350
351#[derive(Clone, Debug, PartialEq)]
358pub struct EffectDef {
359 pub id: String,
361 pub version: u32,
363 pub kind: EffectKind,
365 pub inputs: EffectInputs,
367 pub params: Vec<ParamSpec>,
369 pub resources: Vec<ResourceSpec>,
371 pub wgsl_body: String,
373 pub tier_min: CapabilityTier,
375 pub license: Option<String>,
377}
378
379#[derive(Clone, Debug, Eq, PartialEq)]
381pub struct UniformField {
382 pub name: String,
384 pub offset: u32,
386 pub size: u32,
388}
389
390#[derive(Clone, Debug, Eq, PartialEq)]
392pub struct UniformLayout {
393 pub fields: Vec<UniformField>,
395 pub size: u32,
397}
398
399#[derive(Copy, Clone, Debug, Eq, PartialEq)]
401pub enum DiagnosticSeverity {
402 Error,
404 Warning,
406 Info,
408}
409
410#[derive(Clone, Debug, Eq, PartialEq)]
412pub struct ShaderDiagnostic {
413 pub severity: DiagnosticSeverity,
415 pub message: String,
417 pub line: Option<u32>,
419 pub column: Option<u32>,
421}
422
423#[derive(Clone, Debug, Eq, PartialEq)]
425pub struct ShaderDiagnostics {
426 pub messages: Vec<ShaderDiagnostic>,
428}
429
430impl ShaderDiagnostics {
431 pub(crate) fn error(message: impl Into<String>) -> Self {
432 Self {
433 messages: vec![ShaderDiagnostic {
434 severity: DiagnosticSeverity::Error,
435 message: message.into(),
436 line: None,
437 column: None,
438 }],
439 }
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use std::collections::{HashMap, HashSet};
446
447 use super::{EffectId, EffectKey, EffectParams, ParamValue};
448
449 #[test]
450 fn an_effect_identity_is_its_text_and_version_however_it_holds_the_text() {
451 let shared = EffectId::new("alpha2.effect.vignette".to_owned(), 1);
452 let constant = EffectId::from_static("alpha2.effect.vignette", 1);
453 assert_eq!(shared, constant);
454 assert_ne!(constant, EffectId::from_static("alpha2.effect.vignette", 2));
455 assert_ne!(constant, EffectId::from_static("alpha2.effect.sharpen", 1));
456 let map = HashMap::from([(shared.clone(), "vignette")]);
458 assert_eq!(map.get(&constant), Some(&"vignette"));
459 let key: &dyn EffectKey = &("alpha2.effect.vignette", 1);
460 assert_eq!(map.get_key_value(key).map(|(id, _)| id), Some(&shared));
461 let wrong: &dyn EffectKey = &("alpha2.effect.vignette", 2);
462 assert!(!map.contains_key(wrong));
463 assert_eq!(HashSet::from([shared, constant]).len(), 1);
464 }
465
466 #[test]
467 fn parameters_keep_one_value_per_name_in_name_order() {
468 let project_name = String::from("b_owned");
469 let params = EffectParams::with_capacity(4)
470 .with("c", ParamValue::Float(3.0))
471 .with(project_name.as_str(), ParamValue::Float(2.0))
472 .with(String::from("a"), ParamValue::Float(1.0))
473 .with("c", ParamValue::Float(4.0));
474 assert_eq!(
475 params.sorted_values().collect::<Vec<_>>(),
476 [
477 ("a", &ParamValue::Float(1.0)),
478 ("b_owned", &ParamValue::Float(2.0)),
479 ("c", &ParamValue::Float(4.0)),
480 ]
481 );
482 assert_eq!(params.get("c"), Some(&ParamValue::Float(4.0)));
483 assert_eq!(params.get("d"), None);
484 assert_eq!(params.names().collect::<Vec<_>>(), ["a", "b_owned", "c"]);
485 let again = EffectParams::new()
487 .with(String::from("c"), ParamValue::Float(4.0))
488 .with("a", ParamValue::Float(1.0))
489 .with(String::from("b_owned"), ParamValue::Float(2.0));
490 assert_eq!(params, again);
491 }
492}