1use 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 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
197pub const DEFAULT_VERTEX: &str = include_str!("hlsl/shared/default_vertex.hlsl");
199pub 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
215const 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}