Skip to main content

rdi_platform_windows\shader_library/
mod.rs

1//! Built-in shader bodies, default stages and shared pipeline HLSL.
2
3use crate::shader::ShaderSource;
4use rdi_core::{AnimationPreset, Curve, DesktopError, Duration, Keyframe, KeyframeInterp, ShaderPipeline};
5use std::str::FromStr;
6use windows::core::{PCSTR, s};
7
8macro_rules! builtin_shaders {
9    (@preset) => { AnimationPreset::default() };
10    (@preset $preset:expr) => { $preset };
11    ($(
12        $variant:ident {
13            name: $name:literal,
14            pipeline: $pipeline:ident,
15            execution: $execution:expr,
16            vertex: $vertex:expr,
17            pixel: $pixel:expr $(, default_preset: $preset:expr)? $(,)?
18        }
19    ),+ $(,)?) => {
20        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
21        pub enum BuiltinShader {
22            $($variant),+
23        }
24
25        impl BuiltinShader {
26            pub const ALL: &'static [Self] = &[$(Self::$variant),+];
27
28            /// Return independent recommended values without compiling a shader.
29            pub fn default_preset(self) -> AnimationPreset {
30                match self {
31                    $(Self::$variant => builtin_shaders!(@preset $($preset)?)),+
32                }
33            }
34
35            pub fn source(self) -> ShaderSource<'static> {
36                match self {
37                    $(Self::$variant => ShaderSource {
38                        execution: $execution,
39                        pipeline: ShaderPipeline::$pipeline,
40                        vertex: $vertex,
41                        pixel: $pixel,
42                    }),+
43                }
44            }
45
46            pub const fn name(self) -> &'static str {
47                match self {
48                    $(Self::$variant => $name),+
49                }
50            }
51        }
52
53        impl FromStr for BuiltinShader {
54            type Err = DesktopError;
55
56            fn from_str(name: &str) -> Result<Self, Self::Err> {
57                match name {
58                    $($name => Ok(Self::$variant),)+
59                    _ => Err(DesktopError::InvalidEffect(format!(
60                        "unknown built-in shader: {name}"
61                    ))),
62                }
63            }
64        }
65    };
66}
67
68builtin_shaders! {
69    Identity {
70        name: "identity",
71        pipeline: Sprite,
72        execution: None,
73        vertex: None,
74        pixel: None,
75    },
76    Glitch {
77        name: "glitch",
78        pipeline: Sprite,
79        execution: None,
80        vertex: None,
81        pixel: Some(GLITCH_PIXEL),
82        default_preset: AnimationPreset {
83            movement: Curve::keyframes(
84                vec![Keyframe::new(0.0, 0.0), Keyframe::new(0.15, 0.0), Keyframe::new(0.85, 1.0), Keyframe::new(1.0, 1.0)],
85                KeyframeInterp::SmoothStep,
86            ).expect("valid glitch preset movement"),
87            ..AnimationPreset::default()
88        },
89    },
90    ParticleVortex {
91        name: "particle-vortex",
92        pipeline: Particles,
93        execution: None,
94        vertex: Some(PARTICLE_VORTEX_VERTEX),
95        pixel: Some(PARTICLE_VORTEX_PIXEL),
96        default_preset: AnimationPreset {
97            duration: Duration::fixed(std::time::Duration::from_secs(4)),
98            ..AnimationPreset::default()
99        },
100    },
101    DustTransfer {
102        name: "dust-transfer",
103        pipeline: Particles,
104        execution: None,
105        vertex: Some(DUST_TRANSFER_VERTEX),
106        pixel: Some(PARTICLE_VORTEX_PIXEL),
107        default_preset: transport_preset(4),
108    },
109    SilkFlow {
110        name: "silk-flow",
111        pipeline: Procedural,
112        execution: Some(std::sync::Arc::new(silk_flow_execution())),
113        vertex: Some(SILK_FLOW),
114        pixel: Some(SILK_FLOW),
115        default_preset: transport_preset(5),
116    },
117}
118
119fn transport_preset(seconds: u64) -> AnimationPreset {
120    AnimationPreset {
121        envelope: Curve::keyframes(
122            vec![Keyframe::new(0.0, 1.0), Keyframe::new(1.0, 1.0)],
123            KeyframeInterp::Linear,
124        ).expect("valid transport preset envelope"),
125        duration: Duration::fixed(std::time::Duration::from_secs(seconds)),
126        ..AnimationPreset::default()
127    }
128}
129
130impl<'a> From<BuiltinShader> for ShaderSource<'a> {
131    fn from(shader: BuiltinShader) -> Self {
132        shader.source()
133    }
134}
135
136pub const IDENTITY: &str = DEFAULT_PIXEL;
137pub const GLITCH: ShaderSource<'static> = ShaderSource {
138    execution: None,
139    pipeline: ShaderPipeline::Sprite,
140    vertex: None,
141    pixel: Some(GLITCH_PIXEL),
142};
143pub const PARTICLE_VORTEX: ShaderSource<'static> = ShaderSource {
144    execution: None,
145    pipeline: ShaderPipeline::Particles,
146    vertex: Some(PARTICLE_VORTEX_VERTEX),
147    pixel: Some(PARTICLE_VORTEX_PIXEL),
148};
149
150fn silk_flow_execution() -> crate::shader::ExecutionSource {
151    use rdi_core::{DrawSpec, EffectParameter, ParameterKind};
152    crate::shader::ExecutionSource {
153        parameters: [
154            ("strands", ParameterKind::Integer, 8.0, 2.0, 16.0),
155            ("spread", ParameterKind::Float, 1.0, 0.0, 3.0),
156            ("folds", ParameterKind::Float, 2.0, 0.25, 6.0),
157            ("density", ParameterKind::Float, 0.8, 0.1, 1.0),
158        ]
159        .into_iter()
160        .map(|(name, kind, default, min, max)| EffectParameter {
161            name: name.into(),
162            kind,
163            default,
164            min,
165            max,
166        })
167        .collect(),
168        passes: vec![crate::shader::PassSource {
169            draw: DrawSpec {
170                vertices: 24,
171                parameter: Some(0),
172                multiplier: 128 * 6 * 2,
173                ..Default::default()
174            },
175            ..Default::default()
176        }],
177        body_artwork: true,
178        label_artwork: true,
179        ..Default::default()
180    }
181}
182
183const SILK_FLOW: &str = include_str!("hlsl/effects/silk_flow.hlsl");
184
185pub(crate) fn pipeline_template(pipeline: ShaderPipeline) -> (&'static str, &'static str, PCSTR) {
186    match pipeline {
187        ShaderPipeline::Sprite => (SPRITE_HEADER, SPRITE_FOOTER, s!("rdi_pixel")),
188        ShaderPipeline::Particles => (PARTICLE_HEADER, PARTICLE_FOOTER, s!("rdi_pixel")),
189        ShaderPipeline::Procedural => (
190            include_str!("hlsl/pipelines/procedural.hlsl"),
191            PARTICLE_FOOTER,
192            s!("rdi_pixel"),
193        ),
194    }
195}
196
197/// Default vertex entry point; its helper is supplied by the selected pipeline.
198pub const DEFAULT_VERTEX: &str = include_str!("hlsl/shared/default_vertex.hlsl");
199/// Default pixel entry point; its helper is supplied by the selected pipeline.
200pub const DEFAULT_PIXEL: &str = include_str!("hlsl/shared/default_pixel.hlsl");
201pub(crate) const VERTEX_DECLARATION: &str = include_str!("hlsl/shared/vertex_declaration.hlsl");
202pub(crate) const PIXEL_DECLARATION: &str = include_str!("hlsl/shared/pixel_declaration.hlsl");
203pub(crate) const VERTEX_FOOTER: &str = include_str!("hlsl/shared/vertex_footer.hlsl");
204
205pub(crate) const HEADER: &str = include_str!("hlsl/shared/header.hlsl");
206
207const SPRITE_HEADER: &str = include_str!("hlsl/pipelines/sprite_header.hlsl");
208
209const SPRITE_FOOTER: &str = include_str!("hlsl/shared/sprite_footer.hlsl");
210
211const PARTICLE_HEADER: &str = include_str!("hlsl/pipelines/particle_header.hlsl");
212
213const PARTICLE_FOOTER: &str = include_str!("hlsl/shared/particle_footer.hlsl");
214
215/// Glitch displacement plus RGB split. params: displacement px, split px,
216/// band height px, temporal frequency Hz. timing: elapsed, progress, strength, seed.
217const GLITCH_PIXEL: &str = include_str!("hlsl/effects/glitch_pixel.hlsl");
218
219const PARTICLE_VORTEX_VERTEX: &str = include_str!("hlsl/effects/particle_vortex_vertex.hlsl");
220
221const PARTICLE_VORTEX_PIXEL: &str = include_str!("hlsl/effects/particle_vortex_pixel.hlsl");
222
223const DUST_TRANSFER_VERTEX: &str = include_str!("hlsl/effects/dust_transfer_vertex.hlsl");
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use rdi_core::{AnimationCurve, Effect, Point};
229
230    #[test]
231    fn animation_presets_cover_catalog_and_preserve_clean_endpoints() {
232        for &shader in BuiltinShader::ALL {
233            let preset = shader.default_preset();
234            let seconds = match shader {
235                BuiltinShader::Identity | BuiltinShader::Glitch => 2,
236                BuiltinShader::ParticleVortex | BuiltinShader::DustTransfer => 4,
237                BuiltinShader::SilkFlow => 5,
238            };
239            assert_eq!(preset.duration.resolve(Point::ZERO, Point::new(100, 50)).unwrap().as_secs(), seconds);
240            assert_eq!(preset.movement.eval(0.0), 0.0);
241            assert_eq!(preset.movement.eval(1.0), 1.0);
242            for step in 0..=100 {
243                let progress = step as f32 / 100.0;
244                assert!(preset.movement.eval(progress).is_finite());
245                assert!((0.0..=1.0).contains(&preset.envelope.eval(progress)));
246            }
247            let program = crate::shader::compile(shader).unwrap();
248            let effect = Effect {
249                params: program.default_params(), shader: program, padding_px: 16,
250                envelope: preset.envelope, seed: 0.0,
251            };
252            effect.validate().unwrap();
253            assert_eq!(effect.strength(0.0), 0.0);
254            assert_eq!(effect.strength(1.0), 0.0);
255            assert_eq!(effect.strength(0.5), 1.0);
256        }
257    }
258
259    #[test]
260    fn animation_presets_use_optional_fallback_and_independent_overrides() {
261        let identity = BuiltinShader::Identity.default_preset();
262        let fallback = AnimationPreset::default();
263        assert_eq!(identity.movement, fallback.movement);
264        assert_eq!(identity.envelope, fallback.envelope);
265        assert_eq!(identity.duration, fallback.duration);
266        let glitch = BuiltinShader::Glitch.default_preset();
267        assert_eq!(glitch.movement.eval(0.15), 0.0);
268        assert_eq!(glitch.movement.eval(0.85), 1.0);
269        for shader in [BuiltinShader::DustTransfer, BuiltinShader::SilkFlow] {
270            let mut preset = shader.default_preset();
271            for progress in [0.0, 0.05, 0.5, 0.95, 1.0] {
272                assert_eq!(preset.envelope.eval(progress), 1.0);
273            }
274            preset.envelope = Curve::linear();
275            assert_eq!(shader.default_preset().envelope.eval(0.05), 1.0);
276        }
277    }
278}