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}