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,
129 Transition,
131 Generator,
134}
135
136#[derive(Copy, Clone, Debug, Eq, PartialEq)]
138pub enum ParamType {
139 Float,
141 Vec2,
143 Vec4,
146 Color,
150 Enum,
152 Bool,
154}
155
156#[derive(Copy, Clone, Debug, Eq, PartialEq)]
158pub enum ParamUnit {
159 Normalized,
161 Degrees,
163 Color,
165 Enum,
167 Bool,
169}
170
171#[derive(Clone, Debug, PartialEq)]
173pub enum ParamValue {
174 Float(f32),
176 Vec2([f32; 2]),
178 Vec4([f32; 4]),
180 Color([f32; 4]),
182 Enum(u32),
184 Bool(bool),
186}
187
188#[derive(Clone, Debug, Default, PartialEq)]
195pub struct EffectParams<'a> {
196 values: Vec<(Cow<'a, str>, ParamValue)>,
197}
198
199#[derive(Clone, Debug, Default, Eq, PartialEq)]
201pub struct EffectResources {
202 values: BTreeMap<String, String>,
203}
204
205impl EffectResources {
206 #[must_use]
208 pub fn new() -> Self {
209 Self::default()
210 }
211
212 #[must_use]
214 pub fn with(mut self, name: impl Into<String>, id: impl Into<String>) -> Self {
215 self.values.insert(name.into(), id.into());
216 self
217 }
218
219 pub(crate) fn get(&self, name: &str) -> Option<&str> {
220 self.values.get(name).map(String::as_str)
221 }
222
223 pub(crate) fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
224 self.values
225 .iter()
226 .map(|(name, id)| (name.as_str(), id.as_str()))
227 }
228}
229
230impl<'a> EffectParams<'a> {
231 #[must_use]
233 pub fn new() -> Self {
234 Self::default()
235 }
236
237 #[must_use]
239 pub fn with_capacity(capacity: usize) -> Self {
240 Self {
241 values: Vec::with_capacity(capacity),
242 }
243 }
244
245 #[must_use]
247 pub fn with(mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) -> Self {
248 self.insert(name, value);
249 self
250 }
251
252 pub fn insert(&mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) {
254 let name = name.into();
255 match self.position(&name) {
256 Ok(index) => self.values[index].1 = value,
257 Err(index) => self.values.insert(index, (name, value)),
258 }
259 }
260
261 fn position(&self, name: &str) -> Result<usize, usize> {
262 self.values
263 .binary_search_by(|(held, _)| held.as_ref().cmp(name))
264 }
265
266 pub(crate) fn get(&self, name: &str) -> Option<&ParamValue> {
267 self.position(name).ok().map(|index| &self.values[index].1)
268 }
269
270 pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
271 self.values.iter().map(|(name, _)| name.as_ref())
272 }
273
274 pub(crate) fn sorted_values(&self) -> impl Iterator<Item = (&str, &ParamValue)> {
276 self.values
277 .iter()
278 .map(|(name, value)| (name.as_ref(), value))
279 }
280}
281
282#[derive(Copy, Clone, Debug, PartialEq)]
284pub struct EffectRuntime {
285 pub time: MediaTime,
295 pub progress: f32,
297 pub ratio: f32,
299}
300
301impl Default for EffectRuntime {
302 fn default() -> Self {
303 Self {
304 time: MediaTime::ZERO,
305 progress: 0.0,
306 ratio: 1.0,
307 }
308 }
309}
310
311#[derive(Clone, Debug, PartialEq)]
313pub struct ParamSpec {
314 pub name: String,
316 pub param_type: ParamType,
318 pub default: ParamValue,
320 pub min: Option<f32>,
322 pub max: Option<f32>,
324 pub step: Option<f32>,
326 pub unit: ParamUnit,
328 pub enum_values: Vec<String>,
330}
331
332#[derive(Clone, Debug, Eq, PartialEq)]
334pub struct ResourceSpec {
335 pub name: String,
337 pub kind: EffectResourceKind,
339}
340
341#[derive(Clone, Debug, Eq, PartialEq)]
344pub struct InputSpec {
345 pub name: String,
348}
349
350#[derive(Clone, Debug, PartialEq)]
357pub struct EffectDef {
358 pub id: String,
360 pub version: u32,
362 pub kind: EffectKind,
364 pub inputs: Vec<InputSpec>,
368 pub params: Vec<ParamSpec>,
370 pub resources: Vec<ResourceSpec>,
372 pub wgsl_body: String,
374 pub tier_min: CapabilityTier,
376 pub license: Option<String>,
378}
379
380#[derive(Clone, Debug, Eq, PartialEq)]
382pub struct UniformField {
383 pub name: String,
385 pub offset: u32,
387 pub size: u32,
389}
390
391#[derive(Clone, Debug, Eq, PartialEq)]
393pub struct UniformLayout {
394 pub fields: Vec<UniformField>,
396 pub size: u32,
398}
399
400#[derive(Copy, Clone, Debug, Eq, PartialEq)]
402pub enum DiagnosticSeverity {
403 Error,
405 Warning,
407 Info,
409}
410
411#[derive(Clone, Debug, Eq, PartialEq)]
413pub struct ShaderDiagnostic {
414 pub severity: DiagnosticSeverity,
416 pub message: String,
418 pub line: Option<u32>,
420 pub column: Option<u32>,
422}
423
424#[derive(Clone, Debug, Eq, PartialEq)]
426pub struct ShaderDiagnostics {
427 pub messages: Vec<ShaderDiagnostic>,
429}
430
431impl ShaderDiagnostics {
432 pub(crate) fn error(message: impl Into<String>) -> Self {
433 Self {
434 messages: vec![ShaderDiagnostic {
435 severity: DiagnosticSeverity::Error,
436 message: message.into(),
437 line: None,
438 column: None,
439 }],
440 }
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use std::collections::{HashMap, HashSet};
447
448 use super::{EffectId, EffectKey, EffectParams, ParamValue};
449
450 #[test]
451 fn an_effect_identity_is_its_text_and_version_however_it_holds_the_text() {
452 let shared = EffectId::new("alpha2.effect.vignette".to_owned(), 1);
453 let constant = EffectId::from_static("alpha2.effect.vignette", 1);
454 assert_eq!(shared, constant);
455 assert_ne!(constant, EffectId::from_static("alpha2.effect.vignette", 2));
456 assert_ne!(constant, EffectId::from_static("alpha2.effect.sharpen", 1));
457 let map = HashMap::from([(shared.clone(), "vignette")]);
459 assert_eq!(map.get(&constant), Some(&"vignette"));
460 let key: &dyn EffectKey = &("alpha2.effect.vignette", 1);
461 assert_eq!(map.get_key_value(key).map(|(id, _)| id), Some(&shared));
462 let wrong: &dyn EffectKey = &("alpha2.effect.vignette", 2);
463 assert!(!map.contains_key(wrong));
464 assert_eq!(HashSet::from([shared, constant]).len(), 1);
465 }
466
467 #[test]
468 fn parameters_keep_one_value_per_name_in_name_order() {
469 let project_name = String::from("b_owned");
470 let params = EffectParams::with_capacity(4)
471 .with("c", ParamValue::Float(3.0))
472 .with(project_name.as_str(), ParamValue::Float(2.0))
473 .with(String::from("a"), ParamValue::Float(1.0))
474 .with("c", ParamValue::Float(4.0));
475 assert_eq!(
476 params.sorted_values().collect::<Vec<_>>(),
477 [
478 ("a", &ParamValue::Float(1.0)),
479 ("b_owned", &ParamValue::Float(2.0)),
480 ("c", &ParamValue::Float(4.0)),
481 ]
482 );
483 assert_eq!(params.get("c"), Some(&ParamValue::Float(4.0)));
484 assert_eq!(params.get("d"), None);
485 assert_eq!(params.names().collect::<Vec<_>>(), ["a", "b_owned", "c"]);
486 let again = EffectParams::new()
488 .with(String::from("c"), ParamValue::Float(4.0))
489 .with("a", ParamValue::Float(1.0))
490 .with(String::from("b_owned"), ParamValue::Float(2.0));
491 assert_eq!(params, again);
492 }
493}