1use std::rc::Rc;
8
9use sequoia_core::{BlendMode, Rect, TexInput};
10use sequoia_shader::SequoiShader;
11
12use crate::painter::Painter;
13
14#[derive(Clone)]
20pub struct ShaderPass<S: SequoiShader> {
21 shader: S,
22 rect: Option<Rect>,
23 uv: Option<[f32; 4]>,
24 blend: Option<BlendMode>,
25 inputs: Vec<(&'static str, TexInput)>,
26}
27
28impl<S: SequoiShader> ShaderPass<S> {
29 pub fn new(shader: S) -> Self {
30 Self {
31 shader,
32 rect: None,
33 uv: None,
34 blend: None,
35 inputs: Vec::new(),
36 }
37 }
38
39 pub fn at(mut self, rect: Rect) -> Self {
41 self.rect = Some(rect);
42 self
43 }
44
45 pub fn uv(mut self, uv: [f32; 4]) -> Self {
47 self.uv = Some(uv);
48 self
49 }
50
51 pub fn blend(mut self, blend: BlendMode) -> Self {
53 self.blend = Some(blend);
54 self
55 }
56
57 pub fn input(mut self, name: &'static str, input: TexInput) -> Self {
59 if let Some(existing) = self.inputs.iter_mut().find(|(key, _)| *key == name) {
60 existing.1 = input;
61 } else {
62 self.inputs.push((name, input));
63 }
64 self
65 }
66
67 pub fn primary_input(mut self, input: TexInput) -> Self {
71 if let Some(name) = S::TEXTURES.first().copied() {
72 if !self.inputs.iter().any(|(key, _)| *key == name) {
73 self.inputs.push((name, input));
74 }
75 }
76 self
77 }
78
79 pub fn layers(self, first: TexInput, second: TexInput) -> Self {
81 let mut pass = self;
82 if let Some(name) = S::TEXTURES.first().copied() {
83 pass = pass.input(name, first);
84 }
85 if let Some(name) = S::TEXTURES.get(1).copied() {
86 pass = pass.input(name, second);
87 }
88 pass
89 }
90
91 pub fn name(&self) -> &'static str {
92 S::NAME
93 }
94
95 pub fn target_rect(&self) -> Option<Rect> {
96 self.rect
97 }
98
99 pub fn draw(&self, g: &mut Painter) -> bool
102 where
103 S: Copy,
104 {
105 let rect = self.rect.unwrap_or_else(|| g.rect());
106 let inputs: Vec<(&str, TexInput)> = self
107 .inputs
108 .iter()
109 .map(|(name, input)| (*name, input.clone()))
110 .collect();
111 match (self.uv, self.blend) {
112 (Some(uv), Some(blend)) => {
113 g.try_shader_at_with_blend(self.shader, rect, uv, &inputs, blend)
114 }
115 (Some(uv), None) => g.try_shader_at(self.shader, rect, uv, &inputs),
116 (None, Some(blend)) => g.try_shader_with_blend(self.shader, rect, &inputs, blend),
117 (None, None) => g.try_shader(self.shader, rect, &inputs),
118 }
119 }
120}
121
122type ChainPass = Rc<dyn Fn(&mut Painter, Rect, TexInput) -> bool>;
123
124#[derive(Clone, Default)]
131pub struct ShaderChain {
132 passes: Vec<ChainPass>,
133}
134
135impl ShaderChain {
136 pub fn new() -> Self {
137 Self::default()
138 }
139
140 pub fn effect<S: SequoiShader + Copy + 'static>(self, shader: S) -> Self {
142 self.push(shader, None, None)
143 }
144
145 pub fn effect_with_blend<S: SequoiShader + Copy + 'static>(
147 self,
148 shader: S,
149 blend: BlendMode,
150 ) -> Self {
151 self.push(shader, None, Some(blend))
152 }
153
154 pub fn effect_to<S: SequoiShader + Copy + 'static>(
156 self,
157 shader: S,
158 input: &'static str,
159 blend: Option<BlendMode>,
160 ) -> Self {
161 self.push(shader, Some(input), blend)
162 }
163
164 fn push<S: SequoiShader + Copy + 'static>(
165 mut self,
166 shader: S,
167 input: Option<&'static str>,
168 blend: Option<BlendMode>,
169 ) -> Self {
170 let input = input.unwrap_or_else(|| S::TEXTURES.first().copied().unwrap_or("content"));
171 let pass = Rc::new(
172 move |g: &mut Painter, rect: Rect, target: TexInput| -> bool {
173 let inputs = [(input, target)];
174 match blend {
175 Some(blend) => g.try_shader_with_blend(shader, rect, &inputs, blend),
176 None => g.try_shader(shader, rect, &inputs),
177 }
178 },
179 );
180 self.passes.push(pass);
181 self
182 }
183
184 pub fn extend(&mut self, other: ShaderChain) {
186 self.passes.extend(other.passes);
187 }
188
189 pub fn len(&self) -> usize {
190 self.passes.len()
191 }
192
193 pub fn is_empty(&self) -> bool {
194 self.passes.is_empty()
195 }
196
197 pub(crate) fn passes(&self) -> &[ChainPass] {
198 &self.passes
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use sequoia_core::RenderTargetId;
206 use sequoia_shader::shader;
207
208 shader! {
209 fn invert(uv: FragUv, content: Sampler2D) -> Color {
210 let c = textureSample(content, content_sampler, uv);
211 return vec4<f32>(1.0 - c.rgb, c.a);
212 }
213 }
214
215 #[test]
216 fn primary_input_does_not_override_explicit_binding() {
217 let pass = ShaderPass::new(Invert)
218 .input("content", TexInput::Target(RenderTargetId::new(7)))
219 .primary_input(TexInput::Target(RenderTargetId::new(9)));
220 assert_eq!(pass.inputs.len(), 1);
221 assert_eq!(pass.inputs[0].1, TexInput::Target(RenderTargetId::new(7)));
222 }
223
224 #[test]
225 fn primary_input_uses_the_declared_parameter_name() {
226 let pass = ShaderPass::new(Invert).primary_input(TexInput::Target(RenderTargetId::new(3)));
227 assert_eq!(
228 pass.inputs,
229 vec![("content", TexInput::Target(RenderTargetId::new(3)))]
230 );
231 assert_eq!(pass.name(), Invert::NAME);
232 }
233
234 #[test]
235 fn chain_keeps_order_and_can_be_extended() {
236 let mut chain = ShaderChain::new()
237 .effect(Invert)
238 .effect_with_blend(Invert, BlendMode::Add);
239 assert_eq!(chain.len(), 2);
240 chain.extend(ShaderChain::new().effect(Invert));
241 assert_eq!(chain.len(), 3);
242 assert!(!chain.is_empty());
243 }
244}