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)]
19pub struct EffectId {
20 id: IdText,
21 version: u32,
22}
23
24#[derive(Clone, Debug)]
25enum IdText {
26 Static(&'static str),
27 Shared(Arc<str>),
28}
29
30impl EffectId {
31 #[must_use]
33 pub fn new(id: String, version: u32) -> Self {
34 Self {
35 id: IdText::Shared(id.into()),
36 version,
37 }
38 }
39
40 #[must_use]
42 pub const fn from_static(id: &'static str, version: u32) -> Self {
43 Self {
44 id: IdText::Static(id),
45 version,
46 }
47 }
48
49 #[must_use]
51 pub fn as_str(&self) -> &str {
52 match &self.id {
53 IdText::Static(id) => id,
54 IdText::Shared(id) => id,
55 }
56 }
57
58 #[must_use]
60 pub const fn version(&self) -> u32 {
61 self.version
62 }
63}
64
65impl PartialEq for EffectId {
66 fn eq(&self, other: &Self) -> bool {
67 self.version == other.version && self.as_str() == other.as_str()
68 }
69}
70
71impl Eq for EffectId {}
72
73impl Hash for EffectId {
74 fn hash<H: Hasher>(&self, state: &mut H) {
75 (self as &dyn EffectKey).hash(state);
77 }
78}
79
80pub(crate) trait EffectKey {
83 fn key(&self) -> (&str, u32);
84}
85
86impl EffectKey for EffectId {
87 fn key(&self) -> (&str, u32) {
88 (self.as_str(), self.version)
89 }
90}
91
92impl EffectKey for (&str, u32) {
93 fn key(&self) -> (&str, u32) {
94 *self
95 }
96}
97
98impl<'a> Borrow<dyn EffectKey + 'a> for EffectId {
99 fn borrow(&self) -> &(dyn EffectKey + 'a) {
100 self
101 }
102}
103
104impl Hash for dyn EffectKey + '_ {
105 fn hash<H: Hasher>(&self, state: &mut H) {
106 let (id, version) = self.key();
107 id.hash(state);
108 version.hash(state);
109 }
110}
111
112impl PartialEq for dyn EffectKey + '_ {
113 fn eq(&self, other: &Self) -> bool {
114 self.key() == other.key()
115 }
116}
117
118impl Eq for dyn EffectKey + '_ {}
119
120#[derive(Copy, Clone, Debug, Eq, PartialEq)]
122pub enum EffectKind {
123 Effect,
125 Transition,
127 Generator,
130}
131
132#[derive(Copy, Clone, Debug, Eq, PartialEq)]
134pub enum ParamType {
135 Float,
137 Vec2,
139 Vec4,
141 Color,
144 Enum,
146 Bool,
148}
149
150#[derive(Copy, Clone, Debug, Eq, PartialEq)]
152pub enum ParamUnit {
153 Normalized,
155 Degrees,
157 Color,
159 Enum,
161 Bool,
163}
164
165#[derive(Clone, Debug, PartialEq)]
167pub enum ParamValue {
168 Float(f32),
170 Vec2([f32; 2]),
172 Vec4([f32; 4]),
174 Color([f32; 4]),
176 Enum(u32),
178 Bool(bool),
180}
181
182#[derive(Clone, Debug, Default, PartialEq)]
186pub struct EffectParams<'a> {
187 values: Vec<(Cow<'a, str>, ParamValue)>,
188}
189
190#[derive(Clone, Debug, Default, Eq, PartialEq)]
192pub struct EffectResources {
193 values: BTreeMap<String, String>,
194}
195
196impl EffectResources {
197 #[must_use]
199 pub fn new() -> Self {
200 Self::default()
201 }
202
203 #[must_use]
205 pub fn with(mut self, name: impl Into<String>, id: impl Into<String>) -> Self {
206 self.values.insert(name.into(), id.into());
207 self
208 }
209
210 pub(crate) fn get(&self, name: &str) -> Option<&str> {
211 self.values.get(name).map(String::as_str)
212 }
213
214 pub(crate) fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
215 self.values
216 .iter()
217 .map(|(name, id)| (name.as_str(), id.as_str()))
218 }
219}
220
221impl<'a> EffectParams<'a> {
222 #[must_use]
224 pub fn new() -> Self {
225 Self::default()
226 }
227
228 #[must_use]
230 pub fn with_capacity(capacity: usize) -> Self {
231 Self {
232 values: Vec::with_capacity(capacity),
233 }
234 }
235
236 #[must_use]
238 pub fn with(mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) -> Self {
239 self.insert(name, value);
240 self
241 }
242
243 pub fn insert(&mut self, name: impl Into<Cow<'a, str>>, value: ParamValue) {
245 let name = name.into();
246 match self.position(&name) {
247 Ok(index) => self.values[index].1 = value,
248 Err(index) => self.values.insert(index, (name, value)),
249 }
250 }
251
252 fn position(&self, name: &str) -> Result<usize, usize> {
253 self.values
254 .binary_search_by(|(held, _)| held.as_ref().cmp(name))
255 }
256
257 pub(crate) fn get(&self, name: &str) -> Option<&ParamValue> {
258 self.position(name).ok().map(|index| &self.values[index].1)
259 }
260
261 pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
262 self.values.iter().map(|(name, _)| name.as_ref())
263 }
264
265 pub(crate) fn sorted_values(&self) -> impl Iterator<Item = (&str, &ParamValue)> {
266 self.values
267 .iter()
268 .map(|(name, value)| (name.as_ref(), value))
269 }
270}
271
272#[derive(Copy, Clone, Debug, PartialEq)]
274pub struct EffectRuntime {
275 pub time: MediaTime,
282 pub progress: f32,
284 pub ratio: f32,
286}
287
288impl Default for EffectRuntime {
289 fn default() -> Self {
290 Self {
291 time: MediaTime::ZERO,
292 progress: 0.0,
293 ratio: 1.0,
294 }
295 }
296}
297
298#[derive(Clone, Debug, PartialEq)]
300pub struct ParamSpec {
301 pub name: String,
303 pub param_type: ParamType,
305 pub default: ParamValue,
307 pub min: Option<f32>,
309 pub max: Option<f32>,
311 pub step: Option<f32>,
313 pub unit: ParamUnit,
315 pub enum_values: Vec<String>,
317}
318
319#[derive(Clone, Debug, Eq, PartialEq)]
321pub struct ResourceSpec {
322 pub name: String,
324 pub kind: EffectResourceKind,
326}
327
328#[derive(Clone, Debug, Eq, PartialEq)]
330pub struct InputSpec {
331 pub name: String,
333}
334
335#[derive(Clone, Debug, PartialEq)]
340pub struct EffectDef {
341 pub id: String,
343 pub version: u32,
345 pub kind: EffectKind,
347 pub inputs: Vec<InputSpec>,
349 pub params: Vec<ParamSpec>,
351 pub resources: Vec<ResourceSpec>,
353 pub wgsl_body: String,
355 pub tier_min: CapabilityTier,
357 pub license: Option<String>,
359}
360
361#[derive(Clone, Debug, Eq, PartialEq)]
363pub struct UniformField {
364 pub name: String,
366 pub offset: u32,
368 pub size: u32,
370}
371
372#[derive(Clone, Debug, Eq, PartialEq)]
374pub struct UniformLayout {
375 pub fields: Vec<UniformField>,
377 pub size: u32,
379}
380
381#[derive(Copy, Clone, Debug, Eq, PartialEq)]
383pub enum DiagnosticSeverity {
384 Error,
386 Warning,
388 Info,
390}
391
392#[derive(Clone, Debug, Eq, PartialEq)]
394pub struct ShaderDiagnostic {
395 pub severity: DiagnosticSeverity,
397 pub message: String,
399 pub line: Option<u32>,
401 pub column: Option<u32>,
403}
404
405#[derive(Clone, Debug, Eq, PartialEq)]
407pub struct ShaderDiagnostics {
408 pub messages: Vec<ShaderDiagnostic>,
410}
411
412impl ShaderDiagnostics {
413 pub(crate) fn error(message: impl Into<String>) -> Self {
414 Self {
415 messages: vec![ShaderDiagnostic {
416 severity: DiagnosticSeverity::Error,
417 message: message.into(),
418 line: None,
419 column: None,
420 }],
421 }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use std::collections::{HashMap, HashSet};
428
429 use super::{EffectId, EffectKey, EffectParams, ParamValue};
430
431 #[test]
432 fn an_effect_identity_is_its_text_and_version_however_it_holds_the_text() {
433 let shared = EffectId::new("alpha2.effect.vignette".to_owned(), 1);
434 let constant = EffectId::from_static("alpha2.effect.vignette", 1);
435 assert_eq!(shared, constant);
436 assert_ne!(constant, EffectId::from_static("alpha2.effect.vignette", 2));
437 assert_ne!(constant, EffectId::from_static("alpha2.effect.sharpen", 1));
438 let map = HashMap::from([(shared.clone(), "vignette")]);
439 assert_eq!(map.get(&constant), Some(&"vignette"));
440 let key: &dyn EffectKey = &("alpha2.effect.vignette", 1);
441 assert_eq!(map.get_key_value(key).map(|(id, _)| id), Some(&shared));
442 let wrong: &dyn EffectKey = &("alpha2.effect.vignette", 2);
443 assert!(!map.contains_key(wrong));
444 assert_eq!(HashSet::from([shared, constant]).len(), 1);
445 }
446
447 #[test]
448 fn parameters_keep_one_value_per_name_in_name_order() {
449 let project_name = String::from("b_owned");
450 let params = EffectParams::with_capacity(4)
451 .with("c", ParamValue::Float(3.0))
452 .with(project_name.as_str(), ParamValue::Float(2.0))
453 .with(String::from("a"), ParamValue::Float(1.0))
454 .with("c", ParamValue::Float(4.0));
455 assert_eq!(
456 params.sorted_values().collect::<Vec<_>>(),
457 [
458 ("a", &ParamValue::Float(1.0)),
459 ("b_owned", &ParamValue::Float(2.0)),
460 ("c", &ParamValue::Float(4.0)),
461 ]
462 );
463 assert_eq!(params.get("c"), Some(&ParamValue::Float(4.0)));
464 assert_eq!(params.get("d"), None);
465 assert_eq!(params.names().collect::<Vec<_>>(), ["a", "b_owned", "c"]);
466 let again = EffectParams::new()
467 .with(String::from("c"), ParamValue::Float(4.0))
468 .with("a", ParamValue::Float(1.0))
469 .with(String::from("b_owned"), ParamValue::Float(2.0));
470 assert_eq!(params, again);
471 }
472}