Skip to main content

alpha2_render/builtins/
mod.rs

1//! The built-in effects and transitions as data: one module per catalog
2//! group, whose definitions the catalog lists under that group's name.
3
4use alpha2_gpu::CapabilityTier;
5use alpha2_shaders::{
6    CHROMA_KEY_BODY, EFFECT_HELPERS, EXPANDED_EFFECT, FOCUS_COMBINE, GAUSSIAN_BLUR,
7    GL_TRANSITION_ENTRY, GLOW_COMBINE, GLOW_THRESHOLD, LUT_3D_BODY, SHADOW_COMBINE, SHADOW_OFFSET,
8    TRANSITION_HELPERS, UNSHARP_COMBINE,
9};
10
11use crate::{
12    EffectDef, EffectId, EffectKind, EffectResourceKind, InputSpec, ParamSpec, ParamType,
13    ParamUnit, ParamValue, Renderer, ResourceSpec, ShaderDiagnostics,
14};
15
16mod effects;
17mod transitions;
18
19pub(crate) use effects::blur::{
20    directional_blur_definition, tilt_shift_definition, unsharp_mask_definition,
21};
22pub(crate) use effects::stylize::drop_shadow_definition;
23
24/// Stable identifier of the built-in chroma-key effect.
25pub const CHROMA_KEY_ID: &str = "alpha2.effect.chroma-key";
26
27/// Stable identifier of the atomic Gaussian-blur effect.
28pub const GAUSSIAN_BLUR_ID: &str = "alpha2.effect.gaussian-blur";
29
30/// Internal stable identifier of one separable Gaussian-blur pass.
31pub(crate) const GAUSSIAN_BLUR_PASS_ID: &str = "alpha2.internal.gaussian-blur-pass";
32
33/// Stable identifier of the built-in glow effect.
34pub const GLOW_ID: &str = "alpha2.effect.glow";
35
36/// Internal stable identifier of glow's threshold pass.
37pub(crate) const GLOW_THRESHOLD_ID: &str = "alpha2.internal.glow-threshold";
38
39/// Internal stable identifier of glow's two-input combining pass.
40pub(crate) const GLOW_COMBINE_ID: &str = "alpha2.internal.glow-combine";
41
42/// Stable identifier of the built-in directional (one-axis Gaussian) blur.
43pub(crate) const DIRECTIONAL_BLUR_ID: &str = "alpha2.effect.directional-blur";
44
45/// Stable identifier of the built-in unsharp mask.
46pub(crate) const UNSHARP_MASK_ID: &str = "alpha2.effect.unsharp-mask";
47
48/// Internal stable identifier of unsharp mask's two-input combining pass.
49pub(crate) const UNSHARP_COMBINE_ID: &str = "alpha2.internal.unsharp-combine";
50
51/// Stable identifier of the built-in tilt-shift.
52pub(crate) const TILT_SHIFT_ID: &str = "alpha2.effect.tilt-shift";
53
54/// Internal stable identifier of tilt-shift's pass mixing the source and
55/// its two blurs.
56pub(crate) const FOCUS_COMBINE_ID: &str = "alpha2.internal.focus-combine";
57
58/// Stable identifier of the built-in drop shadow.
59pub(crate) const DROP_SHADOW_ID: &str = "alpha2.effect.drop-shadow";
60
61/// Internal stable identifier of drop shadow's offset pass.
62pub(crate) const SHADOW_OFFSET_ID: &str = "alpha2.internal.shadow-offset";
63
64/// Internal stable identifier of drop shadow's two-input combining pass.
65pub(crate) const SHADOW_COMBINE_ID: &str = "alpha2.internal.shadow-combine";
66
67/// `EffectDef::license` of the effects Alpha2 itself wrote.
68pub(crate) const ALPHA2_EFFECT_LICENSE: &str = "MIT; Copyright Alpha2 contributors";
69
70/// Stable identifier of the resource-backed 3D LUT effect.
71pub const LUT_3D_ID: &str = "alpha2.effect.lut-3d";
72
73/// The commit of gl-transitions the ports come from, as a link to a file.
74pub(crate) const GL_TRANSITIONS_BASE: &str = "https://github.com/gl-transitions/gl-transitions/blob/902218a1b63773ac0d0d9f491951da3392365bfe/transitions/";
75
76/// Returns fresh definitions for every public built-in clip effect.
77#[must_use]
78pub fn builtin_effect_definitions() -> Vec<EffectDef> {
79    builtin_effect_groups()
80        .into_iter()
81        .flat_map(|(_, definitions)| definitions)
82        .collect()
83}
84
85/// The public built-in effects by catalog group, in catalog order.
86pub(crate) fn builtin_effect_groups() -> Vec<(&'static str, Vec<EffectDef>)> {
87    vec![
88        ("Color", effects::color::definitions()),
89        ("Keying", effects::keying::definitions()),
90        ("Blur & Sharpen", effects::blur::definitions()),
91        ("Stylize", effects::stylize::definitions()),
92        ("Distort", effects::distort::definitions()),
93    ]
94}
95
96/// Returns fresh, data-only definitions for all built-in transitions.
97#[must_use]
98pub fn builtin_transition_definitions() -> Vec<EffectDef> {
99    builtin_transition_groups()
100        .into_iter()
101        .flat_map(|(_, definitions)| definitions)
102        .collect()
103}
104
105/// The built-in transitions by catalog group, in catalog order.
106pub(crate) fn builtin_transition_groups() -> Vec<(&'static str, Vec<EffectDef>)> {
107    vec![
108        ("Classic", transitions::classic::definitions()),
109        ("Wipes", transitions::wipes::definitions()),
110        ("Stylized", transitions::stylized::definitions()),
111        ("Shapes", transitions::shapes::definitions()),
112        ("3D", transitions::three_d::definitions()),
113        ("Zoom & Blur", transitions::zoom_blur::definitions()),
114        ("Glitch", transitions::glitch::definitions()),
115    ]
116}
117
118/// Returns the data-only definition of the built-in chroma-key effect.
119///
120/// `key_color` is compared with each pixel's straight color as the renderer
121/// stores it: sRGB-encoded, not linear light. Pick the key as a color picker
122/// shows the screen. With `distance_mode` at `rgb` a pixel within `threshold`
123/// of the key in that RGB space is removed, `softness` wide edges are partly
124/// transparent and `spill_suppression` desaturates them; `chroma` compares
125/// only the chroma plane, so a key survives shadows on a screen.
126#[must_use]
127pub fn chroma_key_definition() -> EffectDef {
128    EffectDef {
129        id: CHROMA_KEY_ID.into(),
130        version: 1,
131        kind: EffectKind::Effect,
132        inputs: Vec::new(),
133        params: vec![
134            color("key_color", [0.0, 1.0, 0.0, 1.0]),
135            float("threshold", 0.35, 0.0, 3.0_f32.sqrt()),
136            float("softness", 0.1, f32::EPSILON, 1.0),
137            float("spill_suppression", 0.2, 0.0, 1.0),
138            choice("distance_mode", 0, &["rgb", "chroma"]),
139        ],
140        resources: Vec::new(),
141        wgsl_body: effect_body(CHROMA_KEY_BODY),
142        tier_min: CapabilityTier::TierB,
143        license: Some(ALPHA2_EFFECT_LICENSE.into()),
144    }
145}
146
147/// Returns the data-only definition of the built-in Gaussian blur.
148///
149/// Its standard deviation is half its `radius`, a fraction of the output
150/// height. A render graph draws it as a cascade of separable passes
151/// ([`crate::RenderGraph::add_effect`]); its own body is never drawn.
152#[must_use]
153pub fn gaussian_blur_definition() -> EffectDef {
154    EffectDef {
155        id: GAUSSIAN_BLUR_ID.into(),
156        version: 1,
157        kind: EffectKind::Effect,
158        inputs: Vec::new(),
159        params: vec![float_step("radius", 0.012, 0.0, 1.0, 0.001)],
160        resources: Vec::new(),
161        wgsl_body: effect_body(EXPANDED_EFFECT),
162        tier_min: CapabilityTier::TierB,
163        license: Some(ALPHA2_EFFECT_LICENSE.into()),
164    }
165}
166
167pub(crate) fn gaussian_blur_pass_definition() -> EffectDef {
168    EffectDef {
169        id: GAUSSIAN_BLUR_PASS_ID.into(),
170        version: 1,
171        kind: EffectKind::Effect,
172        inputs: Vec::new(),
173        params: vec![
174            vec2("step", [0.0, 0.0]),
175            float_step(
176                "sigma",
177                1.0,
178                0.0,
179                crate::gaussian_blur::MAX_PASS_SIGMA_TAPS,
180                0.001,
181            ),
182        ],
183        resources: Vec::new(),
184        wgsl_body: effect_body(GAUSSIAN_BLUR),
185        tier_min: CapabilityTier::TierB,
186        license: Some(ALPHA2_EFFECT_LICENSE.into()),
187    }
188}
189
190/// Returns the data-only definition of the built-in glow.
191///
192/// The straight color above `threshold` is blurred like
193/// [`gaussian_blur_definition`] with this `radius` and added back to the
194/// source, scaled by `intensity`. A render graph draws it as internal passes;
195/// its own body is never drawn.
196#[must_use]
197pub fn glow_definition() -> EffectDef {
198    EffectDef {
199        id: GLOW_ID.into(),
200        version: 1,
201        kind: EffectKind::Effect,
202        inputs: Vec::new(),
203        params: vec![
204            float("threshold", 0.6, 0.0, 1.0),
205            float("intensity", 0.8, 0.0, 3.0),
206            float("radius", 0.012, 0.0, 0.06),
207        ],
208        resources: Vec::new(),
209        wgsl_body: effect_body(EXPANDED_EFFECT),
210        tier_min: CapabilityTier::TierB,
211        license: Some(ALPHA2_EFFECT_LICENSE.into()),
212    }
213}
214
215fn glow_threshold_definition() -> EffectDef {
216    EffectDef {
217        id: GLOW_THRESHOLD_ID.into(),
218        ..simple_effect(
219            "glow-threshold",
220            GLOW_THRESHOLD,
221            vec![float("threshold", 0.6, 0.0, 1.0)],
222        )
223    }
224}
225
226// Two inputs make it a transition to the registry; the graph draws it at progress 0.5, and
227// its body reads no progress.
228fn glow_combine_definition() -> EffectDef {
229    EffectDef {
230        id: GLOW_COMBINE_ID.into(),
231        version: 1,
232        kind: EffectKind::Transition,
233        inputs: Vec::new(),
234        params: vec![float("intensity", 0.8, 0.0, 3.0)],
235        resources: Vec::new(),
236        wgsl_body: effect_body(GLOW_COMBINE),
237        tier_min: CapabilityTier::TierB,
238        license: Some(ALPHA2_EFFECT_LICENSE.into()),
239    }
240}
241
242// Two inputs make it a transition to the registry, as glow's combine is.
243fn unsharp_combine_definition() -> EffectDef {
244    EffectDef {
245        id: UNSHARP_COMBINE_ID.into(),
246        version: 1,
247        kind: EffectKind::Transition,
248        inputs: Vec::new(),
249        params: vec![
250            float("amount", 1.0, 0.0, 5.0),
251            float("threshold", 0.0, 0.0, 1.0),
252        ],
253        resources: Vec::new(),
254        wgsl_body: effect_body(UNSHARP_COMBINE),
255        tier_min: CapabilityTier::TierB,
256        license: Some(ALPHA2_EFFECT_LICENSE.into()),
257    }
258}
259
260// The source and its two blurs, `soft` and `blurred`, as named inputs.
261fn focus_combine_definition() -> EffectDef {
262    EffectDef {
263        id: FOCUS_COMBINE_ID.into(),
264        version: 1,
265        kind: EffectKind::Effect,
266        inputs: vec![
267            InputSpec {
268                name: "soft".into(),
269            },
270            InputSpec {
271                name: "blurred".into(),
272            },
273        ],
274        params: vec![
275            float("position", 0.5, 0.0, 1.0),
276            degrees("angle", 0.0, -90.0, 90.0),
277            float("focus", 0.15, 0.0, 1.0),
278            float_step("falloff", 0.25, 0.001, 1.0, 0.005),
279        ],
280        resources: Vec::new(),
281        wgsl_body: effect_body(FOCUS_COMBINE),
282        tier_min: CapabilityTier::TierB,
283        license: Some(ALPHA2_EFFECT_LICENSE.into()),
284    }
285}
286
287fn shadow_offset_definition() -> EffectDef {
288    EffectDef {
289        id: SHADOW_OFFSET_ID.into(),
290        ..simple_effect(
291            "shadow-offset",
292            SHADOW_OFFSET,
293            vec![
294                vec2("offset", [0.0, 0.0]),
295                color("color", [0.0, 0.0, 0.0, 1.0]),
296                float("opacity", 0.6, 0.0, 1.0),
297            ],
298        )
299    }
300}
301
302// A transition to the registry for its two inputs, as glow's combine.
303fn shadow_combine_definition() -> EffectDef {
304    EffectDef {
305        id: SHADOW_COMBINE_ID.into(),
306        version: 1,
307        kind: EffectKind::Transition,
308        inputs: Vec::new(),
309        params: vec![boolean("shadow_only", false)],
310        resources: Vec::new(),
311        wgsl_body: effect_body(SHADOW_COMBINE),
312        tier_min: CapabilityTier::TierB,
313        license: Some(ALPHA2_EFFECT_LICENSE.into()),
314    }
315}
316
317/// Returns the data-only definition of the resource-backed 3D LUT effect.
318#[must_use]
319pub fn lut_3d_definition() -> EffectDef {
320    EffectDef {
321        id: LUT_3D_ID.into(),
322        version: 1,
323        kind: EffectKind::Effect,
324        inputs: Vec::new(),
325        params: vec![float("strength", 1.0, 0.0, 1.0)],
326        resources: vec![ResourceSpec {
327            name: "lut".into(),
328            kind: EffectResourceKind::Lut3d,
329        }],
330        wgsl_body: effect_body(LUT_3D_BODY),
331        tier_min: CapabilityTier::TierB,
332        license: Some(ALPHA2_EFFECT_LICENSE.into()),
333    }
334}
335
336/// The passes the engine inserts itself, hidden from the effect catalog.
337pub(crate) fn internal_effect_definitions() -> Vec<EffectDef> {
338    let mut definitions = vec![
339        gaussian_blur_pass_definition(),
340        glow_threshold_definition(),
341        glow_combine_definition(),
342        shadow_offset_definition(),
343        shadow_combine_definition(),
344        unsharp_combine_definition(),
345        focus_combine_definition(),
346        crate::masking::track_matte_definition(),
347        crate::generator::generator_definition(),
348    ];
349    definitions.extend(crate::masking::mask_combine_definitions());
350    definitions
351}
352
353impl Renderer {
354    fn register_builtin_definition(
355        &mut self,
356        definition: EffectDef,
357    ) -> Result<EffectId, ShaderDiagnostics> {
358        let id = self.register_lazily(definition)?;
359        self.effects.mark_builtin(id.clone());
360        Ok(id)
361    }
362
363    /// Registers the complete built-in effect and transition catalog. Each
364    /// one's shader module and pipeline are made the first time it is drawn
365    /// or warmed ([`Self::warmup`]).
366    ///
367    /// Built-ins are engine-owned and cannot be removed through the host-effect
368    /// lifecycle methods.
369    ///
370    /// # Errors
371    /// Returns diagnostics from the first definition that fails.
372    pub fn register_builtins(&mut self) -> Result<Vec<EffectId>, ShaderDiagnostics> {
373        for definition in internal_effect_definitions() {
374            let internal = self.register_builtin_definition(definition)?;
375            self.effects.mark_internal(internal);
376        }
377        let mut definitions = builtin_effect_definitions();
378        definitions.extend(builtin_transition_definitions());
379        let mut ids = Vec::with_capacity(definitions.len());
380        for definition in definitions {
381            ids.push(self.register_builtin_definition(definition)?);
382        }
383        Ok(ids)
384    }
385
386    /// Registers the built-in chroma-key effect.
387    ///
388    /// # Errors
389    /// Returns schema diagnostics when registration fails.
390    pub fn register_builtin_chroma_key(&mut self) -> Result<EffectId, ShaderDiagnostics> {
391        self.register_builtin_definition(chroma_key_definition())
392    }
393
394    /// Registers the public Gaussian blur and its internal pass.
395    ///
396    /// # Errors
397    /// Returns schema diagnostics when registration fails.
398    pub fn register_builtin_gaussian_blur(&mut self) -> Result<EffectId, ShaderDiagnostics> {
399        let internal = self.register_builtin_definition(gaussian_blur_pass_definition())?;
400        self.effects.mark_internal(internal);
401        self.register_builtin_definition(gaussian_blur_definition())
402    }
403
404    /// Registers the built-in 3D LUT effect.
405    ///
406    /// # Errors
407    /// Returns schema diagnostics when registration fails.
408    pub fn register_builtin_lut_3d(&mut self) -> Result<EffectId, ShaderDiagnostics> {
409        self.register_builtin_definition(lut_3d_definition())
410    }
411
412    /// Registers all built-in transition definitions.
413    ///
414    /// # Errors
415    /// Returns diagnostics from the first definition that fails. Definitions
416    /// registered before that point remain registered.
417    pub fn register_builtin_transitions(&mut self) -> Result<Vec<EffectId>, ShaderDiagnostics> {
418        let definitions = builtin_transition_definitions();
419        let mut ids = Vec::with_capacity(definitions.len());
420        for definition in definitions {
421            ids.push(self.register_builtin_definition(definition)?);
422        }
423        Ok(ids)
424    }
425}
426
427/// An effect or internal pass body after the shared effect helpers.
428pub(crate) fn effect_body(body: &str) -> String {
429    format!("{EFFECT_HELPERS}\n{body}")
430}
431
432/// A public effect Alpha2 wrote, `alpha2.effect.<name>`.
433pub(crate) fn simple_effect(name: &str, body: &str, params: Vec<ParamSpec>) -> EffectDef {
434    EffectDef {
435        id: format!("alpha2.effect.{name}"),
436        version: 1,
437        kind: EffectKind::Effect,
438        inputs: Vec::new(),
439        params,
440        resources: Vec::new(),
441        wgsl_body: effect_body(body),
442        tier_min: CapabilityTier::TierB,
443        license: Some(ALPHA2_EFFECT_LICENSE.into()),
444    }
445}
446
447/// A port of the gl-transitions file `source` by `author`,
448/// `alpha2.transition.<name>`.
449pub(crate) fn transition(
450    name: &str,
451    body: &str,
452    source: &str,
453    author: &str,
454    params: Vec<ParamSpec>,
455) -> EffectDef {
456    EffectDef {
457        id: format!("alpha2.transition.{name}"),
458        version: 1,
459        kind: EffectKind::Transition,
460        inputs: Vec::new(),
461        params,
462        resources: Vec::new(),
463        wgsl_body: format!("{TRANSITION_HELPERS}\n{GL_TRANSITION_ENTRY}\n{body}"),
464        tier_min: CapabilityTier::TierB,
465        license: Some(format!(
466            "MIT; Author: {author}; Source: {GL_TRANSITIONS_BASE}{source}"
467        )),
468    }
469}
470
471/// A transition Alpha2 wrote, `alpha2.transition.<name>`: its body defines
472/// `transition` over the top-left uv.
473pub(crate) fn original_transition(name: &str, body: &str, params: Vec<ParamSpec>) -> EffectDef {
474    EffectDef {
475        id: format!("alpha2.transition.{name}"),
476        version: 1,
477        kind: EffectKind::Transition,
478        inputs: Vec::new(),
479        params,
480        resources: Vec::new(),
481        wgsl_body: format!("{TRANSITION_HELPERS}\n{body}"),
482        tier_min: CapabilityTier::TierB,
483        license: Some(ALPHA2_EFFECT_LICENSE.into()),
484    }
485}
486
487pub(crate) fn float(name: &str, default: f32, min: f32, max: f32) -> ParamSpec {
488    let span = max - min;
489    let step = if span > 50.0 {
490        1.0
491    } else if span <= 0.1 {
492        0.001
493    } else {
494        0.01
495    };
496    float_step(name, default, min, max, step)
497}
498
499pub(crate) fn float_step(name: &str, default: f32, min: f32, max: f32, step: f32) -> ParamSpec {
500    ParamSpec {
501        name: name.into(),
502        param_type: ParamType::Float,
503        default: ParamValue::Float(default),
504        min: Some(min),
505        max: Some(max),
506        step: Some(step),
507        unit: ParamUnit::Normalized,
508        enum_values: Vec::new(),
509    }
510}
511
512pub(crate) fn degrees(name: &str, default: f32, min: f32, max: f32) -> ParamSpec {
513    ParamSpec {
514        unit: ParamUnit::Degrees,
515        step: Some(1.0),
516        ..float(name, default, min, max)
517    }
518}
519
520pub(crate) fn vec2(name: &str, default: [f32; 2]) -> ParamSpec {
521    ParamSpec {
522        name: name.into(),
523        param_type: ParamType::Vec2,
524        default: ParamValue::Vec2(default),
525        min: None,
526        max: None,
527        step: None,
528        unit: ParamUnit::Normalized,
529        enum_values: Vec::new(),
530    }
531}
532
533pub(crate) fn vec2_range(
534    name: &str,
535    default: [f32; 2],
536    min: f32,
537    max: f32,
538    step: f32,
539) -> ParamSpec {
540    ParamSpec {
541        min: Some(min),
542        max: Some(max),
543        step: Some(step),
544        ..vec2(name, default)
545    }
546}
547
548pub(crate) fn color(name: &str, default: [f32; 4]) -> ParamSpec {
549    ParamSpec {
550        name: name.into(),
551        param_type: ParamType::Color,
552        default: ParamValue::Color(default),
553        min: None,
554        max: None,
555        step: None,
556        unit: ParamUnit::Color,
557        enum_values: Vec::new(),
558    }
559}
560
561/// Four independent progress thresholds, one per channel, in `0..=1`.
562pub(crate) fn thresholds(name: &str, default: [f32; 4]) -> ParamSpec {
563    ParamSpec {
564        name: name.into(),
565        param_type: ParamType::Vec4,
566        default: ParamValue::Vec4(default),
567        min: Some(0.0),
568        max: Some(1.0),
569        step: Some(0.01),
570        unit: ParamUnit::Normalized,
571        enum_values: Vec::new(),
572    }
573}
574
575pub(crate) fn choice(name: &str, default: u32, values: &[&str]) -> ParamSpec {
576    ParamSpec {
577        name: name.into(),
578        param_type: ParamType::Enum,
579        default: ParamValue::Enum(default),
580        min: None,
581        max: None,
582        step: None,
583        unit: ParamUnit::Enum,
584        enum_values: values.iter().map(|value| (*value).into()).collect(),
585    }
586}
587
588pub(crate) fn boolean(name: &str, default: bool) -> ParamSpec {
589    ParamSpec {
590        name: name.into(),
591        param_type: ParamType::Bool,
592        default: ParamValue::Bool(default),
593        min: None,
594        max: None,
595        step: None,
596        unit: ParamUnit::Bool,
597        enum_values: Vec::new(),
598    }
599}