Skip to main content

grafo/renderer/
effects.rs

1use super::*;
2
3fn overwrite_effect_params(storage: &mut Vec<u8>, params: &[u8]) {
4    storage.clear();
5    storage.extend_from_slice(params);
6}
7
8fn validate_params_expectation(
9    effect_id: u64,
10    expects_params: bool,
11    params: &[u8],
12) -> Result<(), EffectError> {
13    if expects_params && params.is_empty() {
14        return Err(EffectError::InvalidParams(format!(
15            "effect {} expects parameters but none were provided",
16            effect_id
17        )));
18    }
19
20    if !expects_params && !params.is_empty() {
21        return Err(EffectError::InvalidParams(format!(
22            "effect {} does not accept parameters but {} bytes were provided",
23            effect_id,
24            params.len()
25        )));
26    }
27
28    Ok(())
29}
30
31fn validate_effect_params(
32    loaded_effects: &HashMap<u64, LoadedEffect>,
33    effect_id: u64,
34    params: &[u8],
35) -> Result<(), EffectError> {
36    let loaded_effect = loaded_effects
37        .get(&effect_id)
38        .ok_or(EffectError::EffectNotLoaded(effect_id))?;
39
40    validate_params_expectation(
41        effect_id,
42        loaded_effect.params_bind_group_layout.is_some(),
43        params,
44    )
45}
46
47fn build_effect_instance(
48    device: &wgpu::Device,
49    loaded_effects: &HashMap<u64, LoadedEffect>,
50    effect_id: u64,
51    params: &[u8],
52    params_buffer_label: &'static str,
53) -> EffectInstance {
54    let mut instance = EffectInstance {
55        effect_id,
56        params: params.to_vec(),
57        params_buffer: None,
58        params_bind_group: None,
59    };
60
61    if params.is_empty() {
62        return instance;
63    }
64
65    let buffer = crate::pipeline::create_buffer_init(
66        device,
67        Some(params_buffer_label),
68        params,
69        BufferUsages::UNIFORM | BufferUsages::COPY_DST,
70    );
71
72    if let Some(loaded_effect) = loaded_effects.get(&effect_id) {
73        if let Some(params_bind_group_layout) = loaded_effect.params_bind_group_layout.as_ref() {
74            let bind_group = create_params_bind_group(device, params_bind_group_layout, &buffer);
75            instance.params_bind_group = Some(bind_group);
76        }
77    }
78
79    instance.params_buffer = Some(buffer);
80    instance
81}
82
83fn update_effect_instance_params(
84    device: &wgpu::Device,
85    queue: &wgpu::Queue,
86    loaded_effects: &HashMap<u64, LoadedEffect>,
87    instance: &mut EffectInstance,
88    params: &[u8],
89    params_buffer_label: &'static str,
90) {
91    overwrite_effect_params(&mut instance.params, params);
92
93    if params.is_empty() {
94        return;
95    }
96
97    if let Some(existing_buffer) = instance.params_buffer.as_ref() {
98        if params.len() as u64 <= existing_buffer.size() {
99            queue.write_buffer(existing_buffer, 0, params);
100            return;
101        }
102    }
103
104    let new_buffer = crate::pipeline::create_buffer_init(
105        device,
106        Some(params_buffer_label),
107        params,
108        BufferUsages::UNIFORM | BufferUsages::COPY_DST,
109    );
110
111    if let Some(loaded_effect) = loaded_effects.get(&instance.effect_id) {
112        if let Some(params_bind_group_layout) = loaded_effect.params_bind_group_layout.as_ref() {
113            let bind_group =
114                create_params_bind_group(device, params_bind_group_layout, &new_buffer);
115            instance.params_bind_group = Some(bind_group);
116        }
117    }
118
119    instance.params_buffer = Some(new_buffer);
120}
121
122impl<'a> Renderer<'a> {
123    pub fn load_effect(
124        &mut self,
125        effect_id: u64,
126        pass_sources: &[&str],
127    ) -> Result<(), EffectError> {
128        let loaded_effect =
129            compile_effect_pipeline(&self.device, pass_sources, self.config.format)?;
130        self.loaded_effects.insert(effect_id, loaded_effect);
131        Ok(())
132    }
133
134    pub fn set_group_effect(
135        &mut self,
136        node_id: usize,
137        effect_id: u64,
138        params: &[u8],
139    ) -> Result<(), EffectError> {
140        if self.draw_tree.get(node_id).is_none() {
141            return Err(EffectError::NodeNotFound(node_id));
142        }
143
144        validate_effect_params(&self.loaded_effects, effect_id, params)?;
145
146        let instance = build_effect_instance(
147            &self.device,
148            &self.loaded_effects,
149            effect_id,
150            params,
151            "effect_params_buffer",
152        );
153
154        self.group_effects.insert(node_id, instance);
155        Ok(())
156    }
157
158    pub fn update_group_effect_params(
159        &mut self,
160        node_id: usize,
161        params: &[u8],
162    ) -> Result<(), EffectError> {
163        let instance = self
164            .group_effects
165            .get_mut(&node_id)
166            .ok_or(EffectError::NodeNotFound(node_id))?;
167
168        validate_effect_params(&self.loaded_effects, instance.effect_id, params)?;
169
170        update_effect_instance_params(
171            &self.device,
172            &self.queue,
173            &self.loaded_effects,
174            instance,
175            params,
176            "effect_params_buffer",
177        );
178
179        Ok(())
180    }
181
182    pub fn remove_group_effect(&mut self, node_id: usize) {
183        self.group_effects.remove(&node_id);
184    }
185
186    pub fn set_shape_backdrop_effect(
187        &mut self,
188        node_id: usize,
189        effect_id: u64,
190        params: &[u8],
191    ) -> Result<(), EffectError> {
192        if self.draw_tree.get(node_id).is_none() {
193            return Err(EffectError::NodeNotFound(node_id));
194        }
195
196        validate_effect_params(&self.loaded_effects, effect_id, params)?;
197
198        let instance = build_effect_instance(
199            &self.device,
200            &self.loaded_effects,
201            effect_id,
202            params,
203            "backdrop_effect_params_buffer",
204        );
205
206        self.backdrop_effects.insert(node_id, instance);
207        Ok(())
208    }
209
210    pub fn update_backdrop_effect_params(
211        &mut self,
212        node_id: usize,
213        params: &[u8],
214    ) -> Result<(), EffectError> {
215        let instance = self
216            .backdrop_effects
217            .get_mut(&node_id)
218            .ok_or(EffectError::NodeNotFound(node_id))?;
219
220        validate_effect_params(&self.loaded_effects, instance.effect_id, params)?;
221
222        update_effect_instance_params(
223            &self.device,
224            &self.queue,
225            &self.loaded_effects,
226            instance,
227            params,
228            "backdrop_effect_params_buffer",
229        );
230
231        Ok(())
232    }
233
234    pub fn remove_backdrop_effect(&mut self, node_id: usize) {
235        self.backdrop_effects.remove(&node_id);
236    }
237
238    pub fn unload_effect(&mut self, effect_id: u64) {
239        self.loaded_effects.remove(&effect_id);
240        self.group_effects
241            .retain(|_, instance| instance.effect_id != effect_id);
242        self.backdrop_effects
243            .retain(|_, instance| instance.effect_id != effect_id);
244    }
245
246    pub(super) fn ensure_composite_pipeline(&mut self) {
247        if self.composite_pipeline.is_none() {
248            let (pipeline, bind_group_layout) =
249                compile_composite_pipeline(&self.device, self.config.format);
250            self.composite_pipeline = Some(pipeline);
251            self.composite_bgl = Some(bind_group_layout);
252        }
253    }
254
255    pub(super) fn ensure_effect_sampler(&mut self) {
256        if self.effect_sampler.is_none() {
257            self.effect_sampler = Some(self.device.create_sampler(&wgpu::SamplerDescriptor {
258                address_mode_u: wgpu::AddressMode::ClampToEdge,
259                address_mode_v: wgpu::AddressMode::ClampToEdge,
260                address_mode_w: wgpu::AddressMode::ClampToEdge,
261                mag_filter: wgpu::FilterMode::Linear,
262                min_filter: wgpu::FilterMode::Linear,
263                mipmap_filter: wgpu::FilterMode::Linear,
264                ..Default::default()
265            }));
266        }
267    }
268
269    pub(super) fn ensure_backdrop_snapshot_texture(&mut self) {
270        let (width, height) = self.physical_size;
271        let needs_recreate = match &self.backdrop_snapshot_texture {
272            Some(texture) => {
273                let size = texture.size();
274                size.width != width || size.height != height
275            }
276            None => true,
277        };
278
279        if needs_recreate {
280            let texture = self.device.create_texture(&wgpu::TextureDescriptor {
281                label: Some("backdrop_snapshot"),
282                size: wgpu::Extent3d {
283                    width,
284                    height,
285                    depth_or_array_layers: 1,
286                },
287                mip_level_count: 1,
288                sample_count: 1,
289                dimension: wgpu::TextureDimension::D2,
290                format: self.config.format,
291                usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
292                view_formats: &[],
293            });
294            let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
295            self.backdrop_snapshot_texture = Some(texture);
296            self.backdrop_snapshot_view = Some(view);
297        }
298    }
299
300    pub(super) fn ensure_stencil_only_pipeline(&mut self) {
301        if self.stencil_only_pipeline.is_some() {
302            return;
303        }
304
305        let uniform_bind_group_layout = self.and_pipeline.get_bind_group_layout(0);
306        let pipeline = crate::pipeline::create_stencil_only_pipeline(
307            &self.device,
308            self.config.format,
309            self.msaa_sample_count,
310            &uniform_bind_group_layout,
311            &self.shape_texture_bind_group_layout_background,
312            &self.shape_texture_bind_group_layout_foreground,
313        );
314        self.stencil_only_pipeline = Some(pipeline);
315    }
316
317    pub(super) fn ensure_backdrop_color_pipeline(&mut self) {
318        if self.backdrop_color_pipeline.is_some() {
319            return;
320        }
321
322        let uniform_bind_group_layout = self.and_pipeline.get_bind_group_layout(0);
323        let pipeline = crate::pipeline::create_stencil_keep_color_pipeline(
324            &self.device,
325            self.config.format,
326            self.msaa_sample_count,
327            &uniform_bind_group_layout,
328            &self.shape_texture_bind_group_layout_background,
329            &self.shape_texture_bind_group_layout_foreground,
330        );
331        self.backdrop_color_pipeline = Some(pipeline);
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::validate_params_expectation;
338
339    #[test]
340    fn validate_effect_params_rejects_missing_required_params() {
341        let result = validate_params_expectation(1, true, &[]);
342        assert!(result.is_err());
343    }
344
345    #[test]
346    fn validate_effect_params_rejects_unexpected_params() {
347        let result = validate_params_expectation(1, false, &[1, 2, 3, 4]);
348        assert!(result.is_err());
349    }
350
351    #[test]
352    fn validate_effect_params_allows_empty_for_paramless_effect() {
353        let result = validate_params_expectation(1, false, &[]);
354        assert!(result.is_ok());
355    }
356
357    #[test]
358    fn validate_effect_params_allows_non_empty_for_param_effect() {
359        let result = validate_params_expectation(1, true, &[1, 2, 3, 4]);
360        assert!(result.is_ok());
361    }
362}